diff --git a/.vscode/settings.json b/.vscode/settings.json index 20b82443..71db0b08 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,8 +5,8 @@ "editor.smoothScrolling": true, "editor.tabSize": 2, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true, - "source.fixAll": true + "source.fixAll.eslint": "explicit", + "source.fixAll": "explicit" }, "prisma-smart-formatter.typescript.defaultFormatter": "esbenp.prettier-vscode", "prisma-smart-formatter.prisma.defaultFormatter": "Prisma.prisma" diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 9781e18c..4fad3269 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -149,6 +149,16 @@ export const textMessageSchema: JSONSchema7 = { required: ['textMessage', 'number'], }; +export const presenceSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema, required: ['presence', 'delay'] }, + }, + required: ['options', 'number'], +}; + export const pollMessageSchema: JSONSchema7 = { $id: v4(), type: 'object', diff --git a/src/whatsapp/controllers/chat.controller.ts b/src/whatsapp/controllers/chat.controller.ts index 0299841c..60a9c618 100644 --- a/src/whatsapp/controllers/chat.controller.ts +++ b/src/whatsapp/controllers/chat.controller.ts @@ -9,6 +9,7 @@ import { ProfilePictureDto, ProfileStatusDto, ReadMessageDto, + SendPresenceDto, WhatsAppNumberDto, } from '../dto/chat.dto'; import { InstanceDto } from '../dto/instance.dto'; @@ -77,6 +78,11 @@ export class ChatController { return await this.waMonitor.waInstances[instanceName].fetchChats(); } + public async sendPresence({ instanceName }: InstanceDto, data: SendPresenceDto) { + logger.verbose('requested sendPresence from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].sendPresence(data); + } + public async fetchPrivacySettings({ instanceName }: InstanceDto) { logger.verbose('requested fetchPrivacySettings from ' + instanceName + ' instance'); return await this.waMonitor.waInstances[instanceName].fetchPrivacySettings(); diff --git a/src/whatsapp/dto/chat.dto.ts b/src/whatsapp/dto/chat.dto.ts index f8a5da5f..07553c90 100644 --- a/src/whatsapp/dto/chat.dto.ts +++ b/src/whatsapp/dto/chat.dto.ts @@ -1,4 +1,4 @@ -import { proto, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; +import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; export class OnWhatsAppDto { constructor(public readonly jid: string, public readonly exists: boolean, public readonly name?: string) {} @@ -83,3 +83,20 @@ export class DeleteMessage { remoteJid: string; participant?: string; } +export class Options { + delay?: number; + presence?: WAPresence; +} +class OptionsMessage { + options: Options; +} +export class Metadata extends OptionsMessage { + number: string; +} + +export class SendPresenceDto extends Metadata { + options: { + presence: WAPresence; + delay: number; + }; +} diff --git a/src/whatsapp/dto/sendMessage.dto.ts b/src/whatsapp/dto/sendMessage.dto.ts index e0d6d8f9..bfa5763f 100644 --- a/src/whatsapp/dto/sendMessage.dto.ts +++ b/src/whatsapp/dto/sendMessage.dto.ts @@ -46,9 +46,13 @@ class PollMessage { values: string[]; messageSecret?: Uint8Array; } + export class SendTextDto extends Metadata { textMessage: TextMessage; } +export class SendPresence extends Metadata { + textMessage: TextMessage; +} export class SendStatusDto extends Metadata { statusMessage: StatusMessage; diff --git a/src/whatsapp/routers/chat.router.ts b/src/whatsapp/routers/chat.router.ts index 285c29a0..29d1cdc3 100644 --- a/src/whatsapp/routers/chat.router.ts +++ b/src/whatsapp/routers/chat.router.ts @@ -7,6 +7,7 @@ import { deleteMessageSchema, messageUpSchema, messageValidateSchema, + presenceSchema, privacySettingsSchema, profileNameSchema, profilePictureSchema, @@ -26,6 +27,7 @@ import { ProfilePictureDto, ProfileStatusDto, ReadMessageDto, + SendPresenceDto, WhatsAppNumberDto, } from '../dto/chat.dto'; import { InstanceDto } from '../dto/instance.dto'; @@ -228,6 +230,22 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) + .post(this.routerPath('sendPresence'), ...guards, async (req, res) => { + logger.verbose('request received in sendPresence'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: presenceSchema, + ClassRef: SendPresenceDto, + execute: (instance, data) => chatController.sendPresence(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) // Profile routes .get(this.routerPath('fetchPrivacySettings'), ...guards, async (req, res) => { logger.verbose('request received in fetchPrivacySettings'); diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 99b4ab32..27137d7e 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -84,6 +84,7 @@ import { OnWhatsAppDto, PrivacySettingDto, ReadMessageDto, + SendPresenceDto, WhatsAppNumberDto, } from '../dto/chat.dto'; import { @@ -2409,6 +2410,38 @@ export class WAStartupService { return this.stateConnection; } + public async sendPresence(data: SendPresenceDto) { + try { + const { number } = data; + + this.logger.verbose(`Check if number "${number}" is WhatsApp`); + const isWA = (await this.whatsappNumber({ numbers: [number] }))?.shift(); + + this.logger.verbose(`Exists: "${isWA.exists}" | jid: ${isWA.jid}`); + if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) { + throw new BadRequestException(isWA); + } + + const sender = isWA.jid; + + this.logger.verbose('Sending presence'); + await this.client.presenceSubscribe(sender); + this.logger.verbose('Subscribing to presence'); + + await this.client.sendPresenceUpdate(data.options?.presence ?? 'composing', sender); + this.logger.verbose('Sending presence update: ' + data.options?.presence ?? 'composing'); + + await delay(data.options.delay); + this.logger.verbose('Set delay: ' + data.options.delay); + + await this.client.sendPresenceUpdate('paused', sender); + this.logger.verbose('Sending presence update: paused'); + } catch (error) { + this.logger.error(error); + throw new BadRequestException(error.toString()); + } + } + // Send Message Controller public async textMessage(data: SendTextDto, isChatwoot = false) { this.logger.verbose('Sending text message');