Skip to content

Commit a08bbab

Browse files
committed
feat: route to send Sticker
1 parent 84f6394 commit a08bbab

File tree

9 files changed

+90
-2
lines changed

9 files changed

+90
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* Now the api key can be exposed in fetch instances if the EXPOSE_IN_FETCH_INSTANCES variable is set to true
1515
* Added option to generate qrcode as soon as the instance is created
1616
* The created instance token can now also be optionally defined manually in the creation endpoint
17+
* Route to send Sticker
1718

1819
### Fixed
1920

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"qrcode": "^1.5.1",
6868
"qrcode-terminal": "^0.12.0",
6969
"redis": "^4.6.5",
70+
"sharp": "^0.30.7",
7071
"uuid": "^9.0.0"
7172
},
7273
"devDependencies": {

src/validate/validate.schema.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,24 @@ export const mediaMessageSchema: JSONSchema7 = {
210210
required: ['mediaMessage', 'number'],
211211
};
212212

213+
export const stickerMessageSchema: JSONSchema7 = {
214+
$id: v4(),
215+
type: 'object',
216+
properties: {
217+
number: { ...numberDefinition },
218+
options: { ...optionsSchema },
219+
stickerMessage: {
220+
type: 'object',
221+
properties: {
222+
image: { type: 'string' },
223+
},
224+
required: ['image'],
225+
...isNotEmpty('image'),
226+
},
227+
},
228+
required: ['stickerMessage', 'number'],
229+
};
230+
213231
export const audioMessageSchema: JSONSchema7 = {
214232
$id: v4(),
215233
type: 'object',

src/whatsapp/controllers/sendMessage.controller.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
SendMediaDto,
1212
SendPollDto,
1313
SendReactionDto,
14+
SendStickerDto,
1415
SendTextDto,
1516
} from '../dto/sendMessage.dto';
1617
import { WAMonitoringService } from '../services/monitor.service';
@@ -32,6 +33,13 @@ export class SendMessageController {
3233
throw new BadRequestException('Owned media must be a url or base64');
3334
}
3435

36+
public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto) {
37+
if (isURL(data.stickerMessage.image) || isBase64(data.stickerMessage.image)) {
38+
return await this.waMonitor.waInstances[instanceName].mediaSticker(data);
39+
}
40+
throw new BadRequestException('Owned media must be a url or base64');
41+
}
42+
3543
public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto) {
3644
if (isURL(data.audioMessage.audio) || isBase64(data.audioMessage.audio)) {
3745
return await this.waMonitor.waInstances[instanceName].audioWhatsapp(data);

src/whatsapp/dto/sendMessage.dto.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ export class MediaMessage {
6262
export class SendMediaDto extends Metadata {
6363
mediaMessage: MediaMessage;
6464
}
65+
class Sticker {
66+
image: string;
67+
}
68+
export class SendStickerDto extends Metadata {
69+
stickerMessage: Sticker;
70+
}
6571

6672
class Audio {
6773
audio: string;

src/whatsapp/routers/sendMessage.router.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
mediaMessageSchema,
1010
pollMessageSchema,
1111
reactionMessageSchema,
12+
stickerMessageSchema,
1213
textMessageSchema,
1314
} from '../../validate/validate.schema';
1415
import {
@@ -21,6 +22,7 @@ import {
2122
SendMediaDto,
2223
SendPollDto,
2324
SendReactionDto,
25+
SendStickerDto,
2426
SendTextDto,
2527
} from '../dto/sendMessage.dto';
2628
import { sendMessageController } from '../whatsapp.module';
@@ -131,6 +133,16 @@ export class MessageRouter extends RouterBroker {
131133
sendMessageController.sendLinkPreview(instance, data),
132134
});
133135

136+
return res.status(HttpStatus.CREATED).json(response);
137+
})
138+
.post(this.routerPath('sendSticker'), ...guards, async (req, res) => {
139+
const response = await this.dataValidate<SendStickerDto>({
140+
request: req,
141+
schema: stickerMessageSchema,
142+
ClassRef: SendStickerDto,
143+
execute: (instance, data) => sendMessageController.sendSticker(instance, data),
144+
});
145+
134146
return res.status(HttpStatus.CREATED).json(response);
135147
});
136148
}

src/whatsapp/services/whatsapp.service.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ import {
8383
SendTextDto,
8484
SendPollDto,
8585
SendLinkPreviewDto,
86+
SendStickerDto,
8687
} from '../dto/sendMessage.dto';
8788
import { arrayUnique, isBase64, isURL } from 'class-validator';
8889
import {
@@ -118,6 +119,8 @@ import { WebhookRaw } from '../models/webhook.model';
118119
import { dbserver } from '../../db/db.connect';
119120
import NodeCache from 'node-cache';
120121
import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db';
122+
import { promisify } from 'util';
123+
import sharp from 'sharp';
121124

122125
export class WAStartupService {
123126
constructor(
@@ -783,7 +786,6 @@ export class WAStartupService {
783786
},
784787

785788
'messages.update': async (args: WAMessageUpdate[], database: Database) => {
786-
console.log('messages.update args: ', args);
787789
const status: Record<number, wa.StatusMessage> = {
788790
0: 'ERROR',
789791
1: 'PENDING',
@@ -1050,7 +1052,12 @@ export class WAStartupService {
10501052
quoted,
10511053
};
10521054

1053-
if (!message['audio'] && !message['poll'] && !message['linkPreview']) {
1055+
if (
1056+
!message['audio'] &&
1057+
!message['poll'] &&
1058+
!message['linkPreview'] &&
1059+
!message['sticker']
1060+
) {
10541061
if (!message['audio']) {
10551062
return await this.client.sendMessage(
10561063
sender,
@@ -1195,6 +1202,41 @@ export class WAStartupService {
11951202
}
11961203
}
11971204

1205+
private async convertToWebP(image: string) {
1206+
try {
1207+
let imagePath: string;
1208+
const outputPath = `${join(process.cwd(), 'temp', 'sticker.webp')}`;
1209+
1210+
if (isBase64(image)) {
1211+
const base64Data = image.replace(/^data:image\/(jpeg|png|gif);base64,/, '');
1212+
const imageBuffer = Buffer.from(base64Data, 'base64');
1213+
imagePath = `${join(process.cwd(), 'temp', 'temp-sticker.png')}`;
1214+
await sharp(imageBuffer).toFile(imagePath);
1215+
} else {
1216+
const response = await axios.get(image, { responseType: 'arraybuffer' });
1217+
const imageBuffer = Buffer.from(response.data, 'binary');
1218+
imagePath = `${join(process.cwd(), 'temp', 'temp-sticker.png')}`;
1219+
await sharp(imageBuffer).toFile(imagePath);
1220+
}
1221+
await sharp(imagePath).webp().toFile(outputPath);
1222+
1223+
return outputPath;
1224+
} catch (error) {
1225+
console.error('Erro ao converter a imagem para WebP:', error);
1226+
}
1227+
}
1228+
1229+
public async mediaSticker(data: SendStickerDto) {
1230+
const convert = await this.convertToWebP(data.stickerMessage.image);
1231+
return await this.sendMessageWithTyping(
1232+
data.number,
1233+
{
1234+
sticker: { url: convert },
1235+
},
1236+
data?.options,
1237+
);
1238+
}
1239+
11981240
public async mediaMessage(data: SendMediaDto) {
11991241
const generate = await this.prepareMediaMessage(data.mediaMessage);
12001242

temp/sticker.webp

8.42 KB
Loading

temp/temp-sticker.png

6.38 KB
Loading

0 commit comments

Comments
 (0)