From 816532053bbc99773ad9a3684527d6358660b0a4 Mon Sep 17 00:00:00 2001 From: Syfaro Date: Fri, 20 Aug 2021 15:31:52 -0400 Subject: [PATCH] Create interface for file data. --- bot.go | 65 +++------- bot_test.go | 50 ++++---- configs.go | 306 ++++++++++++++++++++++++++++++------------------ helpers.go | 34 +++--- helpers_test.go | 2 +- types.go | 10 +- types_test.go | 10 ++ 7 files changed, 268 insertions(+), 209 deletions(-) diff --git a/bot.go b/bot.go index 0d051dd..65f02bb 100644 --- a/bot.go +++ b/bot.go @@ -3,7 +3,6 @@ package tgbotapi import ( - "bytes" "encoding/json" "errors" "fmt" @@ -12,7 +11,6 @@ import ( "mime/multipart" "net/http" "net/url" - "os" "strings" "time" ) @@ -180,54 +178,37 @@ func (bot *BotAPI) UploadFiles(endpoint string, params Params, files []RequestFi } for _, file := range files { - switch f := file.File.(type) { - case string: - fileHandle, err := os.Open(f) - if err != nil { - w.CloseWithError(err) - return - } - defer fileHandle.Close() - - part, err := m.CreateFormFile(file.Name, fileHandle.Name()) + if file.Data.NeedsUpload() { + name, reader, err := file.Data.UploadData() if err != nil { w.CloseWithError(err) return } - io.Copy(part, fileHandle) - case FileBytes: - part, err := m.CreateFormFile(file.Name, f.Name) + part, err := m.CreateFormFile(file.Name, name) if err != nil { w.CloseWithError(err) return } - buf := bytes.NewBuffer(f.Bytes) - io.Copy(part, buf) - case FileReader: - part, err := m.CreateFormFile(file.Name, f.Name) - if err != nil { + if _, err := io.Copy(part, reader); 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 { + if closer, ok := reader.(io.ReadCloser); ok { + if err = closer.Close(); err != nil { + w.CloseWithError(err) + return + } + } + } else { + value := file.Data.SendData() + + if err := m.WriteField(file.Name, value); 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 } } }() @@ -316,8 +297,7 @@ func (bot *BotAPI) IsMessageToMe(message Message) bool { func hasFilesNeedingUpload(files []RequestFile) bool { for _, file := range files { - switch file.File.(type) { - case string, FileBytes, FileReader: + if file.Data.NeedsUpload() { return true } } @@ -344,20 +324,7 @@ func (bot *BotAPI) Request(c Chattable) (*APIResponse, error) { // 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 + params[file.Name] = file.Data.SendData() } } diff --git a/bot_test.go b/bot_test.go index b1ba8aa..96f7ad8 100644 --- a/bot_test.go +++ b/bot_test.go @@ -127,7 +127,7 @@ func TestCopyMessage(t *testing.T) { func TestSendWithNewPhoto(t *testing.T) { bot, _ := getBot(t) - msg := NewPhoto(ChatID, "tests/image.jpg") + msg := NewPhoto(ChatID, FilePath("tests/image.jpg")) msg.Caption = "Test" _, err := bot.Send(msg) @@ -169,7 +169,7 @@ func TestSendWithNewPhotoWithFileReader(t *testing.T) { func TestSendWithNewPhotoReply(t *testing.T) { bot, _ := getBot(t) - msg := NewPhoto(ChatID, "tests/image.jpg") + msg := NewPhoto(ChatID, FilePath("tests/image.jpg")) msg.ReplyToMessageID = ReplyToMessageID _, err := bot.Send(msg) @@ -182,7 +182,7 @@ func TestSendWithNewPhotoReply(t *testing.T) { func TestSendNewPhotoToChannel(t *testing.T) { bot, _ := getBot(t) - msg := NewPhotoToChannel(Channel, "tests/image.jpg") + msg := NewPhotoToChannel(Channel, FilePath("tests/image.jpg")) msg.Caption = "Test" _, err := bot.Send(msg) @@ -239,7 +239,7 @@ func TestSendWithExistingPhoto(t *testing.T) { func TestSendWithNewDocument(t *testing.T) { bot, _ := getBot(t) - msg := NewDocument(ChatID, "tests/image.jpg") + msg := NewDocument(ChatID, FilePath("tests/image.jpg")) _, err := bot.Send(msg) if err != nil { @@ -250,8 +250,8 @@ func TestSendWithNewDocument(t *testing.T) { func TestSendWithNewDocumentAndThumb(t *testing.T) { bot, _ := getBot(t) - msg := NewDocument(ChatID, "tests/voice.ogg") - msg.Thumb = "tests/image.jpg" + msg := NewDocument(ChatID, FilePath("tests/voice.ogg")) + msg.Thumb = FilePath("tests/image.jpg") _, err := bot.Send(msg) if err != nil { @@ -273,7 +273,7 @@ func TestSendWithExistingDocument(t *testing.T) { func TestSendWithNewAudio(t *testing.T) { bot, _ := getBot(t) - msg := NewAudio(ChatID, "tests/audio.mp3") + msg := NewAudio(ChatID, FilePath("tests/audio.mp3")) msg.Title = "TEST" msg.Duration = 10 msg.Performer = "TEST" @@ -302,7 +302,7 @@ func TestSendWithExistingAudio(t *testing.T) { func TestSendWithNewVoice(t *testing.T) { bot, _ := getBot(t) - msg := NewVoice(ChatID, "tests/voice.ogg") + msg := NewVoice(ChatID, FilePath("tests/voice.ogg")) msg.Duration = 10 _, err := bot.Send(msg) @@ -356,7 +356,7 @@ func TestSendWithVenue(t *testing.T) { func TestSendWithNewVideo(t *testing.T) { bot, _ := getBot(t) - msg := NewVideo(ChatID, "tests/video.mp4") + msg := NewVideo(ChatID, FilePath("tests/video.mp4")) msg.Duration = 10 msg.Caption = "TEST" @@ -384,7 +384,7 @@ func TestSendWithExistingVideo(t *testing.T) { func TestSendWithNewVideoNote(t *testing.T) { bot, _ := getBot(t) - msg := NewVideoNote(ChatID, 240, "tests/videonote.mp4") + msg := NewVideoNote(ChatID, 240, FilePath("tests/videonote.mp4")) msg.Duration = 10 _, err := bot.Send(msg) @@ -410,7 +410,7 @@ func TestSendWithExistingVideoNote(t *testing.T) { func TestSendWithNewSticker(t *testing.T) { bot, _ := getBot(t) - msg := NewSticker(ChatID, "tests/image.jpg") + msg := NewSticker(ChatID, FilePath("tests/image.jpg")) _, err := bot.Send(msg) @@ -434,7 +434,7 @@ func TestSendWithExistingSticker(t *testing.T) { func TestSendWithNewStickerAndKeyboardHide(t *testing.T) { bot, _ := getBot(t) - msg := NewSticker(ChatID, "tests/image.jpg") + msg := NewSticker(ChatID, FilePath("tests/image.jpg")) msg.ReplyMarkup = ReplyKeyboardRemove{ RemoveKeyboard: true, Selective: false, @@ -550,7 +550,7 @@ func TestSetWebhookWithCert(t *testing.T) { bot.Request(DeleteWebhookConfig{}) - wh, err := NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, "tests/cert.pem") + wh, err := NewWebhookWithCert("https://example.com/tgbotapi-test/"+bot.Token, FilePath("tests/cert.pem")) if err != nil { t.Error(err) @@ -609,8 +609,8 @@ func TestSendWithMediaGroupPhotoVideo(t *testing.T) { cfg := NewMediaGroup(ChatID, []interface{}{ NewInputMediaPhoto(FileURL("https://github.com/go-telegram-bot-api/telegram-bot-api/raw/0a3a1c8716c4cd8d26a262af9f12dcbab7f3f28c/tests/image.jpg")), - NewInputMediaPhoto("tests/image.jpg"), - NewInputMediaVideo("tests/video.mp4"), + NewInputMediaPhoto(FilePath("tests/image.jpg")), + NewInputMediaVideo(FilePath("tests/video.mp4")), }) messages, err := bot.SendMediaGroup(cfg) @@ -632,7 +632,7 @@ func TestSendWithMediaGroupDocument(t *testing.T) { cfg := NewMediaGroup(ChatID, []interface{}{ NewInputMediaDocument(FileURL("https://i.imgur.com/unQLJIb.jpg")), - NewInputMediaDocument("tests/image.jpg"), + NewInputMediaDocument(FilePath("tests/image.jpg")), }) messages, err := bot.SendMediaGroup(cfg) @@ -653,8 +653,8 @@ func TestSendWithMediaGroupAudio(t *testing.T) { bot, _ := getBot(t) cfg := NewMediaGroup(ChatID, []interface{}{ - NewInputMediaAudio("tests/audio.mp3"), - NewInputMediaAudio("tests/audio.mp3"), + NewInputMediaAudio(FilePath("tests/audio.mp3")), + NewInputMediaAudio(FilePath("tests/audio.mp3")), }) messages, err := bot.SendMediaGroup(cfg) @@ -715,7 +715,7 @@ func ExampleNewWebhook() { log.Printf("Authorized on account %s", bot.Self.UserName) - wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem") + wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, FilePath("cert.pem")) if err != nil { panic(err) @@ -755,7 +755,7 @@ func ExampleWebhookHandler() { log.Printf("Authorized on account %s", bot.Self.UserName) - wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem") + wh, err := NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, FilePath("cert.pem")) if err != nil { panic(err) @@ -1004,7 +1004,7 @@ func TestCommands(t *testing.T) { func TestEditMessageMedia(t *testing.T) { bot, _ := getBot(t) - msg := NewPhoto(ChatID, "tests/image.jpg") + msg := NewPhoto(ChatID, FilePath("tests/image.jpg")) msg.Caption = "Test" m, err := bot.Send(msg) @@ -1017,7 +1017,7 @@ func TestEditMessageMedia(t *testing.T) { ChatID: ChatID, MessageID: m.MessageID, }, - Media: NewInputMediaVideo("tests/video.mp4"), + Media: NewInputMediaVideo(FilePath("tests/video.mp4")), } _, err = bot.Request(edit) @@ -1028,17 +1028,17 @@ func TestEditMessageMedia(t *testing.T) { func TestPrepareInputMediaForParams(t *testing.T) { media := []interface{}{ - NewInputMediaPhoto("tests/image.jpg"), + NewInputMediaPhoto(FilePath("tests/image.jpg")), NewInputMediaVideo(FileID("test")), } prepared := prepareInputMediaForParams(media) - if media[0].(InputMediaPhoto).Media != "tests/image.jpg" { + if media[0].(InputMediaPhoto).Media != FilePath("tests/image.jpg") { t.Error("Original media was changed") } - if prepared[0].(InputMediaPhoto).Media != "attach://file-0" { + if prepared[0].(InputMediaPhoto).Media != FileID("attach://file-0") { t.Error("New media was not replaced") } diff --git a/configs.go b/configs.go index ac09679..4df4b7c 100644 --- a/configs.go +++ b/configs.go @@ -1,9 +1,11 @@ package tgbotapi import ( + "bytes" "fmt" "io" "net/url" + "os" "strconv" ) @@ -97,9 +99,7 @@ const ( // Library errors const ( - // ErrBadFileType happens when you pass an unknown type - ErrBadFileType = "bad file type" - ErrBadURL = "bad or empty url" + ErrBadURL = "bad or empty url" ) // Chattable is any config type that can be sent. @@ -108,21 +108,136 @@ 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 files() []RequestFile } +// RequestFile represents a file associated with a field name. +type RequestFile struct { + // The file field name. + Name string + // The file data to include. + Data RequestFileData +} + +// RequestFileData represents the data to be used for a file. +type RequestFileData interface { + // If the file needs to be uploaded. + NeedsUpload() bool + + // Get the file name and an `io.Reader` for the file to be uploaded. This + // must only be called when the file needs to be uploaded. + UploadData() (string, io.Reader, error) + // Get the file data to send when a file does not need to be uploaded. This + // must only be called when the file does not need to be uploaded. + SendData() string +} + +// FileBytes contains information about a set of bytes to upload +// as a File. +type FileBytes struct { + Name string + Bytes []byte +} + +func (fb FileBytes) NeedsUpload() bool { + return true +} + +func (fb FileBytes) UploadData() (string, io.Reader, error) { + return fb.Name, bytes.NewReader(fb.Bytes), nil +} + +func (fb FileBytes) SendData() string { + panic("FileBytes must be uploaded") +} + +// FileReader contains information about a reader to upload as a File. +type FileReader struct { + Name string + Reader io.Reader +} + +func (fr FileReader) NeedsUpload() bool { + return true +} + +func (fr FileReader) UploadData() (string, io.Reader, error) { + return fr.Name, fr.Reader, nil +} + +func (fr FileReader) SendData() string { + panic("FileReader must be uploaded") +} + +// FilePath is a path to a local file. +type FilePath string + +func (fp FilePath) NeedsUpload() bool { + return true +} + +func (fp FilePath) UploadData() (string, io.Reader, error) { + fileHandle, err := os.Open(string(fp)) + if err != nil { + return "", nil, err + } + + name := fileHandle.Name() + return name, fileHandle, err +} + +func (fp FilePath) SendData() string { + panic("FilePath must be uploaded") +} + +// FileURL is a URL to use as a file for a request. +type FileURL string + +func (fu FileURL) NeedsUpload() bool { + return false +} + +func (fu FileURL) UploadData() (string, io.Reader, error) { + panic("FileURL cannot be uploaded") +} + +func (fu FileURL) SendData() string { + return string(fu) +} + +// FileID is an ID of a file already uploaded to Telegram. +type FileID string + +func (fi FileID) NeedsUpload() bool { + return false +} + +func (fi FileID) UploadData() (string, io.Reader, error) { + panic("FileID cannot be uploaded") +} + +func (fi FileID) SendData() string { + return string(fi) +} + +// fileAttach is a internal file type used for processed media groups. +type fileAttach string + +func (fa fileAttach) NeedsUpload() bool { + return false +} + +func (fa fileAttach) UploadData() (string, io.Reader, error) { + panic("fileAttach cannot be uploaded") +} + +func (fa fileAttach) SendData() string { + return string(fa) +} + // LogOutConfig is a request to log out of the cloud Bot API server. // // Note that you may not log back in for at least 10 minutes. @@ -176,7 +291,7 @@ func (chat *BaseChat) params() (Params, error) { // BaseFile is a base type for all file config types. type BaseFile struct { BaseChat - File interface{} + File RequestFileData } func (file BaseFile) params() (Params, error) { @@ -291,7 +406,7 @@ func (config CopyMessageConfig) method() string { // PhotoConfig contains information about a SendPhoto request. type PhotoConfig struct { BaseFile - Thumb interface{} + Thumb RequestFileData Caption string ParseMode string CaptionEntities []MessageEntity @@ -317,13 +432,13 @@ func (config PhotoConfig) method() string { func (config PhotoConfig) files() []RequestFile { files := []RequestFile{{ Name: "photo", - File: config.File, + Data: config.File, }} if config.Thumb != nil { files = append(files, RequestFile{ Name: "thumb", - File: config.Thumb, + Data: config.Thumb, }) } @@ -333,7 +448,7 @@ func (config PhotoConfig) files() []RequestFile { // AudioConfig contains information about a SendAudio request. type AudioConfig struct { BaseFile - Thumb interface{} + Thumb RequestFileData Caption string ParseMode string CaptionEntities []MessageEntity @@ -365,13 +480,13 @@ func (config AudioConfig) method() string { func (config AudioConfig) files() []RequestFile { files := []RequestFile{{ Name: "audio", - File: config.File, + Data: config.File, }} if config.Thumb != nil { files = append(files, RequestFile{ Name: "thumb", - File: config.Thumb, + Data: config.Thumb, }) } @@ -381,7 +496,7 @@ func (config AudioConfig) files() []RequestFile { // DocumentConfig contains information about a SendDocument request. type DocumentConfig struct { BaseFile - Thumb interface{} + Thumb RequestFileData Caption string ParseMode string CaptionEntities []MessageEntity @@ -405,13 +520,13 @@ func (config DocumentConfig) method() string { func (config DocumentConfig) files() []RequestFile { files := []RequestFile{{ Name: "document", - File: config.File, + Data: config.File, }} if config.Thumb != nil { files = append(files, RequestFile{ Name: "thumb", - File: config.Thumb, + Data: config.Thumb, }) } @@ -434,14 +549,14 @@ func (config StickerConfig) method() string { func (config StickerConfig) files() []RequestFile { return []RequestFile{{ Name: "sticker", - File: config.File, + Data: config.File, }} } // VideoConfig contains information about a SendVideo request. type VideoConfig struct { BaseFile - Thumb interface{} + Thumb RequestFileData Duration int Caption string ParseMode string @@ -471,13 +586,13 @@ func (config VideoConfig) method() string { func (config VideoConfig) files() []RequestFile { files := []RequestFile{{ Name: "video", - File: config.File, + Data: config.File, }} if config.Thumb != nil { files = append(files, RequestFile{ Name: "thumb", - File: config.Thumb, + Data: config.Thumb, }) } @@ -488,7 +603,7 @@ func (config VideoConfig) files() []RequestFile { type AnimationConfig struct { BaseFile Duration int - Thumb interface{} + Thumb RequestFileData Caption string ParseMode string CaptionEntities []MessageEntity @@ -515,13 +630,13 @@ func (config AnimationConfig) method() string { func (config AnimationConfig) files() []RequestFile { files := []RequestFile{{ Name: "animation", - File: config.File, + Data: config.File, }} if config.Thumb != nil { files = append(files, RequestFile{ Name: "thumb", - File: config.Thumb, + Data: config.Thumb, }) } @@ -531,7 +646,7 @@ func (config AnimationConfig) files() []RequestFile { // VideoNoteConfig contains information about a SendVideoNote request. type VideoNoteConfig struct { BaseFile - Thumb interface{} + Thumb RequestFileData Duration int Length int } @@ -552,13 +667,13 @@ func (config VideoNoteConfig) method() string { func (config VideoNoteConfig) files() []RequestFile { files := []RequestFile{{ Name: "video_note", - File: config.File, + Data: config.File, }} if config.Thumb != nil { files = append(files, RequestFile{ Name: "thumb", - File: config.Thumb, + Data: config.Thumb, }) } @@ -568,7 +683,7 @@ func (config VideoNoteConfig) files() []RequestFile { // VoiceConfig contains information about a SendVoice request. type VoiceConfig struct { BaseFile - Thumb interface{} + Thumb RequestFileData Caption string ParseMode string CaptionEntities []MessageEntity @@ -596,13 +711,13 @@ func (config VoiceConfig) method() string { func (config VoiceConfig) files() []RequestFile { files := []RequestFile{{ Name: "voice", - File: config.File, + Data: config.File, }} if config.Thumb != nil { files = append(files, RequestFile{ Name: "thumb", - File: config.Thumb, + Data: config.Thumb, }) } @@ -1045,7 +1160,7 @@ func (config UpdateConfig) params() (Params, error) { // WebhookConfig contains information about a SetWebhook request. type WebhookConfig struct { URL *url.URL - Certificate interface{} + Certificate RequestFileData IPAddress string MaxConnections int AllowedUpdates []string @@ -1075,7 +1190,7 @@ func (config WebhookConfig) files() []RequestFile { if config.Certificate != nil { return []RequestFile{{ Name: "certificate", - File: config.Certificate, + Data: config.Certificate, }} } @@ -1099,25 +1214,6 @@ func (config DeleteWebhookConfig) params() (Params, error) { return params, nil } -// FileBytes contains information about a set of bytes to upload -// as a File. -type FileBytes struct { - Name string - Bytes []byte -} - -// FileReader contains information about a reader to upload as a File. -type FileReader struct { - Name string - Reader io.Reader -} - -// 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"` @@ -1706,7 +1802,7 @@ func (config SetChatPhotoConfig) method() string { func (config SetChatPhotoConfig) files() []RequestFile { return []RequestFile{{ Name: "photo", - File: config.File, + Data: config.File, }} } @@ -1790,7 +1886,7 @@ func (config GetStickerSetConfig) params() (Params, error) { // UploadStickerConfig allows you to upload a sticker for use in a set later. type UploadStickerConfig struct { UserID int64 - PNGSticker interface{} + PNGSticker RequestFileData } func (config UploadStickerConfig) method() string { @@ -1808,7 +1904,7 @@ func (config UploadStickerConfig) params() (Params, error) { func (config UploadStickerConfig) files() []RequestFile { return []RequestFile{{ Name: "png_sticker", - File: config.PNGSticker, + Data: config.PNGSticker, }} } @@ -1819,8 +1915,8 @@ type NewStickerSetConfig struct { UserID int64 Name string Title string - PNGSticker interface{} - TGSSticker interface{} + PNGSticker RequestFileData + TGSSticker RequestFileData Emojis string ContainsMasks bool MaskPosition *MaskPosition @@ -1850,13 +1946,13 @@ func (config NewStickerSetConfig) files() []RequestFile { if config.PNGSticker != nil { return []RequestFile{{ Name: "png_sticker", - File: config.PNGSticker, + Data: config.PNGSticker, }} } return []RequestFile{{ Name: "tgs_sticker", - File: config.TGSSticker, + Data: config.TGSSticker, }} } @@ -1864,8 +1960,8 @@ func (config NewStickerSetConfig) files() []RequestFile { type AddStickerConfig struct { UserID int64 Name string - PNGSticker interface{} - TGSSticker interface{} + PNGSticker RequestFileData + TGSSticker RequestFileData Emojis string MaskPosition *MaskPosition } @@ -1890,13 +1986,13 @@ func (config AddStickerConfig) files() []RequestFile { if config.PNGSticker != nil { return []RequestFile{{ Name: "png_sticker", - File: config.PNGSticker, + Data: config.PNGSticker, }} } return []RequestFile{{ Name: "tgs_sticker", - File: config.TGSSticker, + Data: config.TGSSticker, }} } @@ -1941,7 +2037,7 @@ func (config DeleteStickerConfig) params() (Params, error) { type SetStickerSetThumbConfig struct { Name string UserID int64 - Thumb interface{} + Thumb RequestFileData } func (config SetStickerSetThumbConfig) method() string { @@ -1960,7 +2056,7 @@ func (config SetStickerSetThumbConfig) params() (Params, error) { func (config SetStickerSetThumbConfig) files() []RequestFile { return []RequestFile{{ Name: "thumb", - File: config.Thumb, + Data: config.Thumb, }} } @@ -2134,45 +2230,38 @@ func (config DeleteMyCommandsConfig) params() (Params, error) { func prepareInputMediaParam(inputMedia interface{}, idx int) interface{} { switch m := inputMedia.(type) { case InputMediaPhoto: - switch m.Media.(type) { - case string, FileBytes, FileReader: - m.Media = fmt.Sprintf("attach://file-%d", idx) + if m.Media.NeedsUpload() { + m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx)) } return m case InputMediaVideo: - switch m.Media.(type) { - case string, FileBytes, FileReader: - m.Media = fmt.Sprintf("attach://file-%d", idx) + if m.Media.NeedsUpload() { + m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx)) } - switch m.Thumb.(type) { - case string, FileBytes, FileReader: - m.Thumb = fmt.Sprintf("attach://file-%d-thumb", idx) + if m.Thumb != nil && m.Thumb.NeedsUpload() { + m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx)) } return m case InputMediaAudio: - switch m.Media.(type) { - case string, FileBytes, FileReader: - m.Media = fmt.Sprintf("attach://file-%d", idx) + if m.Media.NeedsUpload() { + m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx)) } - switch m.Thumb.(type) { - case string, FileBytes, FileReader: - m.Thumb = fmt.Sprintf("attach://file-%d-thumb", idx) + if m.Thumb != nil && m.Thumb.NeedsUpload() { + m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx)) } return m case InputMediaDocument: - switch m.Media.(type) { - case string, FileBytes, FileReader: - m.Media = fmt.Sprintf("attach://file-%d", idx) + if m.Media.NeedsUpload() { + m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx)) } - switch m.Thumb.(type) { - case string, FileBytes, FileReader: - m.Thumb = fmt.Sprintf("attach://file-%d-thumb", idx) + if m.Thumb != nil && m.Thumb.NeedsUpload() { + m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx)) } return m @@ -2194,59 +2283,52 @@ func prepareInputMediaFile(inputMedia interface{}, idx int) []RequestFile { switch m := inputMedia.(type) { case InputMediaPhoto: - switch f := m.Media.(type) { - case string, FileBytes, FileReader: + if m.Media.NeedsUpload() { files = append(files, RequestFile{ Name: fmt.Sprintf("file-%d", idx), - File: f, + Data: m.Media, }) } case InputMediaVideo: - switch f := m.Media.(type) { - case string, FileBytes, FileReader: + if m.Media.NeedsUpload() { files = append(files, RequestFile{ Name: fmt.Sprintf("file-%d", idx), - File: f, + Data: m.Media, }) } - switch f := m.Thumb.(type) { - case string, FileBytes, FileReader: + if m.Thumb != nil && m.Thumb.NeedsUpload() { files = append(files, RequestFile{ - Name: fmt.Sprintf("file-%d-thumb", idx), - File: f, + Name: fmt.Sprintf("file-%d", idx), + Data: m.Thumb, }) } case InputMediaDocument: - switch f := m.Media.(type) { - case string, FileBytes, FileReader: + if m.Media.NeedsUpload() { files = append(files, RequestFile{ Name: fmt.Sprintf("file-%d", idx), - File: f, + Data: m.Media, }) } - switch f := m.Thumb.(type) { - case string, FileBytes, FileReader: + if m.Thumb != nil && m.Thumb.NeedsUpload() { files = append(files, RequestFile{ Name: fmt.Sprintf("file-%d", idx), - File: f, + Data: m.Thumb, }) } case InputMediaAudio: - switch f := m.Media.(type) { - case string, FileBytes, FileReader: + if m.Media.NeedsUpload() { files = append(files, RequestFile{ Name: fmt.Sprintf("file-%d", idx), - File: f, + Data: m.Media, }) } - switch f := m.Thumb.(type) { - case string, FileBytes, FileReader: + if m.Thumb != nil && m.Thumb.NeedsUpload() { files = append(files, RequestFile{ Name: fmt.Sprintf("file-%d", idx), - File: f, + Data: m.Thumb, }) } } diff --git a/helpers.go b/helpers.go index fc2a89d..27ada9e 100644 --- a/helpers.go +++ b/helpers.go @@ -70,7 +70,7 @@ func NewCopyMessage(chatID int64, fromChatID int64, messageID int) CopyMessageCo // FileReader, or FileBytes. // // Note that you must send animated GIFs as a document. -func NewPhoto(chatID int64, file interface{}) PhotoConfig { +func NewPhoto(chatID int64, file RequestFileData) PhotoConfig { return PhotoConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ChatID: chatID}, @@ -82,7 +82,7 @@ func NewPhoto(chatID int64, file interface{}) PhotoConfig { // NewPhotoToChannel creates a new photo uploader to send a photo to a channel. // // Note that you must send animated GIFs as a document. -func NewPhotoToChannel(username string, file interface{}) PhotoConfig { +func NewPhotoToChannel(username string, file RequestFileData) PhotoConfig { return PhotoConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ @@ -94,7 +94,7 @@ func NewPhotoToChannel(username string, file interface{}) PhotoConfig { } // NewAudio creates a new sendAudio request. -func NewAudio(chatID int64, file interface{}) AudioConfig { +func NewAudio(chatID int64, file RequestFileData) AudioConfig { return AudioConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ChatID: chatID}, @@ -104,7 +104,7 @@ func NewAudio(chatID int64, file interface{}) AudioConfig { } // NewDocument creates a new sendDocument request. -func NewDocument(chatID int64, file interface{}) DocumentConfig { +func NewDocument(chatID int64, file RequestFileData) DocumentConfig { return DocumentConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ChatID: chatID}, @@ -114,7 +114,7 @@ func NewDocument(chatID int64, file interface{}) DocumentConfig { } // NewSticker creates a new sendSticker request. -func NewSticker(chatID int64, file interface{}) StickerConfig { +func NewSticker(chatID int64, file RequestFileData) StickerConfig { return StickerConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ChatID: chatID}, @@ -124,7 +124,7 @@ func NewSticker(chatID int64, file interface{}) StickerConfig { } // NewVideo creates a new sendVideo request. -func NewVideo(chatID int64, file interface{}) VideoConfig { +func NewVideo(chatID int64, file RequestFileData) VideoConfig { return VideoConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ChatID: chatID}, @@ -134,7 +134,7 @@ func NewVideo(chatID int64, file interface{}) VideoConfig { } // NewAnimation creates a new sendAnimation request. -func NewAnimation(chatID int64, file interface{}) AnimationConfig { +func NewAnimation(chatID int64, file RequestFileData) AnimationConfig { return AnimationConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ChatID: chatID}, @@ -147,7 +147,7 @@ func NewAnimation(chatID int64, file interface{}) AnimationConfig { // // chatID is where to send it, file is a string path to the file, // FileReader, or FileBytes. -func NewVideoNote(chatID int64, length int, file interface{}) VideoNoteConfig { +func NewVideoNote(chatID int64, length int, file RequestFileData) VideoNoteConfig { return VideoNoteConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ChatID: chatID}, @@ -158,7 +158,7 @@ func NewVideoNote(chatID int64, length int, file interface{}) VideoNoteConfig { } // NewVoice creates a new sendVoice request. -func NewVoice(chatID int64, file interface{}) VoiceConfig { +func NewVoice(chatID int64, file RequestFileData) VoiceConfig { return VoiceConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ChatID: chatID}, @@ -177,7 +177,7 @@ func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig { } // NewInputMediaPhoto creates a new InputMediaPhoto. -func NewInputMediaPhoto(media interface{}) InputMediaPhoto { +func NewInputMediaPhoto(media RequestFileData) InputMediaPhoto { return InputMediaPhoto{ BaseInputMedia{ Type: "photo", @@ -187,7 +187,7 @@ func NewInputMediaPhoto(media interface{}) InputMediaPhoto { } // NewInputMediaVideo creates a new InputMediaVideo. -func NewInputMediaVideo(media interface{}) InputMediaVideo { +func NewInputMediaVideo(media RequestFileData) InputMediaVideo { return InputMediaVideo{ BaseInputMedia: BaseInputMedia{ Type: "video", @@ -197,7 +197,7 @@ func NewInputMediaVideo(media interface{}) InputMediaVideo { } // NewInputMediaAnimation creates a new InputMediaAnimation. -func NewInputMediaAnimation(media interface{}) InputMediaAnimation { +func NewInputMediaAnimation(media RequestFileData) InputMediaAnimation { return InputMediaAnimation{ BaseInputMedia: BaseInputMedia{ Type: "animation", @@ -207,7 +207,7 @@ func NewInputMediaAnimation(media interface{}) InputMediaAnimation { } // NewInputMediaAudio creates a new InputMediaAudio. -func NewInputMediaAudio(media interface{}) InputMediaAudio { +func NewInputMediaAudio(media RequestFileData) InputMediaAudio { return InputMediaAudio{ BaseInputMedia: BaseInputMedia{ Type: "audio", @@ -217,7 +217,7 @@ func NewInputMediaAudio(media interface{}) InputMediaAudio { } // NewInputMediaDocument creates a new InputMediaDocument. -func NewInputMediaDocument(media interface{}) InputMediaDocument { +func NewInputMediaDocument(media RequestFileData) InputMediaDocument { return InputMediaDocument{ BaseInputMedia: BaseInputMedia{ Type: "document", @@ -316,7 +316,7 @@ func NewWebhook(link string) (WebhookConfig, error) { // // link is the url you wish to get webhooks, // file contains a string to a file, FileReader, or FileBytes. -func NewWebhookWithCert(link string, file interface{}) (WebhookConfig, error) { +func NewWebhookWithCert(link string, file RequestFileData) (WebhookConfig, error) { u, err := url.Parse(link) if err != nil { @@ -769,7 +769,7 @@ func NewChatDescription(chatID int64, description string) SetChatDescriptionConf } // NewChatPhoto allows you to update the photo for a chat. -func NewChatPhoto(chatID int64, photo interface{}) SetChatPhotoConfig { +func NewChatPhoto(chatID int64, photo RequestFileData) SetChatPhotoConfig { return SetChatPhotoConfig{ BaseFile: BaseFile{ BaseChat: BaseChat{ @@ -781,7 +781,7 @@ func NewChatPhoto(chatID int64, photo interface{}) SetChatPhotoConfig { } // NewDeleteChatPhoto allows you to delete the photo for a chat. -func NewDeleteChatPhoto(chatID int64, photo interface{}) DeleteChatPhotoConfig { +func NewDeleteChatPhoto(chatID int64) DeleteChatPhotoConfig { return DeleteChatPhotoConfig{ ChatID: chatID, } diff --git a/helpers_test.go b/helpers_test.go index 3739892..724f6ac 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -17,7 +17,7 @@ func TestNewWebhook(t *testing.T) { } func TestNewWebhookWithCert(t *testing.T) { - exampleFile := File{FileID: "123"} + exampleFile := FileID("123") result, err := NewWebhookWithCert("https://example.com/token", exampleFile) if err != nil || diff --git a/types.go b/types.go index b269315..e6572b2 100644 --- a/types.go +++ b/types.go @@ -1685,7 +1685,7 @@ type BaseInputMedia struct { // pass an HTTP URL for Telegram to get a file from the Internet, // or pass “attach://” to upload a new one // using multipart/form-data under name. - Media interface{} `json:"media"` + Media RequestFileData `json:"media"` // thumb intentionally missing as it is not currently compatible // Caption of the video to be sent, 0-1024 characters after entities parsing. @@ -1717,7 +1717,7 @@ type InputMediaVideo struct { // the file is supported server-side. // // optional - Thumb interface{} `json:"thumb,omitempty"` + Thumb RequestFileData `json:"thumb,omitempty"` // Width video width // // optional @@ -1743,7 +1743,7 @@ type InputMediaAnimation struct { // the file is supported server-side. // // optional - Thumb interface{} `json:"thumb,omitempty"` + Thumb RequestFileData `json:"thumb,omitempty"` // Width video width // // optional @@ -1765,7 +1765,7 @@ type InputMediaAudio struct { // the file is supported server-side. // // optional - Thumb interface{} `json:"thumb,omitempty"` + Thumb RequestFileData `json:"thumb,omitempty"` // Duration of the audio in seconds // // optional @@ -1787,7 +1787,7 @@ type InputMediaDocument struct { // the file is supported server-side. // // optional - Thumb interface{} `json:"thumb,omitempty"` + Thumb RequestFileData `json:"thumb,omitempty"` // DisableContentTypeDetection disables automatic server-side content type // detection for files uploaded using multipart/form-data. Always true, if // the document is sent as part of an album diff --git a/types_test.go b/types_test.go index b8a52a2..7742556 100644 --- a/types_test.go +++ b/types_test.go @@ -361,3 +361,13 @@ var ( _ Fileable = (*WebhookConfig)(nil) _ Fileable = (*SetStickerSetThumbConfig)(nil) ) + +// Ensure all RequestFileData types are correct. +var ( + _ RequestFileData = (*FilePath)(nil) + _ RequestFileData = (*FileBytes)(nil) + _ RequestFileData = (*FileReader)(nil) + _ RequestFileData = (*FileURL)(nil) + _ RequestFileData = (*FileID)(nil) + _ RequestFileData = (*fileAttach)(nil) +)