Skip to content

Commit 5d4a106

Browse files
committed
feat(events): derive message flags from content
Populate isEdited, isDeleted, isPinned, isUnPinned, isBot, isStatusMention, isGroupStatusMention, isStory, and isHideTags from the message instead of hardcoding them to false.
1 parent 91cba73 commit 5d4a106

2 files changed

Lines changed: 88 additions & 9 deletions

File tree

src/events/context.ts

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,49 @@ export const makeCitation = (
314314
}
315315
}
316316

317+
interface MessageFlags {
318+
isEdited: boolean
319+
isDeleted: boolean
320+
isPinned: boolean
321+
isUnPinned: boolean
322+
isBot: boolean
323+
isStatusMention: boolean
324+
isGroupStatusMention: boolean
325+
}
326+
327+
const PROTOCOL_REVOKE = 0
328+
const PROTOCOL_MESSAGE_EDIT = 14
329+
const PIN_FOR_ALL = 1
330+
const UNPIN_FOR_ALL = 2
331+
332+
const asRecord = (value: unknown): Record<string, unknown> | null =>
333+
value != null && typeof value === 'object' ? (value as Record<string, unknown>) : null
334+
335+
const deriveFlags = (message: WAMessage): MessageFlags => {
336+
const content = asRecord(message.message)
337+
if (content == null) {
338+
return { isEdited: false, isDeleted: false, isPinned: false, isUnPinned: false, isBot: false, isStatusMention: false, isGroupStatusMention: false }
339+
}
340+
const protocol = asRecord(content['protocolMessage'])
341+
const protoType = typeof protocol?.['type'] === 'number' ? protocol['type'] : undefined
342+
const pinType = typeof asRecord(content['pinInChatMessage'])?.['type'] === 'number'
343+
? (content['pinInChatMessage'] as { type: number }).type
344+
: undefined
345+
return {
346+
isEdited: content['editedMessage'] != null || protoType === PROTOCOL_MESSAGE_EDIT,
347+
isDeleted: protoType === PROTOCOL_REVOKE,
348+
isPinned: pinType === PIN_FOR_ALL,
349+
isUnPinned: pinType === UNPIN_FOR_ALL,
350+
isBot: asRecord(content['messageContextInfo'])?.['botMetadata'] != null,
351+
isStatusMention: content['statusMentionMessage'] != null,
352+
isGroupStatusMention: content['groupStatusMentionMessage'] != null,
353+
}
354+
}
355+
317356
export const buildMessageContext = (input: BuildContextInput): MessageContext => {
318357
const remoteJid = typeof input.key.remoteJid === 'string' ? input.key.remoteJid : null
319358
const isGroup = remoteJid !== null && isGroupJid(remoteJid)
359+
const flags = deriveFlags(input.message)
320360
const senderId = input.sender.pn ?? input.sender.jid
321361
const roomId = isGroup
322362
? (remoteJid ? jidNormalizedUser(remoteJid) : null)
@@ -350,16 +390,16 @@ export const buildMessageContext = (input: BuildContextInput): MessageContext =>
350390
isQuestion: isQuestionOf(input.text),
351391
isPrefix: isPrefixOf(input.text, input.prefixes),
352392
isTagMe: isTagMeOf(input.selfJid, input.mentions),
353-
isEdited: false,
354-
isDeleted: false,
355-
isPinned: false,
356-
isUnPinned: false,
357-
isBot: false,
393+
isEdited: flags.isEdited,
394+
isDeleted: flags.isDeleted,
395+
isPinned: flags.isPinned,
396+
isUnPinned: flags.isUnPinned,
397+
isBot: flags.isBot,
358398
isSpam: false,
359-
isHideTags: false,
360-
isStatusMention: false,
361-
isGroupStatusMention: false,
362-
isStory: false,
399+
isHideTags: input.mentions.length > 0 && !/@\d/.test(input.text),
400+
isStatusMention: flags.isStatusMention,
401+
isGroupStatusMention: flags.isGroupStatusMention,
402+
isStory: remoteJid === 'status@broadcast',
363403
roomName: input.resolveRoomName,
364404
receiverName: input.resolveReceiverName,
365405
replied: input.resolveReplied,

tests/events/context.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,45 @@ describe('buildMessageContext', () => {
271271
expect(ctx.isStory).toBe(false)
272272
})
273273

274+
const withMessage = (m: Record<string, unknown>) => ({
275+
...baseInput(),
276+
message: { key: MSG_KEY, messageTimestamp: 1700000000, pushName: 'Alice', message: m } as never,
277+
})
278+
279+
it('isDeleted when protocolMessage is REVOKE', () => {
280+
expect(buildMessageContext(withMessage({ protocolMessage: { type: 0 } })).isDeleted).toBe(true)
281+
})
282+
283+
it('isEdited from MESSAGE_EDIT protocol or editedMessage wrapper', () => {
284+
expect(buildMessageContext(withMessage({ protocolMessage: { type: 14 } })).isEdited).toBe(true)
285+
expect(buildMessageContext(withMessage({ editedMessage: { message: {} } })).isEdited).toBe(true)
286+
})
287+
288+
it('isPinned / isUnPinned from pinInChatMessage type', () => {
289+
expect(buildMessageContext(withMessage({ pinInChatMessage: { type: 1 } })).isPinned).toBe(true)
290+
expect(buildMessageContext(withMessage({ pinInChatMessage: { type: 2 } })).isUnPinned).toBe(true)
291+
})
292+
293+
it('isBot when messageContextInfo carries botMetadata', () => {
294+
expect(buildMessageContext(withMessage({ conversation: 'hi', messageContextInfo: { botMetadata: { botName: 'x' } } })).isBot).toBe(true)
295+
})
296+
297+
it('isStatusMention / isGroupStatusMention from message fields', () => {
298+
expect(buildMessageContext(withMessage({ statusMentionMessage: {} })).isStatusMention).toBe(true)
299+
expect(buildMessageContext(withMessage({ groupStatusMentionMessage: {} })).isGroupStatusMention).toBe(true)
300+
})
301+
302+
it('isStory when remoteJid is status@broadcast', () => {
303+
const key = { remoteJid: 'status@broadcast', id: 'S1', fromMe: false }
304+
const ctx = buildMessageContext({ ...baseInput(), key, message: { key, messageTimestamp: 1700000000, message: { conversation: 'hi' } } as never })
305+
expect(ctx.isStory).toBe(true)
306+
})
307+
308+
it('isHideTags when mentions exist but text has no @number', () => {
309+
expect(buildMessageContext({ ...baseInput(), text: 'halo semua', mentions: ['628999@s.whatsapp.net'] }).isHideTags).toBe(true)
310+
expect(buildMessageContext({ ...baseInput(), text: 'halo @628999', mentions: ['628999@s.whatsapp.net'] }).isHideTags).toBe(false)
311+
})
312+
274313
it('isQuestion flag is set from text', () => {
275314
const ctx = buildMessageContext({ ...baseInput(), text: 'is this a test?' })
276315
expect(ctx.isQuestion).toBe(true)

0 commit comments

Comments
 (0)