Merge branch 'develop' into develop

bot-api-6.1
Syfaro 2021-03-10 22:06:43 -05:00 committed by GitHub
commit 284b093107
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 4734 additions and 1286 deletions

33
.github/workflows/test.yml vendored 100644
View File

@ -0,0 +1,33 @@
name: Test
on:
push:
branches:
- master
- develop
pull_request:
jobs:
build:
name: Test
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.15
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Build
run: go build -v .
- name: Test
run: go test -coverprofile=coverage.out -covermode=atomic -v .
- name: Upload coverage report
uses: codecov/codecov-action@v1
with:
file: ./coverage.out

View File

@ -1,6 +0,0 @@
language: go
go:
- '1.10'
- '1.11'
- tip

View File

@ -3,7 +3,7 @@
[![GoDoc](https://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api?status.svg)](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api) [![GoDoc](https://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api?status.svg)](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api)
[![Travis](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api.svg)](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api) [![Travis](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api.svg)](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api)
All methods are fairly self explanatory, and reading the godoc page should All methods are fairly self explanatory, and reading the [godoc](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api) page should
explain everything. If something isn't clear, open an issue or submit explain everything. If something isn't clear, open an issue or submit
a pull request. a pull request.

394
bot.go
View File

@ -9,43 +9,60 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"mime/multipart"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strings" "strings"
"time" "time"
"github.com/technoweenie/multipartstreamer"
) )
// HTTPClient is the type needed for the bot to perform HTTP requests.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
PostForm(url string, data url.Values) (*http.Response, error)
}
// BotAPI allows you to interact with the Telegram Bot API. // BotAPI allows you to interact with the Telegram Bot API.
type BotAPI struct { type BotAPI struct {
Token string `json:"token"` Token string `json:"token"`
Debug bool `json:"debug"` Debug bool `json:"debug"`
Buffer int `json:"buffer"` Buffer int `json:"buffer"`
Self User `json:"-"` Self User `json:"-"`
Client *http.Client `json:"-"` Client HTTPClient `json:"-"`
shutdownChannel chan interface{} shutdownChannel chan interface{}
apiEndpoint string
} }
// NewBotAPI creates a new BotAPI instance. // NewBotAPI creates a new BotAPI instance.
// //
// It requires a token, provided by @BotFather on Telegram. // It requires a token, provided by @BotFather on Telegram.
func NewBotAPI(token string) (*BotAPI, error) { func NewBotAPI(token string) (*BotAPI, error) {
return NewBotAPIWithClient(token, &http.Client{}) return NewBotAPIWithClient(token, APIEndpoint, &http.Client{})
}
// NewBotAPIWithAPIEndpoint creates a new BotAPI instance
// and allows you to pass API endpoint.
//
// It requires a token, provided by @BotFather on Telegram and API endpoint.
func NewBotAPIWithAPIEndpoint(token, apiEndpoint string) (*BotAPI, error) {
return NewBotAPIWithClient(token, apiEndpoint, &http.Client{})
} }
// NewBotAPIWithClient creates a new BotAPI instance // NewBotAPIWithClient creates a new BotAPI instance
// and allows you to pass a http.Client. // and allows you to pass a http.Client.
// //
// It requires a token, provided by @BotFather on Telegram. // It requires a token, provided by @BotFather on Telegram and API endpoint.
func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { func NewBotAPIWithClient(token, apiEndpoint string, client HTTPClient) (*BotAPI, error) {
bot := &BotAPI{ bot := &BotAPI{
Token: token, Token: token,
Client: client, Client: client,
Buffer: 100, Buffer: 100,
shutdownChannel: make(chan interface{}), shutdownChannel: make(chan interface{}),
apiEndpoint: apiEndpoint,
} }
self, err := bot.GetMe() self, err := bot.GetMe()
@ -58,6 +75,11 @@ func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) {
return bot, nil return bot, nil
} }
// SetAPIEndpoint changes the Telegram Bot API endpoint used by the instance.
func (bot *BotAPI) SetAPIEndpoint(apiEndpoint string) {
bot.apiEndpoint = apiEndpoint
}
func buildParams(in Params) (out url.Values) { func buildParams(in Params) (out url.Values) {
if in == nil { if in == nil {
return url.Values{} return url.Values{}
@ -73,25 +95,25 @@ func buildParams(in Params) (out url.Values) {
} }
// MakeRequest makes a request to a specific endpoint with our token. // MakeRequest makes a request to a specific endpoint with our token.
func (bot *BotAPI) MakeRequest(endpoint string, params Params) (APIResponse, error) { func (bot *BotAPI) MakeRequest(endpoint string, params Params) (*APIResponse, error) {
if bot.Debug { if bot.Debug {
log.Printf("Endpoint: %s, params: %v\n", endpoint, params) log.Printf("Endpoint: %s, params: %v\n", endpoint, params)
} }
method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint) method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
values := buildParams(params) values := buildParams(params)
resp, err := bot.Client.PostForm(method, values) resp, err := bot.Client.PostForm(method, values)
if err != nil { if err != nil {
return APIResponse{}, err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
var apiResp APIResponse var apiResp APIResponse
bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp) bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
if err != nil { if err != nil {
return apiResp, err return &apiResp, err
} }
if bot.Debug { if bot.Debug {
@ -105,13 +127,14 @@ func (bot *BotAPI) MakeRequest(endpoint string, params Params) (APIResponse, err
parameters = *apiResp.Parameters parameters = *apiResp.Parameters
} }
return apiResp, Error{ return &apiResp, &Error{
Code: apiResp.ErrorCode,
Message: apiResp.Description, Message: apiResp.Description,
ResponseParameters: parameters, ResponseParameters: parameters,
} }
} }
return apiResp, nil return &apiResp, nil
} }
// decodeAPIResponse decode response and return slice of bytes if debug enabled. // decodeAPIResponse decode response and return slice of bytes if debug enabled.
@ -138,103 +161,120 @@ func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse)
return data, nil return data, nil
} }
// UploadFile makes a request to the API with a file. // UploadFiles makes a request to the API with files.
// func (bot *BotAPI) UploadFiles(endpoint string, params Params, files []RequestFile) (*APIResponse, error) {
// Requires the parameter to hold the file not be in the params. r, w := io.Pipe()
// File should be a string to a file path, a FileBytes struct, m := multipart.NewWriter(w)
// a FileReader struct, or a url.URL.
//
// Note that if your FileReader has a size set to -1, it will read
// the file into memory to calculate a size.
func (bot *BotAPI) UploadFile(endpoint string, params Params, fieldname string, file interface{}) (APIResponse, error) {
ms := multipartstreamer.New()
switch f := file.(type) { // This code modified from the very helpful @HirbodBehnam
case string: // https://github.com/go-telegram-bot-api/telegram-bot-api/issues/354#issuecomment-663856473
ms.WriteFields(params) go func() {
defer w.Close()
defer m.Close()
fileHandle, err := os.Open(f) for field, value := range params {
if err != nil { if err := m.WriteField(field, value); err != nil {
return APIResponse{}, err w.CloseWithError(err)
} return
defer fileHandle.Close() }
fi, err := os.Stat(f)
if err != nil {
return APIResponse{}, err
} }
ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle) for _, file := range files {
case FileBytes: switch f := file.File.(type) {
ms.WriteFields(params) case string:
fileHandle, err := os.Open(f)
if err != nil {
w.CloseWithError(err)
return
}
defer fileHandle.Close()
buf := bytes.NewBuffer(f.Bytes) part, err := m.CreateFormFile(file.Name, fileHandle.Name())
ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf) if err != nil {
case FileReader: w.CloseWithError(err)
ms.WriteFields(params) return
}
if f.Size != -1 { io.Copy(part, fileHandle)
ms.WriteReader(fieldname, f.Name, f.Size, f.Reader) case FileBytes:
part, err := m.CreateFormFile(file.Name, f.Name)
if err != nil {
w.CloseWithError(err)
return
}
break buf := bytes.NewBuffer(f.Bytes)
io.Copy(part, buf)
case FileReader:
part, err := m.CreateFormFile(file.Name, f.Name)
if err != nil {
w.CloseWithError(err)
return
}
io.Copy(part, f.Reader)
case FileURL:
val := string(f)
if err := m.WriteField(file.Name, val); err != nil {
w.CloseWithError(err)
return
}
case FileID:
val := string(f)
if err := m.WriteField(file.Name, val); err != nil {
w.CloseWithError(err)
return
}
default:
w.CloseWithError(errors.New(ErrBadFileType))
return
}
} }
}()
data, err := ioutil.ReadAll(f.Reader)
if err != nil {
return APIResponse{}, err
}
buf := bytes.NewBuffer(data)
ms.WriteReader(fieldname, f.Name, int64(len(data)), buf)
case url.URL:
params[fieldname] = f.String()
ms.WriteFields(params)
default:
return APIResponse{}, errors.New(ErrBadFileType)
}
if bot.Debug { if bot.Debug {
log.Printf("Endpoint: %s, fieldname: %s, params: %v, file: %T\n", endpoint, fieldname, params, file) log.Printf("Endpoint: %s, params: %v, with %d files\n", endpoint, params, len(files))
} }
method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint) method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
req, err := http.NewRequest("POST", method, nil) req, err := http.NewRequest("POST", method, r)
if err != nil { if err != nil {
return APIResponse{}, err return nil, err
} }
ms.SetupRequest(req) req.Header.Set("Content-Type", m.FormDataContentType())
res, err := bot.Client.Do(req) resp, err := bot.Client.Do(req)
if err != nil { if err != nil {
return APIResponse{}, err return nil, err
} }
defer res.Body.Close() defer resp.Body.Close()
bytes, err := ioutil.ReadAll(res.Body) var apiResp APIResponse
bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
if err != nil { if err != nil {
return APIResponse{}, err return &apiResp, err
} }
if bot.Debug { if bot.Debug {
log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes)) log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
} }
var apiResp APIResponse
err = json.Unmarshal(bytes, &apiResp)
if err != nil {
return APIResponse{}, err
}
if !apiResp.Ok { if !apiResp.Ok {
return APIResponse{}, errors.New(apiResp.Description) var parameters ResponseParameters
if apiResp.Parameters != nil {
parameters = *apiResp.Parameters
}
return &apiResp, &Error{
Message: apiResp.Description,
ResponseParameters: parameters,
}
} }
return apiResp, nil return &apiResp, nil
} }
// GetFileDirectURL returns direct URL to file // GetFileDirectURL returns direct URL to file
@ -274,23 +314,54 @@ func (bot *BotAPI) IsMessageToMe(message Message) bool {
return strings.Contains(message.Text, "@"+bot.Self.UserName) return strings.Contains(message.Text, "@"+bot.Self.UserName)
} }
func hasFilesNeedingUpload(files []RequestFile) bool {
for _, file := range files {
switch file.File.(type) {
case string, FileBytes, FileReader:
return true
}
}
return false
}
// Request sends a Chattable to Telegram, and returns the APIResponse. // Request sends a Chattable to Telegram, and returns the APIResponse.
func (bot *BotAPI) Request(c Chattable) (APIResponse, error) { func (bot *BotAPI) Request(c Chattable) (*APIResponse, error) {
params, err := c.params() params, err := c.params()
if err != nil { if err != nil {
return APIResponse{}, err return nil, err
} }
switch t := c.(type) { if t, ok := c.(Fileable); ok {
case Fileable: files := t.files()
if t.useExistingFile() {
return bot.MakeRequest(t.method(), params) // If we have files that need to be uploaded, we should delegate the
// request to UploadFile.
if hasFilesNeedingUpload(files) {
return bot.UploadFiles(t.method(), params, files)
} }
return bot.UploadFile(t.method(), params, t.name(), t.getFile()) // However, if there are no files to be uploaded, there's likely things
default: // that need to be turned into params instead.
return bot.MakeRequest(c.method(), params) for _, file := range files {
var s string
switch f := file.File.(type) {
case string:
s = f
case FileID:
s = string(f)
case FileURL:
s = string(f)
default:
return nil, errors.New(ErrBadFileType)
}
params[file.Name] = s
}
} }
return bot.MakeRequest(c.method(), params)
} }
// Send will send a Chattable item to Telegram and provides the // Send will send a Chattable item to Telegram and provides the
@ -309,9 +380,7 @@ func (bot *BotAPI) Send(c Chattable) (Message, error) {
// SendMediaGroup sends a media group and returns the resulting messages. // SendMediaGroup sends a media group and returns the resulting messages.
func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) { func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -327,9 +396,7 @@ func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) {
// It requires UserID. // It requires UserID.
// Offset and Limit are optional. // Offset and Limit are optional.
func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) { func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return UserProfilePhotos{}, err return UserProfilePhotos{}, err
} }
@ -344,9 +411,7 @@ func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserPro
// //
// Requires FileID. // Requires FileID.
func (bot *BotAPI) GetFile(config FileConfig) (File, error) { func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return File{}, err return File{}, err
} }
@ -360,14 +425,12 @@ func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
// GetUpdates fetches updates. // GetUpdates fetches updates.
// If a WebHook is set, this will not return any data! // If a WebHook is set, this will not return any data!
// //
// Offset, Limit, and Timeout are optional. // Offset, Limit, Timeout, and AllowedUpdates are optional.
// To avoid stale items, set Offset to one higher than the previous item. // To avoid stale items, set Offset to one higher than the previous item.
// Set Timeout to a large number to reduce requests so you can get updates // Set Timeout to a large number to reduce requests so you can get updates
// instantly instead of having to wait between requests. // instantly instead of having to wait between requests.
func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) { func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return []Update{}, err return []Update{}, err
} }
@ -400,6 +463,7 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel {
for { for {
select { select {
case <-bot.shutdownChannel: case <-bot.shutdownChannel:
close(ch)
return return
default: default:
} }
@ -438,22 +502,66 @@ func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
ch := make(chan Update, bot.Buffer) ch := make(chan Update, bot.Buffer)
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
bytes, _ := ioutil.ReadAll(r.Body) update, err := bot.HandleUpdate(r)
if err != nil {
errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(errMsg)
return
}
var update Update ch <- *update
json.Unmarshal(bytes, &update)
ch <- update
}) })
return ch return ch
} }
// HandleUpdate parses and returns update received via webhook
func (bot *BotAPI) HandleUpdate(r *http.Request) (*Update, error) {
if r.Method != http.MethodPost {
err := errors.New("wrong HTTP method required POST")
return nil, err
}
var update Update
err := json.NewDecoder(r.Body).Decode(&update)
if err != nil {
return nil, err
}
return &update, nil
}
// WriteToHTTPResponse writes the request to the HTTP ResponseWriter.
//
// It doesn't support uploading files.
//
// See https://core.telegram.org/bots/api#making-requests-when-getting-updates
// for details.
func WriteToHTTPResponse(w http.ResponseWriter, c Chattable) error {
params, err := c.params()
if err != nil {
return err
}
if t, ok := c.(Fileable); ok {
if hasFilesNeedingUpload(t.files()) {
return errors.New("unable to use http response to upload files")
}
}
values := buildParams(params)
values.Set("method", c.method())
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
_, err = w.Write([]byte(values.Encode()))
return err
}
// GetChat gets information about a chat. // GetChat gets information about a chat.
func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) { func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return Chat{}, err return Chat{}, err
} }
@ -469,9 +577,7 @@ func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
// If none have been appointed, only the creator will be returned. // If none have been appointed, only the creator will be returned.
// Bots are not shown, even if they are an administrator. // Bots are not shown, even if they are an administrator.
func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) { func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return []ChatMember{}, err return []ChatMember{}, err
} }
@ -484,9 +590,7 @@ func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]Cha
// GetChatMembersCount gets the number of users in a chat. // GetChatMembersCount gets the number of users in a chat.
func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) { func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return -1, err return -1, err
} }
@ -499,9 +603,7 @@ func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error
// GetChatMember gets a specific chat member. // GetChatMember gets a specific chat member.
func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) { func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return ChatMember{}, err return ChatMember{}, err
} }
@ -514,9 +616,7 @@ func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error)
// GetGameHighScores allows you to get the high scores for a game. // GetGameHighScores allows you to get the high scores for a game.
func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) { func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return []GameHighScore{}, err return []GameHighScore{}, err
} }
@ -529,9 +629,7 @@ func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHigh
// GetInviteLink get InviteLink for a chat // GetInviteLink get InviteLink for a chat
func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) { func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -544,9 +642,7 @@ func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
// GetStickerSet returns a StickerSet. // GetStickerSet returns a StickerSet.
func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) { func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
params, _ := config.params() resp, err := bot.Request(config)
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return StickerSet{}, err return StickerSet{}, err
} }
@ -559,12 +655,7 @@ func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error)
// StopPoll stops a poll and returns the result. // StopPoll stops a poll and returns the result.
func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) { func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) {
params, err := config.params() resp, err := bot.Request(config)
if err != nil {
return Poll{}, err
}
resp, err := bot.MakeRequest(config.method(), params)
if err != nil { if err != nil {
return Poll{}, err return Poll{}, err
} }
@ -574,3 +665,38 @@ func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) {
return poll, err return poll, err
} }
// GetMyCommands gets the currently registered commands.
func (bot *BotAPI) GetMyCommands() ([]BotCommand, error) {
config := GetMyCommandsConfig{}
resp, err := bot.Request(config)
if err != nil {
return nil, err
}
var commands []BotCommand
err = json.Unmarshal(resp.Result, &commands)
return commands, err
}
// CopyMessage copy messages of any kind. The method is analogous to the method
// forwardMessage, but the copied message doesn't have a link to the original
// message. Returns the MessageID of the sent message on success.
func (bot *BotAPI) CopyMessage(config CopyMessageConfig) (MessageID, error) {
params, err := config.params()
if err != nil {
return MessageID{}, err
}
resp, err := bot.MakeRequest(config.method(), params)
if err != nil {
return MessageID{}, err
}
var messageID MessageID
err = json.Unmarshal(resp.Result, &messageID)
return messageID, err
}

View File

@ -11,6 +11,7 @@ import (
const ( const (
TestToken = "153667468:AAHlSHlMqSt1f_uFmVRJbm5gntu2HI4WW8I" TestToken = "153667468:AAHlSHlMqSt1f_uFmVRJbm5gntu2HI4WW8I"
ChatID = 76918703 ChatID = 76918703
Channel = "@tgbotapitest"
SupergroupChatID = -1001120141283 SupergroupChatID = -1001120141283
ReplyToMessageID = 35 ReplyToMessageID = 35
ExistingPhotoFileID = "AgADAgADw6cxG4zHKAkr42N7RwEN3IFShCoABHQwXEtVks4EH2wBAAEC" ExistingPhotoFileID = "AgADAgADw6cxG4zHKAkr42N7RwEN3IFShCoABHQwXEtVks4EH2wBAAEC"
@ -22,13 +23,27 @@ const (
ExistingStickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg" ExistingStickerFileID = "BQADAgADcwADjMcoCbdl-6eB--YPAg"
) )
type testLogger struct {
t *testing.T
}
func (t testLogger) Println(v ...interface{}) {
t.t.Log(v...)
}
func (t testLogger) Printf(format string, v ...interface{}) {
t.t.Logf(format, v...)
}
func getBot(t *testing.T) (*BotAPI, error) { func getBot(t *testing.T) (*BotAPI, error) {
bot, err := NewBotAPI(TestToken) bot, err := NewBotAPI(TestToken)
bot.Debug = true bot.Debug = true
logger := testLogger{t}
SetLogger(logger)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
return bot, err return bot, err
@ -39,7 +54,6 @@ func TestNewBotAPI_notoken(t *testing.T) {
if err == nil { if err == nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -52,7 +66,6 @@ func TestGetUpdates(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -60,12 +73,11 @@ func TestSendWithMessage(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api") msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api")
msg.ParseMode = "markdown" msg.ParseMode = ModeMarkdown
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -78,7 +90,6 @@ func TestSendWithMessageReply(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -90,20 +101,38 @@ func TestSendWithMessageForward(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail() }
}
func TestCopyMessage(t *testing.T) {
bot, _ := getBot(t)
msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api")
message, err := bot.Send(msg)
if err != nil {
t.Error(err)
}
copyMessageConfig := NewCopyMessage(SupergroupChatID, message.Chat.ID, message.MessageID)
messageID, err := bot.CopyMessage(copyMessageConfig)
if err != nil {
t.Error(err)
}
if messageID.MessageID == message.MessageID {
t.Error("copied message ID was the same as original message")
} }
} }
func TestSendWithNewPhoto(t *testing.T) { func TestSendWithNewPhoto(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewPhotoUpload(ChatID, "tests/image.jpg") msg := NewPhoto(ChatID, "tests/image.jpg")
msg.Caption = "Test" msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -113,13 +142,12 @@ func TestSendWithNewPhotoWithFileBytes(t *testing.T) {
data, _ := ioutil.ReadFile("tests/image.jpg") data, _ := ioutil.ReadFile("tests/image.jpg")
b := FileBytes{Name: "image.jpg", Bytes: data} b := FileBytes{Name: "image.jpg", Bytes: data}
msg := NewPhotoUpload(ChatID, b) msg := NewPhoto(ChatID, b)
msg.Caption = "Test" msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -127,9 +155,34 @@ func TestSendWithNewPhotoWithFileReader(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
f, _ := os.Open("tests/image.jpg") f, _ := os.Open("tests/image.jpg")
reader := FileReader{Name: "image.jpg", Reader: f, Size: -1} reader := FileReader{Name: "image.jpg", Reader: f}
msg := NewPhotoUpload(ChatID, reader) msg := NewPhoto(ChatID, reader)
msg.Caption = "Test"
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
}
}
func TestSendWithNewPhotoReply(t *testing.T) {
bot, _ := getBot(t)
msg := NewPhoto(ChatID, "tests/image.jpg")
msg.ReplyToMessageID = ReplyToMessageID
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
}
}
func TestSendNewPhotoToChannel(t *testing.T) {
bot, _ := getBot(t)
msg := NewPhotoToChannel(Channel, "tests/image.jpg")
msg.Caption = "Test" msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -139,12 +192,30 @@ func TestSendWithNewPhotoWithFileReader(t *testing.T) {
} }
} }
func TestSendWithNewPhotoReply(t *testing.T) { func TestSendNewPhotoToChannelFileBytes(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewPhotoUpload(ChatID, "tests/image.jpg") data, _ := ioutil.ReadFile("tests/image.jpg")
msg.ReplyToMessageID = ReplyToMessageID b := FileBytes{Name: "image.jpg", Bytes: data}
msg := NewPhotoToChannel(Channel, b)
msg.Caption = "Test"
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
t.Fail()
}
}
func TestSendNewPhotoToChannelFileReader(t *testing.T) {
bot, _ := getBot(t)
f, _ := os.Open("tests/image.jpg")
reader := FileReader{Name: "image.jpg", Reader: f}
msg := NewPhotoToChannel(Channel, reader)
msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
@ -156,61 +227,67 @@ func TestSendWithNewPhotoReply(t *testing.T) {
func TestSendWithExistingPhoto(t *testing.T) { func TestSendWithExistingPhoto(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewPhotoShare(ChatID, ExistingPhotoFileID) msg := NewPhoto(ChatID, FileID(ExistingPhotoFileID))
msg.Caption = "Test" msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithNewDocument(t *testing.T) { func TestSendWithNewDocument(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewDocumentUpload(ChatID, "tests/image.jpg") msg := NewDocument(ChatID, "tests/image.jpg")
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
}
}
func TestSendWithNewDocumentAndThumb(t *testing.T) {
bot, _ := getBot(t)
msg := NewDocument(ChatID, "tests/voice.ogg")
msg.Thumb = "tests/image.jpg"
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithExistingDocument(t *testing.T) { func TestSendWithExistingDocument(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewDocumentShare(ChatID, ExistingDocumentFileID) msg := NewDocument(ChatID, FileID(ExistingDocumentFileID))
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithNewAudio(t *testing.T) { func TestSendWithNewAudio(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewAudioUpload(ChatID, "tests/audio.mp3") msg := NewAudio(ChatID, "tests/audio.mp3")
msg.Title = "TEST" msg.Title = "TEST"
msg.Duration = 10 msg.Duration = 10
msg.Performer = "TEST" msg.Performer = "TEST"
msg.MimeType = "audio/mpeg"
msg.FileSize = 688
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithExistingAudio(t *testing.T) { func TestSendWithExistingAudio(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewAudioShare(ChatID, ExistingAudioFileID) msg := NewAudio(ChatID, FileID(ExistingAudioFileID))
msg.Title = "TEST" msg.Title = "TEST"
msg.Duration = 10 msg.Duration = 10
msg.Performer = "TEST" msg.Performer = "TEST"
@ -219,33 +296,30 @@ func TestSendWithExistingAudio(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithNewVoice(t *testing.T) { func TestSendWithNewVoice(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewVoiceUpload(ChatID, "tests/voice.ogg") msg := NewVoice(ChatID, "tests/voice.ogg")
msg.Duration = 10 msg.Duration = 10
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithExistingVoice(t *testing.T) { func TestSendWithExistingVoice(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewVoiceShare(ChatID, ExistingVoiceFileID) msg := NewVoice(ChatID, FileID(ExistingVoiceFileID))
msg.Duration = 10 msg.Duration = 10
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -256,7 +330,6 @@ func TestSendWithContact(t *testing.T) {
if _, err := bot.Send(contact); err != nil { if _, err := bot.Send(contact); err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -267,7 +340,6 @@ func TestSendWithLocation(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -278,14 +350,13 @@ func TestSendWithVenue(t *testing.T) {
if _, err := bot.Send(venue); err != nil { if _, err := bot.Send(venue); err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithNewVideo(t *testing.T) { func TestSendWithNewVideo(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewVideoUpload(ChatID, "tests/video.mp4") msg := NewVideo(ChatID, "tests/video.mp4")
msg.Duration = 10 msg.Duration = 10
msg.Caption = "TEST" msg.Caption = "TEST"
@ -293,14 +364,13 @@ func TestSendWithNewVideo(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithExistingVideo(t *testing.T) { func TestSendWithExistingVideo(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewVideoShare(ChatID, ExistingVideoFileID) msg := NewVideo(ChatID, FileID(ExistingVideoFileID))
msg.Duration = 10 msg.Duration = 10
msg.Caption = "TEST" msg.Caption = "TEST"
@ -308,68 +378,63 @@ func TestSendWithExistingVideo(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithNewVideoNote(t *testing.T) { func TestSendWithNewVideoNote(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewVideoNoteUpload(ChatID, 240, "tests/videonote.mp4") msg := NewVideoNote(ChatID, 240, "tests/videonote.mp4")
msg.Duration = 10 msg.Duration = 10
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithExistingVideoNote(t *testing.T) { func TestSendWithExistingVideoNote(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewVideoNoteShare(ChatID, 240, ExistingVideoNoteFileID) msg := NewVideoNote(ChatID, 240, FileID(ExistingVideoNoteFileID))
msg.Duration = 10 msg.Duration = 10
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithNewSticker(t *testing.T) { func TestSendWithNewSticker(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewStickerUpload(ChatID, "tests/image.jpg") msg := NewSticker(ChatID, "tests/image.jpg")
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithExistingSticker(t *testing.T) { func TestSendWithExistingSticker(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewStickerShare(ChatID, ExistingStickerFileID) msg := NewSticker(ChatID, FileID(ExistingStickerFileID))
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithNewStickerAndKeyboardHide(t *testing.T) { func TestSendWithNewStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewStickerUpload(ChatID, "tests/image.jpg") msg := NewSticker(ChatID, "tests/image.jpg")
msg.ReplyMarkup = ReplyKeyboardRemove{ msg.ReplyMarkup = ReplyKeyboardRemove{
RemoveKeyboard: true, RemoveKeyboard: true,
Selective: false, Selective: false,
@ -378,14 +443,13 @@ func TestSendWithNewStickerAndKeyboardHide(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) { func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewStickerShare(ChatID, ExistingStickerFileID) msg := NewSticker(ChatID, FileID(ExistingStickerFileID))
msg.ReplyMarkup = ReplyKeyboardRemove{ msg.ReplyMarkup = ReplyKeyboardRemove{
RemoveKeyboard: true, RemoveKeyboard: true,
Selective: false, Selective: false,
@ -393,10 +457,35 @@ func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) {
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil {
t.Error(err)
}
}
func TestSendWithDice(t *testing.T) {
bot, _ := getBot(t)
msg := NewDice(ChatID)
_, err := bot.Send(msg)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail() t.Fail()
} }
}
func TestSendWithDiceWithEmoji(t *testing.T) {
bot, _ := getBot(t)
msg := NewDiceWithEmoji(ChatID, "🏀")
_, err := bot.Send(msg)
if err != nil {
t.Error(err)
t.Fail()
}
} }
func TestGetFile(t *testing.T) { func TestGetFile(t *testing.T) {
@ -410,7 +499,6 @@ func TestGetFile(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -421,7 +509,6 @@ func TestSendChatConfig(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -431,7 +518,6 @@ func TestSendEditMessage(t *testing.T) {
msg, err := bot.Send(NewMessage(ChatID, "Testing editing.")) msg, err := bot.Send(NewMessage(ChatID, "Testing editing."))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
edit := EditMessageTextConfig{ edit := EditMessageTextConfig{
@ -445,7 +531,6 @@ func TestSendEditMessage(t *testing.T) {
_, err = bot.Send(edit) _, err = bot.Send(edit)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -455,7 +540,6 @@ func TestGetUserProfilePhotos(t *testing.T) {
_, err := bot.GetUserProfilePhotos(NewUserProfilePhotos(ChatID)) _, err := bot.GetUserProfilePhotos(NewUserProfilePhotos(ChatID))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -464,13 +548,17 @@ func TestSetWebhookWithCert(t *testing.T) {
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
bot.Request(RemoveWebhookConfig{}) bot.Request(DeleteWebhookConfig{})
wh, err := NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, "tests/cert.pem")
if err != nil {
t.Error(err)
}
_, err = bot.Request(wh)
wh := NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, "tests/cert.pem")
_, err := bot.Request(wh)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
_, err = bot.GetWebhookInfo() _, err = bot.GetWebhookInfo()
@ -479,7 +567,7 @@ func TestSetWebhookWithCert(t *testing.T) {
t.Error(err) t.Error(err)
} }
bot.Request(RemoveWebhookConfig{}) bot.Request(DeleteWebhookConfig{})
} }
func TestSetWebhookWithoutCert(t *testing.T) { func TestSetWebhookWithoutCert(t *testing.T) {
@ -487,13 +575,18 @@ func TestSetWebhookWithoutCert(t *testing.T) {
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
bot.Request(RemoveWebhookConfig{}) bot.Request(DeleteWebhookConfig{})
wh, err := NewWebhook("https://example.com/tgbotapi-test/" + bot.Token)
if err != nil {
t.Error(err)
}
_, err = bot.Request(wh)
wh := NewWebhook("https://example.com/tgbotapi-test/" + bot.Token)
_, err := bot.Request(wh)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
info, err := bot.GetWebhookInfo() info, err := bot.GetWebhookInfo()
@ -501,21 +594,23 @@ func TestSetWebhookWithoutCert(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if info.MaxConnections == 0 {
t.Errorf("Expected maximum connections to be greater than 0")
}
if info.LastErrorDate != 0 { if info.LastErrorDate != 0 {
t.Errorf("failed to set webhook: %s", info.LastErrorMessage) t.Errorf("failed to set webhook: %s", info.LastErrorMessage)
} }
bot.Request(RemoveWebhookConfig{}) bot.Request(DeleteWebhookConfig{})
} }
func TestSendWithMediaGroup(t *testing.T) { func TestSendWithMediaGroupPhotoVideo(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
cfg := NewMediaGroup(ChatID, []interface{}{ cfg := NewMediaGroup(ChatID, []interface{}{
NewInputMediaPhoto("https://i.imgur.com/unQLJIb.jpg"), NewInputMediaPhoto(FileURL("https://github.com/go-telegram-bot-api/telegram-bot-api/raw/0a3a1c8716c4cd8d26a262af9f12dcbab7f3f28c/tests/image.jpg")),
NewInputMediaPhoto("https://i.imgur.com/J5qweNZ.jpg"), NewInputMediaPhoto("tests/image.jpg"),
NewInputMediaVideo("https://i.imgur.com/F6RmI24.mp4"), NewInputMediaVideo("tests/video.mp4"),
}) })
messages, err := bot.SendMediaGroup(cfg) messages, err := bot.SendMediaGroup(cfg)
@ -524,11 +619,55 @@ func TestSendWithMediaGroup(t *testing.T) {
} }
if messages == nil { if messages == nil {
t.Error() t.Error("No received messages")
} }
if len(messages) != 3 { if len(messages) != len(cfg.Media) {
t.Error() t.Errorf("Different number of messages: %d", len(messages))
}
}
func TestSendWithMediaGroupDocument(t *testing.T) {
bot, _ := getBot(t)
cfg := NewMediaGroup(ChatID, []interface{}{
NewInputMediaDocument(FileURL("https://i.imgur.com/unQLJIb.jpg")),
NewInputMediaDocument("tests/image.jpg"),
})
messages, err := bot.SendMediaGroup(cfg)
if err != nil {
t.Error(err)
}
if messages == nil {
t.Error("No received messages")
}
if len(messages) != len(cfg.Media) {
t.Errorf("Different number of messages: %d", len(messages))
}
}
func TestSendWithMediaGroupAudio(t *testing.T) {
bot, _ := getBot(t)
cfg := NewMediaGroup(ChatID, []interface{}{
NewInputMediaAudio("tests/audio.mp3"),
NewInputMediaAudio("tests/audio.mp3"),
})
messages, err := bot.SendMediaGroup(cfg)
if err != nil {
t.Error(err)
}
if messages == nil {
t.Error("No received messages")
}
if len(messages) != len(cfg.Media) {
t.Errorf("Different number of messages: %d", len(messages))
} }
} }
@ -576,7 +715,14 @@ func ExampleNewWebhook() {
log.Printf("Authorized on account %s", bot.Self.UserName) log.Printf("Authorized on account %s", bot.Self.UserName)
_, err = bot.Request(NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem")) wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem")
if err != nil {
panic(err)
}
_, err = bot.Request(wh)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -599,6 +745,46 @@ func ExampleNewWebhook() {
} }
} }
func ExampleWebhookHandler() {
bot, err := NewBotAPI("MyAwesomeBotToken")
if err != nil {
panic(err)
}
bot.Debug = true
log.Printf("Authorized on account %s", bot.Self.UserName)
wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem")
if err != nil {
panic(err)
}
_, err = bot.Request(wh)
if err != nil {
panic(err)
}
info, err := bot.GetWebhookInfo()
if err != nil {
panic(err)
}
if info.LastErrorDate != 0 {
log.Printf("[Telegram callback failed]%s", info.LastErrorMessage)
}
http.HandleFunc("/"+bot.Token, func(w http.ResponseWriter, r *http.Request) {
update, err := bot.HandleUpdate(r)
if err != nil {
log.Printf("%+v\n", err.Error())
} else {
log.Printf("%+v\n", *update)
}
})
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
}
func ExampleInlineConfig() { func ExampleInlineConfig() {
bot, err := NewBotAPI("MyAwesomeBotToken") // create new bot bot, err := NewBotAPI("MyAwesomeBotToken") // create new bot
if err != nil { if err != nil {
@ -637,7 +823,7 @@ func TestDeleteMessage(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api") msg := NewMessage(ChatID, "A test message from the test library in telegram-bot-api")
msg.ParseMode = "markdown" msg.ParseMode = ModeMarkdown
message, _ := bot.Send(msg) message, _ := bot.Send(msg)
deleteMessageConfig := DeleteMessageConfig{ deleteMessageConfig := DeleteMessageConfig{
@ -648,7 +834,6 @@ func TestDeleteMessage(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -656,7 +841,7 @@ func TestPinChatMessage(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api")
msg.ParseMode = "markdown" msg.ParseMode = ModeMarkdown
message, _ := bot.Send(msg) message, _ := bot.Send(msg)
pinChatMessageConfig := PinChatMessageConfig{ pinChatMessageConfig := PinChatMessageConfig{
@ -668,7 +853,6 @@ func TestPinChatMessage(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
} }
@ -676,7 +860,7 @@ func TestUnpinChatMessage(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api") msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api")
msg.ParseMode = "markdown" msg.ParseMode = ModeMarkdown
message, _ := bot.Send(msg) message, _ := bot.Send(msg)
// We need pin message to unpin something // We need pin message to unpin something
@ -688,16 +872,41 @@ func TestUnpinChatMessage(t *testing.T) {
if _, err := bot.Request(pinChatMessageConfig); err != nil { if _, err := bot.Request(pinChatMessageConfig); err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
unpinChatMessageConfig := UnpinChatMessageConfig{ unpinChatMessageConfig := UnpinChatMessageConfig{
ChatID: message.Chat.ID, ChatID: message.Chat.ID,
MessageID: message.MessageID,
} }
if _, err := bot.Request(unpinChatMessageConfig); err != nil { if _, err := bot.Request(unpinChatMessageConfig); err != nil {
t.Error(err) t.Error(err)
t.Fail() }
}
func TestUnpinAllChatMessages(t *testing.T) {
bot, _ := getBot(t)
msg := NewMessage(SupergroupChatID, "A test message from the test library in telegram-bot-api")
msg.ParseMode = ModeMarkdown
message, _ := bot.Send(msg)
pinChatMessageConfig := PinChatMessageConfig{
ChatID: message.Chat.ID,
MessageID: message.MessageID,
DisableNotification: true,
}
if _, err := bot.Request(pinChatMessageConfig); err != nil {
t.Error(err)
}
unpinAllChatMessagesConfig := UnpinAllChatMessagesConfig{
ChatID: message.Chat.ID,
}
if _, err := bot.Request(unpinAllChatMessagesConfig); err != nil {
t.Error(err)
} }
} }
@ -709,27 +918,109 @@ func TestPolls(t *testing.T) {
msg, err := bot.Send(poll) msg, err := bot.Send(poll)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
result, err := bot.StopPoll(NewStopPoll(SupergroupChatID, msg.MessageID)) result, err := bot.StopPoll(NewStopPoll(SupergroupChatID, msg.MessageID))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
t.Fail()
} }
if result.Question != "Are polls working?" { if result.Question != "Are polls working?" {
t.Error("Poll question did not match") t.Error("Poll question did not match")
t.Fail()
} }
if !result.IsClosed { if !result.IsClosed {
t.Error("Poll did not end") t.Error("Poll did not end")
t.Fail()
} }
if result.Options[0].Text != "Yes" || result.Options[0].VoterCount != 0 || result.Options[1].Text != "No" || result.Options[1].VoterCount != 0 { if result.Options[0].Text != "Yes" || result.Options[0].VoterCount != 0 || result.Options[1].Text != "No" || result.Options[1].VoterCount != 0 {
t.Error("Poll options were incorrect") t.Error("Poll options were incorrect")
t.Fail() }
}
func TestSendDice(t *testing.T) {
bot, _ := getBot(t)
dice := NewSendDice(ChatID)
msg, err := bot.Send(dice)
if err != nil {
t.Error("Unable to send dice roll")
}
if msg.Dice == nil {
t.Error("Dice roll was not received")
}
}
func TestSetCommands(t *testing.T) {
bot, _ := getBot(t)
setCommands := NewSetMyCommands(BotCommand{
Command: "test",
Description: "a test command",
})
if _, err := bot.Request(setCommands); err != nil {
t.Error("Unable to set commands")
}
commands, err := bot.GetMyCommands()
if err != nil {
t.Error("Unable to get commands")
}
if len(commands) != 1 {
t.Error("Incorrect number of commands returned")
}
if commands[0].Command != "test" || commands[0].Description != "a test command" {
t.Error("Commands were incorrectly set")
}
}
func TestEditMessageMedia(t *testing.T) {
bot, _ := getBot(t)
msg := NewPhoto(ChatID, "tests/image.jpg")
msg.Caption = "Test"
m, err := bot.Send(msg)
if err != nil {
t.Error(err)
}
edit := EditMessageMediaConfig{
BaseEdit: BaseEdit{
ChatID: ChatID,
MessageID: m.MessageID,
},
Media: NewInputMediaVideo("tests/video.mp4"),
}
_, err = bot.Request(edit)
if err != nil {
t.Error(err)
}
}
func TestPrepareInputMediaForParams(t *testing.T) {
media := []interface{}{
NewInputMediaPhoto("tests/image.jpg"),
NewInputMediaVideo(FileID("test")),
}
prepared := prepareInputMediaForParams(media)
if media[0].(InputMediaPhoto).Media != "tests/image.jpg" {
t.Error("Original media was changed")
}
if prepared[0].(InputMediaPhoto).Media != "attach://file-0" {
t.Error("New media was not replaced")
}
if prepared[1].(InputMediaVideo).Media != FileID("test") {
t.Error("Passthrough value was not the same")
} }
} }

1205
configs.go

File diff suppressed because it is too large Load Diff

2
go.mod
View File

@ -1,3 +1,3 @@
module github.com/go-telegram-bot-api/telegram-bot-api/v5 module github.com/go-telegram-bot-api/telegram-bot-api/v5
require github.com/technoweenie/multipartstreamer v1.0.1 go 1.13

2
go.sum
View File

@ -1,2 +0,0 @@
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=

View File

@ -29,7 +29,8 @@ func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
// NewMessageToChannel creates a new Message that is sent to a channel // NewMessageToChannel creates a new Message that is sent to a channel
// by username. // by username.
// //
// username is the username of the channel, text is the message text. // username is the username of the channel, text is the message text,
// and the username should be in the form of `@username`.
func NewMessageToChannel(username string, text string) MessageConfig { func NewMessageToChannel(username string, text string) MessageConfig {
return MessageConfig{ return MessageConfig{
BaseChat: BaseChat{ BaseChat: BaseChat{
@ -51,241 +52,117 @@ func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig {
} }
} }
// NewPhotoUpload creates a new photo uploader. // NewCopyMessage creates a new copy message.
//
// chatID is where to send it, fromChatID is the source chat,
// and messageID is the ID of the original message.
func NewCopyMessage(chatID int64, fromChatID int64, messageID int) CopyMessageConfig {
return CopyMessageConfig{
BaseChat: BaseChat{ChatID: chatID},
FromChatID: fromChatID,
MessageID: messageID,
}
}
// NewPhoto creates a new sendPhoto request.
// //
// chatID is where to send it, file is a string path to the file, // chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes. // FileReader, or FileBytes.
// //
// Note that you must send animated GIFs as a document. // Note that you must send animated GIFs as a document.
func NewPhotoUpload(chatID int64, file interface{}) PhotoConfig { func NewPhoto(chatID int64, file interface{}) PhotoConfig {
return PhotoConfig{ return PhotoConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
File: file, File: file,
UseExisting: false,
}, },
} }
} }
// NewPhotoShare shares an existing photo. // NewPhotoToChannel creates a new photo uploader to send a photo to a channel.
// You may use this to reshare an existing photo without reuploading it.
// //
// chatID is where to send it, fileID is the ID of the file // Note that you must send animated GIFs as a document.
// already uploaded. func NewPhotoToChannel(username string, file interface{}) PhotoConfig {
func NewPhotoShare(chatID int64, fileID string) PhotoConfig {
return PhotoConfig{ return PhotoConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{
FileID: fileID, ChannelUsername: username,
UseExisting: true, },
File: file,
}, },
} }
} }
// NewAudioUpload creates a new audio uploader. // NewAudio creates a new sendAudio request.
// func NewAudio(chatID int64, file interface{}) AudioConfig {
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewAudioUpload(chatID int64, file interface{}) AudioConfig {
return AudioConfig{ return AudioConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
File: file, File: file,
UseExisting: false,
}, },
} }
} }
// NewAudioShare shares an existing audio file. // NewDocument creates a new sendDocument request.
// You may use this to reshare an existing audio file without func NewDocument(chatID int64, file interface{}) DocumentConfig {
// reuploading it.
//
// chatID is where to send it, fileID is the ID of the audio
// already uploaded.
func NewAudioShare(chatID int64, fileID string) AudioConfig {
return AudioConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
}
}
// NewDocumentUpload creates a new document uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewDocumentUpload(chatID int64, file interface{}) DocumentConfig {
return DocumentConfig{ return DocumentConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
File: file, File: file,
UseExisting: false,
}, },
} }
} }
// NewDocumentShare shares an existing document. // NewSticker creates a new sendSticker request.
// You may use this to reshare an existing document without func NewSticker(chatID int64, file interface{}) StickerConfig {
// reuploading it.
//
// chatID is where to send it, fileID is the ID of the document
// already uploaded.
func NewDocumentShare(chatID int64, fileID string) DocumentConfig {
return DocumentConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
}
}
// NewStickerUpload creates a new sticker uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewStickerUpload(chatID int64, file interface{}) StickerConfig {
return StickerConfig{ return StickerConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
File: file, File: file,
UseExisting: false,
}, },
} }
} }
// NewStickerShare shares an existing sticker. // NewVideo creates a new sendVideo request.
// You may use this to reshare an existing sticker without func NewVideo(chatID int64, file interface{}) VideoConfig {
// reuploading it.
//
// chatID is where to send it, fileID is the ID of the sticker
// already uploaded.
func NewStickerShare(chatID int64, fileID string) StickerConfig {
return StickerConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
}
}
// NewVideoUpload creates a new video uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewVideoUpload(chatID int64, file interface{}) VideoConfig {
return VideoConfig{ return VideoConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
File: file, File: file,
UseExisting: false,
}, },
} }
} }
// NewVideoShare shares an existing video. // NewAnimation creates a new sendAnimation request.
// You may use this to reshare an existing video without reuploading it. func NewAnimation(chatID int64, file interface{}) AnimationConfig {
// return AnimationConfig{
// chatID is where to send it, fileID is the ID of the video
// already uploaded.
func NewVideoShare(chatID int64, fileID string) VideoConfig {
return VideoConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
FileID: fileID, File: file,
UseExisting: true,
}, },
} }
} }
// NewAnimationUpload creates a new animation uploader. // NewVideoNote creates a new sendVideoNote request.
// //
// chatID is where to send it, file is a string path to the file, // chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes. // FileReader, or FileBytes.
func NewAnimationUpload(chatID int64, file interface{}) AnimationConfig { func NewVideoNote(chatID int64, length int, file interface{}) VideoNoteConfig {
return AnimationConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
},
}
}
// NewAnimationShare shares an existing animation.
// You may use this to reshare an existing animation without reuploading it.
//
// chatID is where to send it, fileID is the ID of the animation
// already uploaded.
func NewAnimationShare(chatID int64, fileID string) AnimationConfig {
return AnimationConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
}
}
// NewVideoNoteUpload creates a new video note uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewVideoNoteUpload(chatID int64, length int, file interface{}) VideoNoteConfig {
return VideoNoteConfig{ return VideoNoteConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
File: file, File: file,
UseExisting: false,
}, },
Length: length, Length: length,
} }
} }
// NewVideoNoteShare shares an existing video. // NewVoice creates a new sendVoice request.
// You may use this to reshare an existing video without reuploading it. func NewVoice(chatID int64, file interface{}) VoiceConfig {
//
// chatID is where to send it, fileID is the ID of the video
// already uploaded.
func NewVideoNoteShare(chatID int64, length int, fileID string) VideoNoteConfig {
return VideoNoteConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
Length: length,
}
}
// NewVoiceUpload creates a new voice uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
func NewVoiceUpload(chatID int64, file interface{}) VoiceConfig {
return VoiceConfig{ return VoiceConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
File: file, File: file,
UseExisting: false,
},
}
}
// NewVoiceShare shares an existing voice.
// You may use this to reshare an existing voice without reuploading it.
//
// chatID is where to send it, fileID is the ID of the video
// already uploaded.
func NewVoiceShare(chatID int64, fileID string) VoiceConfig {
return VoiceConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
}, },
} }
} }
@ -300,7 +177,7 @@ func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
} }
// NewInputMediaPhoto creates a new InputMediaPhoto. // NewInputMediaPhoto creates a new InputMediaPhoto.
func NewInputMediaPhoto(media string) InputMediaPhoto { func NewInputMediaPhoto(media interface{}) InputMediaPhoto {
return InputMediaPhoto{ return InputMediaPhoto{
BaseInputMedia{ BaseInputMedia{
Type: "photo", Type: "photo",
@ -310,7 +187,7 @@ func NewInputMediaPhoto(media string) InputMediaPhoto {
} }
// NewInputMediaVideo creates a new InputMediaVideo. // NewInputMediaVideo creates a new InputMediaVideo.
func NewInputMediaVideo(media string) InputMediaVideo { func NewInputMediaVideo(media interface{}) InputMediaVideo {
return InputMediaVideo{ return InputMediaVideo{
BaseInputMedia: BaseInputMedia{ BaseInputMedia: BaseInputMedia{
Type: "video", Type: "video",
@ -320,7 +197,7 @@ func NewInputMediaVideo(media string) InputMediaVideo {
} }
// NewInputMediaAnimation creates a new InputMediaAnimation. // NewInputMediaAnimation creates a new InputMediaAnimation.
func NewInputMediaAnimation(media string) InputMediaAnimation { func NewInputMediaAnimation(media interface{}) InputMediaAnimation {
return InputMediaAnimation{ return InputMediaAnimation{
BaseInputMedia: BaseInputMedia{ BaseInputMedia: BaseInputMedia{
Type: "animation", Type: "animation",
@ -330,7 +207,7 @@ func NewInputMediaAnimation(media string) InputMediaAnimation {
} }
// NewInputMediaAudio creates a new InputMediaAudio. // NewInputMediaAudio creates a new InputMediaAudio.
func NewInputMediaAudio(media string) InputMediaAudio { func NewInputMediaAudio(media interface{}) InputMediaAudio {
return InputMediaAudio{ return InputMediaAudio{
BaseInputMedia: BaseInputMedia{ BaseInputMedia: BaseInputMedia{
Type: "audio", Type: "audio",
@ -340,7 +217,7 @@ func NewInputMediaAudio(media string) InputMediaAudio {
} }
// NewInputMediaDocument creates a new InputMediaDocument. // NewInputMediaDocument creates a new InputMediaDocument.
func NewInputMediaDocument(media string) InputMediaDocument { func NewInputMediaDocument(media interface{}) InputMediaDocument {
return InputMediaDocument{ return InputMediaDocument{
BaseInputMedia: BaseInputMedia{ BaseInputMedia: BaseInputMedia{
Type: "document", Type: "document",
@ -400,7 +277,7 @@ func NewChatAction(chatID int64, action string) ChatActionConfig {
// NewUserProfilePhotos gets user profile photos. // NewUserProfilePhotos gets user profile photos.
// //
// userID is the ID of the user you wish to get profile photos from. // userID is the ID of the user you wish to get profile photos from.
func NewUserProfilePhotos(userID int) UserProfilePhotosConfig { func NewUserProfilePhotos(userID int64) UserProfilePhotosConfig {
return UserProfilePhotosConfig{ return UserProfilePhotosConfig{
UserID: userID, UserID: userID,
Offset: 0, Offset: 0,
@ -423,25 +300,33 @@ func NewUpdate(offset int) UpdateConfig {
// NewWebhook creates a new webhook. // NewWebhook creates a new webhook.
// //
// link is the url parsable link you wish to get the updates. // link is the url parsable link you wish to get the updates.
func NewWebhook(link string) WebhookConfig { func NewWebhook(link string) (WebhookConfig, error) {
u, _ := url.Parse(link) u, err := url.Parse(link)
if err != nil {
return WebhookConfig{}, err
}
return WebhookConfig{ return WebhookConfig{
URL: u, URL: u,
} }, nil
} }
// NewWebhookWithCert creates a new webhook with a certificate. // NewWebhookWithCert creates a new webhook with a certificate.
// //
// link is the url you wish to get webhooks, // link is the url you wish to get webhooks,
// file contains a string to a file, FileReader, or FileBytes. // file contains a string to a file, FileReader, or FileBytes.
func NewWebhookWithCert(link string, file interface{}) WebhookConfig { func NewWebhookWithCert(link string, file interface{}) (WebhookConfig, error) {
u, _ := url.Parse(link) u, err := url.Parse(link)
if err != nil {
return WebhookConfig{}, err
}
return WebhookConfig{ return WebhookConfig{
URL: u, URL: u,
Certificate: file, Certificate: file,
} }, nil
} }
// NewInlineQueryResultArticle creates a new inline query article. // NewInlineQueryResultArticle creates a new inline query article.
@ -469,6 +354,19 @@ func NewInlineQueryResultArticleMarkdown(id, title, messageText string) InlineQu
} }
} }
// NewInlineQueryResultArticleMarkdownV2 creates a new inline query article with MarkdownV2 parsing.
func NewInlineQueryResultArticleMarkdownV2(id, title, messageText string) InlineQueryResultArticle {
return InlineQueryResultArticle{
Type: "article",
ID: id,
Title: title,
InputMessageContent: InputTextMessageContent{
Text: messageText,
ParseMode: "MarkdownV2",
},
}
}
// NewInlineQueryResultArticleHTML creates a new inline query article with HTML parsing. // NewInlineQueryResultArticleHTML creates a new inline query article with HTML parsing.
func NewInlineQueryResultArticleHTML(id, title, messageText string) InlineQueryResultArticle { func NewInlineQueryResultArticleHTML(id, title, messageText string) InlineQueryResultArticle {
return InlineQueryResultArticle{ return InlineQueryResultArticle{
@ -491,6 +389,15 @@ func NewInlineQueryResultGIF(id, url string) InlineQueryResultGIF {
} }
} }
// NewInlineQueryResultCachedGIF create a new inline query with cached photo.
func NewInlineQueryResultCachedGIF(id, gifID string) InlineQueryResultCachedGIF {
return InlineQueryResultCachedGIF{
Type: "gif",
ID: id,
GIFID: gifID,
}
}
// NewInlineQueryResultMPEG4GIF creates a new inline query MPEG4 GIF. // NewInlineQueryResultMPEG4GIF creates a new inline query MPEG4 GIF.
func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF { func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF {
return InlineQueryResultMPEG4GIF{ return InlineQueryResultMPEG4GIF{
@ -500,6 +407,15 @@ func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF {
} }
} }
// NewInlineQueryResultCachedMPEG4GIF create a new inline query with cached MPEG4 GIF.
func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GIFID string) InlineQueryResultCachedMPEG4GIF {
return InlineQueryResultCachedMPEG4GIF{
Type: "mpeg4_gif",
ID: id,
MPEG4FileID: MPEG4GIFID,
}
}
// NewInlineQueryResultPhoto creates a new inline query photo. // NewInlineQueryResultPhoto creates a new inline query photo.
func NewInlineQueryResultPhoto(id, url string) InlineQueryResultPhoto { func NewInlineQueryResultPhoto(id, url string) InlineQueryResultPhoto {
return InlineQueryResultPhoto{ return InlineQueryResultPhoto{
@ -519,6 +435,15 @@ func NewInlineQueryResultPhotoWithThumb(id, url, thumb string) InlineQueryResult
} }
} }
// NewInlineQueryResultCachedPhoto create a new inline query with cached photo.
func NewInlineQueryResultCachedPhoto(id, photoID string) InlineQueryResultCachedPhoto {
return InlineQueryResultCachedPhoto{
Type: "photo",
ID: id,
PhotoID: photoID,
}
}
// NewInlineQueryResultVideo creates a new inline query video. // NewInlineQueryResultVideo creates a new inline query video.
func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo { func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo {
return InlineQueryResultVideo{ return InlineQueryResultVideo{
@ -528,6 +453,26 @@ func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo {
} }
} }
// NewInlineQueryResultCachedVideo create a new inline query with cached video.
func NewInlineQueryResultCachedVideo(id, videoID, title string) InlineQueryResultCachedVideo {
return InlineQueryResultCachedVideo{
Type: "video",
ID: id,
VideoID: videoID,
Title: title,
}
}
// NewInlineQueryResultCachedSticker create a new inline query with cached sticker.
func NewInlineQueryResultCachedSticker(id, stickerID, title string) InlineQueryResultCachedSticker {
return InlineQueryResultCachedSticker{
Type: "sticker",
ID: id,
StickerID: stickerID,
Title: title,
}
}
// NewInlineQueryResultAudio creates a new inline query audio. // NewInlineQueryResultAudio creates a new inline query audio.
func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio { func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio {
return InlineQueryResultAudio{ return InlineQueryResultAudio{
@ -538,6 +483,15 @@ func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio {
} }
} }
// NewInlineQueryResultCachedAudio create a new inline query with cached photo.
func NewInlineQueryResultCachedAudio(id, audioID string) InlineQueryResultCachedAudio {
return InlineQueryResultCachedAudio{
Type: "audio",
ID: id,
AudioID: audioID,
}
}
// NewInlineQueryResultVoice creates a new inline query voice. // NewInlineQueryResultVoice creates a new inline query voice.
func NewInlineQueryResultVoice(id, url, title string) InlineQueryResultVoice { func NewInlineQueryResultVoice(id, url, title string) InlineQueryResultVoice {
return InlineQueryResultVoice{ return InlineQueryResultVoice{
@ -548,6 +502,16 @@ func NewInlineQueryResultVoice(id, url, title string) InlineQueryResultVoice {
} }
} }
// NewInlineQueryResultCachedVoice create a new inline query with cached photo.
func NewInlineQueryResultCachedVoice(id, voiceID, title string) InlineQueryResultCachedVoice {
return InlineQueryResultCachedVoice{
Type: "voice",
ID: id,
VoiceID: voiceID,
Title: title,
}
}
// NewInlineQueryResultDocument creates a new inline query document. // NewInlineQueryResultDocument creates a new inline query document.
func NewInlineQueryResultDocument(id, url, title, mimeType string) InlineQueryResultDocument { func NewInlineQueryResultDocument(id, url, title, mimeType string) InlineQueryResultDocument {
return InlineQueryResultDocument{ return InlineQueryResultDocument{
@ -559,6 +523,16 @@ func NewInlineQueryResultDocument(id, url, title, mimeType string) InlineQueryRe
} }
} }
// NewInlineQueryResultCachedDocument create a new inline query with cached photo.
func NewInlineQueryResultCachedDocument(id, documentID, title string) InlineQueryResultCachedDocument {
return InlineQueryResultCachedDocument{
Type: "document",
ID: id,
DocumentID: documentID,
Title: title,
}
}
// NewInlineQueryResultLocation creates a new inline query location. // NewInlineQueryResultLocation creates a new inline query location.
func NewInlineQueryResultLocation(id, title string, latitude, longitude float64) InlineQueryResultLocation { func NewInlineQueryResultLocation(id, title string, latitude, longitude float64) InlineQueryResultLocation {
return InlineQueryResultLocation{ return InlineQueryResultLocation{
@ -570,6 +544,18 @@ func NewInlineQueryResultLocation(id, title string, latitude, longitude float64)
} }
} }
// NewInlineQueryResultVenue creates a new inline query venue.
func NewInlineQueryResultVenue(id, title, address string, latitude, longitude float64) InlineQueryResultVenue {
return InlineQueryResultVenue{
Type: "venue",
ID: id,
Title: title,
Address: address,
Latitude: latitude,
Longitude: longitude,
}
}
// NewEditMessageText allows you to edit the text of a message. // NewEditMessageText allows you to edit the text of a message.
func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTextConfig { func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTextConfig {
return EditMessageTextConfig{ return EditMessageTextConfig{
@ -581,6 +567,18 @@ func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTex
} }
} }
// NewEditMessageTextAndMarkup allows you to edit the text and replymarkup of a message.
func NewEditMessageTextAndMarkup(chatID int64, messageID int, text string, replyMarkup InlineKeyboardMarkup) EditMessageTextConfig {
return EditMessageTextConfig{
BaseEdit: BaseEdit{
ChatID: chatID,
MessageID: messageID,
ReplyMarkup: &replyMarkup,
},
Text: text,
}
}
// NewEditMessageCaption allows you to edit the caption of a message. // NewEditMessageCaption allows you to edit the caption of a message.
func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig { func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig {
return EditMessageCaptionConfig{ return EditMessageCaptionConfig{
@ -604,17 +602,6 @@ func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKe
} }
} }
// NewHideKeyboard hides the keyboard, with the option for being selective
// or hiding for everyone.
func NewHideKeyboard(selective bool) ReplyKeyboardHide {
log.Println("NewHideKeyboard is deprecated, please use NewRemoveKeyboard")
return ReplyKeyboardHide{
HideKeyboard: true,
Selective: selective,
}
}
// NewRemoveKeyboard hides the keyboard, with the option for being selective // NewRemoveKeyboard hides the keyboard, with the option for being selective
// or hiding for everyone. // or hiding for everyone.
func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove { func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove {
@ -670,6 +657,13 @@ func NewReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup {
} }
} }
// NewOneTimeReplyKeyboard creates a new one time keyboard.
func NewOneTimeReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup {
markup := NewReplyKeyboard(rows...)
markup.OneTimeKeyboard = true
return markup
}
// NewInlineKeyboardButtonData creates an inline keyboard button with text // NewInlineKeyboardButtonData creates an inline keyboard button with text
// and data for a callback. // and data for a callback.
func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton { func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton {
@ -758,37 +752,6 @@ func NewInvoice(chatID int64, title, description, payload, providerToken, startP
Prices: prices} Prices: prices}
} }
// NewSetChatPhotoUpload creates a new chat photo uploader.
//
// chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes.
//
// Note that you must send animated GIFs as a document.
func NewSetChatPhotoUpload(chatID int64, file interface{}) SetChatPhotoConfig {
return SetChatPhotoConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
File: file,
UseExisting: false,
},
}
}
// NewSetChatPhotoShare shares an existing photo.
// You may use this to reshare an existing photo without reuploading it.
//
// chatID is where to send it, fileID is the ID of the file
// already uploaded.
func NewSetChatPhotoShare(chatID int64, fileID string) SetChatPhotoConfig {
return SetChatPhotoConfig{
BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID},
FileID: fileID,
UseExisting: true,
},
}
}
// NewChatTitle allows you to update the title of a chat. // NewChatTitle allows you to update the title of a chat.
func NewChatTitle(chatID int64, title string) SetChatTitleConfig { func NewChatTitle(chatID int64, title string) SetChatTitleConfig {
return SetChatTitleConfig{ return SetChatTitleConfig{
@ -830,8 +793,9 @@ func NewPoll(chatID int64, question string, options ...string) SendPollConfig {
BaseChat: BaseChat{ BaseChat: BaseChat{
ChatID: chatID, ChatID: chatID,
}, },
Question: question, Question: question,
Options: options, Options: options,
IsAnonymous: true, // This is Telegram's default.
} }
} }
@ -844,3 +808,36 @@ func NewStopPoll(chatID int64, messageID int) StopPollConfig {
}, },
} }
} }
// NewSendDice allows you to send a random dice roll.
//
// Deprecated: Use NewDice instead.
func NewSendDice(chatID int64) DiceConfig {
return NewDice(chatID)
}
// NewDice allows you to send a random dice roll.
func NewDice(chatID int64) DiceConfig {
return DiceConfig{
BaseChat: BaseChat{
ChatID: chatID,
},
}
}
// NewDiceWithEmoji allows you to send a random roll of one of many types.
//
// Emoji may be 🎲 (1-6), 🎯 (1-6), or 🏀 (1-5).
func NewDiceWithEmoji(chatID int64, emoji string) DiceConfig {
return DiceConfig{
BaseChat: BaseChat{
ChatID: chatID,
},
Emoji: emoji,
}
}
// NewSetMyCommands allows you to set the registered commands.
func NewSetMyCommands(commands ...BotCommand) SetMyCommandsConfig {
return SetMyCommandsConfig{commands: commands}
}

View File

@ -4,6 +4,31 @@ import (
"testing" "testing"
) )
func TestNewWebhook(t *testing.T) {
result, err := NewWebhook("https://example.com/token")
if err != nil ||
result.URL.String() != "https://example.com/token" ||
result.Certificate != interface{}(nil) ||
result.MaxConnections != 0 ||
len(result.AllowedUpdates) != 0 {
t.Fail()
}
}
func TestNewWebhookWithCert(t *testing.T) {
exampleFile := File{FileID: "123"}
result, err := NewWebhookWithCert("https://example.com/token", exampleFile)
if err != nil ||
result.URL.String() != "https://example.com/token" ||
result.Certificate != exampleFile ||
result.MaxConnections != 0 ||
len(result.AllowedUpdates) != 0 {
t.Fail()
}
}
func TestNewInlineQueryResultArticle(t *testing.T) { func TestNewInlineQueryResultArticle(t *testing.T) {
result := NewInlineQueryResultArticle("id", "title", "message") result := NewInlineQueryResultArticle("id", "title", "message")
@ -176,8 +201,8 @@ func TestNewEditMessageCaption(t *testing.T) {
func TestNewEditMessageReplyMarkup(t *testing.T) { func TestNewEditMessageReplyMarkup(t *testing.T) {
markup := InlineKeyboardMarkup{ markup := InlineKeyboardMarkup{
InlineKeyboard: [][]InlineKeyboardButton{ InlineKeyboard: [][]InlineKeyboardButton{
[]InlineKeyboardButton{ {
InlineKeyboardButton{Text: "test"}, {Text: "test"},
}, },
}, },
} }
@ -191,3 +216,21 @@ func TestNewEditMessageReplyMarkup(t *testing.T) {
} }
} }
func TestNewDice(t *testing.T) {
dice := NewDice(42)
if dice.ChatID != 42 ||
dice.Emoji != "" {
t.Fail()
}
}
func TestNewDiceWithEmoji(t *testing.T) {
dice := NewDiceWithEmoji(42, "🏀")
if dice.ChatID != 42 ||
dice.Emoji != "🏀" {
t.Fail()
}
}

View File

@ -37,13 +37,6 @@ func (p Params) AddBool(key string, value bool) {
} }
} }
// AddNonNilBool adds the value of a bool pointer if not nil.
func (p Params) AddNonNilBool(key string, value *bool) {
if value != nil {
p[key] = strconv.FormatBool(*value)
}
}
// AddNonZeroFloat adds a floating point value that is not zero. // AddNonZeroFloat adds a floating point value that is not zero.
func (p Params) AddNonZeroFloat(key string, value float64) { func (p Params) AddNonZeroFloat(key string, value float64) {
if value != 0 { if value != 0 {
@ -76,14 +69,17 @@ func (p Params) AddFirstValid(key string, args ...interface{}) error {
case int: case int:
if v != 0 { if v != 0 {
p[key] = strconv.Itoa(v) p[key] = strconv.Itoa(v)
return nil
} }
case int64: case int64:
if v != 0 { if v != 0 {
p[key] = strconv.FormatInt(v, 10) p[key] = strconv.FormatInt(v, 10)
return nil
} }
case string: case string:
if v != "" { if v != "" {
p[key] = v p[key] = v
return nil
} }
case nil: case nil:
default: default:
@ -93,6 +89,7 @@ func (p Params) AddFirstValid(key string, args ...interface{}) error {
} }
p[key] = string(b) p[key] = string(b)
return nil
} }
} }

93
params_test.go 100644
View File

@ -0,0 +1,93 @@
package tgbotapi
import (
"testing"
)
func assertLen(t *testing.T, params Params, l int) {
actual := len(params)
if actual != l {
t.Fatalf("Incorrect number of params, expected %d but found %d\n", l, actual)
}
}
func assertEq(t *testing.T, a interface{}, b interface{}) {
if a != b {
t.Fatalf("Values did not match, a: %v, b: %v\n", a, b)
}
}
func TestAddNonEmpty(t *testing.T) {
params := make(Params)
params.AddNonEmpty("value", "value")
assertLen(t, params, 1)
assertEq(t, params["value"], "value")
params.AddNonEmpty("test", "")
assertLen(t, params, 1)
assertEq(t, params["test"], "")
}
func TestAddNonZero(t *testing.T) {
params := make(Params)
params.AddNonZero("value", 1)
assertLen(t, params, 1)
assertEq(t, params["value"], "1")
params.AddNonZero("test", 0)
assertLen(t, params, 1)
assertEq(t, params["test"], "")
}
func TestAddNonZero64(t *testing.T) {
params := make(Params)
params.AddNonZero64("value", 1)
assertLen(t, params, 1)
assertEq(t, params["value"], "1")
params.AddNonZero64("test", 0)
assertLen(t, params, 1)
assertEq(t, params["test"], "")
}
func TestAddBool(t *testing.T) {
params := make(Params)
params.AddBool("value", true)
assertLen(t, params, 1)
assertEq(t, params["value"], "true")
params.AddBool("test", false)
assertLen(t, params, 1)
assertEq(t, params["test"], "")
}
func TestAddNonZeroFloat(t *testing.T) {
params := make(Params)
params.AddNonZeroFloat("value", 1)
assertLen(t, params, 1)
assertEq(t, params["value"], "1.000000")
params.AddNonZeroFloat("test", 0)
assertLen(t, params, 1)
assertEq(t, params["test"], "")
}
func TestAddInterface(t *testing.T) {
params := make(Params)
data := struct {
Name string `json:"name"`
}{
Name: "test",
}
params.AddInterface("value", data)
assertLen(t, params, 1)
assertEq(t, params["value"], `{"name":"test"}`)
params.AddInterface("test", nil)
assertLen(t, params, 1)
assertEq(t, params["test"], "")
}
func TestAddFirstValid(t *testing.T) {
params := make(Params)
params.AddFirstValid("value", 0, "", "test")
assertLen(t, params, 1)
assertEq(t, params["value"], "test")
params.AddFirstValid("value2", 3, "test")
assertLen(t, params, 2)
assertEq(t, params["value2"], "3")
}

View File

@ -61,6 +61,8 @@ type (
// Unique identifier for this file // Unique identifier for this file
FileID string `json:"file_id"` FileID string `json:"file_id"`
FileUniqueID string `json:"file_unique_id"`
// File size // File size
FileSize int `json:"file_size"` FileSize int `json:"file_size"`

3172
types.go

File diff suppressed because it is too large Load Diff

View File

@ -189,6 +189,86 @@ func TestChatIsSuperGroup(t *testing.T) {
} }
} }
func TestMessageEntityIsMention(t *testing.T) {
entity := MessageEntity{Type: "mention"}
if !entity.IsMention() {
t.Fail()
}
}
func TestMessageEntityIsHashtag(t *testing.T) {
entity := MessageEntity{Type: "hashtag"}
if !entity.IsHashtag() {
t.Fail()
}
}
func TestMessageEntityIsBotCommand(t *testing.T) {
entity := MessageEntity{Type: "bot_command"}
if !entity.IsCommand() {
t.Fail()
}
}
func TestMessageEntityIsUrl(t *testing.T) {
entity := MessageEntity{Type: "url"}
if !entity.IsURL() {
t.Fail()
}
}
func TestMessageEntityIsEmail(t *testing.T) {
entity := MessageEntity{Type: "email"}
if !entity.IsEmail() {
t.Fail()
}
}
func TestMessageEntityIsBold(t *testing.T) {
entity := MessageEntity{Type: "bold"}
if !entity.IsBold() {
t.Fail()
}
}
func TestMessageEntityIsItalic(t *testing.T) {
entity := MessageEntity{Type: "italic"}
if !entity.IsItalic() {
t.Fail()
}
}
func TestMessageEntityIsCode(t *testing.T) {
entity := MessageEntity{Type: "code"}
if !entity.IsCode() {
t.Fail()
}
}
func TestMessageEntityIsPre(t *testing.T) {
entity := MessageEntity{Type: "pre"}
if !entity.IsPre() {
t.Fail()
}
}
func TestMessageEntityIsTextLink(t *testing.T) {
entity := MessageEntity{Type: "text_link"}
if !entity.IsTextLink() {
t.Fail()
}
}
func TestFileLink(t *testing.T) { func TestFileLink(t *testing.T) {
file := File{FilePath: "test/test.txt"} file := File{FilePath: "test/test.txt"}
@ -203,31 +283,53 @@ var (
_ Chattable = AudioConfig{} _ Chattable = AudioConfig{}
_ Chattable = CallbackConfig{} _ Chattable = CallbackConfig{}
_ Chattable = ChatActionConfig{} _ Chattable = ChatActionConfig{}
_ Chattable = ChatAdministratorsConfig{}
_ Chattable = ChatInfoConfig{}
_ Chattable = ChatInviteLinkConfig{}
_ Chattable = CloseConfig{}
_ Chattable = ContactConfig{} _ Chattable = ContactConfig{}
_ Chattable = CopyMessageConfig{}
_ Chattable = CreateChatInviteLinkConfig{}
_ Chattable = DeleteChatPhotoConfig{} _ Chattable = DeleteChatPhotoConfig{}
_ Chattable = DeleteChatStickerSetConfig{} _ Chattable = DeleteChatStickerSetConfig{}
_ Chattable = DeleteMessageConfig{} _ Chattable = DeleteMessageConfig{}
_ Chattable = DeleteWebhookConfig{}
_ Chattable = DocumentConfig{} _ Chattable = DocumentConfig{}
_ Chattable = EditChatInviteLinkConfig{}
_ Chattable = EditMessageCaptionConfig{} _ Chattable = EditMessageCaptionConfig{}
_ Chattable = EditMessageLiveLocationConfig{} _ Chattable = EditMessageLiveLocationConfig{}
_ Chattable = EditMessageMediaConfig{}
_ Chattable = EditMessageReplyMarkupConfig{} _ Chattable = EditMessageReplyMarkupConfig{}
_ Chattable = EditMessageTextConfig{} _ Chattable = EditMessageTextConfig{}
_ Chattable = FileConfig{}
_ Chattable = ForwardConfig{} _ Chattable = ForwardConfig{}
_ Chattable = GameConfig{} _ Chattable = GameConfig{}
_ Chattable = GetChatMemberConfig{}
_ Chattable = GetGameHighScoresConfig{} _ Chattable = GetGameHighScoresConfig{}
_ Chattable = InlineConfig{} _ Chattable = InlineConfig{}
_ Chattable = InvoiceConfig{} _ Chattable = InvoiceConfig{}
_ Chattable = KickChatMemberConfig{} _ Chattable = KickChatMemberConfig{}
_ Chattable = LeaveChatConfig{}
_ Chattable = LocationConfig{} _ Chattable = LocationConfig{}
_ Chattable = LogOutConfig{}
_ Chattable = MediaGroupConfig{} _ Chattable = MediaGroupConfig{}
_ Chattable = MessageConfig{} _ Chattable = MessageConfig{}
_ Chattable = PhotoConfig{} _ Chattable = PhotoConfig{}
_ Chattable = PinChatMessageConfig{} _ Chattable = PinChatMessageConfig{}
_ Chattable = PreCheckoutConfig{}
_ Chattable = PromoteChatMemberConfig{}
_ Chattable = RestrictChatMemberConfig{}
_ Chattable = RevokeChatInviteLinkConfig{}
_ Chattable = SendPollConfig{}
_ Chattable = SetChatDescriptionConfig{} _ Chattable = SetChatDescriptionConfig{}
_ Chattable = SetChatPhotoConfig{} _ Chattable = SetChatPhotoConfig{}
_ Chattable = SetChatTitleConfig{} _ Chattable = SetChatTitleConfig{}
_ Chattable = SetGameScoreConfig{} _ Chattable = SetGameScoreConfig{}
_ Chattable = ShippingConfig{}
_ Chattable = StickerConfig{} _ Chattable = StickerConfig{}
_ Chattable = StopMessageLiveLocationConfig{}
_ Chattable = StopPollConfig{}
_ Chattable = UnbanChatMemberConfig{}
_ Chattable = UnpinChatMessageConfig{} _ Chattable = UnpinChatMessageConfig{}
_ Chattable = UpdateConfig{} _ Chattable = UpdateConfig{}
_ Chattable = UserProfilePhotosConfig{} _ Chattable = UserProfilePhotosConfig{}
@ -237,3 +339,24 @@ var (
_ Chattable = VoiceConfig{} _ Chattable = VoiceConfig{}
_ Chattable = WebhookConfig{} _ Chattable = WebhookConfig{}
) )
// Ensure all Fileable types are correct.
var (
_ Fileable = (*PhotoConfig)(nil)
_ Fileable = (*AudioConfig)(nil)
_ Fileable = (*DocumentConfig)(nil)
_ Fileable = (*StickerConfig)(nil)
_ Fileable = (*VideoConfig)(nil)
_ Fileable = (*AnimationConfig)(nil)
_ Fileable = (*VideoNoteConfig)(nil)
_ Fileable = (*VoiceConfig)(nil)
_ Fileable = (*SetChatPhotoConfig)(nil)
_ Fileable = (*EditMessageMediaConfig)(nil)
_ Fileable = (*SetChatPhotoConfig)(nil)
_ Fileable = (*UploadStickerConfig)(nil)
_ Fileable = (*NewStickerSetConfig)(nil)
_ Fileable = (*AddStickerConfig)(nil)
_ Fileable = (*MediaGroupConfig)(nil)
_ Fileable = (*WebhookConfig)(nil)
_ Fileable = (*SetStickerSetThumbConfig)(nil)
)