diff --git a/src/api/controllers/group.controller.ts b/src/api/controllers/group.controller.ts index ebe7c03671..f2e9a2eb8e 100644 --- a/src/api/controllers/group.controller.ts +++ b/src/api/controllers/group.controller.ts @@ -5,11 +5,14 @@ import { GroupDescriptionDto, GroupInvite, GroupJid, + GroupJoinApprovalModeDto, + GroupMemberAddModeDto, GroupPictureDto, GroupSendInvite, GroupSubjectDto, GroupToggleEphemeralDto, GroupUpdateParticipantDto, + GroupUpdateParticipantRequestDto, GroupUpdateSettingDto, } from '@api/dto/group.dto'; import { InstanceDto } from '@api/dto/instance.dto'; @@ -78,6 +81,22 @@ export class GroupController { return await this.waMonitor.waInstances[instance.instanceName].toggleEphemeral(update); } + public async updateMemberAddMode(instance: InstanceDto, update: GroupMemberAddModeDto) { + return await this.waMonitor.waInstances[instance.instanceName].updateMemberAddMode(update); + } + + public async updateJoinApprovalMode(instance: InstanceDto, update: GroupJoinApprovalModeDto) { + return await this.waMonitor.waInstances[instance.instanceName].updateJoinApprovalMode(update); + } + + public async findParticipantRequests(instance: InstanceDto, groupJid: GroupJid) { + return await this.waMonitor.waInstances[instance.instanceName].findParticipantRequests(groupJid); + } + + public async updateParticipantRequests(instance: InstanceDto, update: GroupUpdateParticipantRequestDto) { + return await this.waMonitor.waInstances[instance.instanceName].updateParticipantRequests(update); + } + public async leaveGroup(instance: InstanceDto, groupJid: GroupJid) { return await this.waMonitor.waInstances[instance.instanceName].leaveGroup(groupJid); } diff --git a/src/api/controllers/sendMessage.controller.ts b/src/api/controllers/sendMessage.controller.ts index 64aa1c8468..68c55b0a73 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 { + ForwardMessageDto, SendAudioDto, SendButtonsDto, SendContactDto, @@ -39,6 +40,10 @@ export class SendMessageController { return await this.waMonitor.waInstances[instanceName].textMessage(data); } + public async forwardMessage({ instanceName }: InstanceDto, data: ForwardMessageDto) { + return await this.waMonitor.waInstances[instanceName].forwardMessage(data); + } + public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto, file?: any) { if (isBase64(data?.media) && !data?.fileName && data?.mediatype === 'document') { throw new BadRequestException('For base64 the file name must be informed.'); diff --git a/src/api/dto/group.dto.ts b/src/api/dto/group.dto.ts index 293329d2ed..b9975d3f12 100644 --- a/src/api/dto/group.dto.ts +++ b/src/api/dto/group.dto.ts @@ -54,3 +54,16 @@ export class GroupUpdateSettingDto extends GroupJid { export class GroupToggleEphemeralDto extends GroupJid { expiration: 0 | 86400 | 604800 | 7776000; } + +export class GroupMemberAddModeDto extends GroupJid { + mode: 'admin_add' | 'all_member_add'; +} + +export class GroupJoinApprovalModeDto extends GroupJid { + mode: 'on' | 'off'; +} + +export class GroupUpdateParticipantRequestDto extends GroupJid { + action: 'approve' | 'reject'; + participants: string[]; +} diff --git a/src/api/dto/sendMessage.dto.ts b/src/api/dto/sendMessage.dto.ts index ba9ecf527c..1ba725dfbb 100644 --- a/src/api/dto/sendMessage.dto.ts +++ b/src/api/dto/sendMessage.dto.ts @@ -50,6 +50,10 @@ export class Metadata { export class SendTextDto extends Metadata { text: string; } +export class ForwardMessageDto { + number: string; + messageId: string; +} export class SendPresence extends Metadata { text: string; } diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 60e857fcc1..e717544420 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -22,11 +22,14 @@ import { GroupDescriptionDto, GroupInvite, GroupJid, + GroupJoinApprovalModeDto, + GroupMemberAddModeDto, GroupPictureDto, GroupSendInvite, GroupSubjectDto, GroupToggleEphemeralDto, GroupUpdateParticipantDto, + GroupUpdateParticipantRequestDto, GroupUpdateSettingDto, } from '@api/dto/group.dto'; import { InstanceDto, SetPresenceDto } from '@api/dto/instance.dto'; @@ -34,6 +37,7 @@ import { HandleLabelDto, LabelDto } from '@api/dto/label.dto'; import { Button, ContactMessage, + ForwardMessageDto, KeyType, MediaMessage, Options, @@ -553,6 +557,20 @@ export class BaileysStartupService extends ChannelStartupService { } } + public async forwardMessage(data: ForwardMessageDto) { + try { + const fullMsg = (await this.getMessage({ id: data.messageId }, true)) as unknown as WAMessage; + if (!fullMsg?.message) { + throw new BadRequestException('Message not found'); + } + const number = data.number.replace(/\D/g, ''); + const jid = data.number.includes('@') ? data.number : `${number}@s.whatsapp.net`; + return await this.client.sendMessage(jid, { forward: fullMsg }); + } catch (error) { + throw new BadRequestException('Error forwarding message', error.toString()); + } + } + private async defineAuthState() { const db = this.configService.get('DATABASE'); const cache = this.configService.get('CACHE'); @@ -4597,6 +4615,43 @@ export class BaileysStartupService extends ChannelStartupService { } } + public async updateMemberAddMode(update: GroupMemberAddModeDto) { + try { + await this.client.groupMemberAddMode(update.groupJid, update.mode); + return { success: true }; + } catch (error) { + throw new BadRequestException('Error updating member add mode', error.toString()); + } + } + + public async updateJoinApprovalMode(update: GroupJoinApprovalModeDto) { + try { + await this.client.groupJoinApprovalMode(update.groupJid, update.mode); + return { success: true }; + } catch (error) { + throw new BadRequestException('Error updating join approval mode', error.toString()); + } + } + + public async findParticipantRequests(id: GroupJid) { + try { + const requests = await this.client.groupRequestParticipantsList(id.groupJid); + return { requests: requests || [] }; + } catch (error) { + throw new BadRequestException('Error fetching participant requests', error.toString()); + } + } + + public async updateParticipantRequests(update: GroupUpdateParticipantRequestDto) { + try { + const participants = update.participants.map((p) => (p.includes('@') ? p : `${p}@s.whatsapp.net`)); + const result = await this.client.groupRequestParticipantsUpdate(update.groupJid, participants, update.action); + return { updateParticipantRequests: result }; + } catch (error) { + throw new BadRequestException('Error updating participant requests', error.toString()); + } + } + public async leaveGroup(id: GroupJid) { try { await this.client.groupLeave(id.groupJid); diff --git a/src/api/routes/group.router.ts b/src/api/routes/group.router.ts index 7086b11769..c16d951d18 100644 --- a/src/api/routes/group.router.ts +++ b/src/api/routes/group.router.ts @@ -6,11 +6,14 @@ import { GroupDescriptionDto, GroupInvite, GroupJid, + GroupJoinApprovalModeDto, + GroupMemberAddModeDto, GroupPictureDto, GroupSendInvite, GroupSubjectDto, GroupToggleEphemeralDto, GroupUpdateParticipantDto, + GroupUpdateParticipantRequestDto, GroupUpdateSettingDto, } from '@api/dto/group.dto'; import { groupController } from '@api/server.module'; @@ -21,10 +24,13 @@ import { groupInviteSchema, groupJidSchema, groupSendInviteSchema, + joinApprovalModeSchema, + memberAddModeSchema, toggleEphemeralSchema, updateGroupDescriptionSchema, updateGroupPictureSchema, updateGroupSubjectSchema, + updateParticipantRequestSchema, updateParticipantsSchema, updateSettingsSchema, } from '@validate/validate.schema'; @@ -186,6 +192,46 @@ export class GroupRouter extends RouterBroker { res.status(HttpStatus.CREATED).json(response); }) + .post(this.routerPath('memberAddMode'), ...guards, async (req, res) => { + const response = await this.groupValidate({ + request: req, + schema: memberAddModeSchema, + ClassRef: GroupMemberAddModeDto, + execute: (instance, data) => groupController.updateMemberAddMode(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('joinApprovalMode'), ...guards, async (req, res) => { + const response = await this.groupValidate({ + request: req, + schema: joinApprovalModeSchema, + ClassRef: GroupJoinApprovalModeDto, + execute: (instance, data) => groupController.updateJoinApprovalMode(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('participantRequests'), ...guards, async (req, res) => { + const response = await this.groupValidate({ + request: req, + schema: groupJidSchema, + ClassRef: GroupJid, + execute: (instance, data) => groupController.findParticipantRequests(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('updateParticipantRequests'), ...guards, async (req, res) => { + const response = await this.groupValidate({ + request: req, + schema: updateParticipantRequestSchema, + ClassRef: GroupUpdateParticipantRequestDto, + execute: (instance, data) => groupController.updateParticipantRequests(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) .delete(this.routerPath('leaveGroup'), ...guards, async (req, res) => { const response = await this.groupValidate({ request: req, diff --git a/src/api/routes/sendMessage.router.ts b/src/api/routes/sendMessage.router.ts index cd073dba3d..dee71cbf08 100644 --- a/src/api/routes/sendMessage.router.ts +++ b/src/api/routes/sendMessage.router.ts @@ -1,5 +1,6 @@ import { RouterBroker } from '@api/abstract/abstract.router'; import { + ForwardMessageDto, SendAudioDto, SendButtonsDto, SendContactDto, @@ -19,6 +20,7 @@ import { audioMessageSchema, buttonsMessageSchema, contactMessageSchema, + forwardMessageSchema, listMessageSchema, locationMessageSchema, mediaMessageSchema, @@ -61,6 +63,16 @@ export class MessageRouter extends RouterBroker { return res.status(HttpStatus.CREATED).json(response); }) + .post(this.routerPath('forwardMessage'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: forwardMessageSchema, + ClassRef: ForwardMessageDto, + execute: (instance, data) => sendMessageController.forwardMessage(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) .post(this.routerPath('sendMedia'), ...guards, upload.single('file'), async (req, res) => { const bodyData = req.body; diff --git a/src/validate/group.schema.ts b/src/validate/group.schema.ts index 8da188d87f..75db868953 100644 --- a/src/validate/group.schema.ts +++ b/src/validate/group.schema.ts @@ -159,6 +159,45 @@ export const toggleEphemeralSchema: JSONSchema7 = { ...isNotEmpty('groupJid', 'expiration'), }; +export const memberAddModeSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + mode: { type: 'string', enum: ['admin_add', 'all_member_add'] }, + }, + required: ['groupJid', 'mode'], + ...isNotEmpty('groupJid', 'mode'), +}; + +export const joinApprovalModeSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + mode: { type: 'string', enum: ['on', 'off'] }, + }, + required: ['groupJid', 'mode'], + ...isNotEmpty('groupJid', 'mode'), +}; + +export const updateParticipantRequestSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + action: { type: 'string', enum: ['approve', 'reject'] }, + participants: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { type: 'string' }, + }, + }, + required: ['groupJid', 'action', 'participants'], + ...isNotEmpty('groupJid', 'action'), +}; + export const updateGroupPictureSchema: JSONSchema7 = { $id: v4(), type: 'object', diff --git a/src/validate/message.schema.ts b/src/validate/message.schema.ts index d514c6199e..fcb138e4d0 100644 --- a/src/validate/message.schema.ts +++ b/src/validate/message.schema.ts @@ -65,6 +65,17 @@ export const offerCallSchema: JSONSchema7 = { required: ['number', 'callDuration'], }; +export const forwardMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + messageId: { type: 'string' }, + }, + required: ['number', 'messageId'], + ...isNotEmpty('number', 'messageId'), +}; + export const textMessageSchema: JSONSchema7 = { $id: v4(), type: 'object',