feat: groups share history mode - fix #2282

This commit is contained in:
devlikepro committed 2026-10-02 13:07:08 +07:00
1 parent cc650fca3b
commit 0da9ef68cf
12 files changed
+185 -31

No files matched your search

+17 -1
View File
@@ -362,13 +362,29 @@ export class GroupsController {
@WorkingSessionParam session: WhatsappSession, @WorkingSessionParam session: WhatsappSession,
@Param('id') id: string, @Param('id') id: string,
@Body() request: SettingsMemberShareHistoryMode, @Body() request: SettingsMemberShareHistoryMode,
) { ): Promise<boolean> {
return session.setMemberShareHistoryMode( return session.setMemberShareHistoryMode(
id, id,
request.membersCanShareHistory, request.membersCanShareHistory,
); );
} }
@Get(':id/settings/security/member-share-history-mode')
@SessionApiParam
@GroupIdApiParam
@CheckPolicies(CanSession(Action.Read, FromParam('session')))
@ApiOperation({
summary: 'Get settings - members can send message history to new members',
description:
'The group settings for whether members can share message history with new members, or only admins can.',
})
getMemberShareHistoryMode(
@WorkingSessionParam session: WhatsappSession,
@Param('id') id: string,
): Promise<SettingsMemberShareHistoryMode> {
return session.getMemberShareHistoryMode(id);
}
@Put(':id/settings/security/membership-approval') @Put(':id/settings/security/membership-approval')
@SessionApiParam @SessionApiParam
@GroupIdApiParam @GroupIdApiParam
+42
View File
@@ -9,6 +9,7 @@ import {
GroupIdInput, GroupIdInput,
GroupJoinInput, GroupJoinInput,
GroupMembershipApprovalInput, GroupMembershipApprovalInput,
GroupMemberShareHistoryInput,
GroupParticipantsInput, GroupParticipantsInput,
GroupPictureInput, GroupPictureInput,
GroupsListInput, GroupsListInput,
@@ -405,6 +406,47 @@ export class GroupTools extends McpController {
}); });
} }
@Tool('groups-get-member-share-history', {
title: 'Get member share history setting',
description:
'Get whether all members or only admins can share message history with new members',
inputSchema: GroupIdInput,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
},
})
async getMemberShareHistory({ session, id }: z.infer<typeof GroupIdInput>) {
return this.textRequest({
method: 'GET',
url: `/api/${session}/groups/${id}/settings/security/member-share-history-mode`,
});
}
@Tool('groups-set-member-share-history', {
title: 'Set member share history setting',
description:
'Allow all members or only admins to share message history with new members',
inputSchema: GroupMemberShareHistoryInput,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
},
})
async setMemberShareHistory({
session,
id,
...body
}: z.infer<typeof GroupMemberShareHistoryInput>) {
return this.textRequest({
method: 'PUT',
url: `/api/${session}/groups/${id}/settings/security/member-share-history-mode`,
data: body,
});
}
@Tool('groups-get-join-requests', { @Tool('groups-get-join-requests', {
title: 'Get pending requests to join the group', title: 'Get pending requests to join the group',
description: 'Get pending requests to join the group', description: 'Get pending requests to join the group',
+8
View File
@@ -8,6 +8,7 @@ import {
JoinGroupRequest, JoinGroupRequest,
ParticipantsRequest, ParticipantsRequest,
SettingsMembershipApproval, SettingsMembershipApproval,
SettingsMemberShareHistoryMode,
SettingsSecurityChangeInfo, SettingsSecurityChangeInfo,
SubjectRequest, SubjectRequest,
} from '@waha/structures/groups.dto'; } from '@waha/structures/groups.dto';
@@ -72,3 +73,10 @@ export const GroupMembershipApprovalInput = DtoToZod(
session: SessionField, session: SessionField,
id: GroupIdField, id: GroupIdField,
}); });
export const GroupMemberShareHistoryInput = DtoToZod(
SettingsMemberShareHistoryMode,
).extend({
session: SessionField,
id: GroupIdField,
});
+11 -1
View File
@@ -114,6 +114,7 @@ import {
GroupsListFields, GroupsListFields,
ParticipantsRequest, ParticipantsRequest,
SettingsMemberAddMode, SettingsMemberAddMode,
SettingsMemberShareHistoryMode,
SettingsMembershipApproval, SettingsMembershipApproval,
SettingsSecurityChangeInfo, SettingsSecurityChangeInfo,
} from '../../structures/groups.dto'; } from '../../structures/groups.dto';
@@ -1025,7 +1026,16 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError(); throw new NotImplementedByEngineError();
} }
public setMemberShareHistoryMode(id, value) { public getMemberShareHistoryMode(
id: string,
): Promise<SettingsMemberShareHistoryMode> {
throw new NotImplementedByEngineError();
}
public setMemberShareHistoryMode(
id: string,
value: boolean,
): Promise<boolean> {
throw new NotImplementedByEngineError(); throw new NotImplementedByEngineError();
} }
+13
View File
@@ -11726,6 +11726,15 @@ export namespace messages {
responseSerialize: (message: Empty) => Buffer.from(message.serialize()), responseSerialize: (message: Empty) => Buffer.from(message.serialize()),
responseDeserialize: (bytes: Buffer) => Empty.deserialize(new Uint8Array(bytes)) responseDeserialize: (bytes: Buffer) => Empty.deserialize(new Uint8Array(bytes))
}, },
SetGroupMemberShareHistoryMode: {
path: "/messages.MessageService/SetGroupMemberShareHistoryMode",
requestStream: false,
responseStream: false,
requestSerialize: (message: JidBoolRequest) => Buffer.from(message.serialize()),
requestDeserialize: (bytes: Buffer) => JidBoolRequest.deserialize(new Uint8Array(bytes)),
responseSerialize: (message: Empty) => Buffer.from(message.serialize()),
responseDeserialize: (bytes: Buffer) => Empty.deserialize(new Uint8Array(bytes))
},
UpdateGroupParticipants: { UpdateGroupParticipants: {
path: "/messages.MessageService/UpdateGroupParticipants", path: "/messages.MessageService/UpdateGroupParticipants",
requestStream: false, requestStream: false,
@@ -12144,6 +12153,7 @@ export namespace messages {
abstract SetGroupLocked(call: grpc_1.ServerUnaryCall<JidBoolRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void; abstract SetGroupLocked(call: grpc_1.ServerUnaryCall<JidBoolRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void;
abstract SetGroupAnnounce(call: grpc_1.ServerUnaryCall<JidBoolRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void; abstract SetGroupAnnounce(call: grpc_1.ServerUnaryCall<JidBoolRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void;
abstract SetGroupMemberAddMode(call: grpc_1.ServerUnaryCall<JidBoolRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void; abstract SetGroupMemberAddMode(call: grpc_1.ServerUnaryCall<JidBoolRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void;
abstract SetGroupMemberShareHistoryMode(call: grpc_1.ServerUnaryCall<JidBoolRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void;
abstract UpdateGroupParticipants(call: grpc_1.ServerUnaryCall<UpdateParticipantsRequest, JsonList>, callback: grpc_1.sendUnaryData<JsonList>): void; abstract UpdateGroupParticipants(call: grpc_1.ServerUnaryCall<UpdateParticipantsRequest, JsonList>, callback: grpc_1.sendUnaryData<JsonList>): void;
abstract SetGroupJoinApprovalMode(call: grpc_1.ServerUnaryCall<JidBoolRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void; abstract SetGroupJoinApprovalMode(call: grpc_1.ServerUnaryCall<JidBoolRequest, Empty>, callback: grpc_1.sendUnaryData<Empty>): void;
abstract GetGroupRequestParticipants(call: grpc_1.ServerUnaryCall<JidRequest, JsonList>, callback: grpc_1.sendUnaryData<JsonList>): void; abstract GetGroupRequestParticipants(call: grpc_1.ServerUnaryCall<JidRequest, JsonList>, callback: grpc_1.sendUnaryData<JsonList>): void;
@@ -12279,6 +12289,9 @@ export namespace messages {
SetGroupMemberAddMode: GrpcUnaryServiceInterface<JidBoolRequest, Empty> = (message: JidBoolRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback<Empty>, options?: grpc_1.CallOptions | grpc_1.requestCallback<Empty>, callback?: grpc_1.requestCallback<Empty>): grpc_1.ClientUnaryCall => { SetGroupMemberAddMode: GrpcUnaryServiceInterface<JidBoolRequest, Empty> = (message: JidBoolRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback<Empty>, options?: grpc_1.CallOptions | grpc_1.requestCallback<Empty>, callback?: grpc_1.requestCallback<Empty>): grpc_1.ClientUnaryCall => {
return super.SetGroupMemberAddMode(message, metadata, options, callback); return super.SetGroupMemberAddMode(message, metadata, options, callback);
}; };
SetGroupMemberShareHistoryMode: GrpcUnaryServiceInterface<JidBoolRequest, Empty> = (message: JidBoolRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback<Empty>, options?: grpc_1.CallOptions | grpc_1.requestCallback<Empty>, callback?: grpc_1.requestCallback<Empty>): grpc_1.ClientUnaryCall => {
return super.SetGroupMemberShareHistoryMode(message, metadata, options, callback);
};
UpdateGroupParticipants: GrpcUnaryServiceInterface<UpdateParticipantsRequest, JsonList> = (message: UpdateParticipantsRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback<JsonList>, options?: grpc_1.CallOptions | grpc_1.requestCallback<JsonList>, callback?: grpc_1.requestCallback<JsonList>): grpc_1.ClientUnaryCall => { UpdateGroupParticipants: GrpcUnaryServiceInterface<UpdateParticipantsRequest, JsonList> = (message: UpdateParticipantsRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback<JsonList>, options?: grpc_1.CallOptions | grpc_1.requestCallback<JsonList>, callback?: grpc_1.requestCallback<JsonList>): grpc_1.ClientUnaryCall => {
return super.UpdateGroupParticipants(message, metadata, options, callback); return super.UpdateGroupParticipants(message, metadata, options, callback);
}; };
@@ -1073,6 +1073,18 @@ setGroupMemberAddMode: {
responseDeserialize: deserialize_messages_Empty, responseDeserialize: deserialize_messages_Empty,
}, },
// who can add members - true - all members, false - admins only // who can add members - true - all members, false - admins only
setGroupMemberShareHistoryMode: {
path: '/messages.MessageService/SetGroupMemberShareHistoryMode',
requestStream: false,
responseStream: false,
requestType: gows_pb.JidBoolRequest,
responseType: gows_pb.Empty,
requestSerialize: serialize_messages_JidBoolRequest,
requestDeserialize: deserialize_messages_JidBoolRequest,
responseSerialize: serialize_messages_Empty,
responseDeserialize: deserialize_messages_Empty,
},
// who can share message history with new members - true - all members, false - admins only
updateGroupParticipants: { updateGroupParticipants: {
path: '/messages.MessageService/UpdateGroupParticipants', path: '/messages.MessageService/UpdateGroupParticipants',
requestStream: false, requestStream: false,
@@ -133,6 +133,7 @@ import {
Participant, Participant,
ParticipantsRequest, ParticipantsRequest,
SettingsMemberAddMode, SettingsMemberAddMode,
SettingsMemberShareHistoryMode,
SettingsMembershipApproval, SettingsMembershipApproval,
SettingsSecurityChangeInfo, SettingsSecurityChangeInfo,
} from '@waha/structures/groups.dto'; } from '@waha/structures/groups.dto';
@@ -1804,6 +1805,30 @@ export class WhatsappSessionGoWSCore extends WhatsappSession {
return; return;
} }
public async getMemberShareHistoryMode(
id: string,
): Promise<SettingsMemberShareHistoryMode> {
const group = await this.getGroup(id);
return {
membersCanShareHistory:
group.MemberShareHistoryMode === 'all_member_share',
};
}
@Activity()
public async setMemberShareHistoryMode(
id: string,
value: boolean,
): Promise<boolean> {
const req = new messages.JidBoolRequest({
session: this.session,
jid: id,
value: value,
});
await promisify(this.client.SetGroupMemberShareHistoryMode)(req);
return true;
}
public async getMembershipApprovalMode( public async getMembershipApprovalMode(
id: string, id: string,
): Promise<SettingsMembershipApproval> { ): Promise<SettingsMembershipApproval> {
+1
View File
@@ -101,6 +101,7 @@ export interface GroupInfoFull {
IsAnnounce: boolean; // specifies whether only admins can send messages in the group IsAnnounce: boolean; // specifies whether only admins can send messages in the group
Participants: GOWSGroupParticipant[]; Participants: GOWSGroupParticipant[];
MemberAddMode: string; // all_member_add | admin_add MemberAddMode: string; // all_member_add | admin_add
MemberShareHistoryMode: string; // all_member_share | admin_share
IsJoinApprovalRequired: boolean; IsJoinApprovalRequired: boolean;
} }
+14 -25
View File
@@ -28,7 +28,6 @@ import makeWASocket, {
import { WACallEvent } from '@adiwajshing/baileys/lib/Types/Call'; import { WACallEvent } from '@adiwajshing/baileys/lib/Types/Call';
import { BaileysEventMap } from '@adiwajshing/baileys/lib/Types/Events'; import { BaileysEventMap } from '@adiwajshing/baileys/lib/Types/Events';
import { GroupMetadata } from '@adiwajshing/baileys/lib/Types/GroupMetadata'; import { GroupMetadata } from '@adiwajshing/baileys/lib/Types/GroupMetadata';
import { executeWMexQuery } from '@adiwajshing/baileys/lib/Socket/mex';
import { import {
Label as NOWEBLabel, Label as NOWEBLabel,
LabelActionBody, LabelActionBody,
@@ -298,14 +297,6 @@ export interface NowebConfig {
waVersion: WAVersion; waVersion: WAVersion;
} }
// WhatsApp Web MEX (w:mex) doc/query_id for the group property mutation that sets
// "members can send message history to new members". Captured from WhatsApp Web's
// WAWebMexUpdateGroupPropertyJobMutation on 2026-09-25 (see whatsapp-rust
// wacore/src/iq/mex_operations.rs, mod update_group_property). WhatsApp rotates these
// ids when Web is updated -- if this mutation starts failing, re-capture the query_id
// from a fresh WhatsApp Web session before assuming the endpoint itself is broken.
const MEX_QUERY_ID_UPDATE_GROUP_PROPERTY = '9418211574894172';
export class WhatsappSessionNoWebCore extends WhatsappSession { export class WhatsappSessionNoWebCore extends WhatsappSession {
private START_ATTEMPT_DELAY_SECONDS = 2; private START_ATTEMPT_DELAY_SECONDS = 2;
private AUTO_RESTART_AFTER_SECONDS = 28 * 60; private AUTO_RESTART_AFTER_SECONDS = 28 * 60;
@@ -2248,23 +2239,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return await this.sock.groupMemberAddMode(id, mode); return await this.sock.groupMemberAddMode(id, mode);
} }
public async getMemberShareHistoryMode(
id: string,
): Promise<SettingsMemberShareHistoryMode> {
const group = await this.getGroup(id);
return { membersCanShareHistory: !!group.memberShareHistoryMode };
}
@Activity() @Activity()
public async setMemberShareHistoryMode(id, value) { public async setMemberShareHistoryMode(
const mode = value ? 'ALL_MEMBER_SHARE' : 'ADMIN_SHARE'; id: string,
const result = await executeWMexQuery<{ state?: string }>( value: boolean,
{ group_id: id, update: { member_share_group_history_mode: mode } }, ): Promise<boolean> {
MEX_QUERY_ID_UPDATE_GROUP_PROPERTY, const mode = value ? 'all_member_share' : 'admin_share';
'xwa2_group_update_property', await this.sock.groupMemberShareHistoryMode(id, mode);
this.sock.query, return true;
this.sock.generateMessageTag,
);
if (result?.state !== 'ACTIVE') {
throw new Error(
`Failed to set member share history mode for group '${id}': ` +
`unexpected state '${result?.state}'`,
);
}
return result;
} }
public async getMembershipApprovalMode( public async getMembershipApprovalMode(
@@ -117,6 +117,7 @@ import {
GroupSortField, GroupSortField,
ParticipantsRequest, ParticipantsRequest,
SettingsMemberAddMode, SettingsMemberAddMode,
SettingsMemberShareHistoryMode,
SettingsMembershipApproval, SettingsMembershipApproval,
SettingsSecurityChangeInfo, SettingsSecurityChangeInfo,
} from '@waha/structures/groups.dto'; } from '@waha/structures/groups.dto';
@@ -1757,6 +1758,28 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return groupChat.setAddMembersAdminsOnly(!value); return groupChat.setAddMembersAdminsOnly(!value);
} }
public async getMemberShareHistoryMode(
id: string,
): Promise<SettingsMemberShareHistoryMode> {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
return {
membersCanShareHistory:
// @ts-ignore
groupChat.groupMetadata.memberShareGroupHistoryMode ===
'all_member_share',
};
}
@Activity()
public async setMemberShareHistoryMode(
id: string,
value: boolean,
): Promise<boolean> {
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
// The library setter is inverted - it takes "adminsOnly"
return groupChat.setShareHistoryAdminsOnly(!value);
}
@Activity() @Activity()
public async getMembershipApprovalMode( public async getMembershipApprovalMode(
id: string, id: string,
+15
View File
@@ -178,6 +178,7 @@ import {
NormalizeJoinRequestMethod, NormalizeJoinRequestMethod,
ParticipantsRequest, ParticipantsRequest,
SettingsMemberAddMode, SettingsMemberAddMode,
SettingsMemberShareHistoryMode,
SettingsMembershipApproval, SettingsMembershipApproval,
SettingsSecurityChangeInfo, SettingsSecurityChangeInfo,
} from '@waha/structures/groups.dto'; } from '@waha/structures/groups.dto';
@@ -1624,6 +1625,20 @@ export class WhatsappSessionWPPCore extends WhatsappSession {
); );
} }
public async getMemberShareHistoryMode(
id: string,
): Promise<SettingsMemberShareHistoryMode> {
const groupId = await this.hooks.wid.chat.promise(
id,
'getMemberShareHistoryMode',
);
const group = await this.wpp!.getChatById(groupId);
// Undocumented property - 'all_member_share' or 'admin_share'
// @ts-ignore
const mode = group?.groupMetadata?.memberShareGroupHistoryMode;
return { membersCanShareHistory: mode === 'all_member_share' };
}
public async getMembershipApprovalMode( public async getMembershipApprovalMode(
id: string, id: string,
): Promise<SettingsMembershipApproval> { ): Promise<SettingsMembershipApproval> {
+4 -4
View File
@@ -7,7 +7,7 @@ __metadata:
"@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2026-04-28": "@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2026-04-28":
version: 7.0.0-rc14 version: 7.0.0-rc14
resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=93433a380977fce781d25c5e40d8f12d8ea8109e" resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=14ed18cd9fc524309fbecaed4949ea73eaf6761e"
dependencies: dependencies:
"@cacheable/node-cache": "npm:^1.4.0" "@cacheable/node-cache": "npm:^1.4.0"
"@hapi/boom": "npm:^9.1.3" "@hapi/boom": "npm:^9.1.3"
@@ -32,7 +32,7 @@ __metadata:
optional: true optional: true
link-preview-js: link-preview-js:
optional: true optional: true
checksum: 10/3fdf6e7eea36d474a46c70e645872f588d3c47dcdc99898477be1665302844aa3c5da221cd9773380ea66d28deb9e9e565a1507323be5840eba23352c7707583 checksum: 10/d7c290e64065517c43094f5700daf1230dfa54254aa2965d8efca94bdefbd464904a390f16d695c0305a0e7f408acd9fda566bacb30dc493ff563fcb06892873
languageName: node languageName: node
linkType: hard linkType: hard
@@ -14381,7 +14381,7 @@ __metadata:
"whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2026-06-26": "whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2026-06-26":
version: 1.34.7 version: 1.34.7
resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=a95362738822bcb670f9e20f79acaf83304a6a2d" resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=7bcd3528538372f9420a63472491513066993090"
dependencies: dependencies:
archiver: "npm:7.0.1" archiver: "npm:7.0.1"
fluent-ffmpeg: "npm:2.1.3" fluent-ffmpeg: "npm:2.1.3"
@@ -14398,7 +14398,7 @@ __metadata:
optional: true optional: true
unzipper: unzipper:
optional: true optional: true
checksum: 10/ee21c2d7cf3e94eccc931fca7f386aaf6fd5ba4d8f362dd79df8f45fab22298538eaedb5fa5c4ae80a622e51a229601f20fd41c90a781428ad681ce821861de6 checksum: 10/52b776bd9cde2799fed01ca507f8a43e17cce11254fa58cc3e96fa40f206532311050a38d6c25ee895616c9c39286615e6513c952667b6bd8610d5de03068cdb
languageName: node languageName: node
linkType: hard linkType: hard