]> git.giacomolli.uk - onechat/blobdiff - telegram_api.go
Send stored images to WhatsApp chats
[onechat] / telegram_api.go
index 8f78387d096b3215de9a0246ec7213f338d1f601..508010e409e60e84ab089860a528c312e4251169 100644 (file)
@@ -7,9 +7,11 @@ import (
        "errors"
        "fmt"
        "io"
        "errors"
        "fmt"
        "io"
+       "mime/multipart"
        "net/http"
        "net/url"
        "path"
        "net/http"
        "net/url"
        "path"
+       "strconv"
        "strings"
        "time"
 )
        "strings"
        "time"
 )
@@ -196,6 +198,64 @@ func (b *TelegramAPI) SendRichMessage(ctx context.Context, chat int64, markdown
        }, nil)
 }
 
        }, 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.
 // SendTyping shows the typing action in chat for about five seconds. A call
 // that takes longer than telegramTypingTimeout fails with a
 // TelegramNetworkError.
@@ -226,6 +286,11 @@ func (b *TelegramAPI) call(ctx context.Context, timeout time.Duration, method st
 
        request.Header.Set("Content-Type", "application/json")
 
 
        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)
        response, err := b.http.Do(request)
        if err != nil {
                return b.networkError("no answer", err, timeout)