From 4f0b76ac8793174391f25a863c2ed689bf1d4535 Mon Sep 17 00:00:00 2001 From: moothz Date: Tue, 24 Mar 2026 21:48:55 -0300 Subject: [PATCH 1/4] feat: enhanced media sending (gif-playback, video-stickers, transparent-color) --- pkg/sendMessage/service/send_service.go | 141 ++++++++++++++++++------ 1 file changed, 108 insertions(+), 33 deletions(-) diff --git a/pkg/sendMessage/service/send_service.go b/pkg/sendMessage/service/send_service.go index 09db21a..c9b08b6 100644 --- a/pkg/sendMessage/service/send_service.go +++ b/pkg/sendMessage/service/send_service.go @@ -12,6 +12,7 @@ import ( "io" "mime/multipart" "net/http" + "os" "os/exec" "regexp" "strconv" @@ -122,14 +123,15 @@ type PollStruct struct { } type StickerStruct struct { - Number string `json:"number"` - Sticker string `json:"sticker"` - Id string `json:"id"` - Delay int32 `json:"delay"` - MentionedJID []string `json:"mentionedJid"` - MentionAll bool `json:"mentionAll"` - FormatJid *bool `json:"formatJid,omitempty"` - Quoted QuotedStruct `json:"quoted"` + Number string `json:"number"` + Sticker string `json:"sticker"` + Id string `json:"id"` + Delay int32 `json:"delay"` + MentionedJID []string `json:"mentionedJid"` + MentionAll bool `json:"mentionAll"` + FormatJid *bool `json:"formatJid,omitempty"` + TransparentColor string `json:"transparentColor,omitempty"` + Quoted QuotedStruct `json:"quoted"` } type LocationStruct struct { @@ -933,14 +935,16 @@ func (s *sendService) sendMediaFileWithRetry(data *MediaStruct, fileData []byte, } mediaType = "ImageMessage" case "video": + isGif := strings.HasSuffix(strings.ToLower(data.Filename), ".gif") || strings.HasSuffix(strings.ToLower(data.Url), ".gif") if isNewsletter { media = &waE2E.Message{VideoMessage: &waE2E.VideoMessage{ - Caption: proto.String(data.Caption), - URL: &uploaded.URL, - DirectPath: &uploaded.DirectPath, - Mimetype: proto.String(mimeType), - FileSHA256: uploaded.FileSHA256, - FileLength: &uploaded.FileLength, + Caption: proto.String(data.Caption), + URL: &uploaded.URL, + DirectPath: &uploaded.DirectPath, + Mimetype: proto.String(mimeType), + FileSHA256: uploaded.FileSHA256, + FileLength: &uploaded.FileLength, + GifPlayback: proto.Bool(isGif), }} } else { media = &waE2E.Message{VideoMessage: &waE2E.VideoMessage{ @@ -952,6 +956,7 @@ func (s *sendService) sendMediaFileWithRetry(data *MediaStruct, fileData []byte, FileEncSHA256: uploaded.FileEncSHA256, FileSHA256: uploaded.FileSHA256, FileLength: proto.Uint64(uint64(len(fileData))), + GifPlayback: proto.Bool(isGif), }} } mediaType = "VideoMessage" @@ -1217,14 +1222,16 @@ func (s *sendService) sendMediaUrlWithRetry(data *MediaStruct, instance *instanc } mediaType = "ImageMessage" case "video": + isGif := strings.HasSuffix(strings.ToLower(data.Filename), ".gif") || strings.HasSuffix(strings.ToLower(data.Url), ".gif") if isNewsletter { media = &waE2E.Message{VideoMessage: &waE2E.VideoMessage{ - Caption: proto.String(data.Caption), - URL: &uploaded.URL, - DirectPath: &uploaded.DirectPath, - Mimetype: proto.String(mimeType), - FileSHA256: uploaded.FileSHA256, - FileLength: &uploaded.FileLength, + Caption: proto.String(data.Caption), + URL: &uploaded.URL, + DirectPath: &uploaded.DirectPath, + Mimetype: proto.String(mimeType), + FileSHA256: uploaded.FileSHA256, + FileLength: &uploaded.FileLength, + GifPlayback: proto.Bool(isGif), }} } else { media = &waE2E.Message{VideoMessage: &waE2E.VideoMessage{ @@ -1236,6 +1243,7 @@ func (s *sendService) sendMediaUrlWithRetry(data *MediaStruct, instance *instanc FileEncSHA256: uploaded.FileEncSHA256, FileSHA256: uploaded.FileSHA256, FileLength: proto.Uint64(uint64(len(fileData))), + GifPlayback: proto.Bool(isGif), }} } mediaType = "VideoMessage" @@ -1414,28 +1422,95 @@ func (s *sendService) sendPollWithRetry(data *PollStruct, instance *instance_mod return nil, fmt.Errorf("failed to send poll after %d attempts", maxRetries) } -func convertToWebP(imageData string) ([]byte, error) { - var img image.Image - var err error +func convertVideoToWebP(inputData []byte, transparentColor string) ([]byte, error) { + tmpInput, err := os.CreateTemp("", "sticker-input-*.mp4") + if err != nil { + return nil, fmt.Errorf("failed to create temp file: %v", err) + } + defer os.Remove(tmpInput.Name()) + + if _, err := tmpInput.Write(inputData); err != nil { + tmpInput.Close() + return nil, fmt.Errorf("failed to write to temp file: %v", err) + } + + if err := tmpInput.Close(); err != nil { + return nil, fmt.Errorf("failed to close temp file: %v", err) + } + + tmpOutput := tmpInput.Name() + ".webp" + defer os.Remove(tmpOutput) + + // Filtros base: scale, pad, fps e loop + baseFilters := "fps=15,scale=512:512:force_original_aspect_ratio=decrease,pad=512:512:(ow-iw)/2:(oh-ih)/2:color=0x00000000" + + filters := baseFilters + if transparentColor != "" { + cleanHex := strings.ReplaceAll(transparentColor, "#", "") + filters = fmt.Sprintf("colorkey=0x%s:0.1:0.0,%s", cleanHex, baseFilters) + } + + cmd := exec.Command("ffmpeg", + "-i", tmpInput.Name(), + "-vcodec", "libwebp", + "-filter:v", filters, + "-lossless", "0", + "-compression_level", "4", + "-q:v", "50", + "-loop", "0", + "-an", + "-f", "webp", + tmpOutput, + ) + + var stderr bytes.Buffer + cmd.Stderr = &stderr - resp, err := http.Get(imageData) + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("ffmpeg failed: %v, output: %s", err, stderr.String()) + } + + webpData, err := os.ReadFile(tmpOutput) if err != nil { - return nil, fmt.Errorf("failed to fetch image from URL: %v", err) + return nil, fmt.Errorf("failed to read generated webp: %v", err) } - defer resp.Body.Close() - img, _, err = image.Decode(resp.Body) + return webpData, nil +} + +func convertToWebP(imageDataURL string, transparentColor string) ([]byte, error) { + resp, err := http.Get(imageDataURL) if err != nil { - return nil, fmt.Errorf("failed to decode image: %v", err) + return nil, fmt.Errorf("failed to fetch from URL: %v", err) } + defer resp.Body.Close() - var webpBuffer bytes.Buffer - err = webp.Encode(&webpBuffer, img, &webp.Options{Lossless: false, Quality: 80}) + data, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("failed to encode image to WebP: %v", err) + return nil, fmt.Errorf("failed to read response body: %v", err) + } + + mime := mimetype.Detect(data) + + if mime.Is("image/webp") { + return data, nil + } else if mime.Is("video/mp4") { + return convertVideoToWebP(data, transparentColor) + } else if mime.Is("image/jpeg") || mime.Is("image/png") || mime.Is("image/jpg") { + img, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("failed to decode image: %v", err) + } + + var webpBuffer bytes.Buffer + err = webp.Encode(&webpBuffer, img, &webp.Options{Lossless: false, Quality: 80}) + if err != nil { + return nil, fmt.Errorf("failed to encode image to WebP: %v", err) + } + return webpBuffer.Bytes(), nil } - return webpBuffer.Bytes(), nil + return nil, fmt.Errorf("unsupported format: %s", mime.String()) } func (s *sendService) SendSticker(data *StickerStruct, instance *instance_model.Instance) (*MessageSendStruct, error) { @@ -1448,7 +1523,7 @@ func (s *sendService) SendSticker(data *StickerStruct, instance *instance_model. var filedata []byte if strings.HasPrefix(data.Sticker, "http") { - webpData, err := convertToWebP(data.Sticker) + webpData, err := convertToWebP(data.Sticker, data.TransparentColor) if err != nil { return nil, fmt.Errorf("failed to convert image to WebP: %v", err) } From 65e94e2578b1e48401644456559c2e08b9e8b420 Mon Sep 17 00:00:00 2001 From: moothz Date: Tue, 24 Mar 2026 22:07:32 -0300 Subject: [PATCH 2/4] docs: document gif-playback, video-stickers and transparent stickers --- docs/wiki/guias-api/api-messages.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/wiki/guias-api/api-messages.md b/docs/wiki/guias-api/api-messages.md index 394c17e..dd661cc 100644 --- a/docs/wiki/guias-api/api-messages.md +++ b/docs/wiki/guias-api/api-messages.md @@ -209,7 +209,7 @@ delay: 0 | Tipo | Formatos Aceitos | Observações | |------|------------------|-------------| | `image` | JPG, PNG, WebP | WebP convertido para JPEG | -| `video` | MP4 | Apenas MP4 | +| `video` | MP4, GIF | MP4. **GIF** URLs são convertidos automaticamente para vídeo com `GifPlayback` | | `audio` | Qualquer | Convertido para Opus (PTT) automaticamente | | `document` | Qualquer | Qualquer tipo de arquivo | @@ -334,7 +334,8 @@ Envia um sticker (figurinha) via URL. ```json { "number": "5511999999999", - "sticker": "https://example.com/sticker.webp" + "sticker": "https://example.com/video.mp4", + "transparentColor": "#000000" } ``` @@ -343,9 +344,13 @@ Envia um sticker (figurinha) via URL. | Campo | Tipo | Obrigatório | Descrição | |-------|------|-------------|-----------| | `number` | string | ✅ Sim | Número do destinatário | -| `sticker` | string | ✅ Sim | URL da imagem (convertida para WebP automaticamente) | +| `sticker` | string | ✅ Sim | URL da imagem ou vídeo (convertida para WebP automaticamente) | +| `transparentColor` | string | ❌ Não | Cor em Hex (ex: #000000) para tornar transparente (útil para vídeos) | -**Nota**: O sistema converte automaticamente a imagem para o formato WebP (formato de sticker do WhatsApp). +**Nota**: +- O sistema converte automaticamente imagens e **vídeos (MP4)** para o formato WebP (formato de sticker do WhatsApp). +- Vídeos são convertidos para **stickers animados**. +- O parâmetro `transparentColor` permite remover o fundo de uma cor específica durante a conversão. **Resposta de Sucesso (200)**: ```json @@ -362,6 +367,7 @@ Envia um sticker (figurinha) via URL. **Exemplo cURL**: ```bash +# Sticker estático (Imagem) curl -X POST http://localhost:4000/send/sticker \ -H "Content-Type: application/json" \ -H "apikey: SUA-CHAVE-API" \ @@ -369,6 +375,16 @@ curl -X POST http://localhost:4000/send/sticker \ "number": "5511999999999", "sticker": "https://exemplo.com/figurinha.png" }' + +# Sticker animado (Vídeo com fundo removido) +curl -X POST http://localhost:4000/send/sticker \ + -H "Content-Type: application/json" \ + -H "apikey: SUA-CHAVE-API" \ + -d '{ + "number": "5511999999999", + "sticker": "https://exemplo.com/video.mp4", + "transparentColor": "#000000" + }' ``` --- From 197c747173b681b40bc6dfc9809cdfc410fb5ef5 Mon Sep 17 00:00:00 2001 From: moothz Date: Sat, 4 Apr 2026 16:59:51 -0300 Subject: [PATCH 3/4] feat: consolidate sticker improvements and address code review comments - Consistently use MIME type for GifPlayback detection. - Add hex color validation for transparent stickers. - Implement timeouts and size limits for sticker conversion. - Use http.Client with timeouts instead of bare http.Get. - Use exec.CommandContext with timeouts for ffmpeg. - Fix security placeholder in docs. - Consolidate changes from fix/sticker-image-decoding branch. --- docs/wiki/guias-api/api-messages.md | 42 +++++++++++----------- pkg/sendMessage/service/send_service.go | 46 +++++++++++++++++++++---- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/docs/wiki/guias-api/api-messages.md b/docs/wiki/guias-api/api-messages.md index dd661cc..febbbf3 100644 --- a/docs/wiki/guias-api/api-messages.md +++ b/docs/wiki/guias-api/api-messages.md @@ -96,7 +96,7 @@ apikey: SUA-CHAVE-API ```bash curl -X POST http://localhost:4000/send/text \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "text": "Olá! Esta é uma mensagem de teste." @@ -153,7 +153,7 @@ Envia uma mensagem com preview de link (título, descrição, imagem). ```bash curl -X POST http://localhost:4000/send/link \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "text": "Veja esta notícia: https://g1.globo.com/tecnologia" @@ -239,7 +239,7 @@ delay: 0 ```bash curl -X POST http://localhost:4000/send/media \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "url": "https://exemplo.com/produto.jpg", @@ -251,7 +251,7 @@ curl -X POST http://localhost:4000/send/media \ **Exemplo cURL (Arquivo)**: ```bash curl -X POST http://localhost:4000/send/media \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -F "number=5511999999999" \ -F "type=image" \ -F "caption=Foto enviada" \ @@ -313,7 +313,7 @@ Cria uma enquete (poll) com múltiplas opções. ```bash curl -X POST http://localhost:4000/send/poll \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "question": "Qual plano você prefere?", @@ -370,7 +370,7 @@ Envia um sticker (figurinha) via URL. # Sticker estático (Imagem) curl -X POST http://localhost:4000/send/sticker \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "sticker": "https://exemplo.com/figurinha.png" @@ -379,7 +379,7 @@ curl -X POST http://localhost:4000/send/sticker \ # Sticker animado (Vídeo com fundo removido) curl -X POST http://localhost:4000/send/sticker \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "sticker": "https://exemplo.com/video.mp4", @@ -433,7 +433,7 @@ Envia uma localização geográfica. ```bash curl -X POST http://localhost:4000/send/location \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "name": "Teatro Municipal", @@ -489,7 +489,7 @@ Envia um cartão de contato (VCard). ```bash curl -X POST http://localhost:4000/send/contact \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "vcard": { @@ -601,7 +601,7 @@ Tipos de chave PIX: `phone`, `email`, `cpf`, `cnpj`, `random` (EVP). ```bash curl -X POST http://localhost:4000/send/button \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "title": "Atendimento", @@ -712,7 +712,7 @@ curl -X POST http://localhost:4000/send/button \ ```bash curl -X POST http://localhost:4000/send/list \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "title": "Menu Principal", @@ -782,7 +782,7 @@ Adiciona ou remove uma reação (emoji) em uma mensagem. # Adicionar reação curl -X POST http://localhost:4000/message/react \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "reaction": "❤️", @@ -793,7 +793,7 @@ curl -X POST http://localhost:4000/message/react \ # Remover reação curl -X POST http://localhost:4000/message/react \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "reaction": "remove", @@ -842,7 +842,7 @@ Marca mensagem(ns) como lida(s). ```bash curl -X POST http://localhost:4000/message/markread \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "id": ["3EB0C5A277F7F9B6C599"] @@ -891,7 +891,7 @@ Edita o conteúdo de uma mensagem enviada. ```bash curl -X POST http://localhost:4000/message/edit \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "chat": "5511999999999@s.whatsapp.net", "messageId": "3EB0C5A277F7F9B6C599", @@ -939,7 +939,7 @@ Deleta uma mensagem para todos (revoke). ```bash curl -X POST http://localhost:4000/message/delete \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "chat": "5511999999999@s.whatsapp.net", "messageId": "3EB0C5A277F7F9B6C599" @@ -993,7 +993,7 @@ Define o status de presença no chat (digitando, gravando áudio, online). # Mostrar "digitando..." curl -X POST http://localhost:4000/message/presence \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "state": "composing", @@ -1003,7 +1003,7 @@ curl -X POST http://localhost:4000/message/presence \ # Mostrar "gravando áudio..." curl -X POST http://localhost:4000/message/presence \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "state": "composing", @@ -1013,7 +1013,7 @@ curl -X POST http://localhost:4000/message/presence \ # Parar de digitar curl -X POST http://localhost:4000/message/presence \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "number": "5511999999999", "state": "paused" @@ -1072,7 +1072,7 @@ Faz download de mídia de uma mensagem recebida e retorna em base64. ```bash curl -X POST http://localhost:4000/message/downloadimage \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "message": { "imageMessage": { @@ -1136,7 +1136,7 @@ Consulta o status de entrega/leitura de uma mensagem no banco de dados. ```bash curl -X POST http://localhost:4000/message/status \ -H "Content-Type: application/json" \ - -H "apikey: SUA-CHAVE-API" \ + -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ -d '{ "id": "3EB0C5A277F7F9B6C599" }' diff --git a/pkg/sendMessage/service/send_service.go b/pkg/sendMessage/service/send_service.go index c9b08b6..c9797a7 100644 --- a/pkg/sendMessage/service/send_service.go +++ b/pkg/sendMessage/service/send_service.go @@ -8,6 +8,8 @@ import ( "errors" "fmt" "image" + _ "image/gif" + _ "image/jpeg" "image/png" "io" "mime/multipart" @@ -935,7 +937,8 @@ func (s *sendService) sendMediaFileWithRetry(data *MediaStruct, fileData []byte, } mediaType = "ImageMessage" case "video": - isGif := strings.HasSuffix(strings.ToLower(data.Filename), ".gif") || strings.HasSuffix(strings.ToLower(data.Url), ".gif") + lowerMimeType := strings.ToLower(mimeType) + isGif := strings.HasPrefix(lowerMimeType, "image/gif") || strings.HasPrefix(lowerMimeType, "video/gif") if isNewsletter { media = &waE2E.Message{VideoMessage: &waE2E.VideoMessage{ Caption: proto.String(data.Caption), @@ -1222,7 +1225,8 @@ func (s *sendService) sendMediaUrlWithRetry(data *MediaStruct, instance *instanc } mediaType = "ImageMessage" case "video": - isGif := strings.HasSuffix(strings.ToLower(data.Filename), ".gif") || strings.HasSuffix(strings.ToLower(data.Url), ".gif") + lowerMimeType := strings.ToLower(mimeType) + isGif := strings.HasPrefix(lowerMimeType, "image/gif") || strings.HasPrefix(lowerMimeType, "video/gif") if isNewsletter { media = &waE2E.Message{VideoMessage: &waE2E.VideoMessage{ Caption: proto.String(data.Caption), @@ -1422,6 +1426,17 @@ func (s *sendService) sendPollWithRetry(data *PollStruct, instance *instance_mod return nil, fmt.Errorf("failed to send poll after %d attempts", maxRetries) } +const ( + stickerMaxDownloadSize = 10 * 1024 * 1024 // 10MB + stickerDownloadTimeout = 30 * time.Second + stickerFFmpegTimeout = 60 * time.Second +) + +func isValidHexColor(s string) bool { + match, _ := regexp.MatchString(`^[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$`, s) + return match +} + func convertVideoToWebP(inputData []byte, transparentColor string) ([]byte, error) { tmpInput, err := os.CreateTemp("", "sticker-input-*.mp4") if err != nil { @@ -1447,10 +1462,16 @@ func convertVideoToWebP(inputData []byte, transparentColor string) ([]byte, erro filters := baseFilters if transparentColor != "" { cleanHex := strings.ReplaceAll(transparentColor, "#", "") + if !isValidHexColor(cleanHex) { + return nil, fmt.Errorf("invalid transparent color: %s", transparentColor) + } filters = fmt.Sprintf("colorkey=0x%s:0.1:0.0,%s", cleanHex, baseFilters) } - cmd := exec.Command("ffmpeg", + ctx, cancel := context.WithTimeout(context.Background(), stickerFFmpegTimeout) + defer cancel() + + cmd := exec.CommandContext(ctx, "ffmpeg", "-i", tmpInput.Name(), "-vcodec", "libwebp", "-filter:v", filters, @@ -1479,22 +1500,35 @@ func convertVideoToWebP(inputData []byte, transparentColor string) ([]byte, erro } func convertToWebP(imageDataURL string, transparentColor string) ([]byte, error) { - resp, err := http.Get(imageDataURL) + client := &http.Client{ + Timeout: stickerDownloadTimeout, + } + + resp, err := client.Get(imageDataURL) if err != nil { return nil, fmt.Errorf("failed to fetch from URL: %v", err) } defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch from URL: status code %d", resp.StatusCode) + } + + // Limitar o tamanho da leitura para evitar exaustão de recursos + data, err := io.ReadAll(io.LimitReader(resp.Body, stickerMaxDownloadSize)) if err != nil { return nil, fmt.Errorf("failed to read response body: %v", err) } + if int64(len(data)) >= stickerMaxDownloadSize { + return nil, fmt.Errorf("sticker size exceeds limit of %d bytes", stickerMaxDownloadSize) + } + mime := mimetype.Detect(data) if mime.Is("image/webp") { return data, nil - } else if mime.Is("video/mp4") { + } else if mime.Is("video/mp4") || mime.Is("image/gif") { return convertVideoToWebP(data, transparentColor) } else if mime.Is("image/jpeg") || mime.Is("image/png") || mime.Is("image/jpg") { img, _, err := image.Decode(bytes.NewReader(data)) From 4745f49601e57120635fc23769c1dc378da7359f Mon Sep 17 00:00:00 2001 From: moothz Date: Sat, 4 Apr 2026 17:15:03 -0300 Subject: [PATCH 4/4] docs: restore SUA-CHAVE-API placeholders --- docs/wiki/guias-api/api-messages.md | 42 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/wiki/guias-api/api-messages.md b/docs/wiki/guias-api/api-messages.md index febbbf3..dd661cc 100644 --- a/docs/wiki/guias-api/api-messages.md +++ b/docs/wiki/guias-api/api-messages.md @@ -96,7 +96,7 @@ apikey: SUA-CHAVE-API ```bash curl -X POST http://localhost:4000/send/text \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "text": "Olá! Esta é uma mensagem de teste." @@ -153,7 +153,7 @@ Envia uma mensagem com preview de link (título, descrição, imagem). ```bash curl -X POST http://localhost:4000/send/link \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "text": "Veja esta notícia: https://g1.globo.com/tecnologia" @@ -239,7 +239,7 @@ delay: 0 ```bash curl -X POST http://localhost:4000/send/media \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "url": "https://exemplo.com/produto.jpg", @@ -251,7 +251,7 @@ curl -X POST http://localhost:4000/send/media \ **Exemplo cURL (Arquivo)**: ```bash curl -X POST http://localhost:4000/send/media \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -F "number=5511999999999" \ -F "type=image" \ -F "caption=Foto enviada" \ @@ -313,7 +313,7 @@ Cria uma enquete (poll) com múltiplas opções. ```bash curl -X POST http://localhost:4000/send/poll \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "question": "Qual plano você prefere?", @@ -370,7 +370,7 @@ Envia um sticker (figurinha) via URL. # Sticker estático (Imagem) curl -X POST http://localhost:4000/send/sticker \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "sticker": "https://exemplo.com/figurinha.png" @@ -379,7 +379,7 @@ curl -X POST http://localhost:4000/send/sticker \ # Sticker animado (Vídeo com fundo removido) curl -X POST http://localhost:4000/send/sticker \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "sticker": "https://exemplo.com/video.mp4", @@ -433,7 +433,7 @@ Envia uma localização geográfica. ```bash curl -X POST http://localhost:4000/send/location \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "name": "Teatro Municipal", @@ -489,7 +489,7 @@ Envia um cartão de contato (VCard). ```bash curl -X POST http://localhost:4000/send/contact \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "vcard": { @@ -601,7 +601,7 @@ Tipos de chave PIX: `phone`, `email`, `cpf`, `cnpj`, `random` (EVP). ```bash curl -X POST http://localhost:4000/send/button \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "title": "Atendimento", @@ -712,7 +712,7 @@ curl -X POST http://localhost:4000/send/button \ ```bash curl -X POST http://localhost:4000/send/list \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "title": "Menu Principal", @@ -782,7 +782,7 @@ Adiciona ou remove uma reação (emoji) em uma mensagem. # Adicionar reação curl -X POST http://localhost:4000/message/react \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "reaction": "❤️", @@ -793,7 +793,7 @@ curl -X POST http://localhost:4000/message/react \ # Remover reação curl -X POST http://localhost:4000/message/react \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "reaction": "remove", @@ -842,7 +842,7 @@ Marca mensagem(ns) como lida(s). ```bash curl -X POST http://localhost:4000/message/markread \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "id": ["3EB0C5A277F7F9B6C599"] @@ -891,7 +891,7 @@ Edita o conteúdo de uma mensagem enviada. ```bash curl -X POST http://localhost:4000/message/edit \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "chat": "5511999999999@s.whatsapp.net", "messageId": "3EB0C5A277F7F9B6C599", @@ -939,7 +939,7 @@ Deleta uma mensagem para todos (revoke). ```bash curl -X POST http://localhost:4000/message/delete \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "chat": "5511999999999@s.whatsapp.net", "messageId": "3EB0C5A277F7F9B6C599" @@ -993,7 +993,7 @@ Define o status de presença no chat (digitando, gravando áudio, online). # Mostrar "digitando..." curl -X POST http://localhost:4000/message/presence \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "state": "composing", @@ -1003,7 +1003,7 @@ curl -X POST http://localhost:4000/message/presence \ # Mostrar "gravando áudio..." curl -X POST http://localhost:4000/message/presence \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "state": "composing", @@ -1013,7 +1013,7 @@ curl -X POST http://localhost:4000/message/presence \ # Parar de digitar curl -X POST http://localhost:4000/message/presence \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "number": "5511999999999", "state": "paused" @@ -1072,7 +1072,7 @@ Faz download de mídia de uma mensagem recebida e retorna em base64. ```bash curl -X POST http://localhost:4000/message/downloadimage \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "message": { "imageMessage": { @@ -1136,7 +1136,7 @@ Consulta o status de entrega/leitura de uma mensagem no banco de dados. ```bash curl -X POST http://localhost:4000/message/status \ -H "Content-Type: application/json" \ - -H "apikey: REPLACE_WITH_YOUR_API_KEY" \ + -H "apikey: SUA-CHAVE-API" \ -d '{ "id": "3EB0C5A277F7F9B6C599" }'