You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1779 lines
42 KiB
1779 lines
42 KiB
package telebot |
|
|
|
import ( |
|
"encoding/json" |
|
"fmt" |
|
"io" |
|
"net/http" |
|
"os" |
|
"path" |
|
"regexp" |
|
"strconv" |
|
"strings" |
|
|
|
"github.com/pkg/errors" |
|
) |
|
|
|
// 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 |
|
} |
|
|
|
client := pref.Client |
|
if client == nil { |
|
client = http.DefaultClient |
|
} |
|
|
|
if pref.URL == "" { |
|
pref.URL = DefaultApiURL |
|
} |
|
|
|
bot := &Bot{ |
|
Token: pref.Token, |
|
URL: pref.URL, |
|
Updates: make(chan Update, pref.Updates), |
|
Poller: pref.Poller, |
|
|
|
handlers: make(map[string]interface{}), |
|
synchronous: pref.Synchronous, |
|
verbose: pref.Verbose, |
|
parseMode: pref.ParseMode, |
|
stop: make(chan struct{}), |
|
reporter: pref.Reporter, |
|
client: client, |
|
} |
|
|
|
if pref.Offline { |
|
bot.Me = &User{} |
|
} else { |
|
user, err := bot.getMe() |
|
if err != nil { |
|
return nil, err |
|
} |
|
bot.Me = user |
|
} |
|
|
|
return bot, nil |
|
} |
|
|
|
// Bot represents a separate Telegram bot instance. |
|
type Bot struct { |
|
Me *User |
|
Token string |
|
URL string |
|
Updates chan Update |
|
Poller Poller |
|
|
|
handlers map[string]interface{} |
|
synchronous bool |
|
verbose bool |
|
parseMode ParseMode |
|
reporter func(error) |
|
stop chan struct{} |
|
client *http.Client |
|
} |
|
|
|
// Settings represents a utility struct for passing certain |
|
// properties of a bot around and is required to make bots. |
|
type Settings struct { |
|
// Telegram API Url |
|
URL string |
|
|
|
// Telegram token |
|
Token string |
|
|
|
// 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 |
|
|
|
// Verbose forces bot to log all upcoming requests. |
|
// Use for debugging purposes only. |
|
Verbose bool |
|
|
|
// ParseMode used to set default parse mode of all sent messages. |
|
// It attaches to every send, edit or whatever method. You also |
|
// will be able to override the default mode by passing a new one. |
|
ParseMode ParseMode |
|
|
|
// 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 |
|
|
|
// 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"` |
|
|
|
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"` |
|
ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result,omitempty"` |
|
ShippingQuery *ShippingQuery `json:"shipping_query,omitempty"` |
|
PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query,omitempty"` |
|
Poll *Poll `json:"poll,omitempty"` |
|
PollAnswer *PollAnswer `json:"poll_answer,omitempty"` |
|
MyChatMember *ChatMemberUpdated `json:"my_chat_member,omitempty"` |
|
ChatMember *ChatMemberUpdated `json:"chat_member,omitempty"` |
|
} |
|
|
|
// Command represents a bot command. |
|
type Command struct { |
|
// Text is a text of the command, 1-32 characters. |
|
// Can contain only lowercase English letters, digits and underscores. |
|
Text string `json:"command"` |
|
|
|
// Description of the command, 3-256 characters. |
|
Description string `json:"description"` |
|
} |
|
|
|
// Handle lets you set the handler for some command name or |
|
// one of the supported endpoints. |
|
// |
|
// Example: |
|
// |
|
// b.Handle("/help", func (m *tb.Message) {}) |
|
// b.Handle(tb.OnText, func (m *tb.Message) {}) |
|
// b.Handle(tb.OnQuery, func (q *tb.Query) {}) |
|
// |
|
// // 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: |
|
b.handlers[end.CallbackUnique()] = handler |
|
default: |
|
panic("telebot: unsupported endpoint") |
|
} |
|
} |
|
|
|
var ( |
|
cmdRx = regexp.MustCompile(`^(/\w+)(@(\w+))?(\s|$)(.+)?`) |
|
cbackRx = regexp.MustCompile(`^\f([-\w]+)(\|(.+))?$`) |
|
) |
|
|
|
// 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) |
|
|
|
for { |
|
select { |
|
// handle incoming updates |
|
case upd := <-b.Updates: |
|
b.ProcessUpdate(upd) |
|
// call to stop polling |
|
case <-b.stop: |
|
close(stop) |
|
return |
|
} |
|
} |
|
} |
|
|
|
// 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. |
|
func (b *Bot) ProcessUpdate(upd Update) { |
|
if upd.Message != nil { |
|
m := upd.Message |
|
|
|
if m.PinnedMessage != nil { |
|
b.handle(OnPinned, m) |
|
return |
|
} |
|
|
|
// Commands |
|
if m.Text != "" { |
|
// Filtering malicious messages |
|
if m.Text[0] == '\a' { |
|
return |
|
} |
|
|
|
match := cmdRx.FindAllStringSubmatch(m.Text, -1) |
|
if match != nil { |
|
// Syntax: "</command>@<bot> <payload>" |
|
|
|
command, botName := match[0][1], match[0][3] |
|
if botName != "" && !strings.EqualFold(b.Me.Username, botName) { |
|
return |
|
} |
|
|
|
m.Payload = match[0][5] |
|
if b.handle(command, m) { |
|
return |
|
} |
|
} |
|
|
|
// 1:1 satisfaction |
|
if b.handle(m.Text, m) { |
|
return |
|
} |
|
|
|
b.handle(OnText, m) |
|
return |
|
} |
|
|
|
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)) |
|
if m.GroupCreated || m.SuperGroupCreated || wasAdded { |
|
b.handle(OnAddedToGroup, m) |
|
return |
|
} |
|
|
|
if m.UsersJoined != nil { |
|
for index := range m.UsersJoined { |
|
// Shallow copy message to prevent data race in async mode |
|
mm := *m |
|
mm.UserJoined = &m.UsersJoined[index] |
|
b.handle(OnUserJoined, &mm) |
|
} |
|
return |
|
} |
|
|
|
if m.UserJoined != nil { |
|
b.handle(OnUserJoined, m) |
|
return |
|
} |
|
|
|
if m.UserLeft != nil { |
|
b.handle(OnUserLeft, m) |
|
return |
|
} |
|
|
|
if m.NewGroupTitle != "" { |
|
b.handle(OnNewGroupTitle, m) |
|
return |
|
} |
|
|
|
if m.NewGroupPhoto != nil { |
|
b.handle(OnNewGroupPhoto, m) |
|
return |
|
} |
|
|
|
if m.GroupPhotoDeleted { |
|
b.handle(OnGroupPhotoDeleted, m) |
|
return |
|
} |
|
|
|
if m.GroupCreated { |
|
b.handle(OnGroupCreated, m) |
|
return |
|
} |
|
|
|
if m.SuperGroupCreated { |
|
b.handle(OnSuperGroupCreated, m) |
|
return |
|
} |
|
|
|
if m.ChannelCreated { |
|
b.handle(OnChannelCreated, m) |
|
return |
|
} |
|
|
|
if m.MigrateTo != 0 { |
|
if handler, ok := b.handlers[OnMigration]; ok { |
|
handler, ok := handler.(func(int64, int64)) |
|
if !ok { |
|
panic("telebot: migration handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(m.Chat.ID, m.MigrateTo) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if m.VoiceChatStarted != nil { |
|
if handler, ok := b.handlers[OnVoiceChatStarted]; ok { |
|
handler, ok := handler.(func(*Message)) |
|
if !ok { |
|
panic("telebot: voice chat started handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(m) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if m.VoiceChatEnded != nil { |
|
if handler, ok := b.handlers[OnVoiceChatEnded]; ok { |
|
handler, ok := handler.(func(*Message)) |
|
if !ok { |
|
panic("telebot: voice chat ended handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(m) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if m.VoiceChatParticipantsInvited != nil { |
|
if handler, ok := b.handlers[OnVoiceChatParticipantsInvited]; ok { |
|
handler, ok := handler.(func(*Message)) |
|
if !ok { |
|
panic("telebot: voice chat participants invited handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(m) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if m.ProximityAlert != nil { |
|
if handler, ok := b.handlers[OnProximityAlert]; ok { |
|
handler, ok := handler.(func(*Message)) |
|
if !ok { |
|
panic("telebot: proximity alert handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(m) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if m.AutoDeleteTimer != nil { |
|
if handler, ok := b.handlers[OnAutoDeleteTimer]; ok { |
|
handler, ok := handler.(func(*Message)) |
|
if !ok { |
|
panic("telebot: auto delete timer handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(m) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if m.VoiceChatSchedule != nil { |
|
if handler, ok := b.handlers[OnVoiceChatScheduled]; ok { |
|
handler, ok := handler.(func(*Message)) |
|
if !ok { |
|
panic("telebot: voice chat scheduled is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(m) }) |
|
} |
|
|
|
return |
|
} |
|
} |
|
|
|
if upd.EditedMessage != nil { |
|
b.handle(OnEdited, upd.EditedMessage) |
|
return |
|
} |
|
|
|
if upd.ChannelPost != nil { |
|
m := upd.ChannelPost |
|
|
|
if m.PinnedMessage != nil { |
|
b.handle(OnPinned, m) |
|
return |
|
} |
|
|
|
b.handle(OnChannelPost, upd.ChannelPost) |
|
return |
|
} |
|
|
|
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{ |
|
// InlineID indicates that message |
|
// is inline so we have only its id |
|
InlineID: upd.Callback.MessageID, |
|
} |
|
} |
|
|
|
data := upd.Callback.Data |
|
if data[0] == '\f' { |
|
match := cbackRx.FindAllStringSubmatch(data, -1) |
|
if match != nil { |
|
unique, payload := match[0][1], match[0][3] |
|
|
|
if handler, ok := b.handlers["\f"+unique]; ok { |
|
handler, ok := handler.(func(*Callback)) |
|
if !ok { |
|
panic(fmt.Errorf("telebot: %s callback handler is bad", unique)) |
|
} |
|
|
|
upd.Callback.Data = payload |
|
b.runHandler(func() { handler(upd.Callback) }) |
|
|
|
return |
|
} |
|
} |
|
} |
|
} |
|
|
|
if handler, ok := b.handlers[OnCallback]; ok { |
|
handler, ok := handler.(func(*Callback)) |
|
if !ok { |
|
panic("telebot: callback handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(upd.Callback) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if upd.Query != nil { |
|
if handler, ok := b.handlers[OnQuery]; ok { |
|
handler, ok := handler.(func(*Query)) |
|
if !ok { |
|
panic("telebot: query handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(upd.Query) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if upd.ChosenInlineResult != nil { |
|
if handler, ok := b.handlers[OnChosenInlineResult]; ok { |
|
handler, ok := handler.(func(*ChosenInlineResult)) |
|
if !ok { |
|
panic("telebot: chosen inline result handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(upd.ChosenInlineResult) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
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 |
|
} |
|
|
|
if upd.PreCheckoutQuery != nil { |
|
if handler, ok := b.handlers[OnCheckout]; ok { |
|
handler, ok := handler.(func(*PreCheckoutQuery)) |
|
if !ok { |
|
panic("telebot: pre checkout query handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(upd.PreCheckoutQuery) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if upd.Poll != nil { |
|
if handler, ok := b.handlers[OnPoll]; ok { |
|
handler, ok := handler.(func(*Poll)) |
|
if !ok { |
|
panic("telebot: poll handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(upd.Poll) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if upd.PollAnswer != nil { |
|
if handler, ok := b.handlers[OnPollAnswer]; ok { |
|
handler, ok := handler.(func(*PollAnswer)) |
|
if !ok { |
|
panic("telebot: poll answer handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(upd.PollAnswer) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if upd.MyChatMember != nil { |
|
if handler, ok := b.handlers[OnMyChatMember]; ok { |
|
handler, ok := handler.(func(*ChatMemberUpdated)) |
|
if !ok { |
|
panic("telebot: my chat member handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(upd.MyChatMember) }) |
|
} |
|
|
|
return |
|
} |
|
|
|
if upd.ChatMember != nil { |
|
if handler, ok := b.handlers[OnChatMember]; ok { |
|
handler, ok := handler.(func(*ChatMemberUpdated)) |
|
if !ok { |
|
panic("telebot: chat member handler is bad") |
|
} |
|
|
|
b.runHandler(func() { handler(upd.ChatMember) }) |
|
} |
|
|
|
return |
|
} |
|
} |
|
|
|
func (b *Bot) handle(end string, m *Message) bool { |
|
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 { |
|
switch { |
|
case m.Photo != nil: |
|
b.handle(OnPhoto, m) |
|
case m.Voice != nil: |
|
b.handle(OnVoice, m) |
|
case m.Audio != nil: |
|
b.handle(OnAudio, m) |
|
case m.Animation != nil: |
|
b.handle(OnAnimation, m) |
|
case m.Document != nil: |
|
b.handle(OnDocument, m) |
|
case m.Sticker != nil: |
|
b.handle(OnSticker, m) |
|
case m.Video != nil: |
|
b.handle(OnVideo, m) |
|
case m.VideoNote != nil: |
|
b.handle(OnVideoNote, m) |
|
case m.Contact != nil: |
|
b.handle(OnContact, m) |
|
case m.Location != nil: |
|
b.handle(OnLocation, m) |
|
case m.Venue != nil: |
|
b.handle(OnVenue, m) |
|
case m.Dice != nil: |
|
b.handle(OnDice, m) |
|
case m.Game != nil: |
|
b.handle(OnGame, m) |
|
default: |
|
return false |
|
} |
|
return true |
|
} |
|
|
|
// 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. |
|
// |
|
// 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) |
|
// |
|
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 |
|
} |
|
} |
|
|
|
// SendAlbum sends multiple instances of media as a single message. |
|
// |
|
// From all existing options, it only supports tb.Silent. |
|
func (b *Bot) SendAlbum(to Recipient, a Album, options ...interface{}) ([]Message, error) { |
|
if to == nil { |
|
return nil, ErrBadRecipient |
|
} |
|
|
|
sendOpts := extractOptions(options) |
|
|
|
media := make([]string, len(a)) |
|
files := make(map[string]File) |
|
|
|
for i, x := range a { |
|
var ( |
|
repr string |
|
data []byte |
|
file = x.MediaFile() |
|
) |
|
|
|
switch { |
|
case file.InCloud(): |
|
repr = file.FileID |
|
case file.FileURL != "": |
|
repr = file.FileURL |
|
case file.OnDisk() || file.FileReader != nil: |
|
repr = "attach://" + strconv.Itoa(i) |
|
files[strconv.Itoa(i)] = *file |
|
default: |
|
return nil, errors.Errorf("telebot: album entry #%d does not exist", i) |
|
} |
|
|
|
switch y := x.(type) { |
|
case *Photo: |
|
data, _ = json.Marshal(struct { |
|
Type string `json:"type"` |
|
Media string `json:"media"` |
|
Caption string `json:"caption,omitempty"` |
|
ParseMode string `json:"parse_mode,omitempty"` |
|
}{ |
|
Type: "photo", |
|
Media: repr, |
|
Caption: y.Caption, |
|
ParseMode: sendOpts.ParseMode, |
|
}) |
|
case *Video: |
|
data, _ = json.Marshal(struct { |
|
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"` |
|
ParseMode string `json:"parse_mode,omitempty"` |
|
}{ |
|
Type: "video", |
|
Caption: y.Caption, |
|
Media: repr, |
|
Width: y.Width, |
|
Height: y.Height, |
|
Duration: y.Duration, |
|
SupportsStreaming: y.SupportsStreaming, |
|
ParseMode: sendOpts.ParseMode, |
|
}) |
|
case *Audio: |
|
data, _ = json.Marshal(struct { |
|
Type string `json:"type"` |
|
Media string `json:"media"` |
|
Caption string `json:"caption,omitempty"` |
|
Duration int `json:"duration,omitempty"` |
|
Performer string `json:"performer,omitempty"` |
|
Title string `json:"title,omitempty"` |
|
ParseMode string `json:"parse_mode,omitempty"` |
|
}{ |
|
Type: "audio", |
|
Media: repr, |
|
Caption: y.Caption, |
|
Duration: y.Duration, |
|
Performer: y.Performer, |
|
Title: y.Title, |
|
ParseMode: sendOpts.ParseMode, |
|
}) |
|
case *Document: |
|
data, _ = json.Marshal(struct { |
|
Type string `json:"type"` |
|
Media string `json:"media"` |
|
Caption string `json:"caption,omitempty"` |
|
ParseMode string `json:"parse_mode,omitempty"` |
|
}{ |
|
Type: "document", |
|
Media: repr, |
|
Caption: y.Caption, |
|
ParseMode: sendOpts.ParseMode, |
|
}) |
|
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, ",") + "]", |
|
} |
|
b.embedSendOptions(params, sendOpts) |
|
|
|
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) |
|
r := resp.Result[i] |
|
|
|
var newID string |
|
switch { |
|
case r.Photo != nil: |
|
newID = r.Photo.FileID |
|
case r.Video != nil: |
|
newID = r.Video.FileID |
|
case r.Audio != nil: |
|
newID = r.Audio.FileID |
|
case r.Document != nil: |
|
newID = r.Document.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{} |
|
} |
|
|
|
sendOpts.ReplyTo = to |
|
return b.Send(to.Chat, what, sendOpts) |
|
} |
|
|
|
// Forward behaves just like Send() but of all options it only supports Silent (see Bots API). |
|
// |
|
// 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{ |
|
"chat_id": to.Recipient(), |
|
"from_chat_id": strconv.FormatInt(chatID, 10), |
|
"message_id": msgID, |
|
} |
|
|
|
sendOpts := extractOptions(options) |
|
b.embedSendOptions(params, sendOpts) |
|
|
|
data, err := b.Raw("forwardMessage", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return extractMessage(data) |
|
} |
|
|
|
// Copy behaves just like Forward() but the copied message doesn't have a link to the original message (see Bots API). |
|
// |
|
// This function will panic upon nil Editable. |
|
func (b *Bot) Copy(to Recipient, msg Editable, options ...interface{}) (*Message, error) { |
|
if to == nil { |
|
return nil, ErrBadRecipient |
|
} |
|
msgID, chatID := msg.MessageSig() |
|
|
|
params := map[string]string{ |
|
"chat_id": to.Recipient(), |
|
"from_chat_id": strconv.FormatInt(chatID, 10), |
|
"message_id": msgID, |
|
} |
|
|
|
sendOpts := extractOptions(options) |
|
b.embedSendOptions(params, sendOpts) |
|
|
|
data, err := b.Raw("copyMessage", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return extractMessage(data) |
|
} |
|
|
|
// Edit is magic, it lets you change already sent message. |
|
// |
|
// If edited message is sent by the bot, returns it, |
|
// otherwise returns nil and ErrTrueResult. |
|
// |
|
// Use cases: |
|
// |
|
// 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}) |
|
// |
|
// This function will panic upon nil Editable. |
|
func (b *Bot) Edit(msg Editable, what interface{}, options ...interface{}) (*Message, error) { |
|
var ( |
|
method string |
|
params = make(map[string]string) |
|
) |
|
|
|
switch v := what.(type) { |
|
case *ReplyMarkup: |
|
return b.EditReplyMarkup(msg, v) |
|
case InputMedia: |
|
return b.EditMedia(msg, v, options...) |
|
case string: |
|
method = "editMessageText" |
|
params["text"] = v |
|
case Location: |
|
method = "editMessageLiveLocation" |
|
params["latitude"] = fmt.Sprintf("%f", v.Lat) |
|
params["longitude"] = fmt.Sprintf("%f", v.Lng) |
|
if v.HorizontalAccuracy != nil { |
|
params["horizontal_accuracy"] = fmt.Sprintf("%f", *v.HorizontalAccuracy) |
|
} |
|
if v.Heading != 0 { |
|
params["heading"] = strconv.Itoa(v.Heading) |
|
} |
|
if v.ProximityAlertRadius != 0 { |
|
params["proximity_alert_radius"] = strconv.Itoa(v.Heading) |
|
} |
|
default: |
|
return nil, ErrUnsupportedWhat |
|
} |
|
|
|
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) |
|
b.embedSendOptions(params, sendOpts) |
|
|
|
data, err := b.Raw(method, params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return extractMessage(data) |
|
} |
|
|
|
// EditReplyMarkup edits reply markup of already sent message. |
|
// Pass nil or empty ReplyMarkup to delete it from the message. |
|
// |
|
// If edited message is sent by the bot, returns it, |
|
// otherwise returns nil and ErrTrueResult. |
|
// |
|
// 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() |
|
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) |
|
|
|
data, err := b.Raw("editMessageReplyMarkup", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return extractMessage(data) |
|
} |
|
|
|
// EditCaption edits already sent photo caption with known recipient and message id. |
|
// |
|
// If edited message is sent by the bot, returns it, |
|
// otherwise returns nil and ErrTrueResult. |
|
// |
|
// 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) |
|
b.embedSendOptions(params, sendOpts) |
|
|
|
data, err := b.Raw("editMessageCaption", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return extractMessage(data) |
|
} |
|
|
|
// EditMedia edits already sent media with known recipient and message id. |
|
// |
|
// If edited message is sent by the bot, returns it, |
|
// otherwise returns nil and ErrTrueResult. |
|
// |
|
// Use cases: |
|
// |
|
// b.EditMedia(m, &tb.Photo{File: tb.FromDisk("chicken.jpg")}) |
|
// b.EditMedia(m, &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 { |
|
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. |
|
Width int `json:"width,omitempty"` |
|
Height int `json:"height,omitempty"` |
|
SupportsStreaming bool `json:"supports_streaming,omitempty"` |
|
|
|
// Video and audio. |
|
Duration int `json:"duration,omitempty"` |
|
|
|
// Document. |
|
FileName string `json:"file_name"` |
|
|
|
// Document, video and audio. |
|
Thumbnail string `json:"thumb,omitempty"` |
|
MIME string `json:"mime_type,omitempty"` |
|
|
|
// Audio. |
|
Title string `json:"title,omitempty"` |
|
Performer string `json:"performer,omitempty"` |
|
} |
|
|
|
result := &FileJSON{Media: repr} |
|
|
|
switch m := media.(type) { |
|
case *Photo: |
|
result.Type = "photo" |
|
result.Caption = m.Caption |
|
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 |
|
case *Document: |
|
result.Type = "document" |
|
result.Caption = m.Caption |
|
result.FileName = m.FileName |
|
result.MIME = m.MIME |
|
thumb = m.Thumbnail |
|
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 |
|
default: |
|
return nil, errors.Errorf("telebot: media entry is not valid") |
|
} |
|
|
|
msgID, chatID := msg.MessageSig() |
|
params := make(map[string]string) |
|
|
|
sendOpts := extractOptions(options) |
|
b.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 { |
|
return nil, err |
|
} |
|
|
|
return extractMessage(data) |
|
} |
|
|
|
// 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() |
|
|
|
params := map[string]string{ |
|
"chat_id": strconv.FormatInt(chatID, 10), |
|
"message_id": msgID, |
|
} |
|
|
|
_, err := b.Raw("deleteMessage", params) |
|
return err |
|
} |
|
|
|
// Notify updates the chat action for recipient. |
|
// |
|
// 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. |
|
// |
|
// 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), |
|
} |
|
|
|
_, err := b.Raw("sendChatAction", params) |
|
return err |
|
} |
|
|
|
// 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 |
|
// |
|
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 |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
// 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 |
|
|
|
for _, result := range resp.Results { |
|
result.Process() |
|
} |
|
|
|
_, err := b.Raw("answerInlineQuery", resp) |
|
return err |
|
} |
|
|
|
// Respond sends a response for a given callback query. A callback can |
|
// only be responded to once, subsequent attempts to respond to the same callback |
|
// will result in an error. |
|
// |
|
// Example: |
|
// |
|
// bot.Respond(c) |
|
// bot.Respond(c, response) |
|
// |
|
func (b *Bot) Respond(c *Callback, resp ...*CallbackResponse) error { |
|
var r *CallbackResponse |
|
if resp == nil { |
|
r = &CallbackResponse{} |
|
} else { |
|
r = resp[0] |
|
} |
|
|
|
r.CallbackID = c.ID |
|
_, err := b.Raw("answerCallbackQuery", r) |
|
return err |
|
} |
|
|
|
// FileByID returns full file object including File.FilePath, allowing you to |
|
// download the file from the server. |
|
// |
|
// 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{ |
|
"file_id": fileID, |
|
} |
|
|
|
data, err := b.Raw("getFile", params) |
|
if err != nil { |
|
return File{}, err |
|
} |
|
|
|
var resp struct { |
|
Result File |
|
} |
|
if err := json.Unmarshal(data, &resp); err != nil { |
|
return File{}, wrapError(err) |
|
} |
|
return resp.Result, nil |
|
} |
|
|
|
// Download saves the file from Telegram servers locally. |
|
// |
|
// Maximum file size to download is 20 MB. |
|
func (b *Bot) Download(file *File, localFilename string) error { |
|
reader, err := b.GetFile(file) |
|
if err != nil { |
|
return wrapError(err) |
|
} |
|
defer reader.Close() |
|
|
|
out, err := os.Create(localFilename) |
|
if err != nil { |
|
return wrapError(err) |
|
} |
|
defer out.Close() |
|
|
|
_, err = io.Copy(out, reader) |
|
if err != nil { |
|
return wrapError(err) |
|
} |
|
|
|
file.FileLocal = localFilename |
|
return nil |
|
} |
|
|
|
// GetFile gets a file from Telegram servers. |
|
func (b *Bot) GetFile(file *File) (io.ReadCloser, error) { |
|
f, err := b.FileByID(file.FileID) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
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 { |
|
return nil, wrapError(err) |
|
} |
|
|
|
resp, err := b.client.Do(req) |
|
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) |
|
} |
|
|
|
return resp.Body, nil |
|
} |
|
|
|
// StopLiveLocation stops broadcasting live message location |
|
// before Location.LivePeriod expires. |
|
// |
|
// If the message is sent by the bot, returns it, |
|
// otherwise returns nil and ErrTrueResult. |
|
// |
|
// It supports tb.ReplyMarkup. |
|
// This function will panic upon nil Editable. |
|
func (b *Bot) StopLiveLocation(msg Editable, options ...interface{}) (*Message, error) { |
|
msgID, chatID := msg.MessageSig() |
|
|
|
params := map[string]string{ |
|
"chat_id": strconv.FormatInt(chatID, 10), |
|
"message_id": msgID, |
|
} |
|
|
|
sendOpts := extractOptions(options) |
|
b.embedSendOptions(params, sendOpts) |
|
|
|
data, err := b.Raw("stopMessageLiveLocation", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return extractMessage(data) |
|
} |
|
|
|
// 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) |
|
b.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 |
|
} |
|
|
|
// 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(), |
|
} |
|
|
|
data, err := b.Raw("exportChatInviteLink", params) |
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
var resp struct { |
|
Result string |
|
} |
|
if err := json.Unmarshal(data, &resp); err != nil { |
|
return "", wrapError(err) |
|
} |
|
return resp.Result, nil |
|
} |
|
|
|
// SetGroupTitle should be used to update group title. |
|
func (b *Bot) SetGroupTitle(chat *Chat, title string) error { |
|
params := map[string]string{ |
|
"chat_id": chat.Recipient(), |
|
"title": title, |
|
} |
|
|
|
_, err := b.Raw("setChatTitle", params) |
|
return err |
|
} |
|
|
|
// SetGroupDescription should be used to update group description. |
|
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 |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
// SetGroupPermissions sets default chat permissions for all members. |
|
func (b *Bot) SetGroupPermissions(chat *Chat, perms Rights) error { |
|
params := map[string]interface{}{ |
|
"chat_id": chat.Recipient(), |
|
} |
|
embedRights(params, perms) |
|
|
|
_, err := b.Raw("setChatPermissions", params) |
|
return err |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
// Leave makes bot leave a group, supergroup or channel. |
|
func (b *Bot) Leave(chat *Chat) error { |
|
params := map[string]string{ |
|
"chat_id": chat.Recipient(), |
|
} |
|
|
|
_, err := b.Raw("leaveChat", params) |
|
return err |
|
} |
|
|
|
// 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() |
|
|
|
params := map[string]string{ |
|
"chat_id": strconv.FormatInt(chatID, 10), |
|
"message_id": msgID, |
|
} |
|
|
|
sendOpts := extractOptions(options) |
|
b.embedSendOptions(params, sendOpts) |
|
|
|
_, err := b.Raw("pinChatMessage", params) |
|
return err |
|
} |
|
|
|
// Unpin unpins a message in a supergroup or a channel. |
|
// |
|
// It supports tb.Silent option. |
|
// MessageID is a specific pinned message |
|
func (b *Bot) Unpin(chat *Chat, messageID ...int) error { |
|
params := map[string]string{ |
|
"chat_id": chat.Recipient(), |
|
} |
|
if len(messageID) > 0 { |
|
params["message_id"] = strconv.Itoa(messageID[0]) |
|
} |
|
|
|
_, err := b.Raw("unpinChatMessage", params) |
|
return err |
|
} |
|
|
|
// UnpinAll unpins all messages in a supergroup or a channel. |
|
// |
|
// It supports tb.Silent option. |
|
func (b *Bot) UnpinAll(chat *Chat) error { |
|
params := map[string]string{ |
|
"chat_id": chat.Recipient(), |
|
} |
|
|
|
_, err := b.Raw("unpinAllChatMessages", params) |
|
return err |
|
} |
|
|
|
// 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) { |
|
params := map[string]string{ |
|
"chat_id": id, |
|
} |
|
|
|
data, err := b.Raw("getChat", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var resp struct { |
|
Result *Chat |
|
} |
|
if err := json.Unmarshal(data, &resp); err != nil { |
|
return nil, wrapError(err) |
|
} |
|
if resp.Result.Type == ChatChannel && resp.Result.Username == "" { |
|
resp.Result.Type = ChatChannelPrivate |
|
} |
|
return resp.Result, nil |
|
} |
|
|
|
// ProfilePhotosOf returns list of profile pictures for a user. |
|
func (b *Bot) ProfilePhotosOf(user *User) ([]Photo, error) { |
|
params := map[string]string{ |
|
"user_id": user.Recipient(), |
|
} |
|
|
|
data, err := b.Raw("getUserProfilePhotos", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var resp struct { |
|
Result struct { |
|
Count int `json:"total_count"` |
|
Photos []Photo `json:"photos"` |
|
} |
|
} |
|
if err := json.Unmarshal(data, &resp); err != nil { |
|
return nil, wrapError(err) |
|
} |
|
return resp.Result.Photos, nil |
|
} |
|
|
|
// ChatMemberOf returns information about a member of a chat. |
|
// |
|
// Returns a ChatMember object on success. |
|
func (b *Bot) ChatMemberOf(chat *Chat, user *User) (*ChatMember, error) { |
|
params := map[string]string{ |
|
"chat_id": chat.Recipient(), |
|
"user_id": user.Recipient(), |
|
} |
|
|
|
data, err := b.Raw("getChatMember", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var resp struct { |
|
Result *ChatMember |
|
} |
|
if err := json.Unmarshal(data, &resp); err != nil { |
|
return nil, wrapError(err) |
|
} |
|
return resp.Result, nil |
|
} |
|
|
|
// FileURLByID returns direct url for files using FileID which you can get from |
|
// File object. It returns a file:/// URL when the target file is on the |
|
// local disk (it happens if you are using a local Bot API server, see |
|
// https://core.telegram.org/bots/api#using-a-local-bot-api-server for details). |
|
func (b *Bot) FileURLByID(fileID string) (string, error) { |
|
f, err := b.FileByID(fileID) |
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
if path.IsAbs(f.FilePath) { |
|
return "file://" + f.FilePath, nil |
|
} |
|
|
|
return b.URL + "/file/bot" + b.Token + "/" + f.FilePath, nil |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
func (b *Bot) NewMarkup() *ReplyMarkup { |
|
return &ReplyMarkup{} |
|
} |
|
|
|
// Logout logs out from the cloud Bot API server before launching the bot locally. |
|
func (b *Bot) Logout() (bool, error) { |
|
data, err := b.Raw("logOut", nil) |
|
if err != nil { |
|
return false, err |
|
} |
|
|
|
var resp struct { |
|
Result bool `json:"result"` |
|
} |
|
if err := json.Unmarshal(data, &resp); err != nil { |
|
return false, wrapError(err) |
|
} |
|
|
|
return resp.Result, nil |
|
} |
|
|
|
// Close closes the bot instance before moving it from one local server to another. |
|
func (b *Bot) Close() (bool, error) { |
|
data, err := b.Raw("close", nil) |
|
if err != nil { |
|
return false, err |
|
} |
|
|
|
var resp struct { |
|
Result bool `json:"result"` |
|
} |
|
if err := json.Unmarshal(data, &resp); err != nil { |
|
return false, wrapError(err) |
|
} |
|
|
|
return resp.Result, nil |
|
} |
|
|
|
// CreateInviteLink creates an additional invite link for a chat. |
|
func (b *Bot) CreateInviteLink(chat *Chat, link *ChatInviteLink) (*ChatInviteLink, error) { |
|
params := map[string]string{ |
|
"chat_id": chat.Recipient(), |
|
} |
|
if link != nil { |
|
params["expire_date"] = strconv.FormatInt(link.ExpireUnixtime, 10) |
|
params["member_limit"] = strconv.Itoa(link.MemberLimit) |
|
} |
|
|
|
data, err := b.Raw("createChatInviteLink", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var resp struct { |
|
Result ChatInviteLink `json:"result"` |
|
} |
|
if err := json.Unmarshal(data, &resp); err != nil { |
|
return nil, wrapError(err) |
|
} |
|
|
|
return &resp.Result, nil |
|
} |
|
|
|
// EditInviteLink edits a non-primary invite link created by the bot. |
|
func (b *Bot) EditInviteLink(chat *Chat, link *ChatInviteLink) (*ChatInviteLink, error) { |
|
params := map[string]string{ |
|
"chat_id": chat.Recipient(), |
|
} |
|
if link != nil { |
|
params["invite_link"] = link.InviteLink |
|
params["expire_date"] = strconv.FormatInt(link.ExpireUnixtime, 10) |
|
params["member_limit"] = strconv.Itoa(link.MemberLimit) |
|
} |
|
|
|
data, err := b.Raw("editChatInviteLink", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var resp struct { |
|
Result ChatInviteLink `json:"result"` |
|
} |
|
if err := json.Unmarshal(data, &resp); err != nil { |
|
return nil, wrapError(err) |
|
} |
|
|
|
return &resp.Result, nil |
|
} |
|
|
|
// RevokeInviteLink revokes an invite link created by the bot. |
|
func (b *Bot) RevokeInviteLink(chat *Chat, link string) (*ChatInviteLink, error) { |
|
params := map[string]string{ |
|
"chat_id": chat.Recipient(), |
|
"invite_link": link, |
|
} |
|
|
|
data, err := b.Raw("revokeChatInviteLink", params) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var resp struct { |
|
Result ChatInviteLink `json:"result"` |
|
} |
|
if err := json.Unmarshal(data, &resp); err != nil { |
|
return nil, wrapError(err) |
|
} |
|
|
|
return &resp.Result, nil |
|
}
|
|
|