telebot/bot.go

1425 lines
33 KiB
Go
Raw Normal View History

package telebot
import (
2015-07-03 18:54:40 +00:00
"encoding/json"
"fmt"
2017-11-24 15:58:40 +00:00
"io"
"net/http"
"os"
2017-11-21 02:22:45 +00:00
"regexp"
2015-07-03 18:54:40 +00:00
"strconv"
2017-11-21 02:22:45 +00:00
"strings"
"github.com/pkg/errors"
)
2015-07-03 18:54:40 +00:00
// NewBot does try to build a Bot with token `token`, which
// is a secret API key assigned to particular bot.
func NewBot(pref Settings) (*Bot, error) {
if pref.Updates == 0 {
pref.Updates = 100
2017-08-15 15:00:22 +00:00
}
client := pref.Client
if client == nil {
client = http.DefaultClient
}
2018-12-12 22:45:03 +00:00
if pref.URL == "" {
pref.URL = DefaultApiURL
}
bot := &Bot{
2018-06-30 20:34:04 +00:00
Token: pref.Token,
2018-12-12 22:45:03 +00:00
URL: pref.URL,
2018-06-30 20:34:04 +00:00
Updates: make(chan Update, pref.Updates),
Poller: pref.Poller,
handlers: make(map[string]interface{}),
synchronous: pref.Synchronous,
2020-05-31 12:00:30 +00:00
verbose: pref.Verbose,
stop: make(chan struct{}),
reporter: pref.Reporter,
client: client,
2015-07-03 18:54:40 +00:00
}
2020-04-28 12:29:23 +00:00
if pref.offline {
bot.Me = &User{}
} else {
user, err := bot.getMe()
if err != nil {
return nil, err
}
bot.Me = user
}
return bot, nil
}
2017-11-21 02:22:45 +00:00
// Bot represents a separate Telegram bot instance.
type Bot struct {
Me *User
Token string
2018-12-12 22:45:03 +00:00
URL string
2017-11-21 02:22:45 +00:00
Updates chan Update
Poller Poller
handlers map[string]interface{}
synchronous bool
2020-05-31 12:00:30 +00:00
verbose bool
reporter func(error)
stop chan struct{}
client *http.Client
2017-11-21 02:22:45 +00:00
}
// Settings represents a utility struct for passing certain
// properties of a bot around and is required to make bots.
type Settings struct {
2018-12-12 22:45:03 +00:00
// Telegram API Url
URL string
// Telegram token
Token string
2017-11-21 02:22:45 +00:00
// Updates channel capacity
Updates int // Default: 100
// Poller is the provider of Updates.
Poller Poller
// Synchronous prevents handlers from running in parallel.
// It makes ProcessUpdate return after the handler is finished.
Synchronous bool
2020-05-31 12:00:30 +00:00
// Verbose mode let you to debug the bot
// Shows upcoming requests
Verbose bool
// Reporter is a callback function that will get called
// on any panics recovered from endpoint handlers.
Reporter func(error)
// HTTP Client used to make requests to telegram api
Client *http.Client
2020-04-28 12:29:23 +00:00
// offline allows to create a bot without network for testing purposes.
offline bool
}
// Update object represents an incoming update.
type Update struct {
ID int `json:"update_id"`
2019-07-22 18:10:18 +00:00
Message *Message `json:"message,omitempty"`
EditedMessage *Message `json:"edited_message,omitempty"`
ChannelPost *Message `json:"channel_post,omitempty"`
EditedChannelPost *Message `json:"edited_channel_post,omitempty"`
Callback *Callback `json:"callback_query,omitempty"`
Query *Query `json:"inline_query,omitempty"`
2017-12-26 22:46:50 +00:00
ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result,omitempty"`
2020-05-20 20:40:42 +00:00
ShippingQuery *ShippingQuery `json:"shipping_query,omitempty"`
2019-07-22 18:10:18 +00:00
PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query,omitempty"`
2020-03-30 16:21:06 +00:00
Poll *Poll `json:"poll,omitempty"`
PollAnswer *PollAnswer `json:"poll_answer,omitempty"`
2017-12-26 01:07:36 +00:00
}
2020-04-26 12:55:04 +00:00
// Command represents a bot command.
type Command struct {
// Text is a text of the command, 1-32 characters.
2020-04-26 12:55:04 +00:00
// Can contain only lowercase English letters, digits and underscores.
Text string `json:"command"`
// Description of the command, 3-256 characters.
Description string `json:"description"`
}
2017-11-21 02:22:45 +00:00
// Handle lets you set the handler for some command name or
// one of the supported endpoints.
//
// Example:
//
2020-04-06 08:40:31 +00:00
// b.Handle("/help", func (m *tb.Message) {})
// b.Handle(tb.OnText, func (m *tb.Message) {})
// b.Handle(tb.OnQuery, func (q *tb.Query) {})
//
2020-04-06 08:40:31 +00:00
// // make a hook for one of your preserved (by-pointer) inline buttons.
// b.Handle(&inlineButton, func (c *tb.Callback) {})
//
func (b *Bot) Handle(endpoint interface{}, handler interface{}) {
switch end := endpoint.(type) {
case string:
b.handlers[end] = handler
case CallbackEndpoint:
2017-12-10 18:51:43 +00:00
b.handlers[end.CallbackUnique()] = handler
default:
panic("telebot: unsupported endpoint")
}
2017-11-21 02:22:45 +00:00
}
var (
2020-04-05 17:23:51 +00:00
cmdRx = regexp.MustCompile(`^(/\w+)(@(\w+))?(\s|$)(.+)?`)
2017-12-10 22:32:08 +00:00
cbackRx = regexp.MustCompile(`^\f(\w+)(\|(.+))?$`)
)
2017-11-21 02:22:45 +00:00
// Start brings bot into motion by consuming incoming
// updates (see Bot.Updates channel).
func (b *Bot) Start() {
if b.Poller == nil {
panic("telebot: can't start without a poller")
}
stop := make(chan struct{})
go b.Poller.Poll(b, b.Updates, stop)
2017-11-21 02:22:45 +00:00
2017-11-27 12:52:16 +00:00
for {
select {
// handle incoming updates
case upd := <-b.Updates:
2020-04-28 15:51:38 +00:00
b.ProcessUpdate(upd)
2017-11-27 12:52:16 +00:00
// call to stop polling
case <-b.stop:
close(stop)
2017-11-27 12:52:16 +00:00
return
}
}
}
2017-11-21 02:22:45 +00:00
2020-04-26 18:29:25 +00:00
// Stop gracefully shuts the poller down.
func (b *Bot) Stop() {
b.stop <- struct{}{}
}
// ProcessUpdate processes a single incoming update.
// A started bot calls this function automatically.
2020-04-28 15:51:38 +00:00
func (b *Bot) ProcessUpdate(upd Update) {
2017-11-27 12:52:16 +00:00
if upd.Message != nil {
m := upd.Message
2017-11-23 02:13:15 +00:00
2017-11-27 12:52:16 +00:00
if m.PinnedMessage != nil {
b.handle(OnPinned, m)
return
}
2017-11-23 02:13:15 +00:00
2017-11-27 12:52:16 +00:00
// Commands
if m.Text != "" {
// Filtering malicious messages
2017-11-27 12:52:16 +00:00
if m.Text[0] == '\a' {
return
2017-11-23 02:13:15 +00:00
}
2017-11-27 12:52:16 +00:00
match := cmdRx.FindAllStringSubmatch(m.Text, -1)
2017-11-27 14:56:22 +00:00
if match != nil {
// Syntax: "</command>@<bot> <payload>"
2017-11-27 14:56:22 +00:00
2020-04-06 19:27:58 +00:00
command, botName := match[0][1], match[0][3]
2017-11-27 14:56:22 +00:00
if botName != "" && !strings.EqualFold(b.Me.Username, botName) {
return
}
2020-04-06 19:27:58 +00:00
m.Payload = match[0][5]
if b.handle(command, m) {
return
2017-11-27 14:56:22 +00:00
}
}
// 1:1 satisfaction
if b.handle(m.Text, m) {
return
}
2017-11-27 12:52:16 +00:00
b.handle(OnText, m)
return
}
2017-11-27 12:52:16 +00:00
if b.handleMedia(m) {
return
}
if m.Invoice != nil {
b.handle(OnInvoice, m)
return
}
if m.Payment != nil {
b.handle(OnPayment, m)
return
}
wasAdded := (m.UserJoined != nil && m.UserJoined.ID == b.Me.ID) ||
(m.UsersJoined != nil && isUserInList(b.Me, m.UsersJoined))
2017-11-27 12:52:16 +00:00
if m.GroupCreated || m.SuperGroupCreated || wasAdded {
b.handle(OnAddedToGroup, m)
return
}
2017-11-27 12:52:16 +00:00
if m.UserJoined != nil {
b.handle(OnUserJoined, m)
return
}
2017-11-27 12:52:16 +00:00
if m.UsersJoined != nil {
for _, user := range m.UsersJoined {
m.UserJoined = &user
b.handle(OnUserJoined, m)
}
2017-11-27 12:52:16 +00:00
return
}
2017-11-27 12:52:16 +00:00
if m.UserLeft != nil {
b.handle(OnUserLeft, m)
return
}
2017-11-27 12:52:16 +00:00
if m.NewGroupTitle != "" {
b.handle(OnNewGroupTitle, m)
return
2017-11-21 02:22:45 +00:00
}
2017-11-27 12:52:16 +00:00
if m.NewGroupPhoto != nil {
b.handle(OnNewGroupPhoto, m)
return
2017-11-21 02:22:45 +00:00
}
2017-11-27 12:52:16 +00:00
if m.GroupPhotoDeleted {
b.handle(OnGroupPhotoDeleted, m)
return
}
2017-11-27 12:52:16 +00:00
if m.MigrateTo != 0 {
if handler, ok := b.handlers[OnMigration]; ok {
2020-04-06 19:27:58 +00:00
handler, ok := handler.(func(int64, int64))
if !ok {
panic("telebot: migration handler is bad")
2017-11-27 12:52:16 +00:00
}
2020-04-06 19:27:58 +00:00
2020-04-28 15:51:38 +00:00
b.runHandler(func() { handler(m.Chat.ID, m.MigrateTo) })
2017-11-27 12:52:16 +00:00
}
return
}
2020-05-31 12:00:30 +00:00
2017-11-27 12:52:16 +00:00
}
2017-11-27 12:52:16 +00:00
if upd.EditedMessage != nil {
b.handle(OnEdited, upd.EditedMessage)
return
}
2017-11-27 12:52:16 +00:00
if upd.ChannelPost != nil {
m := upd.ChannelPost
if m.PinnedMessage != nil {
b.handle(OnPinned, m)
return
}
2017-11-27 12:52:16 +00:00
b.handle(OnChannelPost, upd.ChannelPost)
return
}
2017-11-27 12:52:16 +00:00
if upd.EditedChannelPost != nil {
b.handle(OnEditedChannelPost, upd.EditedChannelPost)
return
}
if upd.Callback != nil {
if upd.Callback.Data != "" {
if upd.Callback.MessageID != "" {
upd.Callback.Message = &Message{
2020-04-06 19:27:58 +00:00
// InlineID indicates that message
// is inline so we have only its id
InlineID: upd.Callback.MessageID,
}
}
data := upd.Callback.Data
2017-11-27 12:52:16 +00:00
if data[0] == '\f' {
match := cbackRx.FindAllStringSubmatch(data, -1)
if match != nil {
2017-12-10 22:32:08 +00:00
unique, payload := match[0][1], match[0][3]
2017-11-27 12:52:16 +00:00
if handler, ok := b.handlers["\f"+unique]; ok {
2020-04-06 19:27:58 +00:00
handler, ok := handler.(func(*Callback))
if !ok {
panic(fmt.Errorf("telebot: %s callback handler is bad", unique))
2017-11-27 12:52:16 +00:00
}
2020-04-06 19:27:58 +00:00
upd.Callback.Data = payload
2020-04-28 15:51:38 +00:00
b.runHandler(func() { handler(upd.Callback) })
2020-04-06 19:27:58 +00:00
return
}
}
}
2017-11-27 12:52:16 +00:00
}
2017-11-27 12:52:16 +00:00
if handler, ok := b.handlers[OnCallback]; ok {
2020-04-06 19:27:58 +00:00
handler, ok := handler.(func(*Callback))
if !ok {
panic("telebot: callback handler is bad")
2017-11-21 02:22:45 +00:00
}
2020-04-06 19:27:58 +00:00
2020-04-28 15:51:38 +00:00
b.runHandler(func() { handler(upd.Callback) })
2017-11-21 02:22:45 +00:00
}
2020-04-06 19:27:58 +00:00
2017-11-27 12:52:16 +00:00
return
}
2017-11-21 02:22:45 +00:00
2017-11-27 12:52:16 +00:00
if upd.Query != nil {
if handler, ok := b.handlers[OnQuery]; ok {
2020-04-06 19:27:58 +00:00
handler, ok := handler.(func(*Query))
if !ok {
panic("telebot: query handler is bad")
2017-11-21 02:22:45 +00:00
}
2020-04-06 19:27:58 +00:00
2020-04-28 15:51:38 +00:00
b.runHandler(func() { handler(upd.Query) })
}
2020-04-06 19:27:58 +00:00
2017-11-27 12:52:16 +00:00
return
}
2017-12-26 01:07:36 +00:00
if upd.ChosenInlineResult != nil {
if handler, ok := b.handlers[OnChosenInlineResult]; ok {
2020-04-06 19:27:58 +00:00
handler, ok := handler.(func(*ChosenInlineResult))
if !ok {
2017-12-26 01:07:36 +00:00
panic("telebot: chosen inline result handler is bad")
}
2020-04-06 19:27:58 +00:00
2020-04-28 15:51:38 +00:00
b.runHandler(func() { handler(upd.ChosenInlineResult) })
2017-12-26 01:07:36 +00:00
}
2020-04-06 19:27:58 +00:00
2017-12-26 01:07:36 +00:00
return
}
2019-01-28 00:11:10 +00:00
2020-05-20 20:40:42 +00:00
if upd.ShippingQuery != nil {
if handler, ok := b.handlers[OnShipping]; ok {
handler, ok := handler.(func(*ShippingQuery))
if !ok {
panic("telebot: shipping query handler is bad")
}
b.runHandler(func() { handler(upd.ShippingQuery) })
}
return
}
2019-01-28 00:11:10 +00:00
if upd.PreCheckoutQuery != nil {
if handler, ok := b.handlers[OnCheckout]; ok {
2020-04-06 19:27:58 +00:00
handler, ok := handler.(func(*PreCheckoutQuery))
if !ok {
panic("telebot: pre checkout query handler is bad")
2019-01-28 00:11:10 +00:00
}
2020-04-06 19:27:58 +00:00
2020-04-28 15:51:38 +00:00
b.runHandler(func() { handler(upd.PreCheckoutQuery) })
2019-01-28 00:11:10 +00:00
}
2020-04-06 19:27:58 +00:00
2019-01-28 00:11:10 +00:00
return
}
2020-03-30 16:21:06 +00:00
if upd.Poll != nil {
if handler, ok := b.handlers[OnPoll]; ok {
2020-04-06 19:27:58 +00:00
handler, ok := handler.(func(*Poll))
if !ok {
2020-03-30 16:21:06 +00:00
panic("telebot: poll handler is bad")
}
2020-04-06 19:27:58 +00:00
2020-04-28 15:51:38 +00:00
b.runHandler(func() { handler(upd.Poll) })
2020-03-30 16:21:06 +00:00
}
2020-04-06 19:27:58 +00:00
2020-03-30 16:21:06 +00:00
return
}
if upd.PollAnswer != nil {
if handler, ok := b.handlers[OnPollAnswer]; ok {
2020-04-06 19:27:58 +00:00
handler, ok := handler.(func(*PollAnswer))
if !ok {
2020-03-30 16:21:06 +00:00
panic("telebot: poll answer handler is bad")
}
2020-04-06 19:27:58 +00:00
2020-04-28 15:51:38 +00:00
b.runHandler(func() { handler(upd.PollAnswer) })
2020-03-30 16:21:06 +00:00
}
2020-04-06 19:27:58 +00:00
2020-03-30 16:21:06 +00:00
return
}
2020-05-31 12:00:30 +00:00
}
func (b *Bot) handle(end string, m *Message) bool {
2020-04-06 19:27:58 +00:00
if handler, ok := b.handlers[end]; ok {
handler, ok := handler.(func(*Message))
if !ok {
panic(fmt.Errorf("telebot: %s handler is bad", end))
}
b.runHandler(func() { handler(m) })
return true
}
return false
}
func (b *Bot) handleMedia(m *Message) bool {
2020-04-06 19:27:58 +00:00
switch {
case m.Photo != nil:
b.handle(OnPhoto, m)
2020-04-06 19:27:58 +00:00
case m.Voice != nil:
2018-04-24 15:46:07 +00:00
b.handle(OnVoice, m)
2020-04-06 19:27:58 +00:00
case m.Audio != nil:
b.handle(OnAudio, m)
2020-04-29 13:02:52 +00:00
case m.Animation != nil:
b.handle(OnAnimation, m)
2020-04-06 19:27:58 +00:00
case m.Document != nil:
b.handle(OnDocument, m)
2020-04-06 19:27:58 +00:00
case m.Sticker != nil:
b.handle(OnSticker, m)
2020-04-06 19:27:58 +00:00
case m.Video != nil:
b.handle(OnVideo, m)
2020-04-06 19:27:58 +00:00
case m.VideoNote != nil:
b.handle(OnVideoNote, m)
2020-04-06 19:27:58 +00:00
case m.Contact != nil:
b.handle(OnContact, m)
2020-04-06 19:27:58 +00:00
case m.Location != nil:
b.handle(OnLocation, m)
2020-04-06 19:27:58 +00:00
case m.Venue != nil:
b.handle(OnVenue, m)
2020-04-25 12:59:53 +00:00
case m.Dice != nil:
2020-04-25 12:31:16 +00:00
b.handle(OnDice, m)
2020-04-06 19:27:58 +00:00
default:
return false
}
2020-04-06 19:27:58 +00:00
return true
}
2017-11-17 14:29:44 +00:00
// Send accepts 2+ arguments, starting with destination chat, followed by
// some Sendable (or string!) and optional send options.
//
// Note: since most arguments are of type interface{}, but have pointer
// method receivers, make sure to pass them by-pointer, NOT by-value.
2017-11-17 14:29:44 +00:00
//
// What is a send option exactly? It can be one of the following types:
//
// - *SendOptions (the actual object accepted by Telegram API)
// - *ReplyMarkup (a component of SendOptions)
// - Option (a shortcut flag for popular options)
// - ParseMode (HTML, Markdown, etc)
2017-11-17 14:29:44 +00:00
//
func (b *Bot) Send(to Recipient, what interface{}, options ...interface{}) (*Message, error) {
if to == nil {
return nil, ErrBadRecipient
}
sendOpts := extractOptions(options)
switch object := what.(type) {
case string:
return b.sendText(to, object, sendOpts)
case Sendable:
return object.Send(b, to, sendOpts)
default:
return nil, ErrUnsupportedWhat
2015-07-03 18:54:40 +00:00
}
2015-06-26 16:12:54 +00:00
}
2015-07-02 09:05:50 +00:00
// SendAlbum sends multiple instances of media as a single message.
2017-11-17 14:29:44 +00:00
//
// From all existing options, it only supports tb.Silent option.
func (b *Bot) SendAlbum(to Recipient, a Album, options ...interface{}) ([]Message, error) {
if to == nil {
return nil, ErrBadRecipient
}
media := make([]string, len(a))
files := make(map[string]File)
for i, x := range a {
var (
repr string
data []byte
f = x.MediaFile()
)
switch {
case f.InCloud():
repr = f.FileID
case f.FileURL != "":
repr = f.FileURL
case f.OnDisk() || f.FileReader != nil:
repr = "attach://" + strconv.Itoa(i)
files[strconv.Itoa(i)] = *f
default:
return nil, errors.Errorf("telebot: album entry #%d does not exist", i)
}
2018-09-15 12:15:08 +00:00
switch y := x.(type) {
case *Photo:
data, _ = json.Marshal(struct {
Type string `json:"type"`
Media string `json:"media"`
Caption string `json:"caption,omitempty"`
ParseMode ParseMode `json:"parse_mode,omitempty"`
2018-09-15 12:15:08 +00:00
}{
Type: "photo",
Media: repr,
Caption: y.Caption,
ParseMode: y.ParseMode,
2018-09-15 12:15:08 +00:00
})
case *Video:
data, _ = json.Marshal(struct {
2018-09-15 12:15:08 +00:00
Type string `json:"type"`
Caption string `json:"caption"`
Media string `json:"media"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
Duration int `json:"duration,omitempty"`
SupportsStreaming bool `json:"supports_streaming,omitempty"`
}{
Type: "video",
Caption: y.Caption,
Media: repr,
Width: y.Width,
Height: y.Height,
Duration: y.Duration,
SupportsStreaming: y.SupportsStreaming,
2018-09-15 12:15:08 +00:00
})
default:
return nil, errors.Errorf("telebot: album entry #%d is not valid", i)
}
media[i] = string(data)
}
params := map[string]string{
"chat_id": to.Recipient(),
"media": "[" + strings.Join(media, ",") + "]",
}
sendOpts := extractOptions(options)
embedSendOptions(params, sendOpts)
2020-04-06 13:04:25 +00:00
data, err := b.sendFiles("sendMediaGroup", files, params)
if err != nil {
return nil, err
}
var resp struct {
Result []Message
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
for attachName := range files {
i, _ := strconv.Atoi(attachName)
var newID string
if resp.Result[i].Photo != nil {
newID = resp.Result[i].Photo.FileID
} else {
newID = resp.Result[i].Video.FileID
}
a[i].MediaFile().FileID = newID
}
return resp.Result, nil
}
// Reply behaves just like Send() with an exception of "reply-to" indicator.
//
// This function will panic upon nil Message.
func (b *Bot) Reply(to *Message, what interface{}, options ...interface{}) (*Message, error) {
sendOpts := extractOptions(options)
if sendOpts == nil {
sendOpts = &SendOptions{}
2015-07-03 18:54:40 +00:00
}
sendOpts.ReplyTo = to
return b.Send(to.Chat, what, sendOpts)
2015-07-02 09:05:50 +00:00
}
2015-07-02 18:39:39 +00:00
// Forward behaves just like Send() but of all options it only supports Silent (see Bots API).
2017-11-17 14:29:44 +00:00
//
// This function will panic upon nil Editable.
func (b *Bot) Forward(to Recipient, msg Editable, options ...interface{}) (*Message, error) {
if to == nil {
return nil, ErrBadRecipient
}
msgID, chatID := msg.MessageSig()
params := map[string]string{
2017-11-18 14:41:23 +00:00
"chat_id": to.Recipient(),
"from_chat_id": strconv.FormatInt(chatID, 10),
"message_id": msgID,
}
sendOpts := extractOptions(options)
embedSendOptions(params, sendOpts)
2015-07-03 18:54:40 +00:00
2020-04-06 13:04:25 +00:00
data, err := b.Raw("forwardMessage", params)
2015-07-03 18:54:40 +00:00
if err != nil {
return nil, err
2015-07-03 18:54:40 +00:00
}
2020-04-06 13:04:25 +00:00
return extractMessage(data)
}
2016-07-04 10:56:53 +00:00
// Edit is magic, it lets you change already sent message.
//
// Use cases:
//
// b.Edit(msg, msg.Text, newMarkup)
// b.Edit(msg, "new <b>text</b>", tb.ModeHTML)
// b.Edit(msg, tb.Location{42.1337, 69.4242})
//
// This function will panic upon nil Editable.
func (b *Bot) Edit(msg Editable, what interface{}, options ...interface{}) (*Message, error) {
var (
method string
2020-05-05 20:40:48 +00:00
params = make(map[string]string)
)
2017-11-26 09:15:11 +00:00
switch v := what.(type) {
case string:
method = "editMessageText"
2017-11-26 09:15:11 +00:00
params["text"] = v
case Location:
method = "editMessageLiveLocation"
2017-11-26 09:15:11 +00:00
params["latitude"] = fmt.Sprintf("%f", v.Lat)
params["longitude"] = fmt.Sprintf("%f", v.Lng)
default:
return nil, ErrUnsupportedWhat
2017-11-26 09:15:11 +00:00
}
msgID, chatID := msg.MessageSig()
if chatID == 0 { // if inline message
params["inline_message_id"] = msgID
} else {
params["chat_id"] = strconv.FormatInt(chatID, 10)
params["message_id"] = msgID
}
sendOpts := extractOptions(options)
embedSendOptions(params, sendOpts)
data, err := b.Raw(method, params)
if err != nil {
return nil, err
}
2020-04-06 13:04:25 +00:00
return extractMessage(data)
}
// EditReplyMarkup edits reply markup of already sent message.
// Pass nil or empty ReplyMarkup to delete it from the message.
//
// On success, returns edited message object.
// This function will panic upon nil Editable.
func (b *Bot) EditReplyMarkup(msg Editable, markup *ReplyMarkup) (*Message, error) {
msgID, chatID := msg.MessageSig()
2020-05-05 20:40:48 +00:00
params := make(map[string]string)
if chatID == 0 { // if inline message
params["inline_message_id"] = msgID
} else {
params["chat_id"] = strconv.FormatInt(chatID, 10)
params["message_id"] = msgID
}
if markup == nil {
// will delete reply markup
markup = &ReplyMarkup{}
}
processButtons(markup.InlineKeyboard)
data, _ := json.Marshal(markup)
params["reply_markup"] = string(data)
2020-04-06 13:04:25 +00:00
data, err := b.Raw("editMessageReplyMarkup", params)
if err != nil {
return nil, err
}
2020-04-06 13:04:25 +00:00
return extractMessage(data)
}
// EditCaption edits already sent photo caption with known recipient and message id.
//
// On success, returns edited message object.
// This function will panic upon nil Editable.
func (b *Bot) EditCaption(msg Editable, caption string, options ...interface{}) (*Message, error) {
msgID, chatID := msg.MessageSig()
params := map[string]string{
"caption": caption,
}
if chatID == 0 { // if inline message
params["inline_message_id"] = msgID
} else {
params["chat_id"] = strconv.FormatInt(chatID, 10)
params["message_id"] = msgID
}
sendOpts := extractOptions(options)
embedSendOptions(params, sendOpts)
2020-04-06 13:04:25 +00:00
data, err := b.Raw("editMessageCaption", params)
if err != nil {
return nil, err
}
2020-04-06 13:04:25 +00:00
return extractMessage(data)
}
// EditMedia edits already sent media with known recipient and message id.
//
// Use cases:
//
// bot.EditMedia(msg, &tb.Photo{File: tb.FromDisk("chicken.jpg")})
// bot.EditMedia(msg, &tb.Video{File: tb.FromURL("http://video.mp4")})
//
// This function will panic upon nil Editable.
func (b *Bot) EditMedia(msg Editable, media InputMedia, options ...interface{}) (*Message, error) {
var (
repr string
thumb *Photo
thumbName = "thumb"
file = media.MediaFile()
files = make(map[string]File)
)
switch {
case file.InCloud():
repr = file.FileID
case file.FileURL != "":
repr = file.FileURL
case file.OnDisk() || file.FileReader != nil:
s := file.FileLocal
if file.FileReader != nil {
2019-07-22 18:10:18 +00:00
s = "0"
} else if s == thumbName {
thumbName = "thumb2"
}
repr = "attach://" + s
files[s] = *file
default:
return nil, errors.Errorf("telebot: can't edit media, it does not exist")
}
type FileJSON struct {
// All types.
Type string `json:"type"`
Caption string `json:"caption"`
Media string `json:"media"`
ParseMode ParseMode `json:"parse_mode,omitempty"`
// Video.
2019-07-22 18:10:18 +00:00
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
SupportsStreaming bool `json:"supports_streaming,omitempty"`
// Video and audio.
2019-07-22 18:10:18 +00:00
Duration int `json:"duration,omitempty"`
// Document.
2019-07-22 18:10:18 +00:00
FileName string `json:"file_name"`
// Document, video and audio.
2019-07-22 18:10:18 +00:00
Thumbnail string `json:"thumb,omitempty"`
MIME string `json:"mime_type,omitempty"`
// Audio.
2019-07-22 18:10:18 +00:00
Title string `json:"title,omitempty"`
Performer string `json:"performer,omitempty"`
}
result := &FileJSON{Media: repr}
switch m := media.(type) {
2019-07-22 18:10:18 +00:00
case *Photo:
result.Type = "photo"
result.Caption = m.Caption
2019-07-22 18:10:18 +00:00
case *Video:
result.Type = "video"
result.Caption = m.Caption
result.Width = m.Width
result.Height = m.Height
result.Duration = m.Duration
result.SupportsStreaming = m.SupportsStreaming
result.MIME = m.MIME
thumb = m.Thumbnail
2019-07-22 18:10:18 +00:00
case *Document:
result.Type = "document"
result.Caption = m.Caption
result.FileName = m.FileName
result.MIME = m.MIME
thumb = m.Thumbnail
2019-07-22 18:10:18 +00:00
case *Audio:
result.Type = "audio"
result.Caption = m.Caption
result.Duration = m.Duration
result.MIME = m.MIME
result.Title = m.Title
result.Performer = m.Performer
thumb = m.Thumbnail
2019-07-22 18:10:18 +00:00
default:
return nil, errors.Errorf("telebot: media entry is not valid")
}
msgID, chatID := msg.MessageSig()
params := make(map[string]string)
sendOpts := extractOptions(options)
embedSendOptions(params, sendOpts)
if sendOpts != nil {
result.ParseMode = sendOpts.ParseMode
}
if thumb != nil {
result.Thumbnail = "attach://" + thumbName
files[thumbName] = *thumb.MediaFile()
}
data, _ := json.Marshal(result)
params["media"] = string(data)
if chatID == 0 { // If inline message.
params["inline_message_id"] = msgID
} else {
params["chat_id"] = strconv.FormatInt(chatID, 10)
params["message_id"] = msgID
}
data, err := b.sendFiles("editMessageMedia", files, params)
if err != nil {
2019-07-22 18:10:18 +00:00
return nil, err
}
2020-04-06 13:04:25 +00:00
return extractMessage(data)
}
2017-11-17 14:29:44 +00:00
// Delete removes the message, including service messages,
// with the following limitations:
//
// * A message can only be deleted if it was sent less than 48 hours ago.
// * Bots can delete outgoing messages in groups and supergroups.
// * Bots granted can_post_messages permissions can delete outgoing
// messages in channels.
// * If the bot is an administrator of a group, it can delete any message there.
// * If the bot has can_delete_messages permission in a supergroup or a
// channel, it can delete any message there.
//
// This function will panic upon nil Editable.
func (b *Bot) Delete(msg Editable) error {
msgID, chatID := msg.MessageSig()
2017-11-17 07:22:16 +00:00
params := map[string]string{
"chat_id": strconv.FormatInt(chatID, 10),
"message_id": msgID,
2017-11-17 07:22:16 +00:00
}
_, err := b.Raw("deleteMessage", params)
return err
2017-11-17 07:22:16 +00:00
}
// Notify updates the chat action for recipient.
2015-07-06 16:13:08 +00:00
//
// Chat action is a status message that recipient would see where
// you typically see "Harry is typing" status message. The only
// difference is that bots' chat actions live only for 5 seconds
// and die just once the client receives a message from the bot.
2015-07-06 16:13:08 +00:00
//
// Currently, Telegram supports only a narrow range of possible
// actions, these are aligned as constants of this package.
func (b *Bot) Notify(to Recipient, action ChatAction) error {
if to == nil {
return ErrBadRecipient
}
params := map[string]string{
"chat_id": to.Recipient(),
"action": string(action),
}
2015-07-06 16:13:08 +00:00
_, err := b.Raw("sendChatAction", params)
return err
2015-07-06 16:13:08 +00:00
}
2020-05-20 20:40:42 +00:00
// Ship replies to the shipping query, if you sent an invoice
// requesting an address and the parameter is_flexible was specified.
//
// Usage:
//
// b.Ship(query) // OK
// b.Ship(query, opts...) // OK with options
// b.Ship(query, "Oops!") // Error message
2020-05-20 20:40:42 +00:00
//
func (b *Bot) Ship(query *ShippingQuery, what ...interface{}) error {
params := map[string]string{
"shipping_query_id": query.ID,
}
if len(what) == 0 {
params["ok"] = "True"
} else if s, ok := what[0].(string); ok {
params["ok"] = "False"
params["error_message"] = s
} else {
var opts []ShippingOption
for _, v := range what {
opt, ok := v.(ShippingOption)
if !ok {
return ErrUnsupportedWhat
}
opts = append(opts, opt)
}
params["ok"] = "True"
data, _ := json.Marshal(opts)
params["shipping_options"] = string(data)
}
_, err := b.Raw("answerShippingQuery", params)
return err
}
2019-01-28 00:11:10 +00:00
// Accept finalizes the deal.
func (b *Bot) Accept(query *PreCheckoutQuery, errorMessage ...string) error {
params := map[string]string{
"pre_checkout_query_id": query.ID,
}
if len(errorMessage) == 0 {
params["ok"] = "True"
} else {
params["ok"] = "False"
params["error_message"] = errorMessage[0]
}
_, err := b.Raw("answerPreCheckoutQuery", params)
return err
2019-01-28 00:11:10 +00:00
}
// Answer sends a response for a given inline query. A query can only
// be responded to once, subsequent attempts to respond to the same query
// will result in an error.
func (b *Bot) Answer(query *Query, resp *QueryResponse) error {
resp.QueryID = query.ID
2017-11-29 20:14:25 +00:00
for _, result := range resp.Results {
result.Process()
}
_, err := b.Raw("answerInlineQuery", resp)
return err
}
2016-07-25 22:09:18 +00:00
// Respond sends a response for a given callback query. A callback can
2016-07-25 22:09:18 +00:00
// only be responded to once, subsequent attempts to respond to the same callback
// will result in an error.
2017-12-10 22:32:08 +00:00
//
// Example:
//
// bot.Respond(c)
// bot.Respond(c, response)
//
func (b *Bot) Respond(c *Callback, response ...*CallbackResponse) error {
var resp *CallbackResponse
if response == nil {
resp = &CallbackResponse{}
2017-12-10 22:32:08 +00:00
} else {
resp = response[0]
2017-12-10 22:32:08 +00:00
}
2016-07-25 22:09:18 +00:00
resp.CallbackID = c.ID
_, err := b.Raw("answerCallbackQuery", resp)
return err
2016-07-25 22:09:18 +00:00
}
2017-11-19 15:16:39 +00:00
// FileByID returns full file object including File.FilePath, allowing you to
// download the file from the server.
//
2017-11-19 15:16:39 +00:00
// Usually, Telegram-provided File objects miss FilePath so you might need to
// perform an additional request to fetch them.
func (b *Bot) FileByID(fileID string) (File, error) {
params := map[string]string{
2016-10-09 19:57:08 +00:00
"file_id": fileID,
}
2017-11-18 10:19:58 +00:00
2020-04-06 13:04:25 +00:00
data, err := b.Raw("getFile", params)
if err != nil {
2017-11-19 15:16:39 +00:00
return File{}, err
}
2017-11-18 10:19:58 +00:00
var resp struct {
Result File
}
if err := json.Unmarshal(data, &resp); err != nil {
return File{}, wrapError(err)
}
2017-11-18 10:19:58 +00:00
return resp.Result, nil
}
2017-11-24 15:58:40 +00:00
// Download saves the file from Telegram servers locally.
//
// Maximum file size to download is 20 MB.
2019-03-03 05:14:14 +00:00
func (b *Bot) Download(file *File, localFilename string) error {
reader, err := b.GetFile(file)
2017-11-24 15:58:40 +00:00
if err != nil {
2020-04-05 17:23:51 +00:00
return wrapError(err)
2017-11-24 15:58:40 +00:00
}
2019-03-03 05:14:14 +00:00
defer reader.Close()
2017-11-24 15:58:40 +00:00
out, err := os.Create(localFilename)
if err != nil {
2020-04-05 17:23:51 +00:00
return wrapError(err)
2017-11-24 15:58:40 +00:00
}
defer out.Close()
2019-03-03 05:14:14 +00:00
_, err = io.Copy(out, reader)
2017-11-24 15:58:40 +00:00
if err != nil {
2020-04-05 17:23:51 +00:00
return wrapError(err)
2017-11-24 15:58:40 +00:00
}
file.FileLocal = localFilename
2019-03-03 05:14:14 +00:00
return nil
}
// GetFile gets a file from Telegram servers.
2019-03-03 05:14:14 +00:00
func (b *Bot) GetFile(file *File) (io.ReadCloser, error) {
f, err := b.FileByID(file.FileID)
2017-11-24 15:58:40 +00:00
if err != nil {
2019-03-03 05:14:14 +00:00
return nil, err
2017-11-24 15:58:40 +00:00
}
url := b.URL + "/file/bot" + b.Token + "/" + f.FilePath
file.FilePath = f.FilePath // saving file path
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
2020-04-05 17:23:51 +00:00
return nil, wrapError(err)
}
2017-11-24 15:58:40 +00:00
resp, err := b.client.Do(req)
2019-03-03 05:14:14 +00:00
if err != nil {
return nil, wrapError(err)
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, errors.Errorf("telebot: expected status 200 but got %s", resp.Status)
2019-03-03 05:14:14 +00:00
}
2017-11-24 15:58:40 +00:00
2019-03-03 05:14:14 +00:00
return resp.Body, nil
}
2019-07-22 18:10:18 +00:00
// StopLiveLocation stops broadcasting live message location
2017-11-26 09:15:11 +00:00
// before Location.LivePeriod expires.
//
// It supports tb.ReplyMarkup.
2020-04-26 14:05:21 +00:00
// This function will panic upon nil Editable.
func (b *Bot) StopLiveLocation(msg Editable, options ...interface{}) (*Message, error) {
msgID, chatID := msg.MessageSig()
2017-11-26 09:15:11 +00:00
params := map[string]string{
"chat_id": strconv.FormatInt(chatID, 10),
"message_id": msgID,
2017-11-26 09:15:11 +00:00
}
sendOpts := extractOptions(options)
embedSendOptions(params, sendOpts)
2020-04-06 13:04:25 +00:00
data, err := b.Raw("stopMessageLiveLocation", params)
2017-11-26 09:15:11 +00:00
if err != nil {
return nil, err
}
2020-04-06 13:04:25 +00:00
return extractMessage(data)
2017-11-26 09:15:11 +00:00
}
2020-04-26 14:05:21 +00:00
// StopPoll stops a poll which was sent by the bot and returns
// the stopped Poll object with the final results.
//
// It supports ReplyMarkup.
// This function will panic upon nil Editable.
func (b *Bot) StopPoll(msg Editable, options ...interface{}) (*Poll, error) {
msgID, chatID := msg.MessageSig()
params := map[string]string{
"chat_id": strconv.FormatInt(chatID, 10),
"message_id": msgID,
}
sendOpts := extractOptions(options)
embedSendOptions(params, sendOpts)
data, err := b.Raw("stopPoll", params)
if err != nil {
return nil, err
}
var resp struct {
Result *Poll
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result, nil
}
2017-11-26 09:15:11 +00:00
// GetInviteLink should be used to export chat's invite link.
func (b *Bot) GetInviteLink(chat *Chat) (string, error) {
params := map[string]string{
"chat_id": chat.Recipient(),
}
2020-04-06 13:04:25 +00:00
data, err := b.Raw("exportChatInviteLink", params)
2017-11-26 09:15:11 +00:00
if err != nil {
return "", err
}
var resp struct {
Result string
2017-11-26 09:15:11 +00:00
}
if err := json.Unmarshal(data, &resp); err != nil {
return "", wrapError(err)
2017-11-26 09:15:11 +00:00
}
return resp.Result, nil
}
// SetGroupTitle should be used to update group title.
func (b *Bot) SetGroupTitle(chat *Chat, title string) error {
2017-11-26 09:15:11 +00:00
params := map[string]string{
"chat_id": chat.Recipient(),
"title": title,
2017-11-26 09:15:11 +00:00
}
_, err := b.Raw("setChatTitle", params)
return err
2017-11-26 09:15:11 +00:00
}
// SetGroupDescription should be used to update group title.
func (b *Bot) SetGroupDescription(chat *Chat, description string) error {
params := map[string]string{
"chat_id": chat.Recipient(),
"description": description,
}
_, err := b.Raw("setChatDescription", params)
return err
2017-11-26 09:15:11 +00:00
}
// SetGroupPhoto should be used to update group photo.
func (b *Bot) SetGroupPhoto(chat *Chat, p *Photo) error {
params := map[string]string{
"chat_id": chat.Recipient(),
}
_, err := b.sendFiles("setChatPhoto", map[string]File{"photo": p.File}, params)
return err
2017-11-26 09:15:11 +00:00
}
// SetGroupStickerSet should be used to update group's group sticker set.
func (b *Bot) SetGroupStickerSet(chat *Chat, setName string) error {
params := map[string]string{
"chat_id": chat.Recipient(),
"sticker_set_name": setName,
}
_, err := b.Raw("setChatStickerSet", params)
return err
2017-11-26 09:15:11 +00:00
}
// SetGroupPermissions sets default chat permissions for all members.
func (b *Bot) SetGroupPermissions(chat *Chat, perms Rights) error {
params := map[string]string{
"chat_id": chat.Recipient(),
}
embedRights(params, perms)
_, err := b.Raw("setChatPermissions", params)
return err
}
2017-11-26 09:15:11 +00:00
// DeleteGroupPhoto should be used to just remove group photo.
func (b *Bot) DeleteGroupPhoto(chat *Chat) error {
params := map[string]string{
"chat_id": chat.Recipient(),
}
_, err := b.Raw("deleteChatPhoto", params)
return err
2017-11-26 09:15:11 +00:00
}
// DeleteGroupStickerSet should be used to just remove group sticker set.
func (b *Bot) DeleteGroupStickerSet(chat *Chat) error {
params := map[string]string{
"chat_id": chat.Recipient(),
}
_, err := b.Raw("deleteChatStickerSet", params)
return err
2017-11-26 09:15:11 +00:00
}
// Leave makes bot leave a group, supergroup or channel.
func (b *Bot) Leave(chat *Chat) error {
2016-10-09 19:57:08 +00:00
params := map[string]string{
"chat_id": chat.Recipient(),
2016-10-09 19:57:08 +00:00
}
_, err := b.Raw("leaveChat", params)
return err
2016-10-09 20:00:04 +00:00
}
// Pin pins a message in a supergroup or a channel.
//
// It supports tb.Silent option.
// This function will panic upon nil Editable.
func (b *Bot) Pin(msg Editable, options ...interface{}) error {
msgID, chatID := msg.MessageSig()
2017-11-18 10:19:58 +00:00
2017-11-26 00:44:32 +00:00
params := map[string]string{
"chat_id": strconv.FormatInt(chatID, 10),
"message_id": msgID,
2016-10-09 20:00:04 +00:00
}
2017-11-26 00:44:32 +00:00
sendOpts := extractOptions(options)
embedSendOptions(params, sendOpts)
2016-10-09 20:00:04 +00:00
_, err := b.Raw("pinChatMessage", params)
return err
2016-10-09 19:57:08 +00:00
}
// Unpin unpins a message in a supergroup or a channel.
2016-10-09 20:09:07 +00:00
//
// It supports tb.Silent option.
2017-11-26 00:44:32 +00:00
func (b *Bot) Unpin(chat *Chat) error {
2016-10-09 20:09:07 +00:00
params := map[string]string{
2017-11-19 15:16:39 +00:00
"chat_id": chat.Recipient(),
2016-10-09 20:09:07 +00:00
}
2017-11-18 10:19:58 +00:00
_, err := b.Raw("unpinChatMessage", params)
return err
2016-10-09 20:09:07 +00:00
}
2017-11-26 00:44:32 +00:00
// ChatByID fetches chat info of its ID.
//
// Including current name of the user for one-on-one conversations,
// current username of a user, group or channel, etc.
//
// Returns a Chat object on success.
func (b *Bot) ChatByID(id string) (*Chat, error) {
2016-10-09 20:11:23 +00:00
params := map[string]string{
2017-11-26 00:44:32 +00:00
"chat_id": id,
2016-10-09 20:11:23 +00:00
}
2017-11-18 10:19:58 +00:00
2020-04-06 13:04:25 +00:00
data, err := b.Raw("getChat", params)
2016-10-09 20:11:23 +00:00
if err != nil {
2017-11-26 00:44:32 +00:00
return nil, err
2016-10-09 20:11:23 +00:00
}
2017-11-18 10:19:58 +00:00
var resp struct {
Result *Chat
2016-10-09 20:11:23 +00:00
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
2016-10-09 20:11:23 +00:00
}
if resp.Result.Type == ChatChannel && resp.Result.Username == "" {
resp.Result.Type = ChatChannelPrivate
}
2017-11-18 10:19:58 +00:00
return resp.Result, nil
2016-10-09 20:11:23 +00:00
}
// ProfilePhotosOf returns list of profile pictures for a user.
2017-11-19 15:16:39 +00:00
func (b *Bot) ProfilePhotosOf(user *User) ([]Photo, error) {
2016-10-09 20:29:03 +00:00
params := map[string]string{
2017-11-19 15:16:39 +00:00
"user_id": user.Recipient(),
2016-10-09 20:29:03 +00:00
}
2017-11-18 10:19:58 +00:00
2020-04-06 13:04:25 +00:00
data, err := b.Raw("getUserProfilePhotos", params)
2016-10-09 20:29:03 +00:00
if err != nil {
2017-11-18 10:19:58 +00:00
return nil, err
2016-10-09 20:29:03 +00:00
}
2017-11-18 10:19:58 +00:00
var resp struct {
Result struct {
2017-11-19 15:16:39 +00:00
Count int `json:"total_count"`
Photos []Photo `json:"photos"`
2017-11-18 10:19:58 +00:00
}
2016-10-09 20:29:03 +00:00
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
2016-10-09 20:29:03 +00:00
}
2017-11-18 10:19:58 +00:00
return resp.Result.Photos, nil
2016-10-09 20:29:03 +00:00
}
// ChatMemberOf returns information about a member of a chat.
2016-10-09 20:16:33 +00:00
//
// Returns a ChatMember object on success.
func (b *Bot) ChatMemberOf(chat *Chat, user *User) (*ChatMember, error) {
2016-10-09 20:16:33 +00:00
params := map[string]string{
2017-11-19 15:16:39 +00:00
"chat_id": chat.Recipient(),
2017-11-18 14:41:23 +00:00
"user_id": user.Recipient(),
2016-10-09 20:16:33 +00:00
}
2017-11-18 10:19:58 +00:00
2020-04-06 13:04:25 +00:00
data, err := b.Raw("getChatMember", params)
2016-10-09 20:16:33 +00:00
if err != nil {
return nil, err
2016-10-09 20:16:33 +00:00
}
2017-11-18 10:19:58 +00:00
var resp struct {
Result *ChatMember
2016-10-09 20:16:33 +00:00
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
2016-10-09 20:16:33 +00:00
}
2017-11-18 10:19:58 +00:00
return resp.Result, nil
2016-10-09 20:16:33 +00:00
}
// FileURLByID returns direct url for files using FileId which you can get from File object
func (b *Bot) FileURLByID(fileID string) (string, error) {
2017-11-19 15:16:39 +00:00
f, err := b.FileByID(fileID)
if err != nil {
return "", err
}
return b.URL + "/file/bot" + b.Token + "/" + f.FilePath, nil
}
2020-04-26 12:55:04 +00:00
// GetCommands returns the current list of the bot's commands.
func (b *Bot) GetCommands() ([]Command, error) {
data, err := b.Raw("getMyCommands", nil)
if err != nil {
return nil, err
}
var resp struct {
Result []Command
}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, wrapError(err)
}
return resp.Result, nil
}
// SetCommands changes the list of the bot's commands.
func (b *Bot) SetCommands(cmds []Command) error {
data, _ := json.Marshal(cmds)
params := map[string]string{
"commands": string(data),
}
_, err := b.Raw("setMyCommands", params)
return err
2020-04-26 12:55:04 +00:00
}
2020-05-13 15:21:07 +00:00
2020-05-13 16:55:06 +00:00
func (b *Bot) NewMarkup() *ReplyMarkup {
2020-05-13 15:21:07 +00:00
return &ReplyMarkup{}
2020-05-13 16:55:06 +00:00
}