Merge pull request #469 from go-telegram-bot-api/files

Create interface for file data
bot-api-6.1
Syfaro 2021-11-08 14:26:17 -05:00 committed by GitHub
commit 7c82078b7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 283 additions and 241 deletions

59
bot.go
View File

@ -3,7 +3,6 @@
package tgbotapi package tgbotapi
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -12,7 +11,6 @@ import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strings" "strings"
"time" "time"
) )
@ -185,54 +183,37 @@ func (bot *BotAPI) UploadFiles(endpoint string, params Params, files []RequestFi
} }
for _, file := range files { for _, file := range files {
switch f := file.File.(type) { if file.Data.NeedsUpload() {
case string: name, reader, err := file.Data.UploadData()
fileHandle, err := os.Open(f)
if err != nil {
w.CloseWithError(err)
return
}
defer fileHandle.Close()
part, err := m.CreateFormFile(file.Name, fileHandle.Name())
if err != nil { if err != nil {
w.CloseWithError(err) w.CloseWithError(err)
return return
} }
io.Copy(part, fileHandle) part, err := m.CreateFormFile(file.Name, name)
case FileBytes:
part, err := m.CreateFormFile(file.Name, f.Name)
if err != nil { if err != nil {
w.CloseWithError(err) w.CloseWithError(err)
return return
} }
buf := bytes.NewBuffer(f.Bytes) if _, err := io.Copy(part, reader); err != nil {
io.Copy(part, buf)
case FileReader:
part, err := m.CreateFormFile(file.Name, f.Name)
if err != nil {
w.CloseWithError(err) w.CloseWithError(err)
return return
} }
io.Copy(part, f.Reader) if closer, ok := reader.(io.ReadCloser); ok {
case FileURL: if err = closer.Close(); err != nil {
val := string(f)
if err := m.WriteField(file.Name, val); err != nil {
w.CloseWithError(err) w.CloseWithError(err)
return return
} }
case FileID: }
val := string(f) } else {
if err := m.WriteField(file.Name, val); err != nil { value := file.Data.SendData()
if err := m.WriteField(file.Name, value); err != nil {
w.CloseWithError(err) w.CloseWithError(err)
return return
} }
default:
w.CloseWithError(errors.New(ErrBadFileType))
return
} }
} }
}() }()
@ -321,8 +302,7 @@ func (bot *BotAPI) IsMessageToMe(message Message) bool {
func hasFilesNeedingUpload(files []RequestFile) bool { func hasFilesNeedingUpload(files []RequestFile) bool {
for _, file := range files { for _, file := range files {
switch file.File.(type) { if file.Data.NeedsUpload() {
case string, FileBytes, FileReader:
return true return true
} }
} }
@ -349,20 +329,7 @@ func (bot *BotAPI) Request(c Chattable) (*APIResponse, error) {
// However, if there are no files to be uploaded, there's likely things // However, if there are no files to be uploaded, there's likely things
// that need to be turned into params instead. // that need to be turned into params instead.
for _, file := range files { for _, file := range files {
var s string params[file.Name] = file.Data.SendData()
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
} }
} }

View File

@ -127,7 +127,7 @@ func TestCopyMessage(t *testing.T) {
func TestSendWithNewPhoto(t *testing.T) { func TestSendWithNewPhoto(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewPhoto(ChatID, "tests/image.jpg") msg := NewPhoto(ChatID, FilePath("tests/image.jpg"))
msg.Caption = "Test" msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -169,7 +169,7 @@ func TestSendWithNewPhotoWithFileReader(t *testing.T) {
func TestSendWithNewPhotoReply(t *testing.T) { func TestSendWithNewPhotoReply(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewPhoto(ChatID, "tests/image.jpg") msg := NewPhoto(ChatID, FilePath("tests/image.jpg"))
msg.ReplyToMessageID = ReplyToMessageID msg.ReplyToMessageID = ReplyToMessageID
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -182,7 +182,7 @@ func TestSendWithNewPhotoReply(t *testing.T) {
func TestSendNewPhotoToChannel(t *testing.T) { func TestSendNewPhotoToChannel(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewPhotoToChannel(Channel, "tests/image.jpg") msg := NewPhotoToChannel(Channel, FilePath("tests/image.jpg"))
msg.Caption = "Test" msg.Caption = "Test"
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -239,7 +239,7 @@ func TestSendWithExistingPhoto(t *testing.T) {
func TestSendWithNewDocument(t *testing.T) { func TestSendWithNewDocument(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewDocument(ChatID, "tests/image.jpg") msg := NewDocument(ChatID, FilePath("tests/image.jpg"))
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
@ -250,8 +250,8 @@ func TestSendWithNewDocument(t *testing.T) {
func TestSendWithNewDocumentAndThumb(t *testing.T) { func TestSendWithNewDocumentAndThumb(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewDocument(ChatID, "tests/voice.ogg") msg := NewDocument(ChatID, FilePath("tests/voice.ogg"))
msg.Thumb = "tests/image.jpg" msg.Thumb = FilePath("tests/image.jpg")
_, err := bot.Send(msg) _, err := bot.Send(msg)
if err != nil { if err != nil {
@ -273,7 +273,7 @@ func TestSendWithExistingDocument(t *testing.T) {
func TestSendWithNewAudio(t *testing.T) { func TestSendWithNewAudio(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewAudio(ChatID, "tests/audio.mp3") msg := NewAudio(ChatID, FilePath("tests/audio.mp3"))
msg.Title = "TEST" msg.Title = "TEST"
msg.Duration = 10 msg.Duration = 10
msg.Performer = "TEST" msg.Performer = "TEST"
@ -302,7 +302,7 @@ func TestSendWithExistingAudio(t *testing.T) {
func TestSendWithNewVoice(t *testing.T) { func TestSendWithNewVoice(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewVoice(ChatID, "tests/voice.ogg") msg := NewVoice(ChatID, FilePath("tests/voice.ogg"))
msg.Duration = 10 msg.Duration = 10
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -356,7 +356,7 @@ func TestSendWithVenue(t *testing.T) {
func TestSendWithNewVideo(t *testing.T) { func TestSendWithNewVideo(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewVideo(ChatID, "tests/video.mp4") msg := NewVideo(ChatID, FilePath("tests/video.mp4"))
msg.Duration = 10 msg.Duration = 10
msg.Caption = "TEST" msg.Caption = "TEST"
@ -384,7 +384,7 @@ func TestSendWithExistingVideo(t *testing.T) {
func TestSendWithNewVideoNote(t *testing.T) { func TestSendWithNewVideoNote(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewVideoNote(ChatID, 240, "tests/videonote.mp4") msg := NewVideoNote(ChatID, 240, FilePath("tests/videonote.mp4"))
msg.Duration = 10 msg.Duration = 10
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -410,7 +410,7 @@ func TestSendWithExistingVideoNote(t *testing.T) {
func TestSendWithNewSticker(t *testing.T) { func TestSendWithNewSticker(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewSticker(ChatID, "tests/image.jpg") msg := NewSticker(ChatID, FilePath("tests/image.jpg"))
_, err := bot.Send(msg) _, err := bot.Send(msg)
@ -434,7 +434,7 @@ func TestSendWithExistingSticker(t *testing.T) {
func TestSendWithNewStickerAndKeyboardHide(t *testing.T) { func TestSendWithNewStickerAndKeyboardHide(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
msg := NewSticker(ChatID, "tests/image.jpg") msg := NewSticker(ChatID, FilePath("tests/image.jpg"))
msg.ReplyMarkup = ReplyKeyboardRemove{ msg.ReplyMarkup = ReplyKeyboardRemove{
RemoveKeyboard: true, RemoveKeyboard: true,
Selective: false, Selective: false,
@ -550,7 +550,7 @@ func TestSetWebhookWithCert(t *testing.T) {
bot.Request(DeleteWebhookConfig{}) 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 { if err != nil {
t.Error(err) t.Error(err)
@ -609,8 +609,8 @@ func TestSendWithMediaGroupPhotoVideo(t *testing.T) {
cfg := NewMediaGroup(ChatID, []interface{}{ cfg := NewMediaGroup(ChatID, []interface{}{
NewInputMediaPhoto(FileURL("https://github.com/go-telegram-bot-api/telegram-bot-api/raw/0a3a1c8716c4cd8d26a262af9f12dcbab7f3f28c/tests/image.jpg")), NewInputMediaPhoto(FileURL("https://github.com/go-telegram-bot-api/telegram-bot-api/raw/0a3a1c8716c4cd8d26a262af9f12dcbab7f3f28c/tests/image.jpg")),
NewInputMediaPhoto("tests/image.jpg"), NewInputMediaPhoto(FilePath("tests/image.jpg")),
NewInputMediaVideo("tests/video.mp4"), NewInputMediaVideo(FilePath("tests/video.mp4")),
}) })
messages, err := bot.SendMediaGroup(cfg) messages, err := bot.SendMediaGroup(cfg)
@ -632,7 +632,7 @@ func TestSendWithMediaGroupDocument(t *testing.T) {
cfg := NewMediaGroup(ChatID, []interface{}{ cfg := NewMediaGroup(ChatID, []interface{}{
NewInputMediaDocument(FileURL("https://i.imgur.com/unQLJIb.jpg")), NewInputMediaDocument(FileURL("https://i.imgur.com/unQLJIb.jpg")),
NewInputMediaDocument("tests/image.jpg"), NewInputMediaDocument(FilePath("tests/image.jpg")),
}) })
messages, err := bot.SendMediaGroup(cfg) messages, err := bot.SendMediaGroup(cfg)
@ -653,8 +653,8 @@ func TestSendWithMediaGroupAudio(t *testing.T) {
bot, _ := getBot(t) bot, _ := getBot(t)
cfg := NewMediaGroup(ChatID, []interface{}{ cfg := NewMediaGroup(ChatID, []interface{}{
NewInputMediaAudio("tests/audio.mp3"), NewInputMediaAudio(FilePath("tests/audio.mp3")),
NewInputMediaAudio("tests/audio.mp3"), NewInputMediaAudio(FilePath("tests/audio.mp3")),
}) })
messages, err := bot.SendMediaGroup(cfg) messages, err := bot.SendMediaGroup(cfg)
@ -715,7 +715,7 @@ func ExampleNewWebhook() {
log.Printf("Authorized on account %s", bot.Self.UserName) 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 { if err != nil {
panic(err) panic(err)
@ -755,7 +755,7 @@ func ExampleWebhookHandler() {
log.Printf("Authorized on account %s", bot.Self.UserName) 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 { if err != nil {
panic(err) panic(err)
@ -1019,7 +1019,7 @@ func TestCommands(t *testing.T) {
// ChatID: ChatID, // ChatID: ChatID,
// MessageID: m.MessageID, // MessageID: m.MessageID,
// }, // },
// Media: NewInputMediaVideo("tests/video.mp4"), // Media: NewInputMediaVideo(FilePath("tests/video.mp4")),
// } // }
// _, err = bot.Request(edit) // _, err = bot.Request(edit)
@ -1030,17 +1030,17 @@ func TestCommands(t *testing.T) {
func TestPrepareInputMediaForParams(t *testing.T) { func TestPrepareInputMediaForParams(t *testing.T) {
media := []interface{}{ media := []interface{}{
NewInputMediaPhoto("tests/image.jpg"), NewInputMediaPhoto(FilePath("tests/image.jpg")),
NewInputMediaVideo(FileID("test")), NewInputMediaVideo(FileID("test")),
} }
prepared := prepareInputMediaForParams(media) 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") t.Error("Original media was changed")
} }
if prepared[0].(InputMediaPhoto).Media != "attach://file-0" { if prepared[0].(InputMediaPhoto).Media != fileAttach("attach://file-0") {
t.Error("New media was not replaced") t.Error("New media was not replaced")
} }

View File

@ -1,9 +1,11 @@
package tgbotapi package tgbotapi
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"net/url" "net/url"
"os"
"strconv" "strconv"
) )
@ -98,8 +100,6 @@ const (
// Library errors // Library errors
const ( const (
// ErrBadFileType happens when you pass an unknown type
ErrBadFileType = "bad file type"
ErrBadURL = "bad or empty url" ErrBadURL = "bad or empty url"
) )
@ -109,21 +109,136 @@ type Chattable interface {
method() string 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. // Fileable is any config type that can be sent that includes a file.
type Fileable interface { type Fileable interface {
Chattable Chattable
files() []RequestFile 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. // 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. // Note that you may not log back in for at least 10 minutes.
@ -177,7 +292,7 @@ func (chat *BaseChat) params() (Params, error) {
// BaseFile is a base type for all file config types. // BaseFile is a base type for all file config types.
type BaseFile struct { type BaseFile struct {
BaseChat BaseChat
File interface{} File RequestFileData
} }
func (file BaseFile) params() (Params, error) { func (file BaseFile) params() (Params, error) {
@ -292,7 +407,7 @@ func (config CopyMessageConfig) method() string {
// PhotoConfig contains information about a SendPhoto request. // PhotoConfig contains information about a SendPhoto request.
type PhotoConfig struct { type PhotoConfig struct {
BaseFile BaseFile
Thumb interface{} Thumb RequestFileData
Caption string Caption string
ParseMode string ParseMode string
CaptionEntities []MessageEntity CaptionEntities []MessageEntity
@ -318,13 +433,13 @@ func (config PhotoConfig) method() string {
func (config PhotoConfig) files() []RequestFile { func (config PhotoConfig) files() []RequestFile {
files := []RequestFile{{ files := []RequestFile{{
Name: "photo", Name: "photo",
File: config.File, Data: config.File,
}} }}
if config.Thumb != nil { if config.Thumb != nil {
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: "thumb", Name: "thumb",
File: config.Thumb, Data: config.Thumb,
}) })
} }
@ -334,7 +449,7 @@ func (config PhotoConfig) files() []RequestFile {
// AudioConfig contains information about a SendAudio request. // AudioConfig contains information about a SendAudio request.
type AudioConfig struct { type AudioConfig struct {
BaseFile BaseFile
Thumb interface{} Thumb RequestFileData
Caption string Caption string
ParseMode string ParseMode string
CaptionEntities []MessageEntity CaptionEntities []MessageEntity
@ -366,13 +481,13 @@ func (config AudioConfig) method() string {
func (config AudioConfig) files() []RequestFile { func (config AudioConfig) files() []RequestFile {
files := []RequestFile{{ files := []RequestFile{{
Name: "audio", Name: "audio",
File: config.File, Data: config.File,
}} }}
if config.Thumb != nil { if config.Thumb != nil {
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: "thumb", Name: "thumb",
File: config.Thumb, Data: config.Thumb,
}) })
} }
@ -382,7 +497,7 @@ func (config AudioConfig) files() []RequestFile {
// DocumentConfig contains information about a SendDocument request. // DocumentConfig contains information about a SendDocument request.
type DocumentConfig struct { type DocumentConfig struct {
BaseFile BaseFile
Thumb interface{} Thumb RequestFileData
Caption string Caption string
ParseMode string ParseMode string
CaptionEntities []MessageEntity CaptionEntities []MessageEntity
@ -406,13 +521,13 @@ func (config DocumentConfig) method() string {
func (config DocumentConfig) files() []RequestFile { func (config DocumentConfig) files() []RequestFile {
files := []RequestFile{{ files := []RequestFile{{
Name: "document", Name: "document",
File: config.File, Data: config.File,
}} }}
if config.Thumb != nil { if config.Thumb != nil {
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: "thumb", Name: "thumb",
File: config.Thumb, Data: config.Thumb,
}) })
} }
@ -435,14 +550,14 @@ func (config StickerConfig) method() string {
func (config StickerConfig) files() []RequestFile { func (config StickerConfig) files() []RequestFile {
return []RequestFile{{ return []RequestFile{{
Name: "sticker", Name: "sticker",
File: config.File, Data: config.File,
}} }}
} }
// VideoConfig contains information about a SendVideo request. // VideoConfig contains information about a SendVideo request.
type VideoConfig struct { type VideoConfig struct {
BaseFile BaseFile
Thumb interface{} Thumb RequestFileData
Duration int Duration int
Caption string Caption string
ParseMode string ParseMode string
@ -472,13 +587,13 @@ func (config VideoConfig) method() string {
func (config VideoConfig) files() []RequestFile { func (config VideoConfig) files() []RequestFile {
files := []RequestFile{{ files := []RequestFile{{
Name: "video", Name: "video",
File: config.File, Data: config.File,
}} }}
if config.Thumb != nil { if config.Thumb != nil {
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: "thumb", Name: "thumb",
File: config.Thumb, Data: config.Thumb,
}) })
} }
@ -489,7 +604,7 @@ func (config VideoConfig) files() []RequestFile {
type AnimationConfig struct { type AnimationConfig struct {
BaseFile BaseFile
Duration int Duration int
Thumb interface{} Thumb RequestFileData
Caption string Caption string
ParseMode string ParseMode string
CaptionEntities []MessageEntity CaptionEntities []MessageEntity
@ -516,13 +631,13 @@ func (config AnimationConfig) method() string {
func (config AnimationConfig) files() []RequestFile { func (config AnimationConfig) files() []RequestFile {
files := []RequestFile{{ files := []RequestFile{{
Name: "animation", Name: "animation",
File: config.File, Data: config.File,
}} }}
if config.Thumb != nil { if config.Thumb != nil {
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: "thumb", Name: "thumb",
File: config.Thumb, Data: config.Thumb,
}) })
} }
@ -532,7 +647,7 @@ func (config AnimationConfig) files() []RequestFile {
// VideoNoteConfig contains information about a SendVideoNote request. // VideoNoteConfig contains information about a SendVideoNote request.
type VideoNoteConfig struct { type VideoNoteConfig struct {
BaseFile BaseFile
Thumb interface{} Thumb RequestFileData
Duration int Duration int
Length int Length int
} }
@ -553,13 +668,13 @@ func (config VideoNoteConfig) method() string {
func (config VideoNoteConfig) files() []RequestFile { func (config VideoNoteConfig) files() []RequestFile {
files := []RequestFile{{ files := []RequestFile{{
Name: "video_note", Name: "video_note",
File: config.File, Data: config.File,
}} }}
if config.Thumb != nil { if config.Thumb != nil {
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: "thumb", Name: "thumb",
File: config.Thumb, Data: config.Thumb,
}) })
} }
@ -569,7 +684,7 @@ func (config VideoNoteConfig) files() []RequestFile {
// VoiceConfig contains information about a SendVoice request. // VoiceConfig contains information about a SendVoice request.
type VoiceConfig struct { type VoiceConfig struct {
BaseFile BaseFile
Thumb interface{} Thumb RequestFileData
Caption string Caption string
ParseMode string ParseMode string
CaptionEntities []MessageEntity CaptionEntities []MessageEntity
@ -597,13 +712,13 @@ func (config VoiceConfig) method() string {
func (config VoiceConfig) files() []RequestFile { func (config VoiceConfig) files() []RequestFile {
files := []RequestFile{{ files := []RequestFile{{
Name: "voice", Name: "voice",
File: config.File, Data: config.File,
}} }}
if config.Thumb != nil { if config.Thumb != nil {
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: "thumb", Name: "thumb",
File: config.Thumb, Data: config.Thumb,
}) })
} }
@ -1046,7 +1161,7 @@ func (config UpdateConfig) params() (Params, error) {
// WebhookConfig contains information about a SetWebhook request. // WebhookConfig contains information about a SetWebhook request.
type WebhookConfig struct { type WebhookConfig struct {
URL *url.URL URL *url.URL
Certificate interface{} Certificate RequestFileData
IPAddress string IPAddress string
MaxConnections int MaxConnections int
AllowedUpdates []string AllowedUpdates []string
@ -1076,7 +1191,7 @@ func (config WebhookConfig) files() []RequestFile {
if config.Certificate != nil { if config.Certificate != nil {
return []RequestFile{{ return []RequestFile{{
Name: "certificate", Name: "certificate",
File: config.Certificate, Data: config.Certificate,
}} }}
} }
@ -1100,25 +1215,6 @@ func (config DeleteWebhookConfig) params() (Params, error) {
return params, nil 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. // InlineConfig contains information on making an InlineQuery response.
type InlineConfig struct { type InlineConfig struct {
InlineQueryID string `json:"inline_query_id"` InlineQueryID string `json:"inline_query_id"`
@ -1753,7 +1849,7 @@ func (config SetChatPhotoConfig) method() string {
func (config SetChatPhotoConfig) files() []RequestFile { func (config SetChatPhotoConfig) files() []RequestFile {
return []RequestFile{{ return []RequestFile{{
Name: "photo", Name: "photo",
File: config.File, Data: config.File,
}} }}
} }
@ -1837,7 +1933,7 @@ func (config GetStickerSetConfig) params() (Params, error) {
// UploadStickerConfig allows you to upload a sticker for use in a set later. // UploadStickerConfig allows you to upload a sticker for use in a set later.
type UploadStickerConfig struct { type UploadStickerConfig struct {
UserID int64 UserID int64
PNGSticker interface{} PNGSticker RequestFileData
} }
func (config UploadStickerConfig) method() string { func (config UploadStickerConfig) method() string {
@ -1855,7 +1951,7 @@ func (config UploadStickerConfig) params() (Params, error) {
func (config UploadStickerConfig) files() []RequestFile { func (config UploadStickerConfig) files() []RequestFile {
return []RequestFile{{ return []RequestFile{{
Name: "png_sticker", Name: "png_sticker",
File: config.PNGSticker, Data: config.PNGSticker,
}} }}
} }
@ -1866,8 +1962,8 @@ type NewStickerSetConfig struct {
UserID int64 UserID int64
Name string Name string
Title string Title string
PNGSticker interface{} PNGSticker RequestFileData
TGSSticker interface{} TGSSticker RequestFileData
Emojis string Emojis string
ContainsMasks bool ContainsMasks bool
MaskPosition *MaskPosition MaskPosition *MaskPosition
@ -1897,13 +1993,13 @@ func (config NewStickerSetConfig) files() []RequestFile {
if config.PNGSticker != nil { if config.PNGSticker != nil {
return []RequestFile{{ return []RequestFile{{
Name: "png_sticker", Name: "png_sticker",
File: config.PNGSticker, Data: config.PNGSticker,
}} }}
} }
return []RequestFile{{ return []RequestFile{{
Name: "tgs_sticker", Name: "tgs_sticker",
File: config.TGSSticker, Data: config.TGSSticker,
}} }}
} }
@ -1911,8 +2007,8 @@ func (config NewStickerSetConfig) files() []RequestFile {
type AddStickerConfig struct { type AddStickerConfig struct {
UserID int64 UserID int64
Name string Name string
PNGSticker interface{} PNGSticker RequestFileData
TGSSticker interface{} TGSSticker RequestFileData
Emojis string Emojis string
MaskPosition *MaskPosition MaskPosition *MaskPosition
} }
@ -1937,13 +2033,13 @@ func (config AddStickerConfig) files() []RequestFile {
if config.PNGSticker != nil { if config.PNGSticker != nil {
return []RequestFile{{ return []RequestFile{{
Name: "png_sticker", Name: "png_sticker",
File: config.PNGSticker, Data: config.PNGSticker,
}} }}
} }
return []RequestFile{{ return []RequestFile{{
Name: "tgs_sticker", Name: "tgs_sticker",
File: config.TGSSticker, Data: config.TGSSticker,
}} }}
} }
@ -1988,7 +2084,7 @@ func (config DeleteStickerConfig) params() (Params, error) {
type SetStickerSetThumbConfig struct { type SetStickerSetThumbConfig struct {
Name string Name string
UserID int64 UserID int64
Thumb interface{} Thumb RequestFileData
} }
func (config SetStickerSetThumbConfig) method() string { func (config SetStickerSetThumbConfig) method() string {
@ -2007,7 +2103,7 @@ func (config SetStickerSetThumbConfig) params() (Params, error) {
func (config SetStickerSetThumbConfig) files() []RequestFile { func (config SetStickerSetThumbConfig) files() []RequestFile {
return []RequestFile{{ return []RequestFile{{
Name: "thumb", Name: "thumb",
File: config.Thumb, Data: config.Thumb,
}} }}
} }
@ -2181,45 +2277,38 @@ func (config DeleteMyCommandsConfig) params() (Params, error) {
func prepareInputMediaParam(inputMedia interface{}, idx int) interface{} { func prepareInputMediaParam(inputMedia interface{}, idx int) interface{} {
switch m := inputMedia.(type) { switch m := inputMedia.(type) {
case InputMediaPhoto: case InputMediaPhoto:
switch m.Media.(type) { if m.Media.NeedsUpload() {
case string, FileBytes, FileReader: m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
m.Media = fmt.Sprintf("attach://file-%d", idx)
} }
return m return m
case InputMediaVideo: case InputMediaVideo:
switch m.Media.(type) { if m.Media.NeedsUpload() {
case string, FileBytes, FileReader: m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
m.Media = fmt.Sprintf("attach://file-%d", idx)
} }
switch m.Thumb.(type) { if m.Thumb != nil && m.Thumb.NeedsUpload() {
case string, FileBytes, FileReader: m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx))
m.Thumb = fmt.Sprintf("attach://file-%d-thumb", idx)
} }
return m return m
case InputMediaAudio: case InputMediaAudio:
switch m.Media.(type) { if m.Media.NeedsUpload() {
case string, FileBytes, FileReader: m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
m.Media = fmt.Sprintf("attach://file-%d", idx)
} }
switch m.Thumb.(type) { if m.Thumb != nil && m.Thumb.NeedsUpload() {
case string, FileBytes, FileReader: m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx))
m.Thumb = fmt.Sprintf("attach://file-%d-thumb", idx)
} }
return m return m
case InputMediaDocument: case InputMediaDocument:
switch m.Media.(type) { if m.Media.NeedsUpload() {
case string, FileBytes, FileReader: m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
m.Media = fmt.Sprintf("attach://file-%d", idx)
} }
switch m.Thumb.(type) { if m.Thumb != nil && m.Thumb.NeedsUpload() {
case string, FileBytes, FileReader: m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx))
m.Thumb = fmt.Sprintf("attach://file-%d-thumb", idx)
} }
return m return m
@ -2241,59 +2330,52 @@ func prepareInputMediaFile(inputMedia interface{}, idx int) []RequestFile {
switch m := inputMedia.(type) { switch m := inputMedia.(type) {
case InputMediaPhoto: case InputMediaPhoto:
switch f := m.Media.(type) { if m.Media.NeedsUpload() {
case string, FileBytes, FileReader:
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: fmt.Sprintf("file-%d", idx), Name: fmt.Sprintf("file-%d", idx),
File: f, Data: m.Media,
}) })
} }
case InputMediaVideo: case InputMediaVideo:
switch f := m.Media.(type) { if m.Media.NeedsUpload() {
case string, FileBytes, FileReader:
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: fmt.Sprintf("file-%d", idx), Name: fmt.Sprintf("file-%d", idx),
File: f, Data: m.Media,
}) })
} }
switch f := m.Thumb.(type) { if m.Thumb != nil && m.Thumb.NeedsUpload() {
case string, FileBytes, FileReader:
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: fmt.Sprintf("file-%d-thumb", idx), Name: fmt.Sprintf("file-%d", idx),
File: f, Data: m.Thumb,
}) })
} }
case InputMediaDocument: case InputMediaDocument:
switch f := m.Media.(type) { if m.Media.NeedsUpload() {
case string, FileBytes, FileReader:
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: fmt.Sprintf("file-%d", idx), Name: fmt.Sprintf("file-%d", idx),
File: f, Data: m.Media,
}) })
} }
switch f := m.Thumb.(type) { if m.Thumb != nil && m.Thumb.NeedsUpload() {
case string, FileBytes, FileReader:
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: fmt.Sprintf("file-%d", idx), Name: fmt.Sprintf("file-%d", idx),
File: f, Data: m.Thumb,
}) })
} }
case InputMediaAudio: case InputMediaAudio:
switch f := m.Media.(type) { if m.Media.NeedsUpload() {
case string, FileBytes, FileReader:
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: fmt.Sprintf("file-%d", idx), Name: fmt.Sprintf("file-%d", idx),
File: f, Data: m.Media,
}) })
} }
switch f := m.Thumb.(type) { if m.Thumb != nil && m.Thumb.NeedsUpload() {
case string, FileBytes, FileReader:
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: fmt.Sprintf("file-%d", idx), Name: fmt.Sprintf("file-%d", idx),
File: f, Data: m.Thumb,
}) })
} }
} }

View File

@ -3,20 +3,22 @@
Telegram supports specifying files in many different formats. In order to Telegram supports specifying files in many different formats. In order to
accommodate them all, there are multiple structs and type aliases required. accommodate them all, there are multiple structs and type aliases required.
All of these types implement the `RequestFileData` interface.
| Type | Description | | Type | Description |
| ------------ | ------------------------------------------------------------------------- | | ------------ | ------------------------------------------------------------------------- |
| `string` | Used as a local path to a file | | `FilePath` | A local path to a file |
| `FileID` | Existing file ID on Telegram's servers | | `FileID` | Existing file ID on Telegram's servers |
| `FileURL` | URL to file, must be served with expected MIME type | | `FileURL` | URL to file, must be served with expected MIME type |
| `FileReader` | Use an `io.Reader` to provide a file. Lazily read to save memory. | | `FileReader` | Use an `io.Reader` to provide a file. Lazily read to save memory. |
| `FileBytes` | `[]byte` containing file data. Prefer to use `FileReader` to save memory. | | `FileBytes` | `[]byte` containing file data. Prefer to use `FileReader` to save memory. |
## `string` ## `FilePath`
A path to a local file. A path to a local file.
```go ```go
file := "tests/image.jpg" file := tgbotapi.FilePath("tests/image.jpg")
``` ```
## `FileID` ## `FileID`

View File

@ -19,7 +19,8 @@ type DeleteMessageConfig struct {
} }
``` ```
What type should `ChatID` be? Telegram allows specifying numeric chat IDs or channel usernames. Golang doesn't have union types, and interfaces are entirely What type should `ChatID` be? Telegram allows specifying numeric chat IDs or
channel usernames. Golang doesn't have union types, and interfaces are entirely
untyped. This library solves this by adding two fields, a `ChatID` and a untyped. This library solves this by adding two fields, a `ChatID` and a
`ChannelUsername`. We can now write the struct as follows. `ChannelUsername`. We can now write the struct as follows.
@ -103,8 +104,8 @@ have similar fields for their files.
ChannelUsername string ChannelUsername string
ChatID int64 ChatID int64
MessageID int MessageID int
+ Delete interface{} + Delete RequestFileData
+ Thumb interface{} + Thumb RequestFileData
} }
``` ```
@ -115,13 +116,13 @@ and add the `thumb` file if we have one.
func (config DeleteMessageConfig) files() []RequestFile { func (config DeleteMessageConfig) files() []RequestFile {
files := []RequestFile{{ files := []RequestFile{{
Name: "delete", Name: "delete",
File: config.Delete, Data: config.Delete,
}} }}
if config.Thumb != nil { if config.Thumb != nil {
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: "thumb", Name: "thumb",
File: config.Thumb, Data: config.Thumb,
}) })
} }
@ -129,7 +130,8 @@ func (config DeleteMessageConfig) files() []RequestFile {
} }
``` ```
And now our files will upload! It will transparently handle uploads whether File is a string with a path to a file, `FileURL`, `FileBytes`, `FileReader`, or `FileID`. And now our files will upload! It will transparently handle uploads whether File
is a `FilePath`, `FileURL`, `FileBytes`, `FileReader`, or `FileID`.
### Base Configs ### Base Configs

View File

@ -28,14 +28,14 @@ func (config DocumentConfig) files() []RequestFile {
// there always is a document file, so initialize the array with that. // there always is a document file, so initialize the array with that.
files := []RequestFile{{ files := []RequestFile{{
Name: "document", Name: "document",
File: config.File, Data: config.File,
}} }}
// We'll only add a file if we have one. // We'll only add a file if we have one.
if config.Thumb != nil { if config.Thumb != nil {
files = append(files, RequestFile{ files = append(files, RequestFile{
Name: "thumb", Name: "thumb",
File: config.Thumb, Data: config.Thumb,
}) })
} }
@ -58,7 +58,7 @@ Let's follow through creating a new media group with string and file uploads.
First, we start by creating some `InputMediaPhoto`. First, we start by creating some `InputMediaPhoto`.
```go ```go
photo := tgbotapi.NewInputMediaPhoto("tests/image.jpg") photo := tgbotapi.NewInputMediaPhoto(tgbotapi.FilePath("tests/image.jpg"))
url := tgbotapi.NewInputMediaPhoto(tgbotapi.FileURL("https://i.imgur.com/unQLJIb.jpg")) url := tgbotapi.NewInputMediaPhoto(tgbotapi.FileURL("https://i.imgur.com/unQLJIb.jpg"))
``` ```
@ -85,24 +85,3 @@ are all changed into `attach://file-%d`. When collecting a list of files to
upload, it names them the same way. This creates a nearly transparent way of upload, it names them the same way. This creates a nearly transparent way of
handling multiple files in the background without the user having to consider handling multiple files in the background without the user having to consider
what's going on. what's going on.
## Library Processing
If at some point in the future new upload types are required, let's talk about
where the current types are used.
Upload types are defined in `configs.go`. Where possible, type aliases are
preferred. Structs can be used when multiple fields are required.
The main usage of the upload types happens in `UploadFiles`. It switches on each
file's type in order to determine how to upload it. Files that aren't uploaded
(file IDs, URLs) are converted back into strings and passed through as strings
into the correct field. Uploaded types are processed as needed (opening files,
etc.) and written into the form using a copy approach in a goroutine to reduce
memory usage.
In addition to `UploadFiles`, there's more processing of upload types in the
`prepareInputMediaParam` and `prepareInputMediaFile` functions. These look at
the `InputMedia` types to determine which files are uploaded and which are
passed through as strings. They only need to be aware of which files need to be
replaced with `attach://` fields.

View File

@ -70,7 +70,7 @@ func NewCopyMessage(chatID int64, fromChatID int64, messageID int) CopyMessageCo
// 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 NewPhoto(chatID int64, file interface{}) PhotoConfig { func NewPhoto(chatID int64, file RequestFileData) PhotoConfig {
return PhotoConfig{ return PhotoConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, 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. // NewPhotoToChannel creates a new photo uploader to send a photo to a channel.
// //
// Note that you must send animated GIFs as a document. // 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{ return PhotoConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ BaseChat: BaseChat{
@ -94,7 +94,7 @@ func NewPhotoToChannel(username string, file interface{}) PhotoConfig {
} }
// NewAudio creates a new sendAudio request. // NewAudio creates a new sendAudio request.
func NewAudio(chatID int64, file interface{}) AudioConfig { func NewAudio(chatID int64, file RequestFileData) AudioConfig {
return AudioConfig{ return AudioConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
@ -104,7 +104,7 @@ func NewAudio(chatID int64, file interface{}) AudioConfig {
} }
// NewDocument creates a new sendDocument request. // NewDocument creates a new sendDocument request.
func NewDocument(chatID int64, file interface{}) DocumentConfig { func NewDocument(chatID int64, file RequestFileData) DocumentConfig {
return DocumentConfig{ return DocumentConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
@ -114,7 +114,7 @@ func NewDocument(chatID int64, file interface{}) DocumentConfig {
} }
// NewSticker creates a new sendSticker request. // NewSticker creates a new sendSticker request.
func NewSticker(chatID int64, file interface{}) StickerConfig { func NewSticker(chatID int64, file RequestFileData) StickerConfig {
return StickerConfig{ return StickerConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
@ -124,7 +124,7 @@ func NewSticker(chatID int64, file interface{}) StickerConfig {
} }
// NewVideo creates a new sendVideo request. // NewVideo creates a new sendVideo request.
func NewVideo(chatID int64, file interface{}) VideoConfig { func NewVideo(chatID int64, file RequestFileData) VideoConfig {
return VideoConfig{ return VideoConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
@ -134,7 +134,7 @@ func NewVideo(chatID int64, file interface{}) VideoConfig {
} }
// NewAnimation creates a new sendAnimation request. // NewAnimation creates a new sendAnimation request.
func NewAnimation(chatID int64, file interface{}) AnimationConfig { func NewAnimation(chatID int64, file RequestFileData) AnimationConfig {
return AnimationConfig{ return AnimationConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, 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, // chatID is where to send it, file is a string path to the file,
// FileReader, or FileBytes. // FileReader, or FileBytes.
func NewVideoNote(chatID int64, length int, file interface{}) VideoNoteConfig { func NewVideoNote(chatID int64, length int, file RequestFileData) VideoNoteConfig {
return VideoNoteConfig{ return VideoNoteConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
@ -158,7 +158,7 @@ func NewVideoNote(chatID int64, length int, file interface{}) VideoNoteConfig {
} }
// NewVoice creates a new sendVoice request. // NewVoice creates a new sendVoice request.
func NewVoice(chatID int64, file interface{}) VoiceConfig { func NewVoice(chatID int64, file RequestFileData) VoiceConfig {
return VoiceConfig{ return VoiceConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ChatID: chatID}, BaseChat: BaseChat{ChatID: chatID},
@ -177,7 +177,7 @@ func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
} }
// NewInputMediaPhoto creates a new InputMediaPhoto. // NewInputMediaPhoto creates a new InputMediaPhoto.
func NewInputMediaPhoto(media interface{}) InputMediaPhoto { func NewInputMediaPhoto(media RequestFileData) InputMediaPhoto {
return InputMediaPhoto{ return InputMediaPhoto{
BaseInputMedia{ BaseInputMedia{
Type: "photo", Type: "photo",
@ -187,7 +187,7 @@ func NewInputMediaPhoto(media interface{}) InputMediaPhoto {
} }
// NewInputMediaVideo creates a new InputMediaVideo. // NewInputMediaVideo creates a new InputMediaVideo.
func NewInputMediaVideo(media interface{}) InputMediaVideo { func NewInputMediaVideo(media RequestFileData) InputMediaVideo {
return InputMediaVideo{ return InputMediaVideo{
BaseInputMedia: BaseInputMedia{ BaseInputMedia: BaseInputMedia{
Type: "video", Type: "video",
@ -197,7 +197,7 @@ func NewInputMediaVideo(media interface{}) InputMediaVideo {
} }
// NewInputMediaAnimation creates a new InputMediaAnimation. // NewInputMediaAnimation creates a new InputMediaAnimation.
func NewInputMediaAnimation(media interface{}) InputMediaAnimation { func NewInputMediaAnimation(media RequestFileData) InputMediaAnimation {
return InputMediaAnimation{ return InputMediaAnimation{
BaseInputMedia: BaseInputMedia{ BaseInputMedia: BaseInputMedia{
Type: "animation", Type: "animation",
@ -207,7 +207,7 @@ func NewInputMediaAnimation(media interface{}) InputMediaAnimation {
} }
// NewInputMediaAudio creates a new InputMediaAudio. // NewInputMediaAudio creates a new InputMediaAudio.
func NewInputMediaAudio(media interface{}) InputMediaAudio { func NewInputMediaAudio(media RequestFileData) InputMediaAudio {
return InputMediaAudio{ return InputMediaAudio{
BaseInputMedia: BaseInputMedia{ BaseInputMedia: BaseInputMedia{
Type: "audio", Type: "audio",
@ -217,7 +217,7 @@ func NewInputMediaAudio(media interface{}) InputMediaAudio {
} }
// NewInputMediaDocument creates a new InputMediaDocument. // NewInputMediaDocument creates a new InputMediaDocument.
func NewInputMediaDocument(media interface{}) InputMediaDocument { func NewInputMediaDocument(media RequestFileData) InputMediaDocument {
return InputMediaDocument{ return InputMediaDocument{
BaseInputMedia: BaseInputMedia{ BaseInputMedia: BaseInputMedia{
Type: "document", Type: "document",
@ -316,7 +316,7 @@ func NewWebhook(link string) (WebhookConfig, error) {
// //
// 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, error) { func NewWebhookWithCert(link string, file RequestFileData) (WebhookConfig, error) {
u, err := url.Parse(link) u, err := url.Parse(link)
if err != nil { if err != nil {
@ -769,7 +769,7 @@ func NewChatDescription(chatID int64, description string) SetChatDescriptionConf
} }
// NewChatPhoto allows you to update the photo for a chat. // 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{ return SetChatPhotoConfig{
BaseFile: BaseFile{ BaseFile: BaseFile{
BaseChat: BaseChat{ BaseChat: BaseChat{
@ -781,7 +781,7 @@ func NewChatPhoto(chatID int64, photo interface{}) SetChatPhotoConfig {
} }
// NewDeleteChatPhoto allows you to delete the photo for a chat. // NewDeleteChatPhoto allows you to delete the photo for a chat.
func NewDeleteChatPhoto(chatID int64, photo interface{}) DeleteChatPhotoConfig { func NewDeleteChatPhoto(chatID int64) DeleteChatPhotoConfig {
return DeleteChatPhotoConfig{ return DeleteChatPhotoConfig{
ChatID: chatID, ChatID: chatID,
} }

View File

@ -17,7 +17,7 @@ func TestNewWebhook(t *testing.T) {
} }
func TestNewWebhookWithCert(t *testing.T) { func TestNewWebhookWithCert(t *testing.T) {
exampleFile := File{FileID: "123"} exampleFile := FileID("123")
result, err := NewWebhookWithCert("https://example.com/token", exampleFile) result, err := NewWebhookWithCert("https://example.com/token", exampleFile)
if err != nil || if err != nil ||

View File

@ -1723,7 +1723,7 @@ type BaseInputMedia struct {
// pass an HTTP URL for Telegram to get a file from the Internet, // pass an HTTP URL for Telegram to get a file from the Internet,
// or pass “attach://<file_attach_name>” to upload a new one // or pass “attach://<file_attach_name>” to upload a new one
// using multipart/form-data under <file_attach_name> name. // using multipart/form-data under <file_attach_name> name.
Media interface{} `json:"media"` Media RequestFileData `json:"media"`
// thumb intentionally missing as it is not currently compatible // thumb intentionally missing as it is not currently compatible
// Caption of the video to be sent, 0-1024 characters after entities parsing. // Caption of the video to be sent, 0-1024 characters after entities parsing.
@ -1755,7 +1755,7 @@ type InputMediaVideo struct {
// the file is supported server-side. // the file is supported server-side.
// //
// optional // optional
Thumb interface{} `json:"thumb,omitempty"` Thumb RequestFileData `json:"thumb,omitempty"`
// Width video width // Width video width
// //
// optional // optional
@ -1781,7 +1781,7 @@ type InputMediaAnimation struct {
// the file is supported server-side. // the file is supported server-side.
// //
// optional // optional
Thumb interface{} `json:"thumb,omitempty"` Thumb RequestFileData `json:"thumb,omitempty"`
// Width video width // Width video width
// //
// optional // optional
@ -1803,7 +1803,7 @@ type InputMediaAudio struct {
// the file is supported server-side. // the file is supported server-side.
// //
// optional // optional
Thumb interface{} `json:"thumb,omitempty"` Thumb RequestFileData `json:"thumb,omitempty"`
// Duration of the audio in seconds // Duration of the audio in seconds
// //
// optional // optional
@ -1825,7 +1825,7 @@ type InputMediaDocument struct {
// the file is supported server-side. // the file is supported server-side.
// //
// optional // optional
Thumb interface{} `json:"thumb,omitempty"` Thumb RequestFileData `json:"thumb,omitempty"`
// DisableContentTypeDetection disables automatic server-side content type // DisableContentTypeDetection disables automatic server-side content type
// detection for files uploaded using multipart/form-data. Always true, if // detection for files uploaded using multipart/form-data. Always true, if
// the document is sent as part of an album // the document is sent as part of an album

View File

@ -361,3 +361,13 @@ var (
_ Fileable = (*WebhookConfig)(nil) _ Fileable = (*WebhookConfig)(nil)
_ Fileable = (*SetStickerSetThumbConfig)(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)
)