feat: groups mention all + fix view-once upload path and wrapper

- Add mentionAll?: boolean to Mentionable type (upstream 6afde71691)
- Set contextInfo.nonJidMentions=1 for @everyone group mentions
- Use viewOnceMessageV2 (field 55) wrapper instead of legacy viewOnceMessage (field 37)
- Set viewOnce=true on inner imageMessage/videoMessage/audioMessage
- Route view-once media uploads to /o1/mms/* CDN via mediaTypeOverride

Co-Authored-By: Renato Alcará
This commit is contained in:
Renato Alcara
2026-03-23 01:52:43 -03:00
parent 5926da9493
commit c6cb29fd80
2 changed files with 41 additions and 9 deletions
+3
View File
@@ -155,6 +155,7 @@ export type MessageReceiptType =
| 'sender' | 'sender'
| 'inactive' | 'inactive'
| 'played' | 'played'
| 'view_once_read'
| undefined | undefined
export type MediaConnInfo = { export type MediaConnInfo = {
@@ -178,6 +179,8 @@ export interface WAUrlInfo {
type Mentionable = { type Mentionable = {
/** list of jids that are mentioned in the accompanying text */ /** list of jids that are mentioned in the accompanying text */
mentions?: string[] mentions?: string[]
/** mention all participants in the group */
mentionAll?: boolean
} }
type Contextable = { type Contextable = {
/** add contextInfo to the message */ /** add contextInfo to the message */
+38 -9
View File
@@ -254,7 +254,9 @@ export const prepareWAMessageMedia = async (
(async () => { (async () => {
const result = await options.upload(encFilePath, { const result = await options.upload(encFilePath, {
fileEncSha256B64, fileEncSha256B64,
mediaType, // Use mediaTypeOverride for upload path (e.g. 'viewonce-image' → /o1/mms/image)
// Encryption already used the override via encryptedStream above
mediaType: options.mediaTypeOverride || mediaType,
timeoutMs: options.mediaUploadTimeoutMs timeoutMs: options.mediaUploadTimeoutMs
}) })
logger?.debug({ mediaType, cacheableKey }, 'uploaded media') logger?.debug({ mediaType, cacheableKey }, 'uploaded media')
@@ -1747,22 +1749,49 @@ export const generateWAMessageContent = async (
} }
} }
} else { } else {
m = await prepareWAMessageMedia(message, options) // For view-once media, upload to the one-time CDN (/o1/mms/*) instead of regular CDN
// WA Web view-once messages always have directPath: /o1/v/t24/... (not /v/t62.7118-24/...)
// Using the wrong CDN causes WA server to flag the account and breaks view-once capability
const isViewOnce = hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce
const viewOnceTypeMap: Record<string, string> = {
image: 'viewonce-image',
video: 'viewonce-video',
audio: 'viewonce-audio'
}
const viewOnceOverride = isViewOnce
? viewOnceTypeMap[Object.keys(message).find(k => k in viewOnceTypeMap) || '']
: undefined
m = await prepareWAMessageMedia(message, {
...options,
mediaTypeOverride: (viewOnceOverride as any) || options.mediaTypeOverride
})
} }
if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) { if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
m = { viewOnceMessage: { message: m } } // Also set viewOnce=true on the inner media message (required by WA app to render as view-once)
if (m.imageMessage) m.imageMessage.viewOnce = true
else if (m.videoMessage) m.videoMessage.viewOnce = true
else if (m.audioMessage) m.audioMessage.viewOnce = true
// Use viewOnceMessageV2 (field 55) — modern WA clients use this wrapper
// viewOnceMessage (field 37) is the old format which WA server no longer accepts with view_once_write
m = { viewOnceMessageV2: { message: m } }
} }
if (hasOptionalProperty(message, 'mentions') && message.mentions?.length) { if (
(hasOptionalProperty(message, 'mentions') && message.mentions?.length) ||
(hasOptionalProperty(message, 'mentionAll') && message.mentionAll)
) {
const messageType = Object.keys(m)[0] as Extract<keyof proto.IMessage, MessageWithContextInfo> const messageType = Object.keys(m)[0] as Extract<keyof proto.IMessage, MessageWithContextInfo>
if (messageType) { if (messageType) {
const key = m[messageType] const key = m[messageType]
if (key && 'contextInfo' in key && !!key.contextInfo) { if (key && 'contextInfo' in key) {
key.contextInfo.mentionedJid = message.mentions key.contextInfo = key.contextInfo || {}
} else if (key) { if (message.mentions?.length) {
key.contextInfo = { key.contextInfo.mentionedJid = message.mentions
mentionedJid: message.mentions }
if (message.mentionAll) {
key.contextInfo.nonJidMentions = 1
} }
} }
} }