Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f4e83a8af | |||
| d870ffb2a3 | |||
| f6c72530a2 | |||
| a4e7422487 | |||
| c6511d3034 |
@@ -1277,21 +1277,48 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
: otherRecipients
|
: otherRecipients
|
||||||
const effectiveAllRecipients = [...effectiveMeRecipients, ...effectiveOtherRecipients]
|
const effectiveAllRecipients = [...effectiveMeRecipients, ...effectiveOtherRecipients]
|
||||||
|
|
||||||
await assertSessions(effectiveAllRecipients)
|
// P2: detect actual view-once media by checking inner message's viewOnce flag.
|
||||||
|
// viewOnceMessage wrapper is also used for interactive messages (buttons, lists, etc)
|
||||||
|
// which do NOT carry viewOnce=true on the media — those must NOT be filtered.
|
||||||
|
const viewOnceInner =
|
||||||
|
message.viewOnceMessageV2?.message ||
|
||||||
|
message.viewOnceMessage?.message ||
|
||||||
|
message.viewOnceMessageV2Extension?.message
|
||||||
|
const isViewOnceMsg = !!(
|
||||||
|
viewOnceInner?.imageMessage?.viewOnce ||
|
||||||
|
viewOnceInner?.videoMessage?.viewOnce ||
|
||||||
|
viewOnceInner?.audioMessage?.viewOnce
|
||||||
|
)
|
||||||
|
|
||||||
|
// For view-once: only send DSM to primary phone (device=0).
|
||||||
|
// Companion devices (device>0) are omitted — WA server generates
|
||||||
|
// <unavailable type="view_once"/> for them automatically.
|
||||||
|
// Sending explicit <unavailable> from a companion is rejected by the server.
|
||||||
|
const viewOnceMeRecipients = isViewOnceMsg
|
||||||
|
? effectiveMeRecipients.filter(jid => !jidDecode(jid)?.device)
|
||||||
|
: effectiveMeRecipients
|
||||||
|
|
||||||
|
// P3: assert sessions only for recipients we actually encrypt for.
|
||||||
|
// For view-once, companions are omitted — asserting their sessions is wasteful
|
||||||
|
// and could block the send if a companion session is corrupted.
|
||||||
|
await assertSessions([...viewOnceMeRecipients, ...effectiveOtherRecipients])
|
||||||
|
|
||||||
const [
|
const [
|
||||||
{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
|
{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
|
||||||
{ nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
|
{ nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
// For own devices: use DSM (deviceSentMessage) wrapper
|
// For own devices: use DSM (deviceSentMessage) wrapper
|
||||||
createParticipantNodes(effectiveMeRecipients, meMsg || message, extraAttrs),
|
createParticipantNodes(viewOnceMeRecipients, meMsg || message, extraAttrs),
|
||||||
createParticipantNodes(effectiveOtherRecipients, message, extraAttrs)
|
createParticipantNodes(effectiveOtherRecipients, message, extraAttrs)
|
||||||
])
|
])
|
||||||
participants.push(...meNodes)
|
participants.push(...meNodes)
|
||||||
participants.push(...otherNodes)
|
participants.push(...otherNodes)
|
||||||
|
|
||||||
if (effectiveMeRecipients.length > 0 || effectiveOtherRecipients.length > 0) {
|
const phashRecipients = isViewOnceMsg
|
||||||
extraAttrs['phash'] = generateParticipantHashV2(effectiveAllRecipients)
|
? [...viewOnceMeRecipients, ...effectiveOtherRecipients]
|
||||||
|
: effectiveAllRecipients
|
||||||
|
if (phashRecipients.length > 0) {
|
||||||
|
extraAttrs['phash'] = generateParticipantHashV2(phashRecipients)
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2
|
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2
|
||||||
@@ -1897,6 +1924,15 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getMediaType = (message: proto.IMessage) => {
|
const getMediaType = (message: proto.IMessage) => {
|
||||||
|
// For view-once media, unwrap the viewOnceMessage wrapper before checking media type
|
||||||
|
const inner =
|
||||||
|
message.viewOnceMessage?.message ||
|
||||||
|
message.viewOnceMessageV2?.message ||
|
||||||
|
message.viewOnceMessageV2Extension?.message
|
||||||
|
if (inner) {
|
||||||
|
return getMediaType(inner)
|
||||||
|
}
|
||||||
|
|
||||||
if (message.imageMessage) {
|
if (message.imageMessage) {
|
||||||
return 'image'
|
return 'image'
|
||||||
} else if (message.videoMessage) {
|
} else if (message.videoMessage) {
|
||||||
|
|||||||
@@ -392,6 +392,23 @@ export const decryptMessageNode = (
|
|||||||
} else {
|
} else {
|
||||||
fullMessage.message = msg
|
fullMessage.message = msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect view-once media on stanza 1 received by linked device.
|
||||||
|
// viewOnceMessage wrapper is also used for interactive messages
|
||||||
|
// (interactiveMessage, listMessage, nativeFlowMessage) -- those do NOT have
|
||||||
|
// imageMessage.viewOnce / videoMessage.viewOnce / audioMessage.viewOnce = true.
|
||||||
|
// Only real view-once media carries viewOnce: true on the inner media message.
|
||||||
|
const viewOnceInner =
|
||||||
|
msg.viewOnceMessage?.message ||
|
||||||
|
msg.viewOnceMessageV2?.message ||
|
||||||
|
msg.viewOnceMessageV2Extension?.message
|
||||||
|
if (
|
||||||
|
viewOnceInner?.imageMessage?.viewOnce ||
|
||||||
|
viewOnceInner?.videoMessage?.viewOnce ||
|
||||||
|
viewOnceInner?.audioMessage?.viewOnce
|
||||||
|
) {
|
||||||
|
fullMessage.key.isViewOnce = true
|
||||||
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// Check if this is a final failure after all retries exhausted
|
// Check if this is a final failure after all retries exhausted
|
||||||
const isRetryExhausted = err instanceof RetryExhaustedError
|
const isRetryExhausted = err instanceof RetryExhaustedError
|
||||||
|
|||||||
Reference in New Issue
Block a user