// its own, because the long poll of getUpdates needs a longer limit.
telegramSendTimeout = 30 * time.Second
- // telegramFileLookupTimeout is the longest wait for image file details.
+ // telegramFileLookupTimeout is the longest wait for media file details.
telegramFileLookupTimeout = 30 * time.Second
- // telegramImageDownloadTimeout is the longest wait for image bytes.
- telegramImageDownloadTimeout = 30 * time.Second
+ // telegramFileDownloadTimeout is the longest wait for image bytes.
+ telegramFileDownloadTimeout = 30 * time.Second
// telegramTypingTimeout is the longest wait to show the typing action.
telegramTypingTimeout = 30 * time.Second
return updates, err
}
-// GetImage downloads a file through the authenticated file endpoint. Errors
-// never include its token or URL, and the body is limited before storage.
-func (b *TelegramAPI) GetImage(ctx context.Context, id string) (data []byte, err error) {
+// GetFile downloads bounded media through the authenticated file endpoint.
+// Errors never include its token or URL, and the body is limited before
+// storage.
+func (b *TelegramAPI) GetFile(ctx context.Context, id string, limit int64, tooLarge error) (data []byte, err error) {
var file struct {
Path string `json:"file_path"`
Size int64 `json:"file_size"`
err = b.call(ctx, telegramFileLookupTimeout, "getFile", map[string]any{"file_id": id}, &file)
if err != nil {
- return nil, fmt.Errorf("get image file: %w", err)
+ return nil, fmt.Errorf("get media file: %w", err)
}
- if file.Size > imageMaxBytes {
- return nil, errImageSize
+ if file.Size > limit {
+ return nil, tooLarge
}
if file.Path == "" || path.Clean(file.Path) != file.Path || strings.HasPrefix(file.Path, "/") || strings.HasPrefix(file.Path, "../") || strings.ContainsAny(file.Path, "?%#\\") {
- return nil, errors.New("invalid image file path")
+ return nil, errors.New("invalid media file path")
}
- ctx, cancel := context.WithTimeout(ctx, telegramImageDownloadTimeout)
+ ctx, cancel := context.WithTimeout(ctx, telegramFileDownloadTimeout)
defer cancel()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, b.base+"/file/bot"+b.token+"/"+file.Path, nil)
if err != nil {
- return nil, fmt.Errorf("make image request: %s", b.redact(err))
+ return nil, fmt.Errorf("make media request: %s", b.redact(err))
}
response, err := b.http.Do(request)
if err != nil {
- return nil, b.networkError("image download failed", err, telegramImageDownloadTimeout)
+ return nil, b.networkError("media download failed", err, telegramFileDownloadTimeout)
}
defer func() {
closeErr := response.Body.Close()
if closeErr != nil {
- err = errors.Join(err, b.networkError("close image download", closeErr, telegramImageDownloadTimeout))
+ err = errors.Join(err, b.networkError("close media download", closeErr, telegramFileDownloadTimeout))
}
}()
}
}
- data, err = io.ReadAll(io.LimitReader(response.Body, imageMaxBytes+1))
+ data, err = io.ReadAll(io.LimitReader(response.Body, limit+1))
if err != nil {
- return nil, b.networkError("image cut off", err, telegramImageDownloadTimeout)
+ return nil, b.networkError("media cut off", err, telegramFileDownloadTimeout)
}
- if len(data) > imageMaxBytes {
- return nil, errImageSize
+ if int64(len(data)) > limit {
+ return nil, tooLarge
}
return data, nil
Caption string `json:"caption"`
Photo []TelegramPhoto `json:"photo"`
Document *TelegramDocument `json:"document"`
+ Voice *TelegramVoice `json:"voice"`
+}
+
+// TelegramVoice identifies a voice note in the authenticated file API.
+type TelegramVoice struct {
+ FileID string `json:"file_id"`
+ FileSize int64 `json:"file_size"`
}
// TelegramPhoto is one supplied size of a photo.