diff --git a/package.json b/package.json index 04167b86..e776edc4 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@sentry/node": "^8.28.0", "amqplib": "^0.10.3", "axios": "^1.6.5", - "baileys": "6.7.8", + "baileys": "github:EvolutionAPI/Baileys", "class-validator": "^0.14.1", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/src/api/controllers/call.controller.ts b/src/api/controllers/call.controller.ts new file mode 100644 index 00000000..abc29804 --- /dev/null +++ b/src/api/controllers/call.controller.ts @@ -0,0 +1,11 @@ +import { OfferCallDto } from '@api/dto/call.dto'; +import { InstanceDto } from '@api/dto/instance.dto'; +import { WAMonitoringService } from '@api/services/monitor.service'; + +export class CallController { + constructor(private readonly waMonitor: WAMonitoringService) {} + + public async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) { + return await this.waMonitor.waInstances[instanceName].offerCall(data); + } +} diff --git a/src/api/controllers/sendMessage.controller.ts b/src/api/controllers/sendMessage.controller.ts index 6f60ceb9..8f22685e 100644 --- a/src/api/controllers/sendMessage.controller.ts +++ b/src/api/controllers/sendMessage.controller.ts @@ -1,5 +1,6 @@ import { InstanceDto } from '@api/dto/instance.dto'; import { + OfferCallDto, SendAudioDto, SendButtonDto, SendContactDto, @@ -86,4 +87,8 @@ export class SendMessageController { public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto, file?: any) { return await this.waMonitor.waInstances[instanceName].statusMessage(data, file); } + + public async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) { + return await this.waMonitor.waInstances[instanceName].offerCall(data); + } } diff --git a/src/api/dto/call.dto.ts b/src/api/dto/call.dto.ts new file mode 100644 index 00000000..310b3779 --- /dev/null +++ b/src/api/dto/call.dto.ts @@ -0,0 +1,8 @@ +export class Metadata { + number: string; +} + +export class OfferCallDto extends Metadata { + isVideo?: boolean; + callDuration?: number; +} diff --git a/src/api/integrations/channel/evolution/evolution.channel.service.ts b/src/api/integrations/channel/evolution/evolution.channel.service.ts index 91e232a3..10b28a4d 100644 --- a/src/api/integrations/channel/evolution/evolution.channel.service.ts +++ b/src/api/integrations/channel/evolution/evolution.channel.service.ts @@ -548,6 +548,9 @@ export class EvolutionStartupService extends ChannelStartupService { public async fetchProfile() { throw new BadRequestException('Method not available on Evolution Channel'); } + public async offerCall() { + throw new BadRequestException('Method not available on WhatsApp Business API'); + } public async sendPresence() { throw new BadRequestException('Method not available on Evolution Channel'); } diff --git a/src/api/integrations/channel/meta/whatsapp.business.service.ts b/src/api/integrations/channel/meta/whatsapp.business.service.ts index 57cc5e4a..3e163975 100644 --- a/src/api/integrations/channel/meta/whatsapp.business.service.ts +++ b/src/api/integrations/channel/meta/whatsapp.business.service.ts @@ -1365,6 +1365,9 @@ export class BusinessStartupService extends ChannelStartupService { public async fetchProfile() { throw new BadRequestException('Method not available on WhatsApp Business API'); } + public async offerCall() { + throw new BadRequestException('Method not available on WhatsApp Business API'); + } public async sendPresence() { throw new BadRequestException('Method not available on WhatsApp Business API'); } diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 2e4c14ac..03a4cb67 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1,3 +1,4 @@ +import { OfferCallDto } from '@api/dto/call.dto'; import { ArchiveChatDto, BlockUserDto, @@ -1662,6 +1663,19 @@ export class BaileysStartupService extends ChannelStartupService { } } + public async offerCall({ number, isVideo, callDuration }: OfferCallDto) { + const jid = this.createJid(number); + + try { + const call = await this.client.offerCall(jid, isVideo); + setTimeout(() => this.client.terminateCall(call.id, call.to), callDuration * 1000); + + return call; + } catch (error) { + return error; + } + } + private async sendMessage( sender: string, message: any, diff --git a/src/api/routes/call.router.ts b/src/api/routes/call.router.ts new file mode 100644 index 00000000..f801bd68 --- /dev/null +++ b/src/api/routes/call.router.ts @@ -0,0 +1,25 @@ +import { RouterBroker } from '@api/abstract/abstract.router'; +import { OfferCallDto } from '@api/dto/call.dto'; +import { sendMessageController } from '@api/server.module'; +import { offerCallSchema } from '@validate/validate.schema'; +import { RequestHandler, Router } from 'express'; + +import { HttpStatus } from './index.router'; + +export class CallRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router.post(this.routerPath('offer'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: offerCallSchema, + ClassRef: OfferCallDto, + execute: (instance, data) => sendMessageController.offerCall(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }); + } + + public readonly router: Router = Router(); +} diff --git a/src/api/routes/index.router.ts b/src/api/routes/index.router.ts index 43401725..5587f7b6 100644 --- a/src/api/routes/index.router.ts +++ b/src/api/routes/index.router.ts @@ -11,6 +11,7 @@ import fs from 'fs'; import mime from 'mime'; import path from 'path'; +import { CallRouter } from './call.router'; import { ChatRouter } from './chat.router'; import { GroupRouter } from './group.router'; import { InstanceRouter } from './instance.router'; @@ -79,6 +80,7 @@ router }) .use('/instance', new InstanceRouter(configService, ...guards).router) .use('/message', new MessageRouter(...guards).router) + .use('/call', new CallRouter(...guards).router) .use('/chat', new ChatRouter(...guards).router) .use('/group', new GroupRouter(...guards).router) .use('/template', new TemplateRouter(configService, ...guards).router) diff --git a/src/validate/message.schema.ts b/src/validate/message.schema.ts index 6ec1ab04..4e8651cc 100644 --- a/src/validate/message.schema.ts +++ b/src/validate/message.schema.ts @@ -54,6 +54,17 @@ const quotedOptionsSchema: JSONSchema7 = { }, }; +export const offerCallSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + isVideo: { type: 'boolean', enum: [true, false] }, + callDuration: { type: 'integer', minimum: 1, maximum: 15 }, + }, + required: ['number', 'callDuration'], +}; + export const textMessageSchema: JSONSchema7 = { $id: v4(), type: 'object',