4.0 KiB
Uploading Files
To make files work as expected, there's a lot going on behind the scenes. Make sure to read through the Files section in Getting Started first as we'll be building on that information.
This section only talks about file uploading. For non-uploaded files such as URLs and file IDs, you just need to pass a string.
Fields
Let's start by talking about how the library represents files as part of a Config.
Static Fields
Most endpoints use static file fields. For example, sendPhoto
expects a single
file named photo
. All we have to do is set that single field with the correct
value (either a string or multipart file). Methods like sendDocument
take two
file uploads, a document
and a thumb
. These are pretty straightforward.
Remembering that the Fileable
interface only requires one method, let's
implement it for DocumentConfig
.
func (config DocumentConfig) files() []RequestFile {
// We can have multiple files, so we'll create an array. We also know that
// there always is a document file, so initialize the array with that.
files := []RequestFile{{
Name: "document",
File: config.File,
}}
// We'll only add a file if we have one.
if config.Thumb != nil {
files = append(files, RequestFile{
Name: "thumb",
File: config.Thumb,
})
}
return files
}
Telegram also supports the attach://
syntax (discussed more later) for
thumbnails, but there's no reason to make things more complicated.
Dynamic Fields
Of course, not everything can be so simple. Methods like sendMediaGroup
can accept many files, and each file can have custom markup. Using a static
field isn't possible because we need to specify which field is attached to each
item. Telegram introduced the attach://
syntax for this.
Let's follow through creating a new media group with string and file uploads.
First, we start by creating some InputMediaPhoto
.
photo := tgbotapi.NewInputMediaPhoto("tests/image.jpg")
url := tgbotapi.NewInputMediaPhoto(tgbotapi.FileURL("https://i.imgur.com/unQLJIb.jpg"))
This created a new InputMediaPhoto
struct, with a type of photo
and the
media interface that we specified.
We'll now create our media group with the photo and URL.
mediaGroup := NewMediaGroup(ChatID, []interface{}{
photo,
url,
})
A MediaGroupConfig
stores all of the media in an array of interfaces. We now
have all of the data we need to upload, but how do we figure out field names for
uploads? We didn't specify attach://unique-file
anywhere.
When the library goes to upload the files, it looks at the params
and files
for the Config. The params are generated by transforming the file into a value
more suitable for uploading, file IDs and URLs are untouched but uploaded types
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
handling multiple files in the background without the user having to consider
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.