*group: parse @lids properly when processing group notifications

*This commit is breaking, it changes the format of participants in group-participants.update.
This commit is contained in:
Rajeh Taher
2025-10-05 23:56:14 +03:00
parent 09263aa3d2
commit c5b0001b61
8 changed files with 95 additions and 27 deletions
+1
View File
@@ -465,6 +465,7 @@ export const extractCommunityMetadata = (result: BinaryNode) => {
memberAddMode, memberAddMode,
participants: getBinaryNodeChildren(community, 'participant').map(({ attrs }) => { participants: getBinaryNodeChildren(community, 'participant').map(({ attrs }) => {
return { return {
// TODO: IMPLEMENT THE PN/LID FIELDS HERE!!
id: attrs.jid!, id: attrs.jid!,
admin: (attrs.type || null) as GroupParticipant['admin'] admin: (attrs.type || null) as GroupParticipant['admin']
} }
+1
View File
@@ -344,6 +344,7 @@ export const extractGroupMetadata = (result: BinaryNode) => {
joinApprovalMode: !!getBinaryNodeChild(group, 'membership_approval_mode'), joinApprovalMode: !!getBinaryNodeChild(group, 'membership_approval_mode'),
memberAddMode, memberAddMode,
participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => { participants: getBinaryNodeChildren(group, 'participant').map(({ attrs }) => {
// TODO: Store LID MAPPINGS
return { return {
id: attrs.jid!, id: attrs.jid!,
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined, phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
+44 -11
View File
@@ -5,6 +5,8 @@ import Long from 'long'
import { proto } from '../../WAProto/index.js' import { proto } from '../../WAProto/index.js'
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults' import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults'
import type { import type {
GroupParticipant,
LIDMapping,
MessageReceiptType, MessageReceiptType,
MessageRelayOptions, MessageRelayOptions,
MessageUserReceipt, MessageUserReceipt,
@@ -28,6 +30,7 @@ import {
derivePairingCodeKey, derivePairingCodeKey,
encodeBigEndian, encodeBigEndian,
encodeSignedDeviceIdentity, encodeSignedDeviceIdentity,
extractAddressingContext,
getCallStatusFromNode, getCallStatusFromNode,
getHistoryMsg, getHistoryMsg,
getNextPreKeys, getNextPreKeys,
@@ -51,6 +54,7 @@ import {
isJidGroup, isJidGroup,
isJidStatusBroadcast, isJidStatusBroadcast,
isLidUser, isLidUser,
isPnUser,
jidDecode, jidDecode,
jidNormalizedUser, jidNormalizedUser,
S_WHATSAPP_NET S_WHATSAPP_NET
@@ -550,16 +554,22 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
} }
} }
const handleGroupNotification = (participant: string, child: BinaryNode, msg: Partial<WAMessage>) => { const handleGroupNotification = (fullNode: BinaryNode, child: BinaryNode, msg: Partial<WAMessage>) => {
const participantJid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || participant // TODO: Support PN/LID (Here is only LID now)
// TODO: Add participant LID
const actingParticipantLid = fullNode.attrs.participant
const actingParticipantPn = fullNode.attrs.participant_pn
const affectedParticipantLid = getBinaryNodeChild(child, 'participant')?.attrs?.jid || actingParticipantLid!
const affectedParticipantPn = getBinaryNodeChild(child, 'participant')?.attrs?.phone_number || actingParticipantPn!
switch (child?.tag) { switch (child?.tag) {
case 'create': case 'create':
const metadata = extractGroupMetadata(child) const metadata = extractGroupMetadata(child)
msg.messageStubType = WAMessageStubType.GROUP_CREATE msg.messageStubType = WAMessageStubType.GROUP_CREATE
msg.messageStubParameters = [metadata.subject] msg.messageStubParameters = [metadata.subject]
msg.key = { participant: metadata.owner } msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn }
ev.emit('chats.upsert', [ ev.emit('chats.upsert', [
{ {
@@ -571,7 +581,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
ev.emit('groups.upsert', [ ev.emit('groups.upsert', [
{ {
...metadata, ...metadata,
author: participant author: actingParticipantLid,
authorPn: actingParticipantPn
} }
]) ])
break break
@@ -597,12 +608,22 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}` const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`
msg.messageStubType = WAMessageStubType[stubType as keyof typeof WAMessageStubType] msg.messageStubType = WAMessageStubType[stubType as keyof typeof WAMessageStubType]
const participants = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid!) const participants = getBinaryNodeChildren(child, 'participant').map(({ attrs }) => {
// TODO: Store LID MAPPINGS
return {
id: attrs.jid!,
phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined,
lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined,
admin: (attrs.type || null) as GroupParticipant['admin']
}
})
if ( if (
participants.length === 1 && participants.length === 1 &&
// if recv. "remove" message and sender removed themselves // if recv. "remove" message and sender removed themselves
// mark as left // mark as left
areJidsSameUser(participants[0], participant) && (areJidsSameUser(participants[0]!.id, actingParticipantLid) ||
areJidsSameUser(participants[0]!.id, actingParticipantPn)) &&
child.tag === 'remove' child.tag === 'remove'
) { ) {
msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE msg.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE
@@ -651,12 +672,20 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
break break
case 'created_membership_requests': case 'created_membership_requests':
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD
msg.messageStubParameters = [participantJid, 'created', child.attrs.request_method!] msg.messageStubParameters = [
{ lid: affectedParticipantLid, pn: affectedParticipantPn } as LIDMapping,
'created',
child.attrs.request_method!
]
break break
case 'revoked_membership_requests': case 'revoked_membership_requests':
const isDenied = areJidsSameUser(participantJid, participant) const isDenied = areJidsSameUser(affectedParticipantLid, actingParticipantLid)
// TODO: LIDMAPPING SUPPORT
msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD msg.messageStubType = WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD
msg.messageStubParameters = [participantJid, isDenied ? 'revoked' : 'rejected'] msg.messageStubParameters = [
{ lid: affectedParticipantLid, pn: affectedParticipantPn } as LIDMapping,
isDenied ? 'revoked' : 'rejected'
]
break break
} }
} }
@@ -690,7 +719,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
await handleMexNewsletterNotification(node) await handleMexNewsletterNotification(node)
break break
case 'w:gp2': case 'w:gp2':
handleGroupNotification(node.attrs.participant!, child!, result) // TODO: HANDLE PARTICIPANT_PN
handleGroupNotification(node, child!, result)
break break
case 'mediaretry': case 'mediaretry':
const event = decodeMediaRetryNode(node) const event = decodeMediaRetryNode(node)
@@ -1083,10 +1113,13 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const msg = await processNotification(node) const msg = await processNotification(node)
if (msg) { if (msg) {
const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me!.id) const fromMe = areJidsSameUser(node.attrs.participant || remoteJid, authState.creds.me!.id)
const { senderAlt: participantAlt, addressingMode } = extractAddressingContext(node)
msg.key = { msg.key = {
remoteJid, remoteJid,
fromMe, fromMe,
participant: node.attrs.participant, participant: node.attrs.participant,
participantAlt,
addressingMode,
id: node.attrs.id, id: node.attrs.id,
...(msg.key || {}) ...(msg.key || {})
} }
+16 -2
View File
@@ -4,7 +4,13 @@ import type { AuthenticationCreds } from './Auth'
import type { WACallEvent } from './Call' import type { WACallEvent } from './Call'
import type { Chat, ChatUpdate, PresenceData } from './Chat' import type { Chat, ChatUpdate, PresenceData } from './Chat'
import type { Contact } from './Contact' import type { Contact } from './Contact'
import type { GroupMetadata, ParticipantAction, RequestJoinAction, RequestJoinMethod } from './GroupMetadata' import type {
GroupMetadata,
GroupParticipant,
ParticipantAction,
RequestJoinAction,
RequestJoinMethod
} from './GroupMetadata'
import type { Label } from './Label' import type { Label } from './Label'
import type { LabelAssociation } from './LabelAssociation' import type { LabelAssociation } from './LabelAssociation'
import type { MessageUpsertType, MessageUserReceiptUpdate, WAMessage, WAMessageKey, WAMessageUpdate } from './Message' import type { MessageUpsertType, MessageUserReceiptUpdate, WAMessage, WAMessageKey, WAMessageUpdate } from './Message'
@@ -56,11 +62,19 @@ export type BaileysEventMap = {
'groups.upsert': GroupMetadata[] 'groups.upsert': GroupMetadata[]
'groups.update': Partial<GroupMetadata>[] 'groups.update': Partial<GroupMetadata>[]
/** apply an action to participants in a group */ /** apply an action to participants in a group */
'group-participants.update': { id: string; author: string; participants: string[]; action: ParticipantAction } 'group-participants.update': {
id: string
author: string
authorPn?: string
participants: GroupParticipant[]
action: ParticipantAction
}
'group.join-request': { 'group.join-request': {
id: string id: string
author: string author: string
authorPn?: string
participant: string participant: string
participantPn?: string
action: RequestJoinAction action: RequestJoinAction
method: RequestJoinMethod method: RequestJoinMethod
} }
+1
View File
@@ -55,6 +55,7 @@ export interface GroupMetadata {
inviteCode?: string inviteCode?: string
/** the person who added you to group or changed some setting in group */ /** the person who added you to group or changed some setting in group */
author?: string author?: string
authorPn?: string
} }
export interface WAGroupCreateResponse { export interface WAGroupCreateResponse {
+1 -1
View File
@@ -8,7 +8,7 @@ import type { CacheStore } from './Socket'
// export the WAMessage Prototypes // export the WAMessage Prototypes
export { proto as WAProto } export { proto as WAProto }
export type WAMessage = proto.IWebMessageInfo & { key: WAMessageKey } export type WAMessage = proto.IWebMessageInfo & { key: WAMessageKey; messageStubParameters?: any }
export type WAMessageContent = proto.IMessage export type WAMessageContent = proto.IMessage
export type WAContactMessage = proto.Message.IContactMessage export type WAContactMessage = proto.Message.IContactMessage
export type WAContactsArrayMessage = proto.Message.IContactsArrayMessage export type WAContactsArrayMessage = proto.Message.IContactsArrayMessage
+1 -1
View File
@@ -545,7 +545,7 @@ function append<E extends BufferableEvent>(
const chatId = message.key.remoteJid! const chatId = message.key.remoteJid!
const chat = data.chatUpdates[chatId] || data.chatUpserts[chatId] const chat = data.chatUpdates[chatId] || data.chatUpserts[chatId]
if ( if (
isRealMessage(message, '') && isRealMessage(message) &&
shouldIncrementChatUnread(message) && shouldIncrementChatUnread(message) &&
typeof chat?.unreadCount === 'number' && typeof chat?.unreadCount === 'number' &&
chat.unreadCount > 0 chat.unreadCount > 0
+30 -12
View File
@@ -5,6 +5,8 @@ import type {
CacheStore, CacheStore,
Chat, Chat,
GroupMetadata, GroupMetadata,
GroupParticipant,
LIDMapping,
ParticipantAction, ParticipantAction,
RequestJoinAction, RequestJoinAction,
RequestJoinMethod, RequestJoinMethod,
@@ -101,14 +103,14 @@ export const cleanMessage = (message: WAMessage, meId: string) => {
} }
} }
export const isRealMessage = (message: WAMessage, meId: string) => { // TODO: target:audit AUDIT THIS FUNCTION AGAIN
export const isRealMessage = (message: WAMessage) => {
const normalizedContent = normalizeMessageContent(message.message) const normalizedContent = normalizeMessageContent(message.message)
const hasSomeContent = !!getContentType(normalizedContent) const hasSomeContent = !!getContentType(normalizedContent)
return ( return (
(!!normalizedContent || (!!normalizedContent ||
REAL_MSG_STUB_TYPES.has(message.messageStubType!) || REAL_MSG_STUB_TYPES.has(message.messageStubType!) ||
(REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType!) && REAL_MSG_REQ_ME_STUB_TYPES.has(message.messageStubType!)) &&
message.messageStubParameters?.some(p => areJidsSameUser(meId, p)))) &&
hasSomeContent && hasSomeContent &&
!normalizedContent?.protocolMessage && !normalizedContent?.protocolMessage &&
!normalizedContent?.reactionMessage && !normalizedContent?.reactionMessage &&
@@ -188,7 +190,7 @@ const processMessage = async (
const { accountSettings } = creds const { accountSettings } = creds
const chat: Partial<Chat> = { id: jidNormalizedUser(getChatId(message.key)) } const chat: Partial<Chat> = { id: jidNormalizedUser(getChatId(message.key)) }
const isRealMsg = isRealMessage(message, meId) const isRealMsg = isRealMessage(message)
if (isRealMsg) { if (isRealMsg) {
chat.messages = [{ message }] chat.messages = [{ message }]
@@ -362,18 +364,34 @@ const processMessage = async (
} else if (message.messageStubType) { } else if (message.messageStubType) {
const jid = message.key?.remoteJid! const jid = message.key?.remoteJid!
//let actor = whatsappID (message.participant) //let actor = whatsappID (message.participant)
let participants: string[] let participants: GroupParticipant[]
const emitParticipantsUpdate = (action: ParticipantAction) => const emitParticipantsUpdate = (action: ParticipantAction) =>
ev.emit('group-participants.update', { id: jid, author: message.participant!, participants, action }) ev.emit('group-participants.update', {
id: jid,
author: message.key.participant!,
authorPn: message.key.participantAlt!,
participants,
action
})
const emitGroupUpdate = (update: Partial<GroupMetadata>) => { const emitGroupUpdate = (update: Partial<GroupMetadata>) => {
ev.emit('groups.update', [{ id: jid, ...update, author: message.participant ?? undefined }]) ev.emit('groups.update', [
{ id: jid, ...update, author: message.key.participant ?? undefined, authorPn: message.key.participantAlt }
])
} }
const emitGroupRequestJoin = (participant: string, action: RequestJoinAction, method: RequestJoinMethod) => { const emitGroupRequestJoin = (participant: LIDMapping, action: RequestJoinAction, method: RequestJoinMethod) => {
ev.emit('group.join-request', { id: jid, author: message.participant!, participant, action, method: method! }) ev.emit('group.join-request', {
id: jid,
author: message.key.participant!,
authorPn: message.key.participantAlt!,
participant: participant.lid,
participantPn: participant.pn,
action,
method: method!
})
} }
const participantsIncludesMe = () => participants.find(jid => areJidsSameUser(meId, jid)) const participantsIncludesMe = () => participants.find(jid => areJidsSameUser(meId, jid.phoneNumber)) // ADD SUPPORT FOR LID
switch (message.messageStubType) { switch (message.messageStubType) {
case WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER: case WAMessageStubType.GROUP_PARTICIPANT_CHANGE_NUMBER:
@@ -438,8 +456,8 @@ const processMessage = async (
const approvalMode = message.messageStubParameters?.[0] const approvalMode = message.messageStubParameters?.[0]
emitGroupUpdate({ joinApprovalMode: approvalMode === 'on' }) emitGroupUpdate({ joinApprovalMode: approvalMode === 'on' })
break break
case WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD: case WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD: // TODO: Add other events
const participant = message.messageStubParameters?.[0] as string const participant = message.messageStubParameters?.[0] as LIDMapping
const action = message.messageStubParameters?.[1] as RequestJoinAction const action = message.messageStubParameters?.[1] as RequestJoinAction
const method = message.messageStubParameters?.[2] as RequestJoinMethod const method = message.messageStubParameters?.[2] as RequestJoinMethod
emitGroupRequestJoin(participant, action, method) emitGroupRequestJoin(participant, action, method)