diff --git a/src/client/coordinators/WaMessageDispatchCoordinator.ts b/src/client/coordinators/WaMessageDispatchCoordinator.ts index 9c957bea..7853796c 100644 --- a/src/client/coordinators/WaMessageDispatchCoordinator.ts +++ b/src/client/coordinators/WaMessageDispatchCoordinator.ts @@ -49,6 +49,7 @@ import type { WaMessageClient } from '@message/WaMessageClient' import { proto, type Proto } from '@proto' import { normalizeEphemeralSettingSeconds, + STATUS_MENTION_DELAY, WA_ADDRESSING_MODES, WA_DEFAULTS, WA_NACK_REASONS, @@ -91,6 +92,8 @@ import { buildButtonAddonNode, buildDirectMessageFanoutNode, buildGroupSenderKeyMessageNode, + buildGroupStatusMentionMessage, + buildStatusMentionMetaNode, buildMetaNode } from '@transport/node/builders/message' import type { BinaryNode } from '@transport/types' @@ -760,6 +763,7 @@ export class WaMessageDispatchCoordinator { public async publishStatusMessage(input: { readonly message: Proto.IMessage readonly recipients: readonly string[] + readonly mentionedGroupJids?: readonly string[] readonly statusSetting?: WaStatusDistributionSetting readonly options?: WaSendMessageOptions }): Promise { @@ -783,8 +787,15 @@ export class WaMessageDispatchCoordinator { if (!seen.has(meUserLid)) { recipientsWithSelf.push(meUserLid) } + const mentionedGroups: string[] = [] + const seenGroups = new Set() + for (const jid of input.mentionedGroupJids ?? []) { + if (!isGroupJid(jid) || seenGroups.has(jid)) continue + seenGroups.add(jid) + mentionedGroups.push(jid) + } const statusSetting = input.statusSetting ?? 'contacts' - return this.publishSenderKeyFanout({ + const result = await this.publishSenderKeyFanout({ groupJid: WA_DEFAULTS.STATUS_BROADCAST_JID, senderJid, recipients: recipientsWithSelf, @@ -825,7 +836,11 @@ export class WaMessageDispatchCoordinator { remoteJid: WA_DEFAULTS.STATUS_BROADCAST_JID, context: 'status' }) - const customNodes: BinaryNode[] = [buildMetaNode({ status_setting: statusSetting })] + const customNodes: BinaryNode[] = [ + mentionedGroups.length > 0 + ? buildStatusMentionMetaNode(mentionedGroups, statusSetting) + : buildMetaNode({ status_setting: statusSetting }) + ] if (reportingArtifacts?.node) customNodes.push(reportingArtifacts.node) return { extraParticipants: ackHints, @@ -833,6 +848,38 @@ export class WaMessageDispatchCoordinator { } } }) + if (mentionedGroups.length > 0) { + if (result.id) { + await this.notifyGroupStatusMentions(result.id, mentionedGroups) + } else { + this.deps.logger.warn('invalid id, skipping mention notifications', { + groups: mentionedGroups.length + }) + } + } + return result + } + + private async notifyGroupStatusMentions( + statusId: string, + groupJids: readonly string[] + ): Promise { + const message = buildGroupStatusMentionMessage(statusId) + for (let index = 0; index < groupJids.length; index += 1) { + await new Promise((r) => setTimeout(r, STATUS_MENTION_DELAY)) + try { + await this.sendMessage(groupJids[index], message, { + additionalAttributes: { type: 'text' }, + disableGroupEphemeralAutoInject: true + }) + } catch (error) { + this.deps.logger.warn('failed to notify group of status mention', { + groupJid: groupJids[index], + statusId, + message: toError(error).message + }) + } + } } public async publishBroadcastListMessage(input: { diff --git a/src/client/coordinators/WaStatusCoordinator.ts b/src/client/coordinators/WaStatusCoordinator.ts index 1de7c654..c901db40 100644 --- a/src/client/coordinators/WaStatusCoordinator.ts +++ b/src/client/coordinators/WaStatusCoordinator.ts @@ -18,6 +18,7 @@ export interface WaStatusCoordinatorOptions { readonly publishStatusMessage: (input: { readonly message: Proto.IMessage readonly recipients: readonly string[] + readonly mentionedGroupJids?: readonly string[] readonly statusSetting?: WaStatusDistributionSetting readonly options?: WaSendMessageOptions }) => Promise @@ -26,6 +27,7 @@ export interface WaStatusCoordinatorOptions { export interface WaSendStatusInput { readonly content: WaSendMessageContent readonly recipients: readonly string[] + readonly mentionedGroupJids: readonly string[] readonly statusSetting?: WaStatusDistributionSetting readonly options?: WaSendMessageOptions } @@ -71,6 +73,7 @@ export function createStatusCoordinator(options: WaStatusCoordinatorOptions): Wa message, recipients: input.recipients, statusSetting: input.statusSetting, + mentionedGroupJids: input.mentionedGroupJids, options: input.options }) return built.upload ? { ...published, upload: built.upload } : published diff --git a/src/protocol/constants.ts b/src/protocol/constants.ts index 5567ad7c..2adaec99 100644 --- a/src/protocol/constants.ts +++ b/src/protocol/constants.ts @@ -112,7 +112,7 @@ export { WA_META_NODE_ATTRS_BOT } from '@protocol/bot' export type { WaBizBotType, WaBotMsgBodyType, WaBotMsgEditType } from '@protocol/bot' -export { WA_STATUS_DISTRIBUTION_SETTINGS } from '@protocol/status' +export { WA_STATUS_DISTRIBUTION_SETTINGS, STATUS_MENTION_DELAY } from '@protocol/status' export type { WaStatusDistributionSetting } from '@protocol/status' export { WA_EMAIL_CONTEXTS, diff --git a/src/protocol/status.ts b/src/protocol/status.ts index 3031e058..e68d7799 100644 --- a/src/protocol/status.ts +++ b/src/protocol/status.ts @@ -7,3 +7,5 @@ export const WA_STATUS_DISTRIBUTION_SETTINGS = Object.freeze({ export type WaStatusDistributionSetting = (typeof WA_STATUS_DISTRIBUTION_SETTINGS)[keyof typeof WA_STATUS_DISTRIBUTION_SETTINGS] + +export const STATUS_MENTION_DELAY = 500 diff --git a/src/transport/node/builders/message.ts b/src/transport/node/builders/message.ts index e250299b..61509683 100644 --- a/src/transport/node/builders/message.ts +++ b/src/transport/node/builders/message.ts @@ -1,6 +1,8 @@ import type { WaButtonAddonKind } from '@message/encode/content' -import { WA_MESSAGE_TAGS, WA_MESSAGE_TYPES, WA_NODE_TAGS } from '@protocol/constants' import type { BinaryNode } from '@transport/types' +import { WA_MESSAGE_TAGS, WA_MESSAGE_TYPES, WA_NODE_TAGS } from '@protocol/constants' +import { WA_DEFAULTS } from '@protocol/defaults' +import { proto, type Proto } from '@proto' interface EncryptedParticipant { readonly jid: string @@ -301,3 +303,43 @@ export function buildMetaNode(attrs: Record): BinaryNode { content: undefined } } + +export function buildStatusMentionMetaNode( + groupJids: readonly string[], + statusSetting: string +): BinaryNode { + return { + tag: 'meta', + attrs: { + status_setting: statusSetting + }, + content: [ + { + tag: 'mentioned_users', + attrs: {}, + content: groupJids.map((jid) => ({ + tag: 'to', + attrs: { jid } + })) + } + ] + } +} + +export function buildGroupStatusMentionMessage(statusId: string): Proto.IMessage { + return { + groupStatusMentionMessage: { + message: { + protocolMessage: { + key: { + remoteJid: WA_DEFAULTS.STATUS_BROADCAST_JID, + fromMe: true, + id: statusId, + participant: 'status_me' + }, + type: proto.Message.ProtocolMessage.Type.STATUS_MENTION_MESSAGE + } + } + } + } +}