Skip to content

Commit 9778321

Browse files
committed
feat(events): add umbrella message event and fix quoted context resolution
Emit a single `message` event for any inbound message type. Quoted/replied context now carries media, resolves sender LID->PN via self-LID and parent sender, and inherits the parent chat room (id + name).
1 parent 3262956 commit 9778321

7 files changed

Lines changed: 246 additions & 44 deletions

File tree

src/client/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,8 @@ export class Client extends TypedEventEmitter<ClientEventMap> {
700700
this.inboundHandle?.detach()
701701
this.inboundHandle = attachInboundPipeline(this, socket as unknown as PipelineSocketLike, {
702702
selfJid: typeof me.id === 'string' ? me.id : '',
703+
...(typeof me.lid === 'string' && me.lid.length > 0 ? { selfLid: me.lid } : {}),
704+
...(typeof me.name === 'string' && me.name.length > 0 ? { selfName: me.name } : {}),
703705
channelId: this.sessionId,
704706
receiverId: typeof me.id === 'string' ? me.id : '',
705707
prefixes: this.commandPrefixes,

src/events/context.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,12 @@ export const buildMessageContext = (input: BuildContextInput): MessageContext =>
299299
channelId: input.channelId,
300300
chatId: input.key.id ?? '',
301301
chatType: input.chatType,
302-
receiverId: input.receiverId,
303-
roomId: isGroup ? remoteJid : null,
302+
receiverId: input.receiverId ? jidNormalizedUser(input.receiverId) : input.receiverId,
303+
roomId: isGroup
304+
? (remoteJid ? jidNormalizedUser(remoteJid) : null)
305+
: input.key.fromMe === true && remoteJid
306+
? jidNormalizedUser(remoteJid)
307+
: (input.sender.pn ?? input.sender.jid),
304308
senderId: input.sender.pn ?? input.sender.jid,
305309
senderLid: input.sender.lid ?? null,
306310
senderName: input.sender.pushName ?? null,

src/events/decoders/messages.ts

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import {
55
type ChatType,
66
type CitationConfig,
77
type ContextMedia,
8+
type MediaAttachment,
89
type MentionAllContext,
910
type MentionContext,
1011
type MessageContext,
1112
} from '../context.js'
12-
import type { MediaKind } from '../types.js'
13+
import type { MediaKind, SenderInfo } from '../types.js'
1314
import { createDownloadFn, createStreamFn, type DownloadLogger } from './_media-download.js'
1415
import {
1516
extractMentions,
@@ -21,6 +22,8 @@ import {
2122

2223
export interface DecodeContext {
2324
selfJid: string
25+
selfLid?: string
26+
selfName?: string
2427
logger?: DownloadLogger
2528
channelId?: string
2629
receiverId?: string
@@ -444,10 +447,19 @@ const chatTypeOf = (content: WAMessage['message']): ChatType => {
444447
return 'text'
445448
}
446449

450+
const sameAuthor = (a: SenderInfo | undefined, b: SenderInfo): boolean => {
451+
if (a == null) return false
452+
const av = [a.pn, a.lid, a.jid].filter((v): v is string => typeof v === 'string' && v.length > 0)
453+
const bv = [b.pn, b.lid, b.jid].filter((v): v is string => typeof v === 'string' && v.length > 0)
454+
return av.some((x) => bv.includes(x))
455+
}
456+
447457
const decodeQuotedContext = async (
448458
contextInfo: WAContextInfo | null,
449459
ctx: DecodeContext,
450460
parentRemoteJid: string,
461+
parentSender?: SenderInfo,
462+
parentRoomName?: () => Promise<string | null>,
451463
): Promise<MessageContext | null> => {
452464
try {
453465
if (contextInfo == null) return null
@@ -459,31 +471,44 @@ const decodeQuotedContext = async (
459471
const original = await ctx.resolveQuoted(stanzaId, parentRemoteJid)
460472
if (original != null && original.message != null) {
461473
const originalText = anyText(original) ?? ''
462-
const fromStore = buildContext(original, ctx, chatTypeOf(original.message), originalText)
474+
const fromStore = buildContext(original, ctx, chatTypeOf(original.message), originalText, mediaOf(original, ctx), parentRoomName)
463475
if (fromStore !== null) return fromStore
464476
}
465477
}
466478

467479
const quoted = extractQuoted(contextInfo)
468480
if (quoted === null) return null
469481
if (typeof quoted.key.id !== 'string' || quoted.key.id.length === 0) return null
470-
const remoteJid = quoted.key.remoteJid
471-
if (typeof remoteJid !== 'string' || remoteJid.length === 0) return null
472-
const participant = quoted.key.participant
473-
if (
474-
typeof participant === 'string' &&
475-
ctx.selfJid.length > 0 &&
476-
jidNormalizedUser(participant) === jidNormalizedUser(ctx.selfJid)
477-
) {
482+
483+
if (parentRemoteJid.length > 0) quoted.key.remoteJid = parentRemoteJid
484+
if (typeof quoted.key.remoteJid !== 'string' || quoted.key.remoteJid.length === 0) return null
485+
486+
const author = quoted.key.participant
487+
const isSelf =
488+
typeof author === 'string' &&
489+
((ctx.selfJid.length > 0 && normalizedEquals(author, ctx.selfJid)) ||
490+
(ctx.selfLid != null && normalizedEquals(author, ctx.selfLid)))
491+
const parentIsGroup = isGroupJid(parentRemoteJid)
492+
493+
let pushName = quoted.sender?.pushName
494+
if (isSelf) {
478495
quoted.key.fromMe = true
496+
if (ctx.selfJid.length > 0) quoted.key.participant = ctx.selfJid
497+
if (ctx.selfLid != null) quoted.key.participantAlt = ctx.selfLid
498+
if (pushName == null) pushName = ctx.selfName
499+
} else if (parentSender != null && (!parentIsGroup || sameAuthor(quoted.sender, parentSender))) {
500+
if (parentSender.pn != null) quoted.key.participant = parentSender.pn
501+
if (parentSender.lid != null) quoted.key.participantAlt = parentSender.lid
502+
if (pushName == null) pushName = parentSender.pushName
479503
}
504+
480505
const qm = contextInfo.quotedMessage as WAMessage['message']
481506
const reconstructed = Object.assign(
482507
{ key: quoted.key, message: qm ?? null },
483-
quoted.sender?.pushName != null ? { pushName: quoted.sender.pushName } : {},
508+
pushName != null ? { pushName } : {},
484509
) as WAMessage
485510
const text = anyText(reconstructed) ?? ''
486-
return buildContext(reconstructed, ctx, chatTypeOf(qm), text)
511+
return buildContext(reconstructed, ctx, chatTypeOf(qm), text, mediaOf(reconstructed, ctx), parentRoomName)
487512
} catch {
488513
return null
489514
}
@@ -495,6 +520,7 @@ const buildContext = (
495520
chatType: ChatType,
496521
text: string,
497522
media?: ContextMedia,
523+
roomNameOverride?: () => Promise<string | null>,
498524
): MessageContext | null => {
499525
const key = msg.key
500526
if (key == null) return null
@@ -516,16 +542,16 @@ const buildContext = (
516542
const receiverId = ctx.receiverId ?? ''
517543
const prefixes = ctx.prefixes ?? []
518544

519-
const resolveRoomName = (): Promise<string | null> =>
520-
isGroup && ctx.resolveRoomName != null
521-
? ctx.resolveRoomName(jid)
522-
: Promise.resolve(null)
545+
const resolveRoomName = roomNameOverride ?? ((): Promise<string | null> =>
546+
isGroup
547+
? (ctx.resolveRoomName != null ? ctx.resolveRoomName(jid) : Promise.resolve(null))
548+
: Promise.resolve(sender.pushName ?? null))
523549

524550
const resolveReceiverName: () => Promise<string | null> =
525551
ctx.resolveReceiverName ?? (() => Promise.resolve(null))
526552

527553
const resolveReplied = (): Promise<MessageContext | null> =>
528-
decodeQuotedContext(contextInfo, ctx, jid)
554+
decodeQuotedContext(contextInfo, ctx, jid, sender, resolveRoomName)
529555

530556
const replyTarget = jid.length > 0 ? jid : (sender.pn ?? sender.jid)
531557
const reply = (content: string, opts?: TextOptions): Promise<WAMessageKey> => {
@@ -573,43 +599,67 @@ const buildContext = (
573599
)
574600
}
575601

576-
export const decodeText = (msg: WAMessage, ctx: DecodeContext): MessageContext | null => {
577-
const content = asRecord(msg.message)
578-
if (content == null) return null
579-
const inner = unwrap(content)
580-
const body = bodyText(inner)
581-
const media = structuredMedia(inner) ?? undefined
582-
if (body === null && media === undefined) return null
583-
return buildContext(msg, ctx, chatTypeOf(msg.message), body ?? '', media)
584-
}
602+
const MEDIA_KINDS: readonly string[] = ['image', 'video', 'audio', 'document', 'sticker']
585603

586-
const decodeMedia = <K extends MediaKind>(
587-
kind: K,
604+
const buildMediaAttachment = (
588605
msg: WAMessage,
606+
kind: MediaKind,
589607
ctx: DecodeContext,
590-
): MessageContext | null => {
608+
): MediaAttachment | null => {
591609
const node = mediaNodeOf(msg, kind)
592610
if (node === null) return null
593-
const caption = typeof node.caption === 'string' ? node.caption : ''
594-
const chatType = kind as ChatType
595-
596611
const bufferFn = createDownloadFn(msg, kind, ctx.logger)
597612
const streamFn = createStreamFn(msg, kind, ctx.logger)
598-
const media: ContextMedia = {
613+
return {
599614
type: kind,
600615
mimetype: typeof node.mimetype === 'string' ? node.mimetype : null,
601616
caption: typeof node.caption === 'string' ? node.caption : null,
602617
fileName: typeof node.fileName === 'string' ? node.fileName : null,
603618
fileSize: toNum(node.fileLength),
604619
ptt: node.ptt === true,
605-
buffer: async () => {
606-
const result = await bufferFn()
607-
return result.buffer
608-
},
620+
buffer: async () => (await bufferFn()).buffer,
609621
stream: streamFn,
610622
}
623+
}
624+
625+
const mediaOf =(msg: WAMessage, ctx: DecodeContext): ContextMedia | undefined => {
626+
const content = asRecord(msg.message)
627+
if (content == null) return undefined
628+
const chatType = chatTypeOf(msg.message)
629+
if (MEDIA_KINDS.includes(chatType)) {
630+
return buildMediaAttachment(msg, chatType as MediaKind, ctx) ?? undefined
631+
}
632+
return structuredMedia(unwrap(content)) ?? undefined
633+
}
634+
635+
export const decodeMessage = (msg: WAMessage, ctx: DecodeContext): MessageContext | null => {
636+
const content = asRecord(msg.message)
637+
if (content == null) return null
638+
const chatType = chatTypeOf(msg.message)
639+
const media = mediaOf(msg, ctx)
640+
const text = anyText(msg) ?? ''
641+
if (text === '' && media === undefined) return null
642+
return buildContext(msg, ctx, chatType, text, media)
643+
}
644+
645+
export const decodeText = (msg: WAMessage, ctx: DecodeContext): MessageContext | null => {
646+
const content = asRecord(msg.message)
647+
if (content == null) return null
648+
const inner = unwrap(content)
649+
const body = bodyText(inner)
650+
const media = structuredMedia(inner) ?? undefined
651+
if (body === null && media === undefined) return null
652+
return buildContext(msg, ctx, chatTypeOf(msg.message), body ?? '', media)
653+
}
611654

612-
return buildContext(msg, ctx, chatType, caption, media)
655+
const decodeMedia = <K extends MediaKind>(
656+
kind: K,
657+
msg: WAMessage,
658+
ctx: DecodeContext,
659+
): MessageContext | null => {
660+
const media = buildMediaAttachment(msg, kind, ctx)
661+
if (media === null) return null
662+
return buildContext(msg, ctx, kind as ChatType, media.caption ?? '', media)
613663
}
614664

615665
export const decodeImage = (msg: WAMessage, ctx: DecodeContext): MessageContext | null =>
@@ -646,7 +696,7 @@ export const decodeMention = (msg: WAMessage, ctx: DecodeContext): MentionContex
646696
const content = asRecord(msg.message)
647697
if (content == null) return null
648698
const text = anyText(msg) ?? ''
649-
const media = structuredMedia(unwrap(content)) ?? undefined
699+
const media = mediaOf(msg, ctx)
650700
const base = buildContext(msg, ctx, chatTypeOf(msg.message), text, media)
651701
if (base === null) return null
652702
return { ...base, mentionedJids, selfJid: ctx.selfJid }
@@ -662,7 +712,7 @@ export const decodeMentionAll = (msg: WAMessage, ctx: DecodeContext): MentionAll
662712
const content = asRecord(msg.message)
663713
if (content == null) return null
664714
const text = anyText(msg) ?? ''
665-
const media = structuredMedia(unwrap(content)) ?? undefined
715+
const media = mediaOf(msg, ctx)
666716
const base = buildContext(msg, ctx, chatTypeOf(msg.message), text, media)
667717
if (base === null) return null
668718
return { ...base, isMentionAll: true, selfJid: ctx.selfJid }

src/events/pipeline.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
decodeImage,
3939
decodeMention,
4040
decodeMentionAll,
41+
decodeMessage,
4142
decodeSticker,
4243
decodeText,
4344
decodeVideo,
@@ -59,6 +60,8 @@ export interface InboundPipelineHandle {
5960

6061
export interface InboundPipelineContext {
6162
selfJid: string
63+
selfLid?: string
64+
selfName?: string
6265
logger?: Logger
6366
channelId?: string
6467
receiverId?: string
@@ -112,6 +115,8 @@ export function attachInboundPipeline(
112115
: undefined
113116
const decodeCtx: DecodeContext = {
114117
selfJid: ctx.selfJid,
118+
...(ctx.selfLid != null ? { selfLid: ctx.selfLid } : {}),
119+
...(ctx.selfName != null ? { selfName: ctx.selfName } : {}),
115120
receiverId: ctx.receiverId ?? ctx.selfJid,
116121
...(ctx.logger != null ? { logger: ctx.logger } : {}),
117122
...(ctx.channelId != null ? { channelId: ctx.channelId } : {}),
@@ -141,6 +146,7 @@ export function attachInboundPipeline(
141146
}
142147

143148
const runMessage = (msg: WAMessage): void => {
149+
tryEmit(() => decodeMessage(msg, decodeCtx), (p) => client.emit('message', p))
144150
tryEmit(() => decodeText(msg, decodeCtx), (p) => client.emit('text', p))
145151
tryEmit(() => decodeImage(msg, decodeCtx), (p) => client.emit('image', p))
146152
tryEmit(() => decodeVideo(msg, decodeCtx), (p) => client.emit('video', p))

src/events/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export type NewsletterPayload = {
160160
)
161161

162162
export type InboundEventMap = {
163+
message: MessageContext
163164
text: MessageContext
164165
image: MessageContext
165166
video: MessageContext

0 commit comments

Comments
 (0)