"errors"
"fmt"
"io"
+ "mime/multipart"
"net/http"
"net/url"
"path"
+ "strconv"
"strings"
"time"
)
}, nil)
}
+// SendImage uploads an image to the owner as a photo, or as a document when the
+// Bot API refuses the photo. Network failures do not trigger a second send.
+func (b *TelegramAPI) SendImage(ctx context.Context, chat int64, name string, data []byte, caption string) error {
+ err := b.sendImage(ctx, "sendPhoto", "photo", chat, name, data, caption)
+ if telegramRejected(err) {
+ err = b.sendImage(ctx, "sendDocument", "document", chat, name, data, caption)
+ }
+
+ return err
+}
+
+func (b *TelegramAPI) sendImage(ctx context.Context, method, field string, chat int64, name string, data []byte, caption string) error {
+ ctx, cancel := context.WithTimeout(ctx, telegramSendTimeout)
+ defer cancel()
+
+ var body bytes.Buffer
+
+ writer := multipart.NewWriter(&body)
+ err := writer.WriteField("chat_id", strconv.FormatInt(chat, 10))
+ if err != nil {
+ return fmt.Errorf("write image chat: %w", err)
+ }
+
+ err = writer.WriteField("caption", caption)
+ if err != nil {
+ return fmt.Errorf("write image caption: %w", err)
+ }
+
+ part, err := writer.CreateFormFile(field, name)
+ if err != nil {
+ return fmt.Errorf("make image part: %w", err)
+ }
+
+ _, err = part.Write(data)
+ if err != nil {
+ return fmt.Errorf("write image part: %w", err)
+ }
+
+ err = writer.Close()
+ if err != nil {
+ return fmt.Errorf("close image form: %w", err)
+ }
+
+ request, err := http.NewRequestWithContext(ctx, http.MethodPost, b.base+"/bot"+b.token+"/"+method, &body)
+ if err != nil {
+ return fmt.Errorf("make image upload: %s", b.redact(err))
+ }
+
+ request.Header.Set("Content-Type", writer.FormDataContentType())
+
+ err = b.answer(request, telegramSendTimeout, nil)
+ if err != nil {
+ return fmt.Errorf("upload image: %w", err)
+ }
+
+ return nil
+}
+
// SendTyping shows the typing action in chat for about five seconds. A call
// that takes longer than telegramTypingTimeout fails with a
// TelegramNetworkError.
request.Header.Set("Content-Type", "application/json")
+ return b.answer(request, timeout, result)
+}
+
+// answer reads the bounded Bot API response without keeping its request URL.
+func (b *TelegramAPI) answer(request *http.Request, timeout time.Duration, result any) error {
response, err := b.http.Do(request)
if err != nil {
return b.networkError("no answer", err, timeout)