From 0da9ef68cfaaba3048e236a4ff9f794d56efda3d Mon Sep 17 00:00:00 2001 From: devlikepro Date: Fri, 2 Oct 2026 11:33:01 +0700 Subject: [PATCH] feat: groups share history mode - fix #2282 --- src/api/groups.controller.ts | 18 ++++++++- src/apps/mcp/tools/groups.tools.ts | 42 ++++++++++++++++++++ src/apps/mcp/tools/groups.zod.ts | 8 ++++ src/core/abc/session.abc.ts | 12 +++++- src/core/engines/gows/grpc/gows.ts | 13 ++++++ src/core/engines/gows/grpc/gows_grpc_pb.js | 12 ++++++ src/core/engines/gows/session.gows.core.ts | 25 ++++++++++++ src/core/engines/gows/types.group.ts | 1 + src/core/engines/noweb/session.noweb.core.ts | 39 +++++++----------- src/core/engines/webjs/session.webjs.core.ts | 23 +++++++++++ src/core/engines/wpp/session.wpp.core.ts | 15 +++++++ yarn.lock | 8 ++-- 12 files changed, 185 insertions(+), 31 deletions(-) diff --git a/src/api/groups.controller.ts b/src/api/groups.controller.ts index 3b670a09..778eb3c6 100644 --- a/src/api/groups.controller.ts +++ b/src/api/groups.controller.ts @@ -362,13 +362,29 @@ export class GroupsController { @WorkingSessionParam session: WhatsappSession, @Param('id') id: string, @Body() request: SettingsMemberShareHistoryMode, - ) { + ): Promise { return session.setMemberShareHistoryMode( id, 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 { + return session.getMemberShareHistoryMode(id); + } + @Put(':id/settings/security/membership-approval') @SessionApiParam @GroupIdApiParam diff --git a/src/apps/mcp/tools/groups.tools.ts b/src/apps/mcp/tools/groups.tools.ts index 2754337d..69fe8599 100644 --- a/src/apps/mcp/tools/groups.tools.ts +++ b/src/apps/mcp/tools/groups.tools.ts @@ -9,6 +9,7 @@ import { GroupIdInput, GroupJoinInput, GroupMembershipApprovalInput, + GroupMemberShareHistoryInput, GroupParticipantsInput, GroupPictureInput, 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) { + 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) { + return this.textRequest({ + method: 'PUT', + url: `/api/${session}/groups/${id}/settings/security/member-share-history-mode`, + data: body, + }); + } + @Tool('groups-get-join-requests', { title: 'Get pending requests to join the group', description: 'Get pending requests to join the group', diff --git a/src/apps/mcp/tools/groups.zod.ts b/src/apps/mcp/tools/groups.zod.ts index c1a1cbb5..c1356860 100644 --- a/src/apps/mcp/tools/groups.zod.ts +++ b/src/apps/mcp/tools/groups.zod.ts @@ -8,6 +8,7 @@ import { JoinGroupRequest, ParticipantsRequest, SettingsMembershipApproval, + SettingsMemberShareHistoryMode, SettingsSecurityChangeInfo, SubjectRequest, } from '@waha/structures/groups.dto'; @@ -72,3 +73,10 @@ export const GroupMembershipApprovalInput = DtoToZod( session: SessionField, id: GroupIdField, }); + +export const GroupMemberShareHistoryInput = DtoToZod( + SettingsMemberShareHistoryMode, +).extend({ + session: SessionField, + id: GroupIdField, +}); diff --git a/src/core/abc/session.abc.ts b/src/core/abc/session.abc.ts index a35de7b3..ffda83d4 100644 --- a/src/core/abc/session.abc.ts +++ b/src/core/abc/session.abc.ts @@ -114,6 +114,7 @@ import { GroupsListFields, ParticipantsRequest, SettingsMemberAddMode, + SettingsMemberShareHistoryMode, SettingsMembershipApproval, SettingsSecurityChangeInfo, } from '../../structures/groups.dto'; @@ -1025,7 +1026,16 @@ export abstract class WhatsappSession { throw new NotImplementedByEngineError(); } - public setMemberShareHistoryMode(id, value) { + public getMemberShareHistoryMode( + id: string, + ): Promise { + throw new NotImplementedByEngineError(); + } + + public setMemberShareHistoryMode( + id: string, + value: boolean, + ): Promise { throw new NotImplementedByEngineError(); } diff --git a/src/core/engines/gows/grpc/gows.ts b/src/core/engines/gows/grpc/gows.ts index 195e9674..c60ddf4a 100644 --- a/src/core/engines/gows/grpc/gows.ts +++ b/src/core/engines/gows/grpc/gows.ts @@ -11726,6 +11726,15 @@ export namespace messages { responseSerialize: (message: Empty) => Buffer.from(message.serialize()), 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: { path: "/messages.MessageService/UpdateGroupParticipants", requestStream: false, @@ -12144,6 +12153,7 @@ export namespace messages { abstract SetGroupLocked(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract SetGroupAnnounce(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract SetGroupMemberAddMode(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; + abstract SetGroupMemberShareHistoryMode(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract UpdateGroupParticipants(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract SetGroupJoinApprovalMode(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetGroupRequestParticipants(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; @@ -12279,6 +12289,9 @@ export namespace messages { SetGroupMemberAddMode: GrpcUnaryServiceInterface = (message: JidBoolRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall => { return super.SetGroupMemberAddMode(message, metadata, options, callback); }; + SetGroupMemberShareHistoryMode: GrpcUnaryServiceInterface = (message: JidBoolRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall => { + return super.SetGroupMemberShareHistoryMode(message, metadata, options, callback); + }; UpdateGroupParticipants: GrpcUnaryServiceInterface = (message: UpdateParticipantsRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall => { return super.UpdateGroupParticipants(message, metadata, options, callback); }; diff --git a/src/core/engines/gows/grpc/gows_grpc_pb.js b/src/core/engines/gows/grpc/gows_grpc_pb.js index 1db6b701..47775461 100644 --- a/src/core/engines/gows/grpc/gows_grpc_pb.js +++ b/src/core/engines/gows/grpc/gows_grpc_pb.js @@ -1073,6 +1073,18 @@ setGroupMemberAddMode: { responseDeserialize: deserialize_messages_Empty, }, // 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: { path: '/messages.MessageService/UpdateGroupParticipants', requestStream: false, diff --git a/src/core/engines/gows/session.gows.core.ts b/src/core/engines/gows/session.gows.core.ts index 2c344167..f2bec7cf 100644 --- a/src/core/engines/gows/session.gows.core.ts +++ b/src/core/engines/gows/session.gows.core.ts @@ -133,6 +133,7 @@ import { Participant, ParticipantsRequest, SettingsMemberAddMode, + SettingsMemberShareHistoryMode, SettingsMembershipApproval, SettingsSecurityChangeInfo, } from '@waha/structures/groups.dto'; @@ -1804,6 +1805,30 @@ export class WhatsappSessionGoWSCore extends WhatsappSession { return; } + public async getMemberShareHistoryMode( + id: string, + ): Promise { + const group = await this.getGroup(id); + return { + membersCanShareHistory: + group.MemberShareHistoryMode === 'all_member_share', + }; + } + + @Activity() + public async setMemberShareHistoryMode( + id: string, + value: boolean, + ): Promise { + const req = new messages.JidBoolRequest({ + session: this.session, + jid: id, + value: value, + }); + await promisify(this.client.SetGroupMemberShareHistoryMode)(req); + return true; + } + public async getMembershipApprovalMode( id: string, ): Promise { diff --git a/src/core/engines/gows/types.group.ts b/src/core/engines/gows/types.group.ts index 77542335..d6e04212 100644 --- a/src/core/engines/gows/types.group.ts +++ b/src/core/engines/gows/types.group.ts @@ -101,6 +101,7 @@ export interface GroupInfoFull { IsAnnounce: boolean; // specifies whether only admins can send messages in the group Participants: GOWSGroupParticipant[]; MemberAddMode: string; // all_member_add | admin_add + MemberShareHistoryMode: string; // all_member_share | admin_share IsJoinApprovalRequired: boolean; } diff --git a/src/core/engines/noweb/session.noweb.core.ts b/src/core/engines/noweb/session.noweb.core.ts index fa73994e..cd1ec6e2 100644 --- a/src/core/engines/noweb/session.noweb.core.ts +++ b/src/core/engines/noweb/session.noweb.core.ts @@ -28,7 +28,6 @@ import makeWASocket, { import { WACallEvent } from '@adiwajshing/baileys/lib/Types/Call'; import { BaileysEventMap } from '@adiwajshing/baileys/lib/Types/Events'; import { GroupMetadata } from '@adiwajshing/baileys/lib/Types/GroupMetadata'; -import { executeWMexQuery } from '@adiwajshing/baileys/lib/Socket/mex'; import { Label as NOWEBLabel, LabelActionBody, @@ -298,14 +297,6 @@ export interface NowebConfig { 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 { private START_ATTEMPT_DELAY_SECONDS = 2; private AUTO_RESTART_AFTER_SECONDS = 28 * 60; @@ -2248,23 +2239,21 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { return await this.sock.groupMemberAddMode(id, mode); } + public async getMemberShareHistoryMode( + id: string, + ): Promise { + const group = await this.getGroup(id); + return { membersCanShareHistory: !!group.memberShareHistoryMode }; + } + @Activity() - public async setMemberShareHistoryMode(id, value) { - const mode = value ? 'ALL_MEMBER_SHARE' : 'ADMIN_SHARE'; - const result = await executeWMexQuery<{ state?: string }>( - { group_id: id, update: { member_share_group_history_mode: mode } }, - MEX_QUERY_ID_UPDATE_GROUP_PROPERTY, - 'xwa2_group_update_property', - this.sock.query, - 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 setMemberShareHistoryMode( + id: string, + value: boolean, + ): Promise { + const mode = value ? 'all_member_share' : 'admin_share'; + await this.sock.groupMemberShareHistoryMode(id, mode); + return true; } public async getMembershipApprovalMode( diff --git a/src/core/engines/webjs/session.webjs.core.ts b/src/core/engines/webjs/session.webjs.core.ts index f8942372..a1b24abb 100644 --- a/src/core/engines/webjs/session.webjs.core.ts +++ b/src/core/engines/webjs/session.webjs.core.ts @@ -117,6 +117,7 @@ import { GroupSortField, ParticipantsRequest, SettingsMemberAddMode, + SettingsMemberShareHistoryMode, SettingsMembershipApproval, SettingsSecurityChangeInfo, } from '@waha/structures/groups.dto'; @@ -1757,6 +1758,28 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { return groupChat.setAddMembersAdminsOnly(!value); } + public async getMemberShareHistoryMode( + id: string, + ): Promise { + 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 { + const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat; + // The library setter is inverted - it takes "adminsOnly" + return groupChat.setShareHistoryAdminsOnly(!value); + } + @Activity() public async getMembershipApprovalMode( id: string, diff --git a/src/core/engines/wpp/session.wpp.core.ts b/src/core/engines/wpp/session.wpp.core.ts index f49dd706..38e6b38f 100644 --- a/src/core/engines/wpp/session.wpp.core.ts +++ b/src/core/engines/wpp/session.wpp.core.ts @@ -178,6 +178,7 @@ import { NormalizeJoinRequestMethod, ParticipantsRequest, SettingsMemberAddMode, + SettingsMemberShareHistoryMode, SettingsMembershipApproval, SettingsSecurityChangeInfo, } from '@waha/structures/groups.dto'; @@ -1624,6 +1625,20 @@ export class WhatsappSessionWPPCore extends WhatsappSession { ); } + public async getMemberShareHistoryMode( + id: string, + ): Promise { + 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( id: string, ): Promise { diff --git a/yarn.lock b/yarn.lock index 6603fbfb..32bebe0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,7 +7,7 @@ __metadata: "@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2026-04-28": 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: "@cacheable/node-cache": "npm:^1.4.0" "@hapi/boom": "npm:^9.1.3" @@ -32,7 +32,7 @@ __metadata: optional: true link-preview-js: optional: true - checksum: 10/3fdf6e7eea36d474a46c70e645872f588d3c47dcdc99898477be1665302844aa3c5da221cd9773380ea66d28deb9e9e565a1507323be5840eba23352c7707583 + checksum: 10/d7c290e64065517c43094f5700daf1230dfa54254aa2965d8efca94bdefbd464904a390f16d695c0305a0e7f408acd9fda566bacb30dc493ff563fcb06892873 languageName: node linkType: hard @@ -14381,7 +14381,7 @@ __metadata: "whatsapp-web.js@github:devlikeapro/whatsapp-web.js#fork-main-2026-06-26": 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: archiver: "npm:7.0.1" fluent-ffmpeg: "npm:2.1.3" @@ -14398,7 +14398,7 @@ __metadata: optional: true unzipper: optional: true - checksum: 10/ee21c2d7cf3e94eccc931fca7f386aaf6fd5ba4d8f362dd79df8f45fab22298538eaedb5fa5c4ae80a622e51a229601f20fd41c90a781428ad681ce821861de6 + checksum: 10/52b776bd9cde2799fed01ca507f8a43e17cce11254fa58cc3e96fa40f206532311050a38d6c25ee895616c9c39286615e6513c952667b6bd8610d5de03068cdb languageName: node linkType: hard