Skip to content

Commit 5ebc66d

Browse files
feat(group): member-add-mode, join-approval-mode, participant requests + message forward
Exposes Baileys group/message capabilities that already exist in the underlying library but had no REST endpoints: - POST /group/memberAddMode -> groupMemberAddMode (admin_add | all_member_add) - POST /group/joinApprovalMode -> groupJoinApprovalMode (on | off) - GET /group/participantRequests -> groupRequestParticipantsList (pending join requests) - POST /group/updateParticipantRequests -> groupRequestParticipantsUpdate (approve | reject) - POST /message/forwardMessage -> sendMessage(jid, { forward }) (keeps isForwarded) Each follows the existing pattern (DTO + JSONSchema + router + controller + baileys service). No new dependencies — baileys 7.0.0-rc.9 already ships these. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent fa09d37 commit 5ebc66d

9 files changed

Lines changed: 204 additions & 0 deletions

File tree

src/api/controllers/group.controller.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import {
55
GroupDescriptionDto,
66
GroupInvite,
77
GroupJid,
8+
GroupJoinApprovalModeDto,
9+
GroupMemberAddModeDto,
810
GroupPictureDto,
911
GroupSendInvite,
1012
GroupSubjectDto,
1113
GroupToggleEphemeralDto,
1214
GroupUpdateParticipantDto,
15+
GroupUpdateParticipantRequestDto,
1316
GroupUpdateSettingDto,
1417
} from '@api/dto/group.dto';
1518
import { InstanceDto } from '@api/dto/instance.dto';
@@ -78,6 +81,22 @@ export class GroupController {
7881
return await this.waMonitor.waInstances[instance.instanceName].toggleEphemeral(update);
7982
}
8083

84+
public async updateMemberAddMode(instance: InstanceDto, update: GroupMemberAddModeDto) {
85+
return await this.waMonitor.waInstances[instance.instanceName].updateMemberAddMode(update);
86+
}
87+
88+
public async updateJoinApprovalMode(instance: InstanceDto, update: GroupJoinApprovalModeDto) {
89+
return await this.waMonitor.waInstances[instance.instanceName].updateJoinApprovalMode(update);
90+
}
91+
92+
public async findParticipantRequests(instance: InstanceDto, groupJid: GroupJid) {
93+
return await this.waMonitor.waInstances[instance.instanceName].findParticipantRequests(groupJid);
94+
}
95+
96+
public async updateParticipantRequests(instance: InstanceDto, update: GroupUpdateParticipantRequestDto) {
97+
return await this.waMonitor.waInstances[instance.instanceName].updateParticipantRequests(update);
98+
}
99+
81100
public async leaveGroup(instance: InstanceDto, groupJid: GroupJid) {
82101
return await this.waMonitor.waInstances[instance.instanceName].leaveGroup(groupJid);
83102
}

src/api/controllers/sendMessage.controller.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { InstanceDto } from '@api/dto/instance.dto';
22
import {
3+
ForwardMessageDto,
34
SendAudioDto,
45
SendButtonsDto,
56
SendContactDto,
@@ -39,6 +40,10 @@ export class SendMessageController {
3940
return await this.waMonitor.waInstances[instanceName].textMessage(data);
4041
}
4142

43+
public async forwardMessage({ instanceName }: InstanceDto, data: ForwardMessageDto) {
44+
return await this.waMonitor.waInstances[instanceName].forwardMessage(data);
45+
}
46+
4247
public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto, file?: any) {
4348
if (isBase64(data?.media) && !data?.fileName && data?.mediatype === 'document') {
4449
throw new BadRequestException('For base64 the file name must be informed.');

src/api/dto/group.dto.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,16 @@ export class GroupUpdateSettingDto extends GroupJid {
5454
export class GroupToggleEphemeralDto extends GroupJid {
5555
expiration: 0 | 86400 | 604800 | 7776000;
5656
}
57+
58+
export class GroupMemberAddModeDto extends GroupJid {
59+
mode: 'admin_add' | 'all_member_add';
60+
}
61+
62+
export class GroupJoinApprovalModeDto extends GroupJid {
63+
mode: 'on' | 'off';
64+
}
65+
66+
export class GroupUpdateParticipantRequestDto extends GroupJid {
67+
action: 'approve' | 'reject';
68+
participants: string[];
69+
}

src/api/dto/sendMessage.dto.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ export class Metadata {
5050
export class SendTextDto extends Metadata {
5151
text: string;
5252
}
53+
export class ForwardMessageDto {
54+
number: string;
55+
messageId: string;
56+
}
5357
export class SendPresence extends Metadata {
5458
text: string;
5559
}

src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,22 @@ import {
2222
GroupDescriptionDto,
2323
GroupInvite,
2424
GroupJid,
25+
GroupJoinApprovalModeDto,
26+
GroupMemberAddModeDto,
2527
GroupPictureDto,
2628
GroupSendInvite,
2729
GroupSubjectDto,
2830
GroupToggleEphemeralDto,
2931
GroupUpdateParticipantDto,
32+
GroupUpdateParticipantRequestDto,
3033
GroupUpdateSettingDto,
3134
} from '@api/dto/group.dto';
3235
import { InstanceDto, SetPresenceDto } from '@api/dto/instance.dto';
3336
import { HandleLabelDto, LabelDto } from '@api/dto/label.dto';
3437
import {
3538
Button,
3639
ContactMessage,
40+
ForwardMessageDto,
3741
KeyType,
3842
MediaMessage,
3943
Options,
@@ -553,6 +557,20 @@ export class BaileysStartupService extends ChannelStartupService {
553557
}
554558
}
555559

560+
public async forwardMessage(data: ForwardMessageDto) {
561+
try {
562+
const fullMsg = (await this.getMessage({ id: data.messageId }, true)) as unknown as WAMessage;
563+
if (!fullMsg?.message) {
564+
throw new BadRequestException('Message not found');
565+
}
566+
const number = data.number.replace(/\D/g, '');
567+
const jid = data.number.includes('@') ? data.number : `${number}@s.whatsapp.net`;
568+
return await this.client.sendMessage(jid, { forward: fullMsg });
569+
} catch (error) {
570+
throw new BadRequestException('Error forwarding message', error.toString());
571+
}
572+
}
573+
556574
private async defineAuthState() {
557575
const db = this.configService.get<Database>('DATABASE');
558576
const cache = this.configService.get<CacheConf>('CACHE');
@@ -4597,6 +4615,43 @@ export class BaileysStartupService extends ChannelStartupService {
45974615
}
45984616
}
45994617

4618+
public async updateMemberAddMode(update: GroupMemberAddModeDto) {
4619+
try {
4620+
await this.client.groupMemberAddMode(update.groupJid, update.mode);
4621+
return { success: true };
4622+
} catch (error) {
4623+
throw new BadRequestException('Error updating member add mode', error.toString());
4624+
}
4625+
}
4626+
4627+
public async updateJoinApprovalMode(update: GroupJoinApprovalModeDto) {
4628+
try {
4629+
await this.client.groupJoinApprovalMode(update.groupJid, update.mode);
4630+
return { success: true };
4631+
} catch (error) {
4632+
throw new BadRequestException('Error updating join approval mode', error.toString());
4633+
}
4634+
}
4635+
4636+
public async findParticipantRequests(id: GroupJid) {
4637+
try {
4638+
const requests = await this.client.groupRequestParticipantsList(id.groupJid);
4639+
return { requests: requests || [] };
4640+
} catch (error) {
4641+
throw new BadRequestException('Error fetching participant requests', error.toString());
4642+
}
4643+
}
4644+
4645+
public async updateParticipantRequests(update: GroupUpdateParticipantRequestDto) {
4646+
try {
4647+
const participants = update.participants.map((p) => (p.includes('@') ? p : `${p}@s.whatsapp.net`));
4648+
const result = await this.client.groupRequestParticipantsUpdate(update.groupJid, participants, update.action);
4649+
return { updateParticipantRequests: result };
4650+
} catch (error) {
4651+
throw new BadRequestException('Error updating participant requests', error.toString());
4652+
}
4653+
}
4654+
46004655
public async leaveGroup(id: GroupJid) {
46014656
try {
46024657
await this.client.groupLeave(id.groupJid);

src/api/routes/group.router.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import {
66
GroupDescriptionDto,
77
GroupInvite,
88
GroupJid,
9+
GroupJoinApprovalModeDto,
10+
GroupMemberAddModeDto,
911
GroupPictureDto,
1012
GroupSendInvite,
1113
GroupSubjectDto,
1214
GroupToggleEphemeralDto,
1315
GroupUpdateParticipantDto,
16+
GroupUpdateParticipantRequestDto,
1417
GroupUpdateSettingDto,
1518
} from '@api/dto/group.dto';
1619
import { groupController } from '@api/server.module';
@@ -21,10 +24,13 @@ import {
2124
groupInviteSchema,
2225
groupJidSchema,
2326
groupSendInviteSchema,
27+
joinApprovalModeSchema,
28+
memberAddModeSchema,
2429
toggleEphemeralSchema,
2530
updateGroupDescriptionSchema,
2631
updateGroupPictureSchema,
2732
updateGroupSubjectSchema,
33+
updateParticipantRequestSchema,
2834
updateParticipantsSchema,
2935
updateSettingsSchema,
3036
} from '@validate/validate.schema';
@@ -186,6 +192,46 @@ export class GroupRouter extends RouterBroker {
186192

187193
res.status(HttpStatus.CREATED).json(response);
188194
})
195+
.post(this.routerPath('memberAddMode'), ...guards, async (req, res) => {
196+
const response = await this.groupValidate<GroupMemberAddModeDto>({
197+
request: req,
198+
schema: memberAddModeSchema,
199+
ClassRef: GroupMemberAddModeDto,
200+
execute: (instance, data) => groupController.updateMemberAddMode(instance, data),
201+
});
202+
203+
res.status(HttpStatus.CREATED).json(response);
204+
})
205+
.post(this.routerPath('joinApprovalMode'), ...guards, async (req, res) => {
206+
const response = await this.groupValidate<GroupJoinApprovalModeDto>({
207+
request: req,
208+
schema: joinApprovalModeSchema,
209+
ClassRef: GroupJoinApprovalModeDto,
210+
execute: (instance, data) => groupController.updateJoinApprovalMode(instance, data),
211+
});
212+
213+
res.status(HttpStatus.CREATED).json(response);
214+
})
215+
.get(this.routerPath('participantRequests'), ...guards, async (req, res) => {
216+
const response = await this.groupValidate<GroupJid>({
217+
request: req,
218+
schema: groupJidSchema,
219+
ClassRef: GroupJid,
220+
execute: (instance, data) => groupController.findParticipantRequests(instance, data),
221+
});
222+
223+
res.status(HttpStatus.OK).json(response);
224+
})
225+
.post(this.routerPath('updateParticipantRequests'), ...guards, async (req, res) => {
226+
const response = await this.groupValidate<GroupUpdateParticipantRequestDto>({
227+
request: req,
228+
schema: updateParticipantRequestSchema,
229+
ClassRef: GroupUpdateParticipantRequestDto,
230+
execute: (instance, data) => groupController.updateParticipantRequests(instance, data),
231+
});
232+
233+
res.status(HttpStatus.CREATED).json(response);
234+
})
189235
.delete(this.routerPath('leaveGroup'), ...guards, async (req, res) => {
190236
const response = await this.groupValidate<GroupJid>({
191237
request: req,

src/api/routes/sendMessage.router.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { RouterBroker } from '@api/abstract/abstract.router';
22
import {
3+
ForwardMessageDto,
34
SendAudioDto,
45
SendButtonsDto,
56
SendContactDto,
@@ -19,6 +20,7 @@ import {
1920
audioMessageSchema,
2021
buttonsMessageSchema,
2122
contactMessageSchema,
23+
forwardMessageSchema,
2224
listMessageSchema,
2325
locationMessageSchema,
2426
mediaMessageSchema,
@@ -61,6 +63,16 @@ export class MessageRouter extends RouterBroker {
6163

6264
return res.status(HttpStatus.CREATED).json(response);
6365
})
66+
.post(this.routerPath('forwardMessage'), ...guards, async (req, res) => {
67+
const response = await this.dataValidate<ForwardMessageDto>({
68+
request: req,
69+
schema: forwardMessageSchema,
70+
ClassRef: ForwardMessageDto,
71+
execute: (instance, data) => sendMessageController.forwardMessage(instance, data),
72+
});
73+
74+
return res.status(HttpStatus.CREATED).json(response);
75+
})
6476
.post(this.routerPath('sendMedia'), ...guards, upload.single('file'), async (req, res) => {
6577
const bodyData = req.body;
6678

src/validate/group.schema.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,45 @@ export const toggleEphemeralSchema: JSONSchema7 = {
159159
...isNotEmpty('groupJid', 'expiration'),
160160
};
161161

162+
export const memberAddModeSchema: JSONSchema7 = {
163+
$id: v4(),
164+
type: 'object',
165+
properties: {
166+
groupJid: { type: 'string' },
167+
mode: { type: 'string', enum: ['admin_add', 'all_member_add'] },
168+
},
169+
required: ['groupJid', 'mode'],
170+
...isNotEmpty('groupJid', 'mode'),
171+
};
172+
173+
export const joinApprovalModeSchema: JSONSchema7 = {
174+
$id: v4(),
175+
type: 'object',
176+
properties: {
177+
groupJid: { type: 'string' },
178+
mode: { type: 'string', enum: ['on', 'off'] },
179+
},
180+
required: ['groupJid', 'mode'],
181+
...isNotEmpty('groupJid', 'mode'),
182+
};
183+
184+
export const updateParticipantRequestSchema: JSONSchema7 = {
185+
$id: v4(),
186+
type: 'object',
187+
properties: {
188+
groupJid: { type: 'string' },
189+
action: { type: 'string', enum: ['approve', 'reject'] },
190+
participants: {
191+
type: 'array',
192+
minItems: 1,
193+
uniqueItems: true,
194+
items: { type: 'string' },
195+
},
196+
},
197+
required: ['groupJid', 'action', 'participants'],
198+
...isNotEmpty('groupJid', 'action'),
199+
};
200+
162201
export const updateGroupPictureSchema: JSONSchema7 = {
163202
$id: v4(),
164203
type: 'object',

src/validate/message.schema.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ export const offerCallSchema: JSONSchema7 = {
6565
required: ['number', 'callDuration'],
6666
};
6767

68+
export const forwardMessageSchema: JSONSchema7 = {
69+
$id: v4(),
70+
type: 'object',
71+
properties: {
72+
number: { ...numberDefinition },
73+
messageId: { type: 'string' },
74+
},
75+
required: ['number', 'messageId'],
76+
...isNotEmpty('number', 'messageId'),
77+
};
78+
6879
export const textMessageSchema: JSONSchema7 = {
6980
$id: v4(),
7081
type: 'object',

0 commit comments

Comments
 (0)