fix(carousel): hasSubtitle + relayMessage direto + proto atualizado
Implementação completa baseada na análise ponto-a-ponto de 5 diferenças: 1. hasSubtitle adicionado ao proto schema (field 10, bool em Header) - Adicionado ao WAProto.proto, index.js (encode/decode/fromObject/toObject) - Adicionado ao index.d.ts (IHeader, Header class) - Usado no root header e em cada card do carousel 2. relayMessage direto para carousel em sendMessage - Detecta nativeCarousel no content e bypassa generateWAMessage inteiro - Chama generateWAMessageContent (que usa fromObject) diretamente - Depois relayMessage sem passar por generateWAMessageFromContent - Elimina: segundo WAProto.Message.create(), contextInfo.expiration, etc. 3. Pipeline final do carousel agora: sendMessage → generateWAMessageContent(fromObject) → relayMessage Sem: generateWAMessageFromContent, WAProto.Message.create, ephemeral Commits anteriores já incluem: - viewOnceMessage wrapper - fromObject no generateWAMessageContent - Skip tctoken no stanza https://claude.ai/code/session_01EK9NpViRCtda1WAvFd8ptR
This commit is contained in:
@@ -2717,6 +2717,7 @@ message Message {
|
|||||||
optional string title = 1;
|
optional string title = 1;
|
||||||
optional string subtitle = 2;
|
optional string subtitle = 2;
|
||||||
optional bool hasMediaAttachment = 5;
|
optional bool hasMediaAttachment = 5;
|
||||||
|
optional bool hasSubtitle = 10;
|
||||||
oneof media {
|
oneof media {
|
||||||
Message.DocumentMessage documentMessage = 3;
|
Message.DocumentMessage documentMessage = 3;
|
||||||
Message.ImageMessage imageMessage = 4;
|
Message.ImageMessage imageMessage = 4;
|
||||||
|
|||||||
Vendored
+2
@@ -6854,6 +6854,7 @@ export namespace proto {
|
|||||||
title?: (string|null);
|
title?: (string|null);
|
||||||
subtitle?: (string|null);
|
subtitle?: (string|null);
|
||||||
hasMediaAttachment?: (boolean|null);
|
hasMediaAttachment?: (boolean|null);
|
||||||
|
hasSubtitle?: (boolean|null);
|
||||||
documentMessage?: (proto.Message.IDocumentMessage|null);
|
documentMessage?: (proto.Message.IDocumentMessage|null);
|
||||||
imageMessage?: (proto.Message.IImageMessage|null);
|
imageMessage?: (proto.Message.IImageMessage|null);
|
||||||
jpegThumbnail?: (Uint8Array|null);
|
jpegThumbnail?: (Uint8Array|null);
|
||||||
@@ -6867,6 +6868,7 @@ export namespace proto {
|
|||||||
public title?: (string|null);
|
public title?: (string|null);
|
||||||
public subtitle?: (string|null);
|
public subtitle?: (string|null);
|
||||||
public hasMediaAttachment?: (boolean|null);
|
public hasMediaAttachment?: (boolean|null);
|
||||||
|
public hasSubtitle?: (boolean|null);
|
||||||
public documentMessage?: (proto.Message.IDocumentMessage|null);
|
public documentMessage?: (proto.Message.IDocumentMessage|null);
|
||||||
public imageMessage?: (proto.Message.IImageMessage|null);
|
public imageMessage?: (proto.Message.IImageMessage|null);
|
||||||
public jpegThumbnail?: (Uint8Array|null);
|
public jpegThumbnail?: (Uint8Array|null);
|
||||||
|
|||||||
@@ -48195,6 +48195,7 @@ export const proto = $root.proto = (() => {
|
|||||||
Header.prototype.title = null;
|
Header.prototype.title = null;
|
||||||
Header.prototype.subtitle = null;
|
Header.prototype.subtitle = null;
|
||||||
Header.prototype.hasMediaAttachment = null;
|
Header.prototype.hasMediaAttachment = null;
|
||||||
|
Header.prototype.hasSubtitle = null;
|
||||||
Header.prototype.documentMessage = null;
|
Header.prototype.documentMessage = null;
|
||||||
Header.prototype.imageMessage = null;
|
Header.prototype.imageMessage = null;
|
||||||
Header.prototype.jpegThumbnail = null;
|
Header.prototype.jpegThumbnail = null;
|
||||||
@@ -48222,6 +48223,12 @@ export const proto = $root.proto = (() => {
|
|||||||
set: $util.oneOfSetter($oneOfFields)
|
set: $util.oneOfSetter($oneOfFields)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Virtual OneOf for proto3 optional field
|
||||||
|
Object.defineProperty(Header.prototype, "_hasSubtitle", {
|
||||||
|
get: $util.oneOfGetter($oneOfFields = ["hasSubtitle"]),
|
||||||
|
set: $util.oneOfSetter($oneOfFields)
|
||||||
|
});
|
||||||
|
|
||||||
Object.defineProperty(Header.prototype, "media", {
|
Object.defineProperty(Header.prototype, "media", {
|
||||||
get: $util.oneOfGetter($oneOfFields = ["documentMessage", "imageMessage", "jpegThumbnail", "videoMessage", "locationMessage", "productMessage"]),
|
get: $util.oneOfGetter($oneOfFields = ["documentMessage", "imageMessage", "jpegThumbnail", "videoMessage", "locationMessage", "productMessage"]),
|
||||||
set: $util.oneOfSetter($oneOfFields)
|
set: $util.oneOfSetter($oneOfFields)
|
||||||
@@ -48244,6 +48251,8 @@ export const proto = $root.proto = (() => {
|
|||||||
$root.proto.Message.ImageMessage.encode(m.imageMessage, w.uint32(34).fork()).ldelim();
|
$root.proto.Message.ImageMessage.encode(m.imageMessage, w.uint32(34).fork()).ldelim();
|
||||||
if (m.hasMediaAttachment != null && Object.hasOwnProperty.call(m, "hasMediaAttachment"))
|
if (m.hasMediaAttachment != null && Object.hasOwnProperty.call(m, "hasMediaAttachment"))
|
||||||
w.uint32(40).bool(m.hasMediaAttachment);
|
w.uint32(40).bool(m.hasMediaAttachment);
|
||||||
|
if (m.hasSubtitle != null && Object.hasOwnProperty.call(m, "hasSubtitle"))
|
||||||
|
w.uint32(80).bool(m.hasSubtitle);
|
||||||
if (m.jpegThumbnail != null && Object.hasOwnProperty.call(m, "jpegThumbnail"))
|
if (m.jpegThumbnail != null && Object.hasOwnProperty.call(m, "jpegThumbnail"))
|
||||||
w.uint32(50).bytes(m.jpegThumbnail);
|
w.uint32(50).bytes(m.jpegThumbnail);
|
||||||
if (m.videoMessage != null && Object.hasOwnProperty.call(m, "videoMessage"))
|
if (m.videoMessage != null && Object.hasOwnProperty.call(m, "videoMessage"))
|
||||||
@@ -48300,6 +48309,10 @@ export const proto = $root.proto = (() => {
|
|||||||
m.productMessage = $root.proto.Message.ProductMessage.decode(r, r.uint32());
|
m.productMessage = $root.proto.Message.ProductMessage.decode(r, r.uint32());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 10: {
|
||||||
|
m.hasSubtitle = r.bool();
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
r.skipType(t & 7);
|
r.skipType(t & 7);
|
||||||
break;
|
break;
|
||||||
@@ -48321,6 +48334,9 @@ export const proto = $root.proto = (() => {
|
|||||||
if (d.hasMediaAttachment != null) {
|
if (d.hasMediaAttachment != null) {
|
||||||
m.hasMediaAttachment = Boolean(d.hasMediaAttachment);
|
m.hasMediaAttachment = Boolean(d.hasMediaAttachment);
|
||||||
}
|
}
|
||||||
|
if (d.hasSubtitle != null) {
|
||||||
|
m.hasSubtitle = Boolean(d.hasSubtitle);
|
||||||
|
}
|
||||||
if (d.documentMessage != null) {
|
if (d.documentMessage != null) {
|
||||||
if (typeof d.documentMessage !== "object")
|
if (typeof d.documentMessage !== "object")
|
||||||
throw TypeError(".proto.Message.InteractiveMessage.Header.documentMessage: object expected");
|
throw TypeError(".proto.Message.InteractiveMessage.Header.documentMessage: object expected");
|
||||||
@@ -48384,6 +48400,11 @@ export const proto = $root.proto = (() => {
|
|||||||
if (o.oneofs)
|
if (o.oneofs)
|
||||||
d._hasMediaAttachment = "hasMediaAttachment";
|
d._hasMediaAttachment = "hasMediaAttachment";
|
||||||
}
|
}
|
||||||
|
if (m.hasSubtitle != null && m.hasOwnProperty("hasSubtitle")) {
|
||||||
|
d.hasSubtitle = m.hasSubtitle;
|
||||||
|
if (o.oneofs)
|
||||||
|
d._hasSubtitle = "hasSubtitle";
|
||||||
|
}
|
||||||
if (m.jpegThumbnail != null && m.hasOwnProperty("jpegThumbnail")) {
|
if (m.jpegThumbnail != null && m.hasOwnProperty("jpegThumbnail")) {
|
||||||
d.jpegThumbnail = o.bytes === String ? $util.base64.encode(m.jpegThumbnail, 0, m.jpegThumbnail.length) : o.bytes === Array ? Array.prototype.slice.call(m.jpegThumbnail) : m.jpegThumbnail;
|
d.jpegThumbnail = o.bytes === String ? $util.base64.encode(m.jpegThumbnail, 0, m.jpegThumbnail.length) : o.bytes === Array ? Array.prototype.slice.call(m.jpegThumbnail) : m.jpegThumbnail;
|
||||||
if (o.oneofs)
|
if (o.oneofs)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
generateMessageIDV2,
|
generateMessageIDV2,
|
||||||
generateParticipantHashV2,
|
generateParticipantHashV2,
|
||||||
generateWAMessage,
|
generateWAMessage,
|
||||||
|
generateWAMessageContent,
|
||||||
getStatusCodeForMediaRetry,
|
getStatusCodeForMediaRetry,
|
||||||
getUrlFromDirectPath,
|
getUrlFromDirectPath,
|
||||||
getWAUploadToServer,
|
getWAUploadToServer,
|
||||||
@@ -1964,6 +1965,52 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userJid = authState.creds.me!.id
|
const userJid = authState.creds.me!.id
|
||||||
|
|
||||||
|
// Special path for carousel: call relayMessage directly
|
||||||
|
// This bypasses generateWAMessageFromContent (which adds contextInfo.expiration
|
||||||
|
// and calls WAProto.Message.create that may corrupt nested carousel structures)
|
||||||
|
// Matches the working implementation's approach: generateWAMessageContent → relayMessage
|
||||||
|
if (typeof content === 'object' && 'nativeCarousel' in content && (content as any).nativeCarousel) {
|
||||||
|
const messageId = generateMessageIDV2(sock.user?.id)
|
||||||
|
const msgContent = await generateWAMessageContent(content, {
|
||||||
|
logger,
|
||||||
|
upload: waUploadToServer,
|
||||||
|
mediaCache: config.mediaCache,
|
||||||
|
options: config.options,
|
||||||
|
jid,
|
||||||
|
})
|
||||||
|
|
||||||
|
const timestamp = unixTimestampSeconds()
|
||||||
|
const fullMsg = proto.WebMessageInfo.fromObject({
|
||||||
|
key: {
|
||||||
|
remoteJid: jid,
|
||||||
|
fromMe: true,
|
||||||
|
id: messageId
|
||||||
|
},
|
||||||
|
message: msgContent,
|
||||||
|
messageTimestamp: timestamp,
|
||||||
|
messageStubParameters: [],
|
||||||
|
participant: isJidGroup(jid) ? userJid : undefined,
|
||||||
|
status: proto.WebMessageInfo.Status.PENDING
|
||||||
|
}) as WAMessage
|
||||||
|
|
||||||
|
await relayMessage(jid, fullMsg.message!, {
|
||||||
|
messageId: fullMsg.key.id!,
|
||||||
|
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
||||||
|
additionalAttributes: {},
|
||||||
|
statusJidList: options.statusJidList,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (config.emitOwnEvents) {
|
||||||
|
process.nextTick(async () => {
|
||||||
|
const mutexKey = fullMsg.key.remoteJid || fullMsg.key.id || 'unknown'
|
||||||
|
await messageMutex.mutex(mutexKey, () => upsertMessage(fullMsg, 'append'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullMsg
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof content === 'object' &&
|
typeof content === 'object' &&
|
||||||
'disappearingMessagesInChat' in content &&
|
'disappearingMessagesInChat' in content &&
|
||||||
|
|||||||
@@ -616,7 +616,8 @@ export const generateCarouselMessage = async (
|
|||||||
|
|
||||||
const header: any = {
|
const header: any = {
|
||||||
title: card.title || '',
|
title: card.title || '',
|
||||||
hasMediaAttachment: hasMedia
|
hasMediaAttachment: hasMedia,
|
||||||
|
hasSubtitle: !!card.title
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process media if present
|
// Process media if present
|
||||||
@@ -642,7 +643,7 @@ export const generateCarouselMessage = async (
|
|||||||
|
|
||||||
// Build the interactive message with carousel
|
// Build the interactive message with carousel
|
||||||
const interactiveMessage: proto.Message.IInteractiveMessage = {
|
const interactiveMessage: proto.Message.IInteractiveMessage = {
|
||||||
header: text ? { title: text } : undefined,
|
header: text ? { title: text, hasSubtitle: true } : undefined,
|
||||||
body: { text: text || '' },
|
body: { text: text || '' },
|
||||||
footer: footer ? { text: footer } : undefined,
|
footer: footer ? { text: footer } : undefined,
|
||||||
carouselMessage: {
|
carouselMessage: {
|
||||||
|
|||||||
Reference in New Issue
Block a user