diff --git a/CHANGELOG.md b/CHANGELOG.md index f03ec516..8c513bff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * Now the api key can be exposed in fetch instances if the EXPOSE_IN_FETCH_INSTANCES variable is set to true * Added option to generate qrcode as soon as the instance is created * The created instance token can now also be optionally defined manually in the creation endpoint +* Route to send Sticker ### Fixed diff --git a/package.json b/package.json index 21b675d0..791a9e6f 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "qrcode": "^1.5.1", "qrcode-terminal": "^0.12.0", "redis": "^4.6.5", + "sharp": "^0.30.7", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 554301e8..d95f9268 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -210,6 +210,24 @@ export const mediaMessageSchema: JSONSchema7 = { required: ['mediaMessage', 'number'], }; +export const stickerMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema }, + stickerMessage: { + type: 'object', + properties: { + image: { type: 'string' }, + }, + required: ['image'], + ...isNotEmpty('image'), + }, + }, + required: ['stickerMessage', 'number'], +}; + export const audioMessageSchema: JSONSchema7 = { $id: v4(), type: 'object', diff --git a/src/whatsapp/controllers/sendMessage.controller.ts b/src/whatsapp/controllers/sendMessage.controller.ts index df26cf40..b985d5e7 100644 --- a/src/whatsapp/controllers/sendMessage.controller.ts +++ b/src/whatsapp/controllers/sendMessage.controller.ts @@ -11,6 +11,7 @@ import { SendMediaDto, SendPollDto, SendReactionDto, + SendStickerDto, SendTextDto, } from '../dto/sendMessage.dto'; import { WAMonitoringService } from '../services/monitor.service'; @@ -32,6 +33,13 @@ export class SendMessageController { throw new BadRequestException('Owned media must be a url or base64'); } + public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto) { + if (isURL(data.stickerMessage.image) || isBase64(data.stickerMessage.image)) { + return await this.waMonitor.waInstances[instanceName].mediaSticker(data); + } + throw new BadRequestException('Owned media must be a url or base64'); + } + public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto) { if (isURL(data.audioMessage.audio) || isBase64(data.audioMessage.audio)) { return await this.waMonitor.waInstances[instanceName].audioWhatsapp(data); diff --git a/src/whatsapp/dto/sendMessage.dto.ts b/src/whatsapp/dto/sendMessage.dto.ts index dd545250..273d8aae 100644 --- a/src/whatsapp/dto/sendMessage.dto.ts +++ b/src/whatsapp/dto/sendMessage.dto.ts @@ -62,6 +62,12 @@ export class MediaMessage { export class SendMediaDto extends Metadata { mediaMessage: MediaMessage; } +class Sticker { + image: string; +} +export class SendStickerDto extends Metadata { + stickerMessage: Sticker; +} class Audio { audio: string; diff --git a/src/whatsapp/routers/sendMessage.router.ts b/src/whatsapp/routers/sendMessage.router.ts index cd7a7dde..b2400f7f 100644 --- a/src/whatsapp/routers/sendMessage.router.ts +++ b/src/whatsapp/routers/sendMessage.router.ts @@ -9,6 +9,7 @@ import { mediaMessageSchema, pollMessageSchema, reactionMessageSchema, + stickerMessageSchema, textMessageSchema, } from '../../validate/validate.schema'; import { @@ -21,6 +22,7 @@ import { SendMediaDto, SendPollDto, SendReactionDto, + SendStickerDto, SendTextDto, } from '../dto/sendMessage.dto'; import { sendMessageController } from '../whatsapp.module'; @@ -131,6 +133,16 @@ export class MessageRouter extends RouterBroker { sendMessageController.sendLinkPreview(instance, data), }); + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendSticker'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: stickerMessageSchema, + ClassRef: SendStickerDto, + execute: (instance, data) => sendMessageController.sendSticker(instance, data), + }); + return res.status(HttpStatus.CREATED).json(response); }); } diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 437b7721..cb5d67e6 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -83,6 +83,7 @@ import { SendTextDto, SendPollDto, SendLinkPreviewDto, + SendStickerDto, } from '../dto/sendMessage.dto'; import { arrayUnique, isBase64, isURL } from 'class-validator'; import { @@ -118,6 +119,8 @@ import { WebhookRaw } from '../models/webhook.model'; import { dbserver } from '../../db/db.connect'; import NodeCache from 'node-cache'; import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db'; +import { promisify } from 'util'; +import sharp from 'sharp'; export class WAStartupService { constructor( @@ -783,7 +786,6 @@ export class WAStartupService { }, 'messages.update': async (args: WAMessageUpdate[], database: Database) => { - console.log('messages.update args: ', args); const status: Record = { 0: 'ERROR', 1: 'PENDING', @@ -1050,7 +1052,12 @@ export class WAStartupService { quoted, }; - if (!message['audio'] && !message['poll'] && !message['linkPreview']) { + if ( + !message['audio'] && + !message['poll'] && + !message['linkPreview'] && + !message['sticker'] + ) { if (!message['audio']) { return await this.client.sendMessage( sender, @@ -1195,6 +1202,41 @@ export class WAStartupService { } } + private async convertToWebP(image: string) { + try { + let imagePath: string; + const outputPath = `${join(process.cwd(), 'temp', 'sticker.webp')}`; + + if (isBase64(image)) { + const base64Data = image.replace(/^data:image\/(jpeg|png|gif);base64,/, ''); + const imageBuffer = Buffer.from(base64Data, 'base64'); + imagePath = `${join(process.cwd(), 'temp', 'temp-sticker.png')}`; + await sharp(imageBuffer).toFile(imagePath); + } else { + const response = await axios.get(image, { responseType: 'arraybuffer' }); + const imageBuffer = Buffer.from(response.data, 'binary'); + imagePath = `${join(process.cwd(), 'temp', 'temp-sticker.png')}`; + await sharp(imageBuffer).toFile(imagePath); + } + await sharp(imagePath).webp().toFile(outputPath); + + return outputPath; + } catch (error) { + console.error('Erro ao converter a imagem para WebP:', error); + } + } + + public async mediaSticker(data: SendStickerDto) { + const convert = await this.convertToWebP(data.stickerMessage.image); + return await this.sendMessageWithTyping( + data.number, + { + sticker: { url: convert }, + }, + data?.options, + ); + } + public async mediaMessage(data: SendMediaDto) { const generate = await this.prepareMediaMessage(data.mediaMessage); diff --git a/temp/sticker.webp b/temp/sticker.webp new file mode 100644 index 00000000..b04e82da Binary files /dev/null and b/temp/sticker.webp differ diff --git a/temp/temp-sticker.png b/temp/temp-sticker.png new file mode 100644 index 00000000..fde5f86f Binary files /dev/null and b/temp/temp-sticker.png differ