fix(carousel): remove hasSubtitle proto field + add debug logging

- Remove hasSubtitle (field 10) from Header proto definition, index.js
  and index.d.ts - this field was adding extra bytes to encoded protobuf
  that working implementations don't send, potentially causing rejection
- Remove hasSubtitle from carousel card headers and root header
- Add [CAROUSEL DEBUG] logging in relayMessage to dump:
  - Encoded message bytes as base64 (for binary comparison)
  - Message structure as JSON
  - Per-device encoded bytes with DSM flag
- This enables byte-level comparison with working implementations

https://claude.ai/code/session_01EK9NpViRCtda1WAvFd8ptR
This commit is contained in:
Claude
2026-02-06 21:27:40 +00:00
parent 3ac2c33d93
commit 879970fda8
5 changed files with 44 additions and 27 deletions
-1
View File
@@ -2717,7 +2717,6 @@ message Message {
optional string title = 1;
optional string subtitle = 2;
optional bool hasMediaAttachment = 5;
optional bool hasSubtitle = 10;
oneof media {
Message.DocumentMessage documentMessage = 3;
Message.ImageMessage imageMessage = 4;
-2
View File
@@ -6854,7 +6854,6 @@ export namespace proto {
title?: (string|null);
subtitle?: (string|null);
hasMediaAttachment?: (boolean|null);
hasSubtitle?: (boolean|null);
documentMessage?: (proto.Message.IDocumentMessage|null);
imageMessage?: (proto.Message.IImageMessage|null);
jpegThumbnail?: (Uint8Array|null);
@@ -6868,7 +6867,6 @@ export namespace proto {
public title?: (string|null);
public subtitle?: (string|null);
public hasMediaAttachment?: (boolean|null);
public hasSubtitle?: (boolean|null);
public documentMessage?: (proto.Message.IDocumentMessage|null);
public imageMessage?: (proto.Message.IImageMessage|null);
public jpegThumbnail?: (Uint8Array|null);
-21
View File
@@ -48195,7 +48195,6 @@ export const proto = $root.proto = (() => {
Header.prototype.title = null;
Header.prototype.subtitle = null;
Header.prototype.hasMediaAttachment = null;
Header.prototype.hasSubtitle = null;
Header.prototype.documentMessage = null;
Header.prototype.imageMessage = null;
Header.prototype.jpegThumbnail = null;
@@ -48223,12 +48222,6 @@ export const proto = $root.proto = (() => {
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", {
get: $util.oneOfGetter($oneOfFields = ["documentMessage", "imageMessage", "jpegThumbnail", "videoMessage", "locationMessage", "productMessage"]),
set: $util.oneOfSetter($oneOfFields)
@@ -48251,8 +48244,6 @@ export const proto = $root.proto = (() => {
$root.proto.Message.ImageMessage.encode(m.imageMessage, w.uint32(34).fork()).ldelim();
if (m.hasMediaAttachment != null && Object.hasOwnProperty.call(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"))
w.uint32(50).bytes(m.jpegThumbnail);
if (m.videoMessage != null && Object.hasOwnProperty.call(m, "videoMessage"))
@@ -48309,10 +48300,6 @@ export const proto = $root.proto = (() => {
m.productMessage = $root.proto.Message.ProductMessage.decode(r, r.uint32());
break;
}
case 10: {
m.hasSubtitle = r.bool();
break;
}
default:
r.skipType(t & 7);
break;
@@ -48334,9 +48321,6 @@ export const proto = $root.proto = (() => {
if (d.hasMediaAttachment != null) {
m.hasMediaAttachment = Boolean(d.hasMediaAttachment);
}
if (d.hasSubtitle != null) {
m.hasSubtitle = Boolean(d.hasSubtitle);
}
if (d.documentMessage != null) {
if (typeof d.documentMessage !== "object")
throw TypeError(".proto.Message.InteractiveMessage.Header.documentMessage: object expected");
@@ -48400,11 +48384,6 @@ export const proto = $root.proto = (() => {
if (o.oneofs)
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")) {
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)
+42
View File
@@ -572,6 +572,26 @@ export const makeMessagesSocket = (config: SocketConfig) => {
}
}
// Debug: log per-device carousel encoding
const carouselInMsg = msgToEncrypt?.viewOnceMessage?.message?.interactiveMessage?.carouselMessage
|| msgToEncrypt?.deviceSentMessage?.message?.viewOnceMessage?.message?.interactiveMessage?.carouselMessage
if (carouselInMsg) {
try {
const debugBytes = proto.Message.encode(proto.Message.fromObject(msgToEncrypt)).finish()
logger.info(
{
recipientJid: jid,
isDSM: !!msgToEncrypt?.deviceSentMessage,
encodedBase64: Buffer.from(debugBytes).toString('base64'),
encodedLength: debugBytes.length,
},
'[CAROUSEL DEBUG] Per-device encoded message'
)
} catch (err) {
logger.warn({ jid, error: (err as Error).message }, '[CAROUSEL DEBUG] Per-device encode failed')
}
}
const bytes = encodeWAMessage(msgToEncrypt)
const mutexKey = jid
@@ -899,6 +919,28 @@ export const makeMessagesSocket = (config: SocketConfig) => {
messageContextInfo: message.messageContextInfo
}
// Debug logging for carousel messages - dump encoded bytes for binary comparison
if (isCarouselMessage(message)) {
try {
const msgBytes = proto.Message.encode(proto.Message.fromObject(message)).finish()
const meMsgBytes = proto.Message.encode(proto.Message.fromObject(meMsg)).finish()
logger.info(
{
msgId,
messageBase64: Buffer.from(msgBytes).toString('base64'),
meMsgBase64: Buffer.from(meMsgBytes).toString('base64'),
messageBytesLength: msgBytes.length,
meMsgBytesLength: meMsgBytes.length,
messageStructure: JSON.stringify(proto.Message.toObject(proto.Message.fromObject(message), { defaults: false })),
meMsgStructure: JSON.stringify(proto.Message.toObject(proto.Message.fromObject(meMsg), { defaults: false })),
},
'[CAROUSEL DEBUG] Encoded message bytes for binary comparison'
)
} catch (err) {
logger.warn({ msgId, error: (err as Error).message }, '[CAROUSEL DEBUG] Failed to encode for debug')
}
}
const extraAttrs: BinaryNodeAttributes = {}
if (participant) {
+2 -3
View File
@@ -616,8 +616,7 @@ export const generateCarouselMessage = async (
const header: any = {
title: card.title || '',
hasMediaAttachment: hasMedia,
hasSubtitle: !!card.title
hasMediaAttachment: hasMedia
}
// Process media if present
@@ -643,7 +642,7 @@ export const generateCarouselMessage = async (
// Build the interactive message with carousel
const interactiveMessage: proto.Message.IInteractiveMessage = {
header: text ? { title: text, hasSubtitle: true } : undefined,
header: text ? { title: text } : undefined,
body: { text: text || '' },
footer: footer ? { text: footer } : undefined,
carouselMessage: {