From 994eb078788aebf545e4bed931ba2d28bfcd7b8d Mon Sep 17 00:00:00 2001 From: cleitonme Date: Tue, 31 Mar 2026 22:48:41 -0300 Subject: [PATCH] =?UTF-8?q?Adiciona=20suporte=20a=20URLs=20HTTP=20para=20d?= =?UTF-8?q?ocumentos,=20=C3=A1udios,=20imagens=20e=20stickers.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers.go | 164 +++++++++++++++++++++++++++++++++------------------- helpers.go | 9 ++- 2 files changed, 110 insertions(+), 63 deletions(-) diff --git a/handlers.go b/handlers.go index a44d1b27..5ed821f8 100644 --- a/handlers.go +++ b/handlers.go @@ -177,18 +177,18 @@ func (s *server) authalice(next http.Handler) http.Handler { log.Debug().Str("userId", txtid).Bool("historyValid", history.Valid).Int64("historyValue", history.Int64).Str("historyStr", historyStr).Msg("User authentication - history debug") v := Values{map[string]string{ - "Id": txtid, - "Name": name, - "Jid": jid, - "Webhook": webhook, - "Token": token, - "Proxy": proxy_url, - "Events": events, - "Qrcode": qrcode, - "History": historyStr, - "HasHmac": strconv.FormatBool(hasHmac), - "S3Enabled": s3Enabled, - "MediaDelivery": mediaDelivery, + "Id": txtid, + "Name": name, + "Jid": jid, + "Webhook": webhook, + "Token": token, + "Proxy": proxy_url, + "Events": events, + "Qrcode": qrcode, + "History": historyStr, + "HasHmac": strconv.FormatBool(hasHmac), + "S3Enabled": s3Enabled, + "MediaDelivery": mediaDelivery, }} userinfocache.Set(token, v, cache.NoExpiration) @@ -813,13 +813,13 @@ func (s *server) GetStatus() http.HandlerFunc { func (s *server) SendDocument() http.HandlerFunc { type documentStruct struct { - Caption string - Phone string - Document string - FileName string - Id string - MimeType string - ContextInfo waE2E.ContextInfo + Caption string + Phone string + Document string + FileName string + Id string + MimeType string + ContextInfo waE2E.ContextInfo QuotedMessage *waE2E.Message `json:"QuotedMessage,omitempty"` } @@ -874,21 +874,36 @@ func (s *server) SendDocument() http.HandlerFunc { var uploaded whatsmeow.UploadResponse var filedata []byte - if t.Document[0:29] == "data:application/octet-stream" { + if strings.HasPrefix(t.Document, "data:") { var dataURL, err = dataurl.DecodeString(t.Document) if err != nil { s.Respond(w, r, http.StatusBadRequest, errors.New("could not decode base64 encoded data from payload")) return - } else { - filedata = dataURL.Data - uploaded, err = clientManager.GetWhatsmeowClient(txtid).Upload(context.Background(), filedata, whatsmeow.MediaDocument) - if err != nil { - s.Respond(w, r, http.StatusInternalServerError, errors.New(fmt.Sprintf("failed to upload file: %v", err))) - return - } } + filedata = dataURL.Data + } else if isHTTPURL(t.Document) { + data, ct, err := fetchURLBytes(r.Context(), t.Document, fetchDocumentMaxBytes) + if err != nil { + s.Respond(w, r, http.StatusBadRequest, errors.New(fmt.Sprintf("failed to fetch document from url: %v", err))) + return + } + if t.MimeType == "" { + t.MimeType = ct + } + filedata = data } else { - s.Respond(w, r, http.StatusBadRequest, errors.New("document data should start with \"data:application/octet-stream;base64,\"")) + s.Respond(w, r, http.StatusBadRequest, errors.New("document data should start with \"data:\" or be a valid HTTP URL")) + return + } + + uploaded, err = mediaCache.GetOrUploadDocument(context.Background(), clientManager.GetWhatsmeowClient(txtid), filedata, func() string { + if t.MimeType != "" { + return t.MimeType + } + return http.DetectContentType(filedata) + }()) + if err != nil { + s.Respond(w, r, http.StatusInternalServerError, errors.New(fmt.Sprintf("failed to upload file: %v", err))) return } @@ -973,15 +988,15 @@ func (s *server) SendDocument() http.HandlerFunc { func (s *server) SendAudio() http.HandlerFunc { type audioStruct struct { - Phone string - Audio string - Caption string - Id string - PTT *bool `json:"ptt,omitempty"` - MimeType string `json:"mimetype,omitempty"` - Seconds uint32 - Waveform []byte - ContextInfo waE2E.ContextInfo + Phone string + Audio string + Caption string + Id string + PTT *bool `json:"ptt,omitempty"` + MimeType string `json:"mimetype,omitempty"` + Seconds uint32 + Waveform []byte + ContextInfo waE2E.ContextInfo QuotedMessage *waE2E.Message `json:"QuotedMessage,omitempty"` } @@ -1035,16 +1050,29 @@ func (s *server) SendAudio() http.HandlerFunc { if err != nil { s.Respond(w, r, http.StatusBadRequest, errors.New("could not decode base64 encoded data from payload")) return - } else { - filedata = dataURL.Data - uploaded, err = clientManager.GetWhatsmeowClient(txtid).Upload(context.Background(), filedata, whatsmeow.MediaAudio) - if err != nil { - s.Respond(w, r, http.StatusInternalServerError, errors.New(fmt.Sprintf("failed to upload file: %v", err))) - return + } + filedata = dataURL.Data + } else if isHTTPURL(t.Audio) { + data, ct, err := fetchURLBytes(r.Context(), t.Audio, fetchAudioMaxBytes) + if err != nil { + s.Respond(w, r, http.StatusBadRequest, errors.New(fmt.Sprintf("failed to fetch audio from url: %v", err))) + return + } + if t.MimeType == "" { + if strings.HasPrefix(strings.ToLower(ct), "audio/") { + mime = ct } + // else mantém o default já definido (ogg ou mpeg) } + filedata = data } else { - s.Respond(w, r, http.StatusBadRequest, errors.New("audio data should start with \"data:audio/\"")) + s.Respond(w, r, http.StatusBadRequest, errors.New("audio data should start with \"data:audio/\" or be a valid HTTP URL")) + return + } + + uploaded, err = mediaCache.GetOrUploadAudio(context.Background(), clientManager.GetWhatsmeowClient(txtid), filedata, mime) + if err != nil { + s.Respond(w, r, http.StatusInternalServerError, errors.New(fmt.Sprintf("failed to upload file: %v", err))) return } @@ -1208,7 +1236,7 @@ func (s *server) SendImage() http.HandlerFunc { filedata = dataURL.Data } } else if isHTTPURL(t.Image) { - data, ct, err := fetchURLBytes(r.Context(), t.Image, openGraphImageMaxBytes) + data, ct, err := fetchURLBytes(r.Context(), t.Image, fetchImageMaxBytes) if err != nil { s.Respond(w, r, http.StatusBadRequest, errors.New(fmt.Sprintf("failed to fetch image from url: %v", err))) return @@ -1402,6 +1430,20 @@ func (s *server) SendSticker() http.HandlerFunc { msgid = t.Id } + if isHTTPURL(t.Sticker) { + data, ct, err := fetchURLBytes(r.Context(), t.Sticker, fetchImageMaxBytes) + if err != nil { + s.Respond(w, r, http.StatusBadRequest, errors.New(fmt.Sprintf("failed to fetch sticker from url: %v", err))) + return + } + mimeType := ct + if !strings.HasPrefix(strings.ToLower(mimeType), "image/") { + mimeType = "image/webp" + } + imgDataURL := dataurl.New(data, mimeType) + t.Sticker = imgDataURL.String() + } + processedData, detectedMimeType, err := processStickerData( t.Sticker, t.MimeType, @@ -1566,7 +1608,7 @@ func (s *server) SendVideo() http.HandlerFunc { } } else if isHTTPURL(t.Video) { - data, ct, err := fetchURLBytes(r.Context(), t.Video, openGraphImageMaxBytes) + data, ct, err := fetchURLBytes(r.Context(), t.Video, fetchVideoMaxBytes) if err != nil { s.Respond(w, r, http.StatusBadRequest, errors.New(fmt.Sprintf("failed to fetch image from url: %v", err))) return @@ -1675,11 +1717,11 @@ func (s *server) SendVideo() http.HandlerFunc { func (s *server) SendContact() http.HandlerFunc { type contactStruct struct { - Phone string - Id string - Name string - Vcard string - ContextInfo waE2E.ContextInfo + Phone string + Id string + Name string + Vcard string + ContextInfo waE2E.ContextInfo QuotedMessage *waE2E.Message `json:"QuotedMessage,omitempty"` } @@ -1797,12 +1839,12 @@ func (s *server) SendContact() http.HandlerFunc { func (s *server) SendLocation() http.HandlerFunc { type locationStruct struct { - Phone string - Id string - Name string - Latitude float64 - Longitude float64 - ContextInfo waE2E.ContextInfo + Phone string + Id string + Name string + Latitude float64 + Longitude float64 + ContextInfo waE2E.ContextInfo QuotedMessage *waE2E.Message `json:"QuotedMessage,omitempty"` } @@ -2244,8 +2286,8 @@ func (s *server) SendMessage() http.HandlerFunc { LinkPreview bool Id string ContextInfo waE2E.ContextInfo - QuotedText string `json:"QuotedText,omitempty"` - QuotedMessage *waE2E.Message `json:"QuotedMessage,omitempty"` + QuotedText string `json:"QuotedText,omitempty"` + QuotedMessage *waE2E.Message `json:"QuotedMessage,omitempty"` } return func(w http.ResponseWriter, r *http.Request) { txtid := r.Context().Value("userinfo").(Values).Get("Id") @@ -6701,14 +6743,14 @@ func (s *server) publishSentMessageEvent(token, userID, txtid string, recipient var recipientLID types.JID if client.Store != nil && client.Store.LIDs != nil { ctx := context.Background() - + // Get sender LID if !senderJID.IsEmpty() { if lid, err := client.Store.LIDs.GetLIDForPN(ctx, senderJID); err == nil && !lid.IsEmpty() { senderLID = lid } } - + // Get recipient LID (only for non-group chats) if !isGroup && !recipient.IsEmpty() { if lid, err := client.Store.LIDs.GetLIDForPN(ctx, recipient); err == nil && !lid.IsEmpty() { diff --git a/helpers.go b/helpers.go index 83c4c628..48625686 100644 --- a/helpers.go +++ b/helpers.go @@ -44,8 +44,12 @@ import ( const ( openGraphFetchTimeout = 5 * time.Second - openGraphPageMaxBytes = 2 * 1024 * 1024 // 2MB - openGraphImageMaxBytes = 10 * 1024 * 1024 // 10MB + fetchImageMaxBytes = 16 * 1024 * 1024 // 16MB + fetchVideoMaxBytes = 100 * 1024 * 1024 // 100MB + fetchAudioMaxBytes = 16 * 1024 * 1024 // 16MB + fetchDocumentMaxBytes = 100 * 1024 * 1024 // 100MB + openGraphPageMaxBytes = 2 * 1024 * 1024 // 2MB + openGraphImageMaxBytes = 10 * 1024 * 1024 // 10MB openGraphThumbnailWidth = 100 openGraphThumbnailHeight = 100 openGraphJpegQuality = 80 @@ -136,6 +140,7 @@ func isHTTPURL(input string) bool { } return parsed.Host != "" } + func fetchURLBytes(ctx context.Context, resourceURL string, limit int64) ([]byte, string, error) { req, err := http.NewRequestWithContext(ctx, "GET", resourceURL, nil) if err != nil {