diff --git a/bot.go b/bot.go index b028f9d..c7ded98 100644 --- a/bot.go +++ b/bot.go @@ -9,13 +9,12 @@ import ( "fmt" "io" "io/ioutil" + "mime/multipart" "net/http" "net/url" "os" "strings" "time" - - "github.com/technoweenie/multipartstreamer" ) // BotAPI allows you to interact with the Telegram Bot API. @@ -82,7 +81,7 @@ func buildParams(in Params) (out url.Values) { } // 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 { log.Printf("Endpoint: %s, params: %v\n", endpoint, params) } @@ -93,14 +92,14 @@ func (bot *BotAPI) MakeRequest(endpoint string, params Params) (APIResponse, err resp, err := bot.Client.PostForm(method, values) if err != nil { - return APIResponse{}, err + return nil, err } defer resp.Body.Close() var apiResp APIResponse bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp) if err != nil { - return apiResp, err + return &apiResp, err } if bot.Debug { @@ -114,14 +113,14 @@ func (bot *BotAPI) MakeRequest(endpoint string, params Params) (APIResponse, err parameters = *apiResp.Parameters } - return apiResp, Error{ + return &apiResp, &Error{ Code: apiResp.ErrorCode, Message: apiResp.Description, ResponseParameters: parameters, } } - return apiResp, nil + return &apiResp, nil } // decodeAPIResponse decode response and return slice of bytes if debug enabled. @@ -148,86 +147,102 @@ func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse) return data, nil } -// UploadFile makes a request to the API with a file. -// -// Requires the parameter to hold the file not be in the params. -// File should be a string to a file path, a FileBytes struct, -// 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() +// UploadFiles makes a request to the API with files. +func (bot *BotAPI) UploadFiles(endpoint string, params Params, files []RequestFile) (*APIResponse, error) { + r, w := io.Pipe() + m := multipart.NewWriter(w) - switch f := file.(type) { - case string: - ms.WriteFields(params) + // This code modified from the very helpful @HirbodBehnam + // https://github.com/go-telegram-bot-api/telegram-bot-api/issues/354#issuecomment-663856473 + go func() { + defer w.Close() + defer m.Close() - fileHandle, err := os.Open(f) - if err != nil { - return APIResponse{}, err - } - defer fileHandle.Close() - - fi, err := os.Stat(f) - if err != nil { - return APIResponse{}, err + for field, value := range params { + if err := m.WriteField(field, value); err != nil { + panic(err) + } } - ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle) - case FileBytes: - ms.WriteFields(params) + for _, file := range files { + switch f := file.File.(type) { + case string: + fileHandle, err := os.Open(f) + if err != nil { + panic(err) + } + defer fileHandle.Close() - buf := bytes.NewBuffer(f.Bytes) - ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf) - case FileReader: - ms.WriteFields(params) + part, err := m.CreateFormFile(file.Name, fileHandle.Name()) + if err != nil { + panic(err) + } - if f.Size != -1 { - ms.WriteReader(fieldname, f.Name, f.Size, f.Reader) + io.Copy(part, fileHandle) + case FileBytes: + part, err := m.CreateFormFile(file.Name, f.Name) + if err != nil { + panic(err) + } - break + buf := bytes.NewBuffer(f.Bytes) + io.Copy(part, buf) + case FileReader: + part, err := m.CreateFormFile(file.Name, f.Name) + if err != nil { + panic(err) + } + + if f.Size != -1 { + io.Copy(part, f.Reader) + } else { + data, err := ioutil.ReadAll(f.Reader) + if err != nil { + panic(err) + } + + buf := bytes.NewBuffer(data) + io.Copy(part, buf) + } + case FileURL: + val := string(f) + if err := m.WriteField(file.Name, val); err != nil { + panic(err) + } + case FileID: + val := string(f) + if err := m.WriteField(file.Name, val); err != nil { + panic(err) + } + default: + panic(errors.New(ErrBadFileType)) + } } - - 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 { - 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(bot.apiEndpoint, bot.Token, endpoint) - req, err := http.NewRequest("POST", method, nil) + req, err := http.NewRequest("POST", method, r) if err != nil { - return APIResponse{}, err + return nil, err } - ms.SetupRequest(req) + req.Header.Set("Content-Type", m.FormDataContentType()) resp, err := bot.Client.Do(req) if err != nil { - return APIResponse{}, err + return nil, err } defer resp.Body.Close() var apiResp APIResponse bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp) if err != nil { - return apiResp, err + return &apiResp, err } if bot.Debug { @@ -241,13 +256,13 @@ func (bot *BotAPI) UploadFile(endpoint string, params Params, fieldname string, parameters = *apiResp.Parameters } - return apiResp, Error{ + return &apiResp, &Error{ Message: apiResp.Description, ResponseParameters: parameters, } } - return apiResp, nil + return &apiResp, nil } // GetFileDirectURL returns direct URL to file @@ -287,23 +302,54 @@ func (bot *BotAPI) IsMessageToMe(message Message) bool { 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. -func (bot *BotAPI) Request(c Chattable) (APIResponse, error) { +func (bot *BotAPI) Request(c Chattable) (*APIResponse, error) { params, err := c.params() if err != nil { - return APIResponse{}, err + return nil, err } - switch t := c.(type) { - case Fileable: - if t.useExistingFile() { - return bot.MakeRequest(t.method(), params) + if t, ok := c.(Fileable); ok { + files := t.files() + + // 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()) - default: - return bot.MakeRequest(c.method(), params) + // However, if there are no files to be uploaded, there's likely things + // that need to be turned into params instead. + 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 @@ -322,9 +368,51 @@ func (bot *BotAPI) Send(c Chattable) (Message, error) { // SendMediaGroup sends a media group and returns the resulting messages. func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) { - params, _ := config.params() + filesToUpload := []RequestFile{} - resp, err := bot.MakeRequest(config.method(), params) + newMedia := []interface{}{} + + for idx, media := range config.Media { + switch m := media.(type) { + case InputMediaPhoto: + switch f := m.Media.(type) { + case string, FileBytes, FileReader: + m.Media = fmt.Sprintf("attach://file-%d", idx) + newMedia = append(newMedia, m) + + filesToUpload = append(filesToUpload, RequestFile{ + Name: fmt.Sprintf("file-%d", idx), + File: f, + }) + default: + newMedia = append(newMedia, m) + } + case InputMediaVideo: + switch f := m.Media.(type) { + case string, FileBytes, FileReader: + m.Media = fmt.Sprintf("attach://file-%d", idx) + newMedia = append(newMedia, m) + + filesToUpload = append(filesToUpload, RequestFile{ + Name: fmt.Sprintf("file-%d", idx), + File: f, + }) + default: + newMedia = append(newMedia, m) + } + default: + return nil, errors.New(ErrBadFileType) + } + } + + params, err := config.params() + if err != nil { + return nil, err + } + + params.AddInterface("media", newMedia) + + resp, err := bot.UploadFiles(config.method(), params, filesToUpload) if err != nil { return nil, err } @@ -340,9 +428,7 @@ func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) { // It requires UserID. // Offset and Limit are optional. func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) { - params, _ := config.params() - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return UserProfilePhotos{}, err } @@ -357,9 +443,7 @@ func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserPro // // Requires FileID. func (bot *BotAPI) GetFile(config FileConfig) (File, error) { - params, _ := config.params() - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return File{}, err } @@ -378,9 +462,7 @@ func (bot *BotAPI) GetFile(config FileConfig) (File, error) { // Set Timeout to a large number to reduce requests so you can get updates // instantly instead of having to wait between requests. func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) { - params, _ := config.params() - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return []Update{}, err } @@ -481,7 +563,7 @@ func WriteToHTTPResponse(w http.ResponseWriter, c Chattable) error { } if t, ok := c.(Fileable); ok { - if !t.useExistingFile() { + if hasFilesNeedingUpload(t.files()) { return errors.New("unable to use http response to upload files") } } @@ -496,9 +578,7 @@ func WriteToHTTPResponse(w http.ResponseWriter, c Chattable) error { // GetChat gets information about a chat. func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) { - params, _ := config.params() - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return Chat{}, err } @@ -514,9 +594,7 @@ func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) { // If none have been appointed, only the creator will be returned. // Bots are not shown, even if they are an administrator. func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) { - params, _ := config.params() - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return []ChatMember{}, err } @@ -529,9 +607,7 @@ func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]Cha // GetChatMembersCount gets the number of users in a chat. func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) { - params, _ := config.params() - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return -1, err } @@ -544,9 +620,7 @@ func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error // GetChatMember gets a specific chat member. func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) { - params, _ := config.params() - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return ChatMember{}, err } @@ -559,9 +633,7 @@ func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) // GetGameHighScores allows you to get the high scores for a game. func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) { - params, _ := config.params() - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return []GameHighScore{}, err } @@ -574,9 +646,7 @@ func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHigh // GetInviteLink get InviteLink for a chat func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) { - params, _ := config.params() - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return "", err } @@ -589,9 +659,7 @@ func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) { // GetStickerSet returns a StickerSet. func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) { - params, _ := config.params() - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return StickerSet{}, err } @@ -604,12 +672,7 @@ func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) // StopPoll stops a poll and returns the result. func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) { - params, err := config.params() - if err != nil { - return Poll{}, err - } - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return Poll{}, err } @@ -624,12 +687,7 @@ func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) { func (bot *BotAPI) GetMyCommands() ([]BotCommand, error) { config := GetMyCommandsConfig{} - params, err := config.params() - if err != nil { - return nil, err - } - - resp, err := bot.MakeRequest(config.method(), params) + resp, err := bot.Request(config) if err != nil { return nil, err } diff --git a/bot_test.go b/bot_test.go index 3229a8e..cb4ee42 100644 --- a/bot_test.go +++ b/bot_test.go @@ -92,7 +92,7 @@ func TestSendWithMessageForward(t *testing.T) { func TestSendWithNewPhoto(t *testing.T) { bot, _ := getBot(t) - msg := NewPhotoUpload(ChatID, "tests/image.jpg") + msg := NewPhoto(ChatID, "tests/image.jpg") msg.Caption = "Test" _, err := bot.Send(msg) @@ -107,7 +107,7 @@ func TestSendWithNewPhotoWithFileBytes(t *testing.T) { data, _ := ioutil.ReadFile("tests/image.jpg") b := FileBytes{Name: "image.jpg", Bytes: data} - msg := NewPhotoUpload(ChatID, b) + msg := NewPhoto(ChatID, b) msg.Caption = "Test" _, err := bot.Send(msg) @@ -122,7 +122,7 @@ func TestSendWithNewPhotoWithFileReader(t *testing.T) { f, _ := os.Open("tests/image.jpg") reader := FileReader{Name: "image.jpg", Reader: f, Size: -1} - msg := NewPhotoUpload(ChatID, reader) + msg := NewPhoto(ChatID, reader) msg.Caption = "Test" _, err := bot.Send(msg) @@ -134,7 +134,7 @@ func TestSendWithNewPhotoWithFileReader(t *testing.T) { func TestSendWithNewPhotoReply(t *testing.T) { bot, _ := getBot(t) - msg := NewPhotoUpload(ChatID, "tests/image.jpg") + msg := NewPhoto(ChatID, "tests/image.jpg") msg.ReplyToMessageID = ReplyToMessageID _, err := bot.Send(msg) @@ -147,7 +147,7 @@ func TestSendWithNewPhotoReply(t *testing.T) { func TestSendNewPhotoToChannel(t *testing.T) { bot, _ := getBot(t) - msg := NewPhotoUploadToChannel(Channel, "tests/image.jpg") + msg := NewPhotoToChannel(Channel, "tests/image.jpg") msg.Caption = "Test" _, err := bot.Send(msg) @@ -163,7 +163,7 @@ func TestSendNewPhotoToChannelFileBytes(t *testing.T) { data, _ := ioutil.ReadFile("tests/image.jpg") b := FileBytes{Name: "image.jpg", Bytes: data} - msg := NewPhotoUploadToChannel(Channel, b) + msg := NewPhotoToChannel(Channel, b) msg.Caption = "Test" _, err := bot.Send(msg) @@ -179,7 +179,7 @@ func TestSendNewPhotoToChannelFileReader(t *testing.T) { f, _ := os.Open("tests/image.jpg") reader := FileReader{Name: "image.jpg", Reader: f, Size: -1} - msg := NewPhotoUploadToChannel(Channel, reader) + msg := NewPhotoToChannel(Channel, reader) msg.Caption = "Test" _, err := bot.Send(msg) @@ -192,7 +192,7 @@ func TestSendNewPhotoToChannelFileReader(t *testing.T) { func TestSendWithExistingPhoto(t *testing.T) { bot, _ := getBot(t) - msg := NewPhotoShare(ChatID, ExistingPhotoFileID) + msg := NewPhoto(ChatID, FileID(ExistingPhotoFileID)) msg.Caption = "Test" _, err := bot.Send(msg) @@ -204,7 +204,19 @@ func TestSendWithExistingPhoto(t *testing.T) { func TestSendWithNewDocument(t *testing.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.AddFile("thumb", "tests/image.jpg") _, err := bot.Send(msg) if err != nil { @@ -215,7 +227,7 @@ func TestSendWithNewDocument(t *testing.T) { func TestSendWithExistingDocument(t *testing.T) { bot, _ := getBot(t) - msg := NewDocumentShare(ChatID, ExistingDocumentFileID) + msg := NewDocument(ChatID, FileID(ExistingDocumentFileID)) _, err := bot.Send(msg) if err != nil { @@ -226,7 +238,7 @@ func TestSendWithExistingDocument(t *testing.T) { func TestSendWithNewAudio(t *testing.T) { bot, _ := getBot(t) - msg := NewAudioUpload(ChatID, "tests/audio.mp3") + msg := NewAudio(ChatID, "tests/audio.mp3") msg.Title = "TEST" msg.Duration = 10 msg.Performer = "TEST" @@ -242,7 +254,7 @@ func TestSendWithNewAudio(t *testing.T) { func TestSendWithExistingAudio(t *testing.T) { bot, _ := getBot(t) - msg := NewAudioShare(ChatID, ExistingAudioFileID) + msg := NewAudio(ChatID, FileID(ExistingAudioFileID)) msg.Title = "TEST" msg.Duration = 10 msg.Performer = "TEST" @@ -257,7 +269,7 @@ func TestSendWithExistingAudio(t *testing.T) { func TestSendWithNewVoice(t *testing.T) { bot, _ := getBot(t) - msg := NewVoiceUpload(ChatID, "tests/voice.ogg") + msg := NewVoice(ChatID, "tests/voice.ogg") msg.Duration = 10 _, err := bot.Send(msg) @@ -269,7 +281,7 @@ func TestSendWithNewVoice(t *testing.T) { func TestSendWithExistingVoice(t *testing.T) { bot, _ := getBot(t) - msg := NewVoiceShare(ChatID, ExistingVoiceFileID) + msg := NewVoice(ChatID, FileID(ExistingVoiceFileID)) msg.Duration = 10 _, err := bot.Send(msg) @@ -311,7 +323,7 @@ func TestSendWithVenue(t *testing.T) { func TestSendWithNewVideo(t *testing.T) { bot, _ := getBot(t) - msg := NewVideoUpload(ChatID, "tests/video.mp4") + msg := NewVideo(ChatID, "tests/video.mp4") msg.Duration = 10 msg.Caption = "TEST" @@ -325,7 +337,7 @@ func TestSendWithNewVideo(t *testing.T) { func TestSendWithExistingVideo(t *testing.T) { bot, _ := getBot(t) - msg := NewVideoShare(ChatID, ExistingVideoFileID) + msg := NewVideo(ChatID, FileID(ExistingVideoFileID)) msg.Duration = 10 msg.Caption = "TEST" @@ -339,7 +351,7 @@ func TestSendWithExistingVideo(t *testing.T) { func TestSendWithNewVideoNote(t *testing.T) { bot, _ := getBot(t) - msg := NewVideoNoteUpload(ChatID, 240, "tests/videonote.mp4") + msg := NewVideoNote(ChatID, 240, "tests/videonote.mp4") msg.Duration = 10 _, err := bot.Send(msg) @@ -352,7 +364,7 @@ func TestSendWithNewVideoNote(t *testing.T) { func TestSendWithExistingVideoNote(t *testing.T) { bot, _ := getBot(t) - msg := NewVideoNoteShare(ChatID, 240, ExistingVideoNoteFileID) + msg := NewVideoNote(ChatID, 240, FileID(ExistingVideoNoteFileID)) msg.Duration = 10 _, err := bot.Send(msg) @@ -365,7 +377,7 @@ func TestSendWithExistingVideoNote(t *testing.T) { func TestSendWithNewSticker(t *testing.T) { bot, _ := getBot(t) - msg := NewStickerUpload(ChatID, "tests/image.jpg") + msg := NewSticker(ChatID, "tests/image.jpg") _, err := bot.Send(msg) @@ -377,7 +389,7 @@ func TestSendWithNewSticker(t *testing.T) { func TestSendWithExistingSticker(t *testing.T) { bot, _ := getBot(t) - msg := NewStickerShare(ChatID, ExistingStickerFileID) + msg := NewSticker(ChatID, FileID(ExistingStickerFileID)) _, err := bot.Send(msg) @@ -389,7 +401,7 @@ func TestSendWithExistingSticker(t *testing.T) { func TestSendWithNewStickerAndKeyboardHide(t *testing.T) { bot, _ := getBot(t) - msg := NewStickerUpload(ChatID, "tests/image.jpg") + msg := NewSticker(ChatID, "tests/image.jpg") msg.ReplyMarkup = ReplyKeyboardRemove{ RemoveKeyboard: true, Selective: false, @@ -404,7 +416,7 @@ func TestSendWithNewStickerAndKeyboardHide(t *testing.T) { func TestSendWithExistingStickerAndKeyboardHide(t *testing.T) { bot, _ := getBot(t) - msg := NewStickerShare(ChatID, ExistingStickerFileID) + msg := NewSticker(ChatID, FileID(ExistingStickerFileID)) msg.ReplyMarkup = ReplyKeyboardRemove{ RemoveKeyboard: true, Selective: false, @@ -526,9 +538,9 @@ func TestSendWithMediaGroup(t *testing.T) { bot, _ := getBot(t) cfg := NewMediaGroup(ChatID, []interface{}{ - NewInputMediaPhoto("https://i.imgur.com/unQLJIb.jpg"), - NewInputMediaPhoto("https://i.imgur.com/J5qweNZ.jpg"), - NewInputMediaVideo("https://i.imgur.com/F6RmI24.mp4"), + NewInputMediaPhoto(FileURL("https://i.imgur.com/unQLJIb.jpg")), + NewInputMediaPhoto("tests/image.jpg"), + NewInputMediaVideo("tests/video.mp4"), }) messages, err := bot.SendMediaGroup(cfg) @@ -537,11 +549,11 @@ func TestSendWithMediaGroup(t *testing.T) { } if messages == nil { - t.Error() + t.Error("No received messages") } - if len(messages) != 3 { - t.Error() + if len(messages) != len(cfg.Media) { + t.Errorf("Different number of messages: %d", len(messages)) } } diff --git a/configs.go b/configs.go index 939522b..2dfc777 100644 --- a/configs.go +++ b/configs.go @@ -55,12 +55,19 @@ type Chattable interface { method() string } +// RequestFile represents a file associated with a request. May involve +// uploading a file, or passing an existing ID. +type RequestFile struct { + // The multipart upload field name. + Name string + // The file to upload. + File interface{} +} + // Fileable is any config type that can be sent that includes a file. type Fileable interface { Chattable - name() string - getFile() interface{} - useExistingFile() bool + files() []RequestFile } // BaseChat is base type for all chat config types. @@ -87,11 +94,21 @@ func (chat *BaseChat) params() (Params, error) { // BaseFile is a base type for all file config types. type BaseFile struct { BaseChat - File interface{} - FileID string - UseExisting bool - MimeType string - FileSize int + Files []RequestFile + MimeType string + FileSize int +} + +// AddFile specifies a file for a Telegram request. +func (file *BaseFile) AddFile(name string, f interface{}) { + if file.Files == nil { + file.Files = make([]RequestFile, 0, 1) + } + + file.Files = append(file.Files, RequestFile{ + Name: name, + File: f, + }) } func (file BaseFile) params() (Params, error) { @@ -103,12 +120,8 @@ func (file BaseFile) params() (Params, error) { return params, err } -func (file BaseFile) getFile() interface{} { - return file.File -} - -func (file BaseFile) useExistingFile() bool { - return file.UseExisting +func (file BaseFile) files() []RequestFile { + return file.Files } // BaseEdit is base type of all chat edits. @@ -194,7 +207,6 @@ type PhotoConfig struct { func (config PhotoConfig) params() (Params, error) { params, err := config.BaseFile.params() - params.AddNonEmpty(config.name(), config.FileID) params.AddNonEmpty("caption", config.Caption) params.AddNonEmpty("parse_mode", config.ParseMode) @@ -225,7 +237,6 @@ func (config AudioConfig) params() (Params, error) { return params, err } - params.AddNonEmpty(config.name(), config.FileID) params.AddNonZero("duration", config.Duration) params.AddNonEmpty("performer", config.Performer) params.AddNonEmpty("title", config.Title) @@ -253,7 +264,6 @@ type DocumentConfig struct { func (config DocumentConfig) params() (Params, error) { params, err := config.BaseFile.params() - params.AddNonEmpty(config.name(), config.FileID) params.AddNonEmpty("caption", config.Caption) params.AddNonEmpty("parse_mode", config.ParseMode) @@ -274,11 +284,7 @@ type StickerConfig struct { } func (config StickerConfig) params() (Params, error) { - params, err := config.BaseChat.params() - - params.AddNonEmpty(config.name(), config.FileID) - - return params, err + return config.BaseChat.params() } func (config StickerConfig) name() string { @@ -301,7 +307,6 @@ type VideoConfig struct { func (config VideoConfig) params() (Params, error) { params, err := config.BaseChat.params() - params.AddNonEmpty(config.name(), config.FileID) params.AddNonZero("duration", config.Duration) params.AddNonEmpty("caption", config.Caption) params.AddNonEmpty("parse_mode", config.ParseMode) @@ -329,7 +334,6 @@ type AnimationConfig struct { func (config AnimationConfig) params() (Params, error) { params, err := config.BaseChat.params() - params.AddNonEmpty(config.name(), config.FileID) params.AddNonZero("duration", config.Duration) params.AddNonEmpty("caption", config.Caption) params.AddNonEmpty("parse_mode", config.ParseMode) @@ -355,7 +359,6 @@ type VideoNoteConfig struct { func (config VideoNoteConfig) params() (Params, error) { params, err := config.BaseChat.params() - params.AddNonEmpty(config.name(), config.FileID) params.AddNonZero("duration", config.Duration) params.AddNonZero("length", config.Length) @@ -381,7 +384,6 @@ type VoiceConfig struct { func (config VoiceConfig) params() (Params, error) { params, err := config.BaseChat.params() - params.AddNonEmpty(config.name(), config.FileID) params.AddNonZero("duration", config.Duration) params.AddNonEmpty("caption", config.Caption) params.AddNonEmpty("parse_mode", config.ParseMode) @@ -683,23 +685,28 @@ func (config EditMessageCaptionConfig) method() string { return "editMessageCaption" } -// EditMessageMediaConfig contains information about editing a message's media. +// EditMessageMediaConfig allows you to make an editMessageMedia request. type EditMessageMediaConfig struct { BaseEdit Media interface{} } +func (config EditMessageMediaConfig) files() []RequestFile { + return []RequestFile{ + { + Name: "media", + File: config.Media, + }, + } +} + func (EditMessageMediaConfig) method() string { return "editMessageMedia" } func (config EditMessageMediaConfig) params() (Params, error) { - params, err := config.BaseEdit.params() - - params.AddInterface("media", config.Media) - - return params, err + return config.BaseEdit.params() } // EditMessageReplyMarkupConfig allows you to modify the reply markup @@ -818,14 +825,6 @@ func (config WebhookConfig) name() string { return "certificate" } -func (config WebhookConfig) getFile() interface{} { - return config.Certificate -} - -func (config WebhookConfig) useExistingFile() bool { - return config.URL != nil -} - // RemoveWebhookConfig is a helper to remove a webhook. type RemoveWebhookConfig struct { } @@ -854,6 +853,12 @@ type FileReader struct { Size int64 } +// FileURL is a URL to use as a file for a request. +type FileURL string + +// FileID is an ID of a file already uploaded to Telegram. +type FileID string + // InlineConfig contains information on making an InlineQuery response. type InlineConfig struct { InlineQueryID string `json:"inline_query_id"` @@ -1312,14 +1317,6 @@ func (config SetChatPhotoConfig) name() string { return "photo" } -func (config SetChatPhotoConfig) getFile() interface{} { - return config.File -} - -func (config SetChatPhotoConfig) useExistingFile() bool { - return config.UseExisting -} - // DeleteChatPhotoConfig allows you to delete a group, supergroup, or channel's photo. type DeleteChatPhotoConfig struct { ChatID int64 @@ -1415,18 +1412,13 @@ func (config UploadStickerConfig) params() (Params, error) { return params, nil } -func (config UploadStickerConfig) name() string { - return "png_sticker" -} - -func (config UploadStickerConfig) getFile() interface{} { - return config.PNGSticker -} - -func (config UploadStickerConfig) useExistingFile() bool { - _, ok := config.PNGSticker.(string) - - return ok +func (config UploadStickerConfig) files() []RequestFile { + return []RequestFile{ + { + Name: "png_sticker", + File: config.PNGSticker, + }, + } } // NewStickerSetConfig allows creating a new sticker set. @@ -1454,12 +1446,6 @@ func (config NewStickerSetConfig) params() (Params, error) { params["name"] = config.Name params["title"] = config.Title - if sticker, ok := config.PNGSticker.(string); ok { - params[config.name()] = sticker - } else if sticker, ok := config.TGSSticker.(string); ok { - params[config.name()] = sticker - } - params["emojis"] = config.Emojis params.AddBool("contains_masks", config.ContainsMasks) @@ -1469,26 +1455,18 @@ func (config NewStickerSetConfig) params() (Params, error) { return params, err } -func (config NewStickerSetConfig) getFile() interface{} { - return config.PNGSticker -} - -func (config NewStickerSetConfig) name() string { - return "png_sticker" -} - -func (config NewStickerSetConfig) useExistingFile() bool { +func (config NewStickerSetConfig) files() []RequestFile { if config.PNGSticker != nil { - _, ok := config.PNGSticker.(string) - return ok + return []RequestFile{{ + Name: "png_sticker", + File: config.PNGSticker, + }} } - if config.TGSSticker != nil { - _, ok := config.TGSSticker.(string) - return ok - } - - panic("NewStickerSetConfig had nil PNGSticker and TGSSticker") + return []RequestFile{{ + Name: "tgs_sticker", + File: config.TGSSticker, + }} } // AddStickerConfig allows you to add a sticker to a set. @@ -1512,29 +1490,24 @@ func (config AddStickerConfig) params() (Params, error) { params["name"] = config.Name params["emojis"] = config.Emojis - if sticker, ok := config.PNGSticker.(string); ok { - params[config.name()] = sticker - } else if sticker, ok := config.TGSSticker.(string); ok { - params[config.name()] = sticker - } - err := params.AddInterface("mask_position", config.MaskPosition) return params, err } -func (config AddStickerConfig) name() string { - return "png_sticker" -} +func (config AddStickerConfig) files() []RequestFile { + if config.PNGSticker != nil { + return []RequestFile{{ + Name: "png_sticker", + File: config.PNGSticker, + }} + } -func (config AddStickerConfig) getFile() interface{} { - return config.PNGSticker -} + return []RequestFile{{ + Name: "tgs_sticker", + File: config.TGSSticker, + }} -func (config AddStickerConfig) useExistingFile() bool { - _, ok := config.PNGSticker.(string) - - return ok } // SetStickerPositionConfig allows you to change the position of a sticker in a set. @@ -1601,15 +1574,6 @@ func (config SetStickerSetThumbConfig) name() string { return "thumb" } -func (config SetStickerSetThumbConfig) getFile() interface{} { - return config.Thumb -} - -func (config SetStickerSetThumbConfig) useExistingFile() bool { - _, ok := config.Thumb.(string) - return ok -} - // SetChatStickerSetConfig allows you to set the sticker set for a supergroup. type SetChatStickerSetConfig struct { ChatID int64 @@ -1652,6 +1616,9 @@ func (config DeleteChatStickerSetConfig) params() (Params, error) { // MediaGroupConfig allows you to send a group of media. // // Media consist of InputMedia items (InputMediaPhoto, InputMediaVideo). +// +// Due to additional processing required, this config is not Chattable or +// Fileable. It must be uploaded with SendMediaGroup. type MediaGroupConfig struct { ChatID int64 ChannelUsername string @@ -1669,9 +1636,6 @@ func (config MediaGroupConfig) params() (Params, error) { params := make(Params) params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername) - if err := params.AddInterface("media", config.Media); err != nil { - return params, nil - } params.AddBool("disable_notification", config.DisableNotification) params.AddNonZero("reply_to_message_id", config.ReplyToMessageID) diff --git a/go.mod b/go.mod index 42f56cf..9e7f65c 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module github.com/go-telegram-bot-api/telegram-bot-api/v5 -require github.com/technoweenie/multipartstreamer v1.0.1 - go 1.13 diff --git a/go.sum b/go.sum index 8660600..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +0,0 @@ -github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= -github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= diff --git a/helpers.go b/helpers.go index 1e95367..5684c64 100644 --- a/helpers.go +++ b/helpers.go @@ -51,261 +51,131 @@ func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig { } } -// NewPhotoUpload creates a new photo uploader. -// -// chatID is where to send it, file is a string path to the file, -// FileReader, or FileBytes. +// NewPhoto creates a new sendPhoto request. // // Note that you must send animated GIFs as a document. -func NewPhotoUpload(chatID int64, file interface{}) PhotoConfig { - return PhotoConfig{ +func NewPhoto(chatID int64, file interface{}) PhotoConfig { + config := PhotoConfig{ BaseFile: BaseFile{ - BaseChat: BaseChat{ChatID: chatID}, - File: file, - UseExisting: false, + BaseChat: BaseChat{ChatID: chatID}, }, } + + config.AddFile(config.name(), file) + + return config } -// NewPhotoUploadToChannel creates a new photo uploader to send a photo to a channel. -// -// username is the username of the channel, file is a string path to the file, -// FileReader, or FileBytes. +// NewPhotoToChannel creates a new photo uploader to send a photo to a channel. // // Note that you must send animated GIFs as a document. -func NewPhotoUploadToChannel(username string, file interface{}) PhotoConfig { - return PhotoConfig{ +func NewPhotoToChannel(username string, file interface{}) PhotoConfig { + config := PhotoConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ ChannelUsername: username, }, - File: file, - UseExisting: false, }, } + + config.AddFile(config.name(), file) + + return config } -// NewPhotoShare 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 NewPhotoShare(chatID int64, fileID string) PhotoConfig { - return PhotoConfig{ +// NewAudio creates a new sendAudio request. +func NewAudio(chatID int64, file interface{}) AudioConfig { + config := AudioConfig{ BaseFile: BaseFile{ - BaseChat: BaseChat{ChatID: chatID}, - FileID: fileID, - UseExisting: true, + BaseChat: BaseChat{ChatID: chatID}, }, } + + config.AddFile(config.name(), file) + + return config } -// NewAudioUpload creates a new audio uploader. +// NewDocument creates a new sendDocument request. +func NewDocument(chatID int64, file interface{}) DocumentConfig { + config := DocumentConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + }, + } + + config.AddFile(config.name(), file) + + return config +} + +// NewSticker creates a new sendSticker request. +func NewSticker(chatID int64, file interface{}) StickerConfig { + config := StickerConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + }, + } + + config.AddFile(config.name(), file) + + return config +} + +// NewVideo creates a new sendVideo request. +func NewVideo(chatID int64, file interface{}) VideoConfig { + config := VideoConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + }, + } + + config.AddFile(config.name(), file) + + return config +} + +// NewAnimation creates a new sendAnimation request. +func NewAnimation(chatID int64, file interface{}) AnimationConfig { + config := AnimationConfig{ + BaseFile: BaseFile{ + BaseChat: BaseChat{ChatID: chatID}, + }, + } + + config.AddFile(config.name(), file) + + return config +} + +// NewVideoNote creates a new sendVideoNote request. // // 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{ +func NewVideoNote(chatID int64, length int, file interface{}) VideoNoteConfig { + config := VideoNoteConfig{ BaseFile: BaseFile{ - BaseChat: BaseChat{ChatID: chatID}, - File: file, - UseExisting: false, - }, - } -} - -// NewAudioShare shares an existing audio file. -// You may use this to reshare an existing audio file without -// 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{ - BaseFile: BaseFile{ - BaseChat: BaseChat{ChatID: chatID}, - File: file, - UseExisting: false, - }, - } -} - -// NewDocumentShare shares an existing document. -// You may use this to reshare an existing document without -// 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{ - BaseFile: BaseFile{ - BaseChat: BaseChat{ChatID: chatID}, - File: file, - UseExisting: false, - }, - } -} - -// NewStickerShare shares an existing sticker. -// You may use this to reshare an existing sticker without -// 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{ - BaseFile: BaseFile{ - BaseChat: BaseChat{ChatID: chatID}, - File: file, - UseExisting: false, - }, - } -} - -// NewVideoShare shares an existing video. -// You may use this to reshare an existing video without reuploading it. -// -// 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{ - BaseChat: BaseChat{ChatID: chatID}, - FileID: fileID, - UseExisting: true, - }, - } -} - -// NewAnimationUpload creates a new animation uploader. -// -// chatID is where to send it, file is a string path to the file, -// FileReader, or FileBytes. -func NewAnimationUpload(chatID int64, file interface{}) AnimationConfig { - 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{ - BaseFile: BaseFile{ - BaseChat: BaseChat{ChatID: chatID}, - File: file, - UseExisting: false, + BaseChat: BaseChat{ChatID: chatID}, }, Length: length, } + + config.AddFile(config.name(), file) + + return config } -// NewVideoNoteShare shares an existing video. -// You may use this to reshare an existing video without reuploading it. -// -// 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{ +// NewVoice creates a new sendVoice request. +func NewVoice(chatID int64, file interface{}) VoiceConfig { + config := VoiceConfig{ BaseFile: BaseFile{ - BaseChat: BaseChat{ChatID: chatID}, - FileID: fileID, - UseExisting: true, + BaseChat: BaseChat{ChatID: chatID}, }, - 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{ - BaseFile: BaseFile{ - BaseChat: BaseChat{ChatID: chatID}, - File: file, - UseExisting: false, - }, - } -} + config.AddFile(config.name(), file) -// 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, - }, - } + return config } // NewMediaGroup creates a new media group. Files should be an array of @@ -318,7 +188,7 @@ func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig { } // NewInputMediaPhoto creates a new InputMediaPhoto. -func NewInputMediaPhoto(media string) InputMediaPhoto { +func NewInputMediaPhoto(media interface{}) InputMediaPhoto { return InputMediaPhoto{ BaseInputMedia{ Type: "photo", @@ -328,7 +198,7 @@ func NewInputMediaPhoto(media string) InputMediaPhoto { } // NewInputMediaVideo creates a new InputMediaVideo. -func NewInputMediaVideo(media string) InputMediaVideo { +func NewInputMediaVideo(media interface{}) InputMediaVideo { return InputMediaVideo{ BaseInputMedia: BaseInputMedia{ Type: "video", @@ -338,7 +208,7 @@ func NewInputMediaVideo(media string) InputMediaVideo { } // NewInputMediaAnimation creates a new InputMediaAnimation. -func NewInputMediaAnimation(media string) InputMediaAnimation { +func NewInputMediaAnimation(media interface{}) InputMediaAnimation { return InputMediaAnimation{ BaseInputMedia: BaseInputMedia{ Type: "animation", @@ -348,7 +218,7 @@ func NewInputMediaAnimation(media string) InputMediaAnimation { } // NewInputMediaAudio creates a new InputMediaAudio. -func NewInputMediaAudio(media string) InputMediaAudio { +func NewInputMediaAudio(media interface{}) InputMediaAudio { return InputMediaAudio{ BaseInputMedia: BaseInputMedia{ Type: "audio", @@ -875,37 +745,6 @@ func NewInvoice(chatID int64, title, description, payload, providerToken, startP 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. func NewChatTitle(chatID int64, title string) SetChatTitleConfig { return SetChatTitleConfig{ @@ -924,14 +763,17 @@ func NewChatDescription(chatID int64, description string) SetChatDescriptionConf // NewChatPhoto allows you to update the photo for a chat. func NewChatPhoto(chatID int64, photo interface{}) SetChatPhotoConfig { - return SetChatPhotoConfig{ + config := SetChatPhotoConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ ChatID: chatID, }, - File: photo, }, } + + config.AddFile(config.name(), photo) + + return config } // NewDeleteChatPhoto allows you to delete the photo for a chat. diff --git a/types.go b/types.go index 95a871d..5e3159b 100644 --- a/types.go +++ b/types.go @@ -1112,10 +1112,10 @@ type BotCommand struct { // BaseInputMedia is a base type for the InputMedia types. type BaseInputMedia struct { - Type string `json:"type"` - Media string `json:"media"` - Caption string `json:"caption"` - ParseMode string `json:"parse_mode"` + Type string `json:"type"` + Media interface{} `json:"media"` + Caption string `json:"caption,omitempty"` + ParseMode string `json:"parse_mode,omitempty"` } // InputMediaPhoto is a photo to send as part of a media group. @@ -1126,10 +1126,10 @@ type InputMediaPhoto struct { // InputMediaVideo is a video to send as part of a media group. type InputMediaVideo struct { BaseInputMedia - Width int `json:"width"` - Height int `json:"height"` - Duration int `json:"duration"` - SupportsStreaming bool `json:"supports_streaming"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + Duration int `json:"duration,omitempty"` + SupportsStreaming bool `json:"supports_streaming,omitempty"` } // InputMediaAnimation is an animation to send as part of a media group. diff --git a/types_test.go b/types_test.go index 401cb6a..46ec2d1 100644 --- a/types_test.go +++ b/types_test.go @@ -331,3 +331,21 @@ var ( _ Chattable = VoiceConfig{} _ 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) +)