From 935484c790d657b5edb4b56fade770e28b084ebc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 00:37:49 -0300 Subject: [PATCH 01/32] chore: update proto/version to v2.3000.1034696150 (#269) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 2 +- src/Defaults/baileys-version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index 84d2500b..7ef18c74 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1034621774 +/// WhatsApp Version: 2.3000.1034696150 message ADVDeviceIdentity { optional uint32 rawId = 1; diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 24477c3f..dc57bc5a 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1034641024]} +{"version": [2, 3000, 1034696150]} From 54d6a023a4c682248bc45e1fba73718841026a57 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Sat, 7 Mar 2026 06:05:25 -0300 Subject: [PATCH 02/32] chore: update WhatsApp Web version to v2.3000.1034715486 (#270) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index dc57bc5a..e9a80754 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1034696150]} +{"version":[2,3000,1034715486]} From ad4f676e2baaf5c52ee6939b8e689d0927645c75 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:45:02 -0300 Subject: [PATCH 03/32] chore: update proto/version to v2.3000.1034732001 (#271) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 2 +- src/Defaults/baileys-version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index 7ef18c74..772fa79c 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1034696150 +/// WhatsApp Version: 2.3000.1034732001 message ADVDeviceIdentity { optional uint32 rawId = 1; diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index e9a80754..4feea4fd 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1034715486]} +{"version": [2, 3000, 1034732001]} From 7ecfaefcfab8aae440f46d727339e838bcd01033 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Sun, 8 Mar 2026 06:05:18 -0300 Subject: [PATCH 04/32] chore: update WhatsApp Web version to v2.3000.1034735671 (#272) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 4feea4fd..b2df6402 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1034732001]} +{"version":[2,3000,1034735671]} From ad81d1535deb863f1a04b7cc3a50298fd35c6338 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 00:46:14 -0300 Subject: [PATCH 05/32] chore: update proto/version to v2.3000.1034750325 (#273) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 2 +- src/Defaults/baileys-version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index 772fa79c..17d421a5 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1034732001 +/// WhatsApp Version: 2.3000.1034750325 message ADVDeviceIdentity { optional uint32 rawId = 1; diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index b2df6402..da2f614d 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1034735671]} +{"version": [2, 3000, 1034750325]} From 07f775dd18417d3dff9d763f67993edf217d1b00 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Mon, 9 Mar 2026 06:14:04 -0300 Subject: [PATCH 06/32] chore: update WhatsApp Web version to v2.3000.1034756368 (#274) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index da2f614d..d330ddca 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1034750325]} +{"version":[2,3000,1034756368]} From 2236ef83a91b8d1846551a8da61f1f2fc876283a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 00:44:00 -0300 Subject: [PATCH 07/32] chore: update proto/version to v2.3000.1034813620 (#275) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 2 +- src/Defaults/baileys-version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index 17d421a5..8a771aa6 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1034750325 +/// WhatsApp Version: 2.3000.1034813620 message ADVDeviceIdentity { optional uint32 rawId = 1; diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index d330ddca..178a6a74 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1034756368]} +{"version": [2, 3000, 1034813620]} From d7b2ceb161c06b32fbe10562e4788ea2c2e3ed78 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Tue, 10 Mar 2026 06:12:48 -0300 Subject: [PATCH 08/32] chore: update WhatsApp Web version to v2.3000.1034844076 (#276) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 178a6a74..7dc625b0 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1034813620]} +{"version":[2,3000,1034844076]} From 9daee4d8fdaa6c98f9c468538d79cde121434218 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:44:13 -0300 Subject: [PATCH 09/32] chore: update proto/version to v2.3000.1034906703 (#277) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 2 +- src/Defaults/baileys-version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index 8a771aa6..c4038040 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1034813620 +/// WhatsApp Version: 2.3000.1034906703 message ADVDeviceIdentity { optional uint32 rawId = 1; diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 7dc625b0..ed88213b 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1034844076]} +{"version": [2, 3000, 1034906703]} From 6d7e0d7b35f04767fe85edee256ff15975d2edd0 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Wed, 11 Mar 2026 06:12:36 -0300 Subject: [PATCH 10/32] chore: update WhatsApp Web version to v2.3000.1034928492 (#278) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index ed88213b..72a4310d 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1034906703]} +{"version":[2,3000,1034928492]} From 542b7a278d41959e621f6140aded094104024e24 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 00:45:49 -0300 Subject: [PATCH 11/32] chore: update proto/version to v2.3000.1034989030 (#279) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 52 +- WAProto/index.d.ts | 150 +++- WAProto/index.js | 1139 ++++++++++++++++++++++++++++- src/Defaults/baileys-version.json | 2 +- 4 files changed, 1319 insertions(+), 24 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index c4038040..76dd41c4 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1034906703 +/// WhatsApp Version: 2.3000.1034989030 message ADVDeviceIdentity { optional uint32 rawId = 1; @@ -345,6 +345,14 @@ message BotAgeCollectionMetadata { } } +message BotAgentDeepLinkMetadata { + optional string token = 1; +} + +message BotAgentMetadata { + optional BotAgentDeepLinkMetadata deepLinkMetadata = 1; +} + message BotCapabilityMetadata { repeated BotCapabilityType capabilities = 1; enum BotCapabilityType { @@ -1635,6 +1643,8 @@ message DeviceCapabilities { message BusinessBroadcast { optional bool importListEnabled = 1; optional bool companionSupportEnabled = 2; + optional bool campaignSyncEnabled = 3; + optional bool insightsSyncEnabled = 4; } enum ChatLockSupportLevel { @@ -2275,6 +2285,7 @@ message Message { optional PollCreationMessage pollCreationMessageV6 = 119; optional ConditionalRevealMessage conditionalRevealMessage = 120; optional PollAddOptionMessage pollAddOptionMessage = 121; + optional EventInviteMessage eventInviteMessage = 122; message AlbumMessage { optional uint32 expectedImageCount = 2; optional uint32 expectedVideoCount = 3; @@ -2555,6 +2566,16 @@ message Message { optional bytes encIv = 3; } + message EventInviteMessage { + optional ContextInfo contextInfo = 1; + optional string eventId = 2; + optional string eventTitle = 3; + optional bytes jpegThumbnail = 4; + optional int64 startTime = 5; + optional string caption = 6; + optional bool isCanceled = 7; + } + message EventMessage { optional ContextInfo contextInfo = 1; optional bool isCanceled = 2; @@ -3081,9 +3102,10 @@ message Message { message MessageHistoryMetadata { repeated string historyReceivers = 1; - optional int64 oldestMessageTimestamp = 2; + optional int64 oldestMessageTimestampInWindow = 2; optional int64 messageCount = 3; repeated string nonHistoryReceivers = 4; + optional int64 oldestMessageTimestampInBundle = 5; } message MessageHistoryNotice { @@ -3597,6 +3619,7 @@ message Message { message RequestWelcomeMessageMetadata { optional LocalChatState localChatState = 1; optional WelcomeTrigger welcomeTrigger = 2; + optional BotAgentMetadata botAgentMetadata = 3; enum LocalChatState { EMPTY = 0; NON_EMPTY = 1; @@ -4124,6 +4147,7 @@ enum MutationProps { BUSINESS_BROADCAST_CAMPAIGN_ACTION = 81; BUSINESS_BROADCAST_INSIGHTS_ACTION = 82; CUSTOMER_DATA_ACTION = 83; + SUBSCRIPTIONS_SYNC_V2_ACTION = 84; SHARE_OWN_PN = 10001; BUSINESS_BROADCAST_ACTION = 10002; AI_THREAD_DELETE_ACTION = 10003; @@ -4760,6 +4784,7 @@ message SyncActionValue { optional BusinessBroadcastCampaignAction businessBroadcastCampaignAction = 81; optional BusinessBroadcastInsightsAction businessBroadcastInsightsAction = 82; optional CustomerDataAction customerDataAction = 83; + optional SubscriptionsSyncV2Action subscriptionsSyncV2Action = 84; message AgentAction { optional string name = 1; optional int32 deviceID = 2; @@ -5275,6 +5300,29 @@ message SyncActionValue { optional int64 expirationDate = 3; } + message SubscriptionsSyncV2Action { + repeated SubscriptionInfo subscriptions = 1; + repeated PaidFeature paidFeature = 2; + message PaidFeature { + optional string name = 1; + optional bool enabled = 2; + optional int32 limit = 3; + optional int64 expirationTime = 4; + } + + message SubscriptionInfo { + optional string id = 1; + optional int32 tier = 2; + optional string status = 3; + optional int64 startTime = 4; + optional int64 endTime = 5; + optional bool isPlatformChanged = 6; + optional string source = 7; + optional int64 creationTime = 8; + } + + } + message SyncActionMessage { optional MessageKey key = 1; optional int64 timestamp = 2; diff --git a/WAProto/index.d.ts b/WAProto/index.d.ts index 0613088d..e2bee454 100644 --- a/WAProto/index.d.ts +++ b/WAProto/index.d.ts @@ -981,6 +981,38 @@ export namespace proto { } } + interface IBotAgentDeepLinkMetadata { + token?: (string|null); + } + + class BotAgentDeepLinkMetadata implements IBotAgentDeepLinkMetadata { + constructor(p?: proto.IBotAgentDeepLinkMetadata); + public token?: (string|null); + public static create(properties?: proto.IBotAgentDeepLinkMetadata): proto.BotAgentDeepLinkMetadata; + public static encode(m: proto.IBotAgentDeepLinkMetadata, w?: $protobuf.Writer): $protobuf.Writer; + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.BotAgentDeepLinkMetadata; + public static fromObject(d: { [k: string]: any }): proto.BotAgentDeepLinkMetadata; + public static toObject(m: proto.BotAgentDeepLinkMetadata, o?: $protobuf.IConversionOptions): { [k: string]: any }; + public toJSON(): { [k: string]: any }; + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + interface IBotAgentMetadata { + deepLinkMetadata?: (proto.IBotAgentDeepLinkMetadata|null); + } + + class BotAgentMetadata implements IBotAgentMetadata { + constructor(p?: proto.IBotAgentMetadata); + public deepLinkMetadata?: (proto.IBotAgentDeepLinkMetadata|null); + public static create(properties?: proto.IBotAgentMetadata): proto.BotAgentMetadata; + public static encode(m: proto.IBotAgentMetadata, w?: $protobuf.Writer): $protobuf.Writer; + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.BotAgentMetadata; + public static fromObject(d: { [k: string]: any }): proto.BotAgentMetadata; + public static toObject(m: proto.BotAgentMetadata, o?: $protobuf.IConversionOptions): { [k: string]: any }; + public toJSON(): { [k: string]: any }; + public static getTypeUrl(typeUrlPrefix?: string): string; + } + interface IBotCapabilityMetadata { capabilities?: (proto.BotCapabilityMetadata.BotCapabilityType[]|null); } @@ -3983,12 +4015,16 @@ export namespace proto { interface IBusinessBroadcast { importListEnabled?: (boolean|null); companionSupportEnabled?: (boolean|null); + campaignSyncEnabled?: (boolean|null); + insightsSyncEnabled?: (boolean|null); } class BusinessBroadcast implements IBusinessBroadcast { constructor(p?: proto.DeviceCapabilities.IBusinessBroadcast); public importListEnabled?: (boolean|null); public companionSupportEnabled?: (boolean|null); + public campaignSyncEnabled?: (boolean|null); + public insightsSyncEnabled?: (boolean|null); public static create(properties?: proto.DeviceCapabilities.IBusinessBroadcast): proto.DeviceCapabilities.BusinessBroadcast; public static encode(m: proto.DeviceCapabilities.IBusinessBroadcast, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.DeviceCapabilities.BusinessBroadcast; @@ -5546,6 +5582,7 @@ export namespace proto { pollCreationMessageV6?: (proto.Message.IPollCreationMessage|null); conditionalRevealMessage?: (proto.Message.IConditionalRevealMessage|null); pollAddOptionMessage?: (proto.Message.IPollAddOptionMessage|null); + eventInviteMessage?: (proto.Message.IEventInviteMessage|null); } class Message implements IMessage { @@ -5651,6 +5688,7 @@ export namespace proto { public pollCreationMessageV6?: (proto.Message.IPollCreationMessage|null); public conditionalRevealMessage?: (proto.Message.IConditionalRevealMessage|null); public pollAddOptionMessage?: (proto.Message.IPollAddOptionMessage|null); + public eventInviteMessage?: (proto.Message.IEventInviteMessage|null); public static create(properties?: proto.IMessage): proto.Message; public static encode(m: proto.IMessage, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message; @@ -6454,6 +6492,34 @@ export namespace proto { public static getTypeUrl(typeUrlPrefix?: string): string; } + interface IEventInviteMessage { + contextInfo?: (proto.IContextInfo|null); + eventId?: (string|null); + eventTitle?: (string|null); + jpegThumbnail?: (Uint8Array|null); + startTime?: (number|Long|null); + caption?: (string|null); + isCanceled?: (boolean|null); + } + + class EventInviteMessage implements IEventInviteMessage { + constructor(p?: proto.Message.IEventInviteMessage); + public contextInfo?: (proto.IContextInfo|null); + public eventId?: (string|null); + public eventTitle?: (string|null); + public jpegThumbnail?: (Uint8Array|null); + public startTime?: (number|Long|null); + public caption?: (string|null); + public isCanceled?: (boolean|null); + public static create(properties?: proto.Message.IEventInviteMessage): proto.Message.EventInviteMessage; + public static encode(m: proto.Message.IEventInviteMessage, w?: $protobuf.Writer): $protobuf.Writer; + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.EventInviteMessage; + public static fromObject(d: { [k: string]: any }): proto.Message.EventInviteMessage; + public static toObject(m: proto.Message.EventInviteMessage, o?: $protobuf.IConversionOptions): { [k: string]: any }; + public toJSON(): { [k: string]: any }; + public static getTypeUrl(typeUrlPrefix?: string): string; + } + interface IEventMessage { contextInfo?: (proto.IContextInfo|null); isCanceled?: (boolean|null); @@ -7818,17 +7884,19 @@ export namespace proto { interface IMessageHistoryMetadata { historyReceivers?: (string[]|null); - oldestMessageTimestamp?: (number|Long|null); + oldestMessageTimestampInWindow?: (number|Long|null); messageCount?: (number|Long|null); nonHistoryReceivers?: (string[]|null); + oldestMessageTimestampInBundle?: (number|Long|null); } class MessageHistoryMetadata implements IMessageHistoryMetadata { constructor(p?: proto.Message.IMessageHistoryMetadata); public historyReceivers: string[]; - public oldestMessageTimestamp?: (number|Long|null); + public oldestMessageTimestampInWindow?: (number|Long|null); public messageCount?: (number|Long|null); public nonHistoryReceivers: string[]; + public oldestMessageTimestampInBundle?: (number|Long|null); public static create(properties?: proto.Message.IMessageHistoryMetadata): proto.Message.MessageHistoryMetadata; public static encode(m: proto.Message.IMessageHistoryMetadata, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.MessageHistoryMetadata; @@ -9261,12 +9329,14 @@ export namespace proto { interface IRequestWelcomeMessageMetadata { localChatState?: (proto.Message.RequestWelcomeMessageMetadata.LocalChatState|null); welcomeTrigger?: (proto.Message.RequestWelcomeMessageMetadata.WelcomeTrigger|null); + botAgentMetadata?: (proto.IBotAgentMetadata|null); } class RequestWelcomeMessageMetadata implements IRequestWelcomeMessageMetadata { constructor(p?: proto.Message.IRequestWelcomeMessageMetadata); public localChatState?: (proto.Message.RequestWelcomeMessageMetadata.LocalChatState|null); public welcomeTrigger?: (proto.Message.RequestWelcomeMessageMetadata.WelcomeTrigger|null); + public botAgentMetadata?: (proto.IBotAgentMetadata|null); public static create(properties?: proto.Message.IRequestWelcomeMessageMetadata): proto.Message.RequestWelcomeMessageMetadata; public static encode(m: proto.Message.IRequestWelcomeMessageMetadata, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.RequestWelcomeMessageMetadata; @@ -10462,6 +10532,7 @@ export namespace proto { BUSINESS_BROADCAST_CAMPAIGN_ACTION = 81, BUSINESS_BROADCAST_INSIGHTS_ACTION = 82, CUSTOMER_DATA_ACTION = 83, + SUBSCRIPTIONS_SYNC_V2_ACTION = 84, SHARE_OWN_PN = 10001, BUSINESS_BROADCAST_ACTION = 10002, AI_THREAD_DELETE_ACTION = 10003 @@ -12056,6 +12127,7 @@ export namespace proto { businessBroadcastCampaignAction?: (proto.SyncActionValue.IBusinessBroadcastCampaignAction|null); businessBroadcastInsightsAction?: (proto.SyncActionValue.IBusinessBroadcastInsightsAction|null); customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null); + subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null); } class SyncActionValue implements ISyncActionValue { @@ -12134,6 +12206,7 @@ export namespace proto { public businessBroadcastCampaignAction?: (proto.SyncActionValue.IBusinessBroadcastCampaignAction|null); public businessBroadcastInsightsAction?: (proto.SyncActionValue.IBusinessBroadcastInsightsAction|null); public customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null); + public subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null); public static create(properties?: proto.ISyncActionValue): proto.SyncActionValue; public static encode(m: proto.ISyncActionValue, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue; @@ -13666,6 +13739,79 @@ export namespace proto { public static getTypeUrl(typeUrlPrefix?: string): string; } + interface ISubscriptionsSyncV2Action { + subscriptions?: (proto.SyncActionValue.SubscriptionsSyncV2Action.ISubscriptionInfo[]|null); + paidFeature?: (proto.SyncActionValue.SubscriptionsSyncV2Action.IPaidFeature[]|null); + } + + class SubscriptionsSyncV2Action implements ISubscriptionsSyncV2Action { + constructor(p?: proto.SyncActionValue.ISubscriptionsSyncV2Action); + public subscriptions: proto.SyncActionValue.SubscriptionsSyncV2Action.ISubscriptionInfo[]; + public paidFeature: proto.SyncActionValue.SubscriptionsSyncV2Action.IPaidFeature[]; + public static create(properties?: proto.SyncActionValue.ISubscriptionsSyncV2Action): proto.SyncActionValue.SubscriptionsSyncV2Action; + public static encode(m: proto.SyncActionValue.ISubscriptionsSyncV2Action, w?: $protobuf.Writer): $protobuf.Writer; + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.SubscriptionsSyncV2Action; + public static fromObject(d: { [k: string]: any }): proto.SyncActionValue.SubscriptionsSyncV2Action; + public static toObject(m: proto.SyncActionValue.SubscriptionsSyncV2Action, o?: $protobuf.IConversionOptions): { [k: string]: any }; + public toJSON(): { [k: string]: any }; + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + namespace SubscriptionsSyncV2Action { + + interface IPaidFeature { + name?: (string|null); + enabled?: (boolean|null); + limit?: (number|null); + expirationTime?: (number|Long|null); + } + + class PaidFeature implements IPaidFeature { + constructor(p?: proto.SyncActionValue.SubscriptionsSyncV2Action.IPaidFeature); + public name?: (string|null); + public enabled?: (boolean|null); + public limit?: (number|null); + public expirationTime?: (number|Long|null); + public static create(properties?: proto.SyncActionValue.SubscriptionsSyncV2Action.IPaidFeature): proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature; + public static encode(m: proto.SyncActionValue.SubscriptionsSyncV2Action.IPaidFeature, w?: $protobuf.Writer): $protobuf.Writer; + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature; + public static fromObject(d: { [k: string]: any }): proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature; + public static toObject(m: proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature, o?: $protobuf.IConversionOptions): { [k: string]: any }; + public toJSON(): { [k: string]: any }; + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + interface ISubscriptionInfo { + id?: (string|null); + tier?: (number|null); + status?: (string|null); + startTime?: (number|Long|null); + endTime?: (number|Long|null); + isPlatformChanged?: (boolean|null); + source?: (string|null); + creationTime?: (number|Long|null); + } + + class SubscriptionInfo implements ISubscriptionInfo { + constructor(p?: proto.SyncActionValue.SubscriptionsSyncV2Action.ISubscriptionInfo); + public id?: (string|null); + public tier?: (number|null); + public status?: (string|null); + public startTime?: (number|Long|null); + public endTime?: (number|Long|null); + public isPlatformChanged?: (boolean|null); + public source?: (string|null); + public creationTime?: (number|Long|null); + public static create(properties?: proto.SyncActionValue.SubscriptionsSyncV2Action.ISubscriptionInfo): proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo; + public static encode(m: proto.SyncActionValue.SubscriptionsSyncV2Action.ISubscriptionInfo, w?: $protobuf.Writer): $protobuf.Writer; + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo; + public static fromObject(d: { [k: string]: any }): proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo; + public static toObject(m: proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo, o?: $protobuf.IConversionOptions): { [k: string]: any }; + public toJSON(): { [k: string]: any }; + public static getTypeUrl(typeUrlPrefix?: string): string; + } + } + interface ISyncActionMessage { key?: (proto.IMessageKey|null); timestamp?: (number|Long|null); diff --git a/WAProto/index.js b/WAProto/index.js index 8d6e2b07..c7fc0510 100644 --- a/WAProto/index.js +++ b/WAProto/index.js @@ -6404,6 +6404,184 @@ export const proto = $root.proto = (() => { return BotAgeCollectionMetadata; })(); + proto.BotAgentDeepLinkMetadata = (function() { + + function BotAgentDeepLinkMetadata(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + BotAgentDeepLinkMetadata.prototype.token = null; + + let $oneOfFields; + + // Virtual OneOf for proto3 optional field + Object.defineProperty(BotAgentDeepLinkMetadata.prototype, "_token", { + get: $util.oneOfGetter($oneOfFields = ["token"]), + set: $util.oneOfSetter($oneOfFields) + }); + + BotAgentDeepLinkMetadata.create = function create(properties) { + return new BotAgentDeepLinkMetadata(properties); + }; + + BotAgentDeepLinkMetadata.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.token != null && Object.hasOwnProperty.call(m, "token")) + w.uint32(10).string(m.token); + return w; + }; + + BotAgentDeepLinkMetadata.decode = function decode(r, l, e) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.proto.BotAgentDeepLinkMetadata(); + while (r.pos < c) { + var t = r.uint32(); + if (t === e) + break; + switch (t >>> 3) { + case 1: { + m.token = r.string(); + break; + } + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + BotAgentDeepLinkMetadata.fromObject = function fromObject(d) { + if (d instanceof $root.proto.BotAgentDeepLinkMetadata) + return d; + var m = new $root.proto.BotAgentDeepLinkMetadata(); + if (d.token != null) { + m.token = String(d.token); + } + return m; + }; + + BotAgentDeepLinkMetadata.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (m.token != null && m.hasOwnProperty("token")) { + d.token = m.token; + if (o.oneofs) + d._token = "token"; + } + return d; + }; + + BotAgentDeepLinkMetadata.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + BotAgentDeepLinkMetadata.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/proto.BotAgentDeepLinkMetadata"; + }; + + return BotAgentDeepLinkMetadata; + })(); + + proto.BotAgentMetadata = (function() { + + function BotAgentMetadata(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + BotAgentMetadata.prototype.deepLinkMetadata = null; + + let $oneOfFields; + + // Virtual OneOf for proto3 optional field + Object.defineProperty(BotAgentMetadata.prototype, "_deepLinkMetadata", { + get: $util.oneOfGetter($oneOfFields = ["deepLinkMetadata"]), + set: $util.oneOfSetter($oneOfFields) + }); + + BotAgentMetadata.create = function create(properties) { + return new BotAgentMetadata(properties); + }; + + BotAgentMetadata.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.deepLinkMetadata != null && Object.hasOwnProperty.call(m, "deepLinkMetadata")) + $root.proto.BotAgentDeepLinkMetadata.encode(m.deepLinkMetadata, w.uint32(10).fork()).ldelim(); + return w; + }; + + BotAgentMetadata.decode = function decode(r, l, e) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.proto.BotAgentMetadata(); + while (r.pos < c) { + var t = r.uint32(); + if (t === e) + break; + switch (t >>> 3) { + case 1: { + m.deepLinkMetadata = $root.proto.BotAgentDeepLinkMetadata.decode(r, r.uint32()); + break; + } + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + BotAgentMetadata.fromObject = function fromObject(d) { + if (d instanceof $root.proto.BotAgentMetadata) + return d; + var m = new $root.proto.BotAgentMetadata(); + if (d.deepLinkMetadata != null) { + if (typeof d.deepLinkMetadata !== "object") + throw TypeError(".proto.BotAgentMetadata.deepLinkMetadata: object expected"); + m.deepLinkMetadata = $root.proto.BotAgentDeepLinkMetadata.fromObject(d.deepLinkMetadata); + } + return m; + }; + + BotAgentMetadata.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (m.deepLinkMetadata != null && m.hasOwnProperty("deepLinkMetadata")) { + d.deepLinkMetadata = $root.proto.BotAgentDeepLinkMetadata.toObject(m.deepLinkMetadata, o); + if (o.oneofs) + d._deepLinkMetadata = "deepLinkMetadata"; + } + return d; + }; + + BotAgentMetadata.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + BotAgentMetadata.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/proto.BotAgentMetadata"; + }; + + return BotAgentMetadata; + })(); + proto.BotCapabilityMetadata = (function() { function BotCapabilityMetadata(p) { @@ -27136,6 +27314,8 @@ export const proto = $root.proto = (() => { BusinessBroadcast.prototype.importListEnabled = null; BusinessBroadcast.prototype.companionSupportEnabled = null; + BusinessBroadcast.prototype.campaignSyncEnabled = null; + BusinessBroadcast.prototype.insightsSyncEnabled = null; let $oneOfFields; @@ -27151,6 +27331,18 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(BusinessBroadcast.prototype, "_campaignSyncEnabled", { + get: $util.oneOfGetter($oneOfFields = ["campaignSyncEnabled"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(BusinessBroadcast.prototype, "_insightsSyncEnabled", { + get: $util.oneOfGetter($oneOfFields = ["insightsSyncEnabled"]), + set: $util.oneOfSetter($oneOfFields) + }); + BusinessBroadcast.create = function create(properties) { return new BusinessBroadcast(properties); }; @@ -27162,6 +27354,10 @@ export const proto = $root.proto = (() => { w.uint32(8).bool(m.importListEnabled); if (m.companionSupportEnabled != null && Object.hasOwnProperty.call(m, "companionSupportEnabled")) w.uint32(16).bool(m.companionSupportEnabled); + if (m.campaignSyncEnabled != null && Object.hasOwnProperty.call(m, "campaignSyncEnabled")) + w.uint32(24).bool(m.campaignSyncEnabled); + if (m.insightsSyncEnabled != null && Object.hasOwnProperty.call(m, "insightsSyncEnabled")) + w.uint32(32).bool(m.insightsSyncEnabled); return w; }; @@ -27182,6 +27378,14 @@ export const proto = $root.proto = (() => { m.companionSupportEnabled = r.bool(); break; } + case 3: { + m.campaignSyncEnabled = r.bool(); + break; + } + case 4: { + m.insightsSyncEnabled = r.bool(); + break; + } default: r.skipType(t & 7); break; @@ -27200,6 +27404,12 @@ export const proto = $root.proto = (() => { if (d.companionSupportEnabled != null) { m.companionSupportEnabled = Boolean(d.companionSupportEnabled); } + if (d.campaignSyncEnabled != null) { + m.campaignSyncEnabled = Boolean(d.campaignSyncEnabled); + } + if (d.insightsSyncEnabled != null) { + m.insightsSyncEnabled = Boolean(d.insightsSyncEnabled); + } return m; }; @@ -27217,6 +27427,16 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._companionSupportEnabled = "companionSupportEnabled"; } + if (m.campaignSyncEnabled != null && m.hasOwnProperty("campaignSyncEnabled")) { + d.campaignSyncEnabled = m.campaignSyncEnabled; + if (o.oneofs) + d._campaignSyncEnabled = "campaignSyncEnabled"; + } + if (m.insightsSyncEnabled != null && m.hasOwnProperty("insightsSyncEnabled")) { + d.insightsSyncEnabled = m.insightsSyncEnabled; + if (o.oneofs) + d._insightsSyncEnabled = "insightsSyncEnabled"; + } return d; }; @@ -37369,6 +37589,7 @@ export const proto = $root.proto = (() => { Message.prototype.pollCreationMessageV6 = null; Message.prototype.conditionalRevealMessage = null; Message.prototype.pollAddOptionMessage = null; + Message.prototype.eventInviteMessage = null; let $oneOfFields; @@ -37978,6 +38199,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(Message.prototype, "_eventInviteMessage", { + get: $util.oneOfGetter($oneOfFields = ["eventInviteMessage"]), + set: $util.oneOfSetter($oneOfFields) + }); + Message.create = function create(properties) { return new Message(properties); }; @@ -38187,6 +38414,8 @@ export const proto = $root.proto = (() => { $root.proto.Message.ConditionalRevealMessage.encode(m.conditionalRevealMessage, w.uint32(962).fork()).ldelim(); if (m.pollAddOptionMessage != null && Object.hasOwnProperty.call(m, "pollAddOptionMessage")) $root.proto.Message.PollAddOptionMessage.encode(m.pollAddOptionMessage, w.uint32(970).fork()).ldelim(); + if (m.eventInviteMessage != null && Object.hasOwnProperty.call(m, "eventInviteMessage")) + $root.proto.Message.EventInviteMessage.encode(m.eventInviteMessage, w.uint32(978).fork()).ldelim(); return w; }; @@ -38603,6 +38832,10 @@ export const proto = $root.proto = (() => { m.pollAddOptionMessage = $root.proto.Message.PollAddOptionMessage.decode(r, r.uint32()); break; } + case 122: { + m.eventInviteMessage = $root.proto.Message.EventInviteMessage.decode(r, r.uint32()); + break; + } default: r.skipType(t & 7); break; @@ -39118,6 +39351,11 @@ export const proto = $root.proto = (() => { throw TypeError(".proto.Message.pollAddOptionMessage: object expected"); m.pollAddOptionMessage = $root.proto.Message.PollAddOptionMessage.fromObject(d.pollAddOptionMessage); } + if (d.eventInviteMessage != null) { + if (typeof d.eventInviteMessage !== "object") + throw TypeError(".proto.Message.eventInviteMessage: object expected"); + m.eventInviteMessage = $root.proto.Message.EventInviteMessage.fromObject(d.eventInviteMessage); + } return m; }; @@ -39630,6 +39868,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._pollAddOptionMessage = "pollAddOptionMessage"; } + if (m.eventInviteMessage != null && m.hasOwnProperty("eventInviteMessage")) { + d.eventInviteMessage = $root.proto.Message.EventInviteMessage.toObject(m.eventInviteMessage, o); + if (o.oneofs) + d._eventInviteMessage = "eventInviteMessage"; + } return d; }; @@ -45105,6 +45348,235 @@ export const proto = $root.proto = (() => { return EncReactionMessage; })(); + Message.EventInviteMessage = (function() { + + function EventInviteMessage(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + EventInviteMessage.prototype.contextInfo = null; + EventInviteMessage.prototype.eventId = null; + EventInviteMessage.prototype.eventTitle = null; + EventInviteMessage.prototype.jpegThumbnail = null; + EventInviteMessage.prototype.startTime = null; + EventInviteMessage.prototype.caption = null; + EventInviteMessage.prototype.isCanceled = null; + + let $oneOfFields; + + // Virtual OneOf for proto3 optional field + Object.defineProperty(EventInviteMessage.prototype, "_contextInfo", { + get: $util.oneOfGetter($oneOfFields = ["contextInfo"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(EventInviteMessage.prototype, "_eventId", { + get: $util.oneOfGetter($oneOfFields = ["eventId"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(EventInviteMessage.prototype, "_eventTitle", { + get: $util.oneOfGetter($oneOfFields = ["eventTitle"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(EventInviteMessage.prototype, "_jpegThumbnail", { + get: $util.oneOfGetter($oneOfFields = ["jpegThumbnail"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(EventInviteMessage.prototype, "_startTime", { + get: $util.oneOfGetter($oneOfFields = ["startTime"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(EventInviteMessage.prototype, "_caption", { + get: $util.oneOfGetter($oneOfFields = ["caption"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(EventInviteMessage.prototype, "_isCanceled", { + get: $util.oneOfGetter($oneOfFields = ["isCanceled"]), + set: $util.oneOfSetter($oneOfFields) + }); + + EventInviteMessage.create = function create(properties) { + return new EventInviteMessage(properties); + }; + + EventInviteMessage.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.contextInfo != null && Object.hasOwnProperty.call(m, "contextInfo")) + $root.proto.ContextInfo.encode(m.contextInfo, w.uint32(10).fork()).ldelim(); + if (m.eventId != null && Object.hasOwnProperty.call(m, "eventId")) + w.uint32(18).string(m.eventId); + if (m.eventTitle != null && Object.hasOwnProperty.call(m, "eventTitle")) + w.uint32(26).string(m.eventTitle); + if (m.jpegThumbnail != null && Object.hasOwnProperty.call(m, "jpegThumbnail")) + w.uint32(34).bytes(m.jpegThumbnail); + if (m.startTime != null && Object.hasOwnProperty.call(m, "startTime")) + w.uint32(40).int64(m.startTime); + if (m.caption != null && Object.hasOwnProperty.call(m, "caption")) + w.uint32(50).string(m.caption); + if (m.isCanceled != null && Object.hasOwnProperty.call(m, "isCanceled")) + w.uint32(56).bool(m.isCanceled); + return w; + }; + + EventInviteMessage.decode = function decode(r, l, e) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.proto.Message.EventInviteMessage(); + while (r.pos < c) { + var t = r.uint32(); + if (t === e) + break; + switch (t >>> 3) { + case 1: { + m.contextInfo = $root.proto.ContextInfo.decode(r, r.uint32()); + break; + } + case 2: { + m.eventId = r.string(); + break; + } + case 3: { + m.eventTitle = r.string(); + break; + } + case 4: { + m.jpegThumbnail = r.bytes(); + break; + } + case 5: { + m.startTime = r.int64(); + break; + } + case 6: { + m.caption = r.string(); + break; + } + case 7: { + m.isCanceled = r.bool(); + break; + } + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + EventInviteMessage.fromObject = function fromObject(d) { + if (d instanceof $root.proto.Message.EventInviteMessage) + return d; + var m = new $root.proto.Message.EventInviteMessage(); + if (d.contextInfo != null) { + if (typeof d.contextInfo !== "object") + throw TypeError(".proto.Message.EventInviteMessage.contextInfo: object expected"); + m.contextInfo = $root.proto.ContextInfo.fromObject(d.contextInfo); + } + if (d.eventId != null) { + m.eventId = String(d.eventId); + } + if (d.eventTitle != null) { + m.eventTitle = String(d.eventTitle); + } + if (d.jpegThumbnail != null) { + if (typeof d.jpegThumbnail === "string") + $util.base64.decode(d.jpegThumbnail, m.jpegThumbnail = $util.newBuffer($util.base64.length(d.jpegThumbnail)), 0); + else if (d.jpegThumbnail.length >= 0) + m.jpegThumbnail = d.jpegThumbnail; + } + if (d.startTime != null) { + if ($util.Long) + (m.startTime = $util.Long.fromValue(d.startTime)).unsigned = false; + else if (typeof d.startTime === "string") + m.startTime = parseInt(d.startTime, 10); + else if (typeof d.startTime === "number") + m.startTime = d.startTime; + else if (typeof d.startTime === "object") + m.startTime = new $util.LongBits(d.startTime.low >>> 0, d.startTime.high >>> 0).toNumber(); + } + if (d.caption != null) { + m.caption = String(d.caption); + } + if (d.isCanceled != null) { + m.isCanceled = Boolean(d.isCanceled); + } + return m; + }; + + EventInviteMessage.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (m.contextInfo != null && m.hasOwnProperty("contextInfo")) { + d.contextInfo = $root.proto.ContextInfo.toObject(m.contextInfo, o); + if (o.oneofs) + d._contextInfo = "contextInfo"; + } + if (m.eventId != null && m.hasOwnProperty("eventId")) { + d.eventId = m.eventId; + if (o.oneofs) + d._eventId = "eventId"; + } + if (m.eventTitle != null && m.hasOwnProperty("eventTitle")) { + d.eventTitle = m.eventTitle; + if (o.oneofs) + d._eventTitle = "eventTitle"; + } + 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) + d._jpegThumbnail = "jpegThumbnail"; + } + if (m.startTime != null && m.hasOwnProperty("startTime")) { + if (typeof m.startTime === "number") + d.startTime = o.longs === String ? String(m.startTime) : m.startTime; + else + d.startTime = o.longs === String ? longToString(m.startTime) : o.longs === Number ? longToNumber(m.startTime) : m.startTime; + if (o.oneofs) + d._startTime = "startTime"; + } + if (m.caption != null && m.hasOwnProperty("caption")) { + d.caption = m.caption; + if (o.oneofs) + d._caption = "caption"; + } + if (m.isCanceled != null && m.hasOwnProperty("isCanceled")) { + d.isCanceled = m.isCanceled; + if (o.oneofs) + d._isCanceled = "isCanceled"; + } + return d; + }; + + EventInviteMessage.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + EventInviteMessage.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/proto.Message.EventInviteMessage"; + }; + + return EventInviteMessage; + })(); + Message.EventMessage = (function() { function EventMessage(p) { @@ -54792,15 +55264,16 @@ export const proto = $root.proto = (() => { } MessageHistoryMetadata.prototype.historyReceivers = $util.emptyArray; - MessageHistoryMetadata.prototype.oldestMessageTimestamp = null; + MessageHistoryMetadata.prototype.oldestMessageTimestampInWindow = null; MessageHistoryMetadata.prototype.messageCount = null; MessageHistoryMetadata.prototype.nonHistoryReceivers = $util.emptyArray; + MessageHistoryMetadata.prototype.oldestMessageTimestampInBundle = null; let $oneOfFields; // Virtual OneOf for proto3 optional field - Object.defineProperty(MessageHistoryMetadata.prototype, "_oldestMessageTimestamp", { - get: $util.oneOfGetter($oneOfFields = ["oldestMessageTimestamp"]), + Object.defineProperty(MessageHistoryMetadata.prototype, "_oldestMessageTimestampInWindow", { + get: $util.oneOfGetter($oneOfFields = ["oldestMessageTimestampInWindow"]), set: $util.oneOfSetter($oneOfFields) }); @@ -54810,6 +55283,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(MessageHistoryMetadata.prototype, "_oldestMessageTimestampInBundle", { + get: $util.oneOfGetter($oneOfFields = ["oldestMessageTimestampInBundle"]), + set: $util.oneOfSetter($oneOfFields) + }); + MessageHistoryMetadata.create = function create(properties) { return new MessageHistoryMetadata(properties); }; @@ -54821,14 +55300,16 @@ export const proto = $root.proto = (() => { for (var i = 0; i < m.historyReceivers.length; ++i) w.uint32(10).string(m.historyReceivers[i]); } - if (m.oldestMessageTimestamp != null && Object.hasOwnProperty.call(m, "oldestMessageTimestamp")) - w.uint32(16).int64(m.oldestMessageTimestamp); + if (m.oldestMessageTimestampInWindow != null && Object.hasOwnProperty.call(m, "oldestMessageTimestampInWindow")) + w.uint32(16).int64(m.oldestMessageTimestampInWindow); if (m.messageCount != null && Object.hasOwnProperty.call(m, "messageCount")) w.uint32(24).int64(m.messageCount); if (m.nonHistoryReceivers != null && m.nonHistoryReceivers.length) { for (var i = 0; i < m.nonHistoryReceivers.length; ++i) w.uint32(34).string(m.nonHistoryReceivers[i]); } + if (m.oldestMessageTimestampInBundle != null && Object.hasOwnProperty.call(m, "oldestMessageTimestampInBundle")) + w.uint32(40).int64(m.oldestMessageTimestampInBundle); return w; }; @@ -54848,7 +55329,7 @@ export const proto = $root.proto = (() => { break; } case 2: { - m.oldestMessageTimestamp = r.int64(); + m.oldestMessageTimestampInWindow = r.int64(); break; } case 3: { @@ -54861,6 +55342,10 @@ export const proto = $root.proto = (() => { m.nonHistoryReceivers.push(r.string()); break; } + case 5: { + m.oldestMessageTimestampInBundle = r.int64(); + break; + } default: r.skipType(t & 7); break; @@ -54881,15 +55366,15 @@ export const proto = $root.proto = (() => { m.historyReceivers[i] = String(d.historyReceivers[i]); } } - if (d.oldestMessageTimestamp != null) { + if (d.oldestMessageTimestampInWindow != null) { if ($util.Long) - (m.oldestMessageTimestamp = $util.Long.fromValue(d.oldestMessageTimestamp)).unsigned = false; - else if (typeof d.oldestMessageTimestamp === "string") - m.oldestMessageTimestamp = parseInt(d.oldestMessageTimestamp, 10); - else if (typeof d.oldestMessageTimestamp === "number") - m.oldestMessageTimestamp = d.oldestMessageTimestamp; - else if (typeof d.oldestMessageTimestamp === "object") - m.oldestMessageTimestamp = new $util.LongBits(d.oldestMessageTimestamp.low >>> 0, d.oldestMessageTimestamp.high >>> 0).toNumber(); + (m.oldestMessageTimestampInWindow = $util.Long.fromValue(d.oldestMessageTimestampInWindow)).unsigned = false; + else if (typeof d.oldestMessageTimestampInWindow === "string") + m.oldestMessageTimestampInWindow = parseInt(d.oldestMessageTimestampInWindow, 10); + else if (typeof d.oldestMessageTimestampInWindow === "number") + m.oldestMessageTimestampInWindow = d.oldestMessageTimestampInWindow; + else if (typeof d.oldestMessageTimestampInWindow === "object") + m.oldestMessageTimestampInWindow = new $util.LongBits(d.oldestMessageTimestampInWindow.low >>> 0, d.oldestMessageTimestampInWindow.high >>> 0).toNumber(); } if (d.messageCount != null) { if ($util.Long) @@ -54909,6 +55394,16 @@ export const proto = $root.proto = (() => { m.nonHistoryReceivers[i] = String(d.nonHistoryReceivers[i]); } } + if (d.oldestMessageTimestampInBundle != null) { + if ($util.Long) + (m.oldestMessageTimestampInBundle = $util.Long.fromValue(d.oldestMessageTimestampInBundle)).unsigned = false; + else if (typeof d.oldestMessageTimestampInBundle === "string") + m.oldestMessageTimestampInBundle = parseInt(d.oldestMessageTimestampInBundle, 10); + else if (typeof d.oldestMessageTimestampInBundle === "number") + m.oldestMessageTimestampInBundle = d.oldestMessageTimestampInBundle; + else if (typeof d.oldestMessageTimestampInBundle === "object") + m.oldestMessageTimestampInBundle = new $util.LongBits(d.oldestMessageTimestampInBundle.low >>> 0, d.oldestMessageTimestampInBundle.high >>> 0).toNumber(); + } return m; }; @@ -54926,13 +55421,13 @@ export const proto = $root.proto = (() => { d.historyReceivers[j] = m.historyReceivers[j]; } } - if (m.oldestMessageTimestamp != null && m.hasOwnProperty("oldestMessageTimestamp")) { - if (typeof m.oldestMessageTimestamp === "number") - d.oldestMessageTimestamp = o.longs === String ? String(m.oldestMessageTimestamp) : m.oldestMessageTimestamp; + if (m.oldestMessageTimestampInWindow != null && m.hasOwnProperty("oldestMessageTimestampInWindow")) { + if (typeof m.oldestMessageTimestampInWindow === "number") + d.oldestMessageTimestampInWindow = o.longs === String ? String(m.oldestMessageTimestampInWindow) : m.oldestMessageTimestampInWindow; else - d.oldestMessageTimestamp = o.longs === String ? longToString(m.oldestMessageTimestamp) : o.longs === Number ? longToNumber(m.oldestMessageTimestamp) : m.oldestMessageTimestamp; + d.oldestMessageTimestampInWindow = o.longs === String ? longToString(m.oldestMessageTimestampInWindow) : o.longs === Number ? longToNumber(m.oldestMessageTimestampInWindow) : m.oldestMessageTimestampInWindow; if (o.oneofs) - d._oldestMessageTimestamp = "oldestMessageTimestamp"; + d._oldestMessageTimestampInWindow = "oldestMessageTimestampInWindow"; } if (m.messageCount != null && m.hasOwnProperty("messageCount")) { if (typeof m.messageCount === "number") @@ -54948,6 +55443,14 @@ export const proto = $root.proto = (() => { d.nonHistoryReceivers[j] = m.nonHistoryReceivers[j]; } } + if (m.oldestMessageTimestampInBundle != null && m.hasOwnProperty("oldestMessageTimestampInBundle")) { + if (typeof m.oldestMessageTimestampInBundle === "number") + d.oldestMessageTimestampInBundle = o.longs === String ? String(m.oldestMessageTimestampInBundle) : m.oldestMessageTimestampInBundle; + else + d.oldestMessageTimestampInBundle = o.longs === String ? longToString(m.oldestMessageTimestampInBundle) : o.longs === Number ? longToNumber(m.oldestMessageTimestampInBundle) : m.oldestMessageTimestampInBundle; + if (o.oneofs) + d._oldestMessageTimestampInBundle = "oldestMessageTimestampInBundle"; + } return d; }; @@ -64896,6 +65399,7 @@ export const proto = $root.proto = (() => { RequestWelcomeMessageMetadata.prototype.localChatState = null; RequestWelcomeMessageMetadata.prototype.welcomeTrigger = null; + RequestWelcomeMessageMetadata.prototype.botAgentMetadata = null; let $oneOfFields; @@ -64911,6 +65415,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(RequestWelcomeMessageMetadata.prototype, "_botAgentMetadata", { + get: $util.oneOfGetter($oneOfFields = ["botAgentMetadata"]), + set: $util.oneOfSetter($oneOfFields) + }); + RequestWelcomeMessageMetadata.create = function create(properties) { return new RequestWelcomeMessageMetadata(properties); }; @@ -64922,6 +65432,8 @@ export const proto = $root.proto = (() => { w.uint32(8).int32(m.localChatState); if (m.welcomeTrigger != null && Object.hasOwnProperty.call(m, "welcomeTrigger")) w.uint32(16).int32(m.welcomeTrigger); + if (m.botAgentMetadata != null && Object.hasOwnProperty.call(m, "botAgentMetadata")) + $root.proto.BotAgentMetadata.encode(m.botAgentMetadata, w.uint32(26).fork()).ldelim(); return w; }; @@ -64942,6 +65454,10 @@ export const proto = $root.proto = (() => { m.welcomeTrigger = r.int32(); break; } + case 3: { + m.botAgentMetadata = $root.proto.BotAgentMetadata.decode(r, r.uint32()); + break; + } default: r.skipType(t & 7); break; @@ -64986,6 +65502,11 @@ export const proto = $root.proto = (() => { m.welcomeTrigger = 1; break; } + if (d.botAgentMetadata != null) { + if (typeof d.botAgentMetadata !== "object") + throw TypeError(".proto.Message.RequestWelcomeMessageMetadata.botAgentMetadata: object expected"); + m.botAgentMetadata = $root.proto.BotAgentMetadata.fromObject(d.botAgentMetadata); + } return m; }; @@ -65003,6 +65524,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._welcomeTrigger = "welcomeTrigger"; } + if (m.botAgentMetadata != null && m.hasOwnProperty("botAgentMetadata")) { + d.botAgentMetadata = $root.proto.BotAgentMetadata.toObject(m.botAgentMetadata, o); + if (o.oneofs) + d._botAgentMetadata = "botAgentMetadata"; + } return d; }; @@ -73360,6 +73886,7 @@ export const proto = $root.proto = (() => { values[valuesById[81] = "BUSINESS_BROADCAST_CAMPAIGN_ACTION"] = 81; values[valuesById[82] = "BUSINESS_BROADCAST_INSIGHTS_ACTION"] = 82; values[valuesById[83] = "CUSTOMER_DATA_ACTION"] = 83; + values[valuesById[84] = "SUBSCRIPTIONS_SYNC_V2_ACTION"] = 84; values[valuesById[10001] = "SHARE_OWN_PN"] = 10001; values[valuesById[10002] = "BUSINESS_BROADCAST_ACTION"] = 10002; values[valuesById[10003] = "AI_THREAD_DELETE_ACTION"] = 10003; @@ -83608,6 +84135,7 @@ export const proto = $root.proto = (() => { SyncActionValue.prototype.businessBroadcastCampaignAction = null; SyncActionValue.prototype.businessBroadcastInsightsAction = null; SyncActionValue.prototype.customerDataAction = null; + SyncActionValue.prototype.subscriptionsSyncV2Action = null; let $oneOfFields; @@ -84055,6 +84583,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(SyncActionValue.prototype, "_subscriptionsSyncV2Action", { + get: $util.oneOfGetter($oneOfFields = ["subscriptionsSyncV2Action"]), + set: $util.oneOfSetter($oneOfFields) + }); + SyncActionValue.create = function create(properties) { return new SyncActionValue(properties); }; @@ -84210,6 +84744,8 @@ export const proto = $root.proto = (() => { $root.proto.SyncActionValue.BusinessBroadcastInsightsAction.encode(m.businessBroadcastInsightsAction, w.uint32(658).fork()).ldelim(); if (m.customerDataAction != null && Object.hasOwnProperty.call(m, "customerDataAction")) $root.proto.SyncActionValue.CustomerDataAction.encode(m.customerDataAction, w.uint32(666).fork()).ldelim(); + if (m.subscriptionsSyncV2Action != null && Object.hasOwnProperty.call(m, "subscriptionsSyncV2Action")) + $root.proto.SyncActionValue.SubscriptionsSyncV2Action.encode(m.subscriptionsSyncV2Action, w.uint32(674).fork()).ldelim(); return w; }; @@ -84518,6 +85054,10 @@ export const proto = $root.proto = (() => { m.customerDataAction = $root.proto.SyncActionValue.CustomerDataAction.decode(r, r.uint32()); break; } + case 84: { + m.subscriptionsSyncV2Action = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.decode(r, r.uint32()); + break; + } default: r.skipType(t & 7); break; @@ -84905,6 +85445,11 @@ export const proto = $root.proto = (() => { throw TypeError(".proto.SyncActionValue.customerDataAction: object expected"); m.customerDataAction = $root.proto.SyncActionValue.CustomerDataAction.fromObject(d.customerDataAction); } + if (d.subscriptionsSyncV2Action != null) { + if (typeof d.subscriptionsSyncV2Action !== "object") + throw TypeError(".proto.SyncActionValue.subscriptionsSyncV2Action: object expected"); + m.subscriptionsSyncV2Action = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.fromObject(d.subscriptionsSyncV2Action); + } return m; }; @@ -85285,6 +85830,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._customerDataAction = "customerDataAction"; } + if (m.subscriptionsSyncV2Action != null && m.hasOwnProperty("subscriptionsSyncV2Action")) { + d.subscriptionsSyncV2Action = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.toObject(m.subscriptionsSyncV2Action, o); + if (o.oneofs) + d._subscriptionsSyncV2Action = "subscriptionsSyncV2Action"; + } return d; }; @@ -94698,6 +95248,557 @@ export const proto = $root.proto = (() => { return SubscriptionAction; })(); + SyncActionValue.SubscriptionsSyncV2Action = (function() { + + function SubscriptionsSyncV2Action(p) { + this.subscriptions = []; + this.paidFeature = []; + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + SubscriptionsSyncV2Action.prototype.subscriptions = $util.emptyArray; + SubscriptionsSyncV2Action.prototype.paidFeature = $util.emptyArray; + + SubscriptionsSyncV2Action.create = function create(properties) { + return new SubscriptionsSyncV2Action(properties); + }; + + SubscriptionsSyncV2Action.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.subscriptions != null && m.subscriptions.length) { + for (var i = 0; i < m.subscriptions.length; ++i) + $root.proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo.encode(m.subscriptions[i], w.uint32(10).fork()).ldelim(); + } + if (m.paidFeature != null && m.paidFeature.length) { + for (var i = 0; i < m.paidFeature.length; ++i) + $root.proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature.encode(m.paidFeature[i], w.uint32(18).fork()).ldelim(); + } + return w; + }; + + SubscriptionsSyncV2Action.decode = function decode(r, l, e) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.proto.SyncActionValue.SubscriptionsSyncV2Action(); + while (r.pos < c) { + var t = r.uint32(); + if (t === e) + break; + switch (t >>> 3) { + case 1: { + if (!(m.subscriptions && m.subscriptions.length)) + m.subscriptions = []; + m.subscriptions.push($root.proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo.decode(r, r.uint32())); + break; + } + case 2: { + if (!(m.paidFeature && m.paidFeature.length)) + m.paidFeature = []; + m.paidFeature.push($root.proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature.decode(r, r.uint32())); + break; + } + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + SubscriptionsSyncV2Action.fromObject = function fromObject(d) { + if (d instanceof $root.proto.SyncActionValue.SubscriptionsSyncV2Action) + return d; + var m = new $root.proto.SyncActionValue.SubscriptionsSyncV2Action(); + if (d.subscriptions) { + if (!Array.isArray(d.subscriptions)) + throw TypeError(".proto.SyncActionValue.SubscriptionsSyncV2Action.subscriptions: array expected"); + m.subscriptions = []; + for (var i = 0; i < d.subscriptions.length; ++i) { + if (typeof d.subscriptions[i] !== "object") + throw TypeError(".proto.SyncActionValue.SubscriptionsSyncV2Action.subscriptions: object expected"); + m.subscriptions[i] = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo.fromObject(d.subscriptions[i]); + } + } + if (d.paidFeature) { + if (!Array.isArray(d.paidFeature)) + throw TypeError(".proto.SyncActionValue.SubscriptionsSyncV2Action.paidFeature: array expected"); + m.paidFeature = []; + for (var i = 0; i < d.paidFeature.length; ++i) { + if (typeof d.paidFeature[i] !== "object") + throw TypeError(".proto.SyncActionValue.SubscriptionsSyncV2Action.paidFeature: object expected"); + m.paidFeature[i] = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature.fromObject(d.paidFeature[i]); + } + } + return m; + }; + + SubscriptionsSyncV2Action.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.arrays || o.defaults) { + d.subscriptions = []; + d.paidFeature = []; + } + if (m.subscriptions && m.subscriptions.length) { + d.subscriptions = []; + for (var j = 0; j < m.subscriptions.length; ++j) { + d.subscriptions[j] = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo.toObject(m.subscriptions[j], o); + } + } + if (m.paidFeature && m.paidFeature.length) { + d.paidFeature = []; + for (var j = 0; j < m.paidFeature.length; ++j) { + d.paidFeature[j] = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature.toObject(m.paidFeature[j], o); + } + } + return d; + }; + + SubscriptionsSyncV2Action.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + SubscriptionsSyncV2Action.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/proto.SyncActionValue.SubscriptionsSyncV2Action"; + }; + + SubscriptionsSyncV2Action.PaidFeature = (function() { + + function PaidFeature(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + PaidFeature.prototype.name = null; + PaidFeature.prototype.enabled = null; + PaidFeature.prototype.limit = null; + PaidFeature.prototype.expirationTime = null; + + let $oneOfFields; + + // Virtual OneOf for proto3 optional field + Object.defineProperty(PaidFeature.prototype, "_name", { + get: $util.oneOfGetter($oneOfFields = ["name"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(PaidFeature.prototype, "_enabled", { + get: $util.oneOfGetter($oneOfFields = ["enabled"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(PaidFeature.prototype, "_limit", { + get: $util.oneOfGetter($oneOfFields = ["limit"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(PaidFeature.prototype, "_expirationTime", { + get: $util.oneOfGetter($oneOfFields = ["expirationTime"]), + set: $util.oneOfSetter($oneOfFields) + }); + + PaidFeature.create = function create(properties) { + return new PaidFeature(properties); + }; + + PaidFeature.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.name != null && Object.hasOwnProperty.call(m, "name")) + w.uint32(10).string(m.name); + if (m.enabled != null && Object.hasOwnProperty.call(m, "enabled")) + w.uint32(16).bool(m.enabled); + if (m.limit != null && Object.hasOwnProperty.call(m, "limit")) + w.uint32(24).int32(m.limit); + if (m.expirationTime != null && Object.hasOwnProperty.call(m, "expirationTime")) + w.uint32(32).int64(m.expirationTime); + return w; + }; + + PaidFeature.decode = function decode(r, l, e) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature(); + while (r.pos < c) { + var t = r.uint32(); + if (t === e) + break; + switch (t >>> 3) { + case 1: { + m.name = r.string(); + break; + } + case 2: { + m.enabled = r.bool(); + break; + } + case 3: { + m.limit = r.int32(); + break; + } + case 4: { + m.expirationTime = r.int64(); + break; + } + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + PaidFeature.fromObject = function fromObject(d) { + if (d instanceof $root.proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature) + return d; + var m = new $root.proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature(); + if (d.name != null) { + m.name = String(d.name); + } + if (d.enabled != null) { + m.enabled = Boolean(d.enabled); + } + if (d.limit != null) { + m.limit = d.limit | 0; + } + if (d.expirationTime != null) { + if ($util.Long) + (m.expirationTime = $util.Long.fromValue(d.expirationTime)).unsigned = false; + else if (typeof d.expirationTime === "string") + m.expirationTime = parseInt(d.expirationTime, 10); + else if (typeof d.expirationTime === "number") + m.expirationTime = d.expirationTime; + else if (typeof d.expirationTime === "object") + m.expirationTime = new $util.LongBits(d.expirationTime.low >>> 0, d.expirationTime.high >>> 0).toNumber(); + } + return m; + }; + + PaidFeature.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (m.name != null && m.hasOwnProperty("name")) { + d.name = m.name; + if (o.oneofs) + d._name = "name"; + } + if (m.enabled != null && m.hasOwnProperty("enabled")) { + d.enabled = m.enabled; + if (o.oneofs) + d._enabled = "enabled"; + } + if (m.limit != null && m.hasOwnProperty("limit")) { + d.limit = m.limit; + if (o.oneofs) + d._limit = "limit"; + } + if (m.expirationTime != null && m.hasOwnProperty("expirationTime")) { + if (typeof m.expirationTime === "number") + d.expirationTime = o.longs === String ? String(m.expirationTime) : m.expirationTime; + else + d.expirationTime = o.longs === String ? longToString(m.expirationTime) : o.longs === Number ? longToNumber(m.expirationTime) : m.expirationTime; + if (o.oneofs) + d._expirationTime = "expirationTime"; + } + return d; + }; + + PaidFeature.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + PaidFeature.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/proto.SyncActionValue.SubscriptionsSyncV2Action.PaidFeature"; + }; + + return PaidFeature; + })(); + + SubscriptionsSyncV2Action.SubscriptionInfo = (function() { + + function SubscriptionInfo(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + SubscriptionInfo.prototype.id = null; + SubscriptionInfo.prototype.tier = null; + SubscriptionInfo.prototype.status = null; + SubscriptionInfo.prototype.startTime = null; + SubscriptionInfo.prototype.endTime = null; + SubscriptionInfo.prototype.isPlatformChanged = null; + SubscriptionInfo.prototype.source = null; + SubscriptionInfo.prototype.creationTime = null; + + let $oneOfFields; + + // Virtual OneOf for proto3 optional field + Object.defineProperty(SubscriptionInfo.prototype, "_id", { + get: $util.oneOfGetter($oneOfFields = ["id"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(SubscriptionInfo.prototype, "_tier", { + get: $util.oneOfGetter($oneOfFields = ["tier"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(SubscriptionInfo.prototype, "_status", { + get: $util.oneOfGetter($oneOfFields = ["status"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(SubscriptionInfo.prototype, "_startTime", { + get: $util.oneOfGetter($oneOfFields = ["startTime"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(SubscriptionInfo.prototype, "_endTime", { + get: $util.oneOfGetter($oneOfFields = ["endTime"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(SubscriptionInfo.prototype, "_isPlatformChanged", { + get: $util.oneOfGetter($oneOfFields = ["isPlatformChanged"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(SubscriptionInfo.prototype, "_source", { + get: $util.oneOfGetter($oneOfFields = ["source"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(SubscriptionInfo.prototype, "_creationTime", { + get: $util.oneOfGetter($oneOfFields = ["creationTime"]), + set: $util.oneOfSetter($oneOfFields) + }); + + SubscriptionInfo.create = function create(properties) { + return new SubscriptionInfo(properties); + }; + + SubscriptionInfo.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.id != null && Object.hasOwnProperty.call(m, "id")) + w.uint32(10).string(m.id); + if (m.tier != null && Object.hasOwnProperty.call(m, "tier")) + w.uint32(16).int32(m.tier); + if (m.status != null && Object.hasOwnProperty.call(m, "status")) + w.uint32(26).string(m.status); + if (m.startTime != null && Object.hasOwnProperty.call(m, "startTime")) + w.uint32(32).int64(m.startTime); + if (m.endTime != null && Object.hasOwnProperty.call(m, "endTime")) + w.uint32(40).int64(m.endTime); + if (m.isPlatformChanged != null && Object.hasOwnProperty.call(m, "isPlatformChanged")) + w.uint32(48).bool(m.isPlatformChanged); + if (m.source != null && Object.hasOwnProperty.call(m, "source")) + w.uint32(58).string(m.source); + if (m.creationTime != null && Object.hasOwnProperty.call(m, "creationTime")) + w.uint32(64).int64(m.creationTime); + return w; + }; + + SubscriptionInfo.decode = function decode(r, l, e) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo(); + while (r.pos < c) { + var t = r.uint32(); + if (t === e) + break; + switch (t >>> 3) { + case 1: { + m.id = r.string(); + break; + } + case 2: { + m.tier = r.int32(); + break; + } + case 3: { + m.status = r.string(); + break; + } + case 4: { + m.startTime = r.int64(); + break; + } + case 5: { + m.endTime = r.int64(); + break; + } + case 6: { + m.isPlatformChanged = r.bool(); + break; + } + case 7: { + m.source = r.string(); + break; + } + case 8: { + m.creationTime = r.int64(); + break; + } + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + SubscriptionInfo.fromObject = function fromObject(d) { + if (d instanceof $root.proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo) + return d; + var m = new $root.proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo(); + if (d.id != null) { + m.id = String(d.id); + } + if (d.tier != null) { + m.tier = d.tier | 0; + } + if (d.status != null) { + m.status = String(d.status); + } + if (d.startTime != null) { + if ($util.Long) + (m.startTime = $util.Long.fromValue(d.startTime)).unsigned = false; + else if (typeof d.startTime === "string") + m.startTime = parseInt(d.startTime, 10); + else if (typeof d.startTime === "number") + m.startTime = d.startTime; + else if (typeof d.startTime === "object") + m.startTime = new $util.LongBits(d.startTime.low >>> 0, d.startTime.high >>> 0).toNumber(); + } + if (d.endTime != null) { + if ($util.Long) + (m.endTime = $util.Long.fromValue(d.endTime)).unsigned = false; + else if (typeof d.endTime === "string") + m.endTime = parseInt(d.endTime, 10); + else if (typeof d.endTime === "number") + m.endTime = d.endTime; + else if (typeof d.endTime === "object") + m.endTime = new $util.LongBits(d.endTime.low >>> 0, d.endTime.high >>> 0).toNumber(); + } + if (d.isPlatformChanged != null) { + m.isPlatformChanged = Boolean(d.isPlatformChanged); + } + if (d.source != null) { + m.source = String(d.source); + } + if (d.creationTime != null) { + if ($util.Long) + (m.creationTime = $util.Long.fromValue(d.creationTime)).unsigned = false; + else if (typeof d.creationTime === "string") + m.creationTime = parseInt(d.creationTime, 10); + else if (typeof d.creationTime === "number") + m.creationTime = d.creationTime; + else if (typeof d.creationTime === "object") + m.creationTime = new $util.LongBits(d.creationTime.low >>> 0, d.creationTime.high >>> 0).toNumber(); + } + return m; + }; + + SubscriptionInfo.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (m.id != null && m.hasOwnProperty("id")) { + d.id = m.id; + if (o.oneofs) + d._id = "id"; + } + if (m.tier != null && m.hasOwnProperty("tier")) { + d.tier = m.tier; + if (o.oneofs) + d._tier = "tier"; + } + if (m.status != null && m.hasOwnProperty("status")) { + d.status = m.status; + if (o.oneofs) + d._status = "status"; + } + if (m.startTime != null && m.hasOwnProperty("startTime")) { + if (typeof m.startTime === "number") + d.startTime = o.longs === String ? String(m.startTime) : m.startTime; + else + d.startTime = o.longs === String ? longToString(m.startTime) : o.longs === Number ? longToNumber(m.startTime) : m.startTime; + if (o.oneofs) + d._startTime = "startTime"; + } + if (m.endTime != null && m.hasOwnProperty("endTime")) { + if (typeof m.endTime === "number") + d.endTime = o.longs === String ? String(m.endTime) : m.endTime; + else + d.endTime = o.longs === String ? longToString(m.endTime) : o.longs === Number ? longToNumber(m.endTime) : m.endTime; + if (o.oneofs) + d._endTime = "endTime"; + } + if (m.isPlatformChanged != null && m.hasOwnProperty("isPlatformChanged")) { + d.isPlatformChanged = m.isPlatformChanged; + if (o.oneofs) + d._isPlatformChanged = "isPlatformChanged"; + } + if (m.source != null && m.hasOwnProperty("source")) { + d.source = m.source; + if (o.oneofs) + d._source = "source"; + } + if (m.creationTime != null && m.hasOwnProperty("creationTime")) { + if (typeof m.creationTime === "number") + d.creationTime = o.longs === String ? String(m.creationTime) : m.creationTime; + else + d.creationTime = o.longs === String ? longToString(m.creationTime) : o.longs === Number ? longToNumber(m.creationTime) : m.creationTime; + if (o.oneofs) + d._creationTime = "creationTime"; + } + return d; + }; + + SubscriptionInfo.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + SubscriptionInfo.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/proto.SyncActionValue.SubscriptionsSyncV2Action.SubscriptionInfo"; + }; + + return SubscriptionInfo; + })(); + + return SubscriptionsSyncV2Action; + })(); + SyncActionValue.SyncActionMessage = (function() { function SyncActionMessage(p) { diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 72a4310d..b380c2a2 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1034928492]} +{"version": [2, 3000, 1034989030]} From e46b9750c22d23f58a0931de63b99e767f9628c1 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Thu, 12 Mar 2026 06:13:34 -0300 Subject: [PATCH 12/32] chore: update WhatsApp Web version to v2.3000.1035018135 (#280) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index b380c2a2..a3a66711 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1034989030]} +{"version":[2,3000,1035018135]} From c2c156d70731aeaa0227c198c5104d12f1acfc90 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Thu, 12 Mar 2026 23:52:05 -0300 Subject: [PATCH 13/32] chore(deps): update music-metadata from 11.12.0 to 11.12.3 (#281) Includes security fix for CWE-85 (infinite loop in ASF parsing), Ogg/Vorbis duration fix, and restored TypeScript declaration files. --- package.json | 2 +- yarn.lock | 29 ++++++++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 12a5d3f1..3086c9f8 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "fflate": "^0.8.2", "libsignal": "github:whiskeysockets/libsignal-node", "lru-cache": "^11.2.6", - "music-metadata": "^11.12.0", + "music-metadata": "^11.12.3", "p-queue": "^9.0.0", "pino": "^10.3.1", "prom-client": "^15.1.3", diff --git a/yarn.lock b/yarn.lock index d5dc3f91..69d792b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -388,6 +388,13 @@ __metadata: languageName: node linkType: hard +"@borewit/text-codec@npm:^0.2.2": + version: 0.2.2 + resolution: "@borewit/text-codec@npm:0.2.2" + checksum: 10c0/2d3fb132bc6a132914a8fbf8e9ff2fa1ead210ecc395b28bb7355bd7719548a5e351ffe39f21c3bee8048f6cabd99eabd404bb5cc809cad9cba25abed19d271f + languageName: node + linkType: hard + "@cacheable/node-cache@npm:^2.0.1": version: 2.0.1 resolution: "@cacheable/node-cache@npm:2.0.1" @@ -3105,7 +3112,7 @@ __metadata: libsignal: "github:whiskeysockets/libsignal-node" link-preview-js: "npm:^4.0.0" lru-cache: "npm:^11.2.6" - music-metadata: "npm:^11.12.0" + music-metadata: "npm:^11.12.3" open: "npm:^11.0.0" p-queue: "npm:^9.0.0" pino: "npm:^10.3.1" @@ -4556,15 +4563,15 @@ __metadata: languageName: node linkType: hard -"file-type@npm:^21.3.0": - version: 21.3.0 - resolution: "file-type@npm:21.3.0" +"file-type@npm:^21.3.1": + version: 21.3.1 + resolution: "file-type@npm:21.3.1" dependencies: "@tokenizer/inflate": "npm:^0.4.1" strtok3: "npm:^10.3.4" token-types: "npm:^6.1.1" uint8array-extras: "npm:^1.4.0" - checksum: 10c0/1b1fa909e6063044a6da1d2ea348ee4d747ed9286382d3f0d4d6532c11fb2ea9f2e7e67b2bc7d745d1bc937e05dee1aa8cb912c64250933bcb393a3744f4e284 + checksum: 10c0/66a8eda781c803c6fc372464abba88cee564cfe30f029716f06909da1564bc51d0a4e8d34654b881dc751cdb0513338b3c0747e06a608cee0dd5c5499b018d47 languageName: node linkType: hard @@ -6504,21 +6511,21 @@ __metadata: languageName: node linkType: hard -"music-metadata@npm:^11.12.0": - version: 11.12.0 - resolution: "music-metadata@npm:11.12.0" +"music-metadata@npm:^11.12.3": + version: 11.12.3 + resolution: "music-metadata@npm:11.12.3" dependencies: - "@borewit/text-codec": "npm:^0.2.1" + "@borewit/text-codec": "npm:^0.2.2" "@tokenizer/token": "npm:^0.3.0" content-type: "npm:^1.0.5" debug: "npm:^4.4.3" - file-type: "npm:^21.3.0" + file-type: "npm:^21.3.1" media-typer: "npm:^1.1.0" strtok3: "npm:^10.3.4" token-types: "npm:^6.1.2" uint8array-extras: "npm:^1.5.0" win-guid: "npm:^0.2.1" - checksum: 10c0/014a120b8941ab80452171def0ec3cba013041ba4b94f8061ad493a037d3423e6d316131a13fb0433404685ebd3c8e3ae2255c1bafaeb20673fcd73af2df0148 + checksum: 10c0/57df905f51ec792e5b4c994645eab64d47d5608b9de11151f54b26fbd5f563598ec04d8aed7f244ef1479030d78ddab8af1ee84dfcee40a93b3241941b50999d languageName: node linkType: hard From 8fe71b03d5cf1f7871ed4ac198bfc48d096a537d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:45:20 -0300 Subject: [PATCH 14/32] chore: update proto/version to v2.3000.1035083696 (#282) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 2 +- src/Defaults/baileys-version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index 76dd41c4..ae6ef6e0 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1034989030 +/// WhatsApp Version: 2.3000.1035083696 message ADVDeviceIdentity { optional uint32 rawId = 1; diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index a3a66711..fc6405ee 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1035018135]} +{"version": [2, 3000, 1035083696]} From aaf3bd83eb9c764b78f232927b8cf40ef247715c Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Fri, 13 Mar 2026 06:11:31 -0300 Subject: [PATCH 15/32] chore: update WhatsApp Web version to v2.3000.1035102652 (#283) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index fc6405ee..263012eb 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1035083696]} +{"version":[2,3000,1035102652]} From 9f8f74fc7a12630f9ad874842233a7617d97ad04 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 00:44:33 -0300 Subject: [PATCH 16/32] chore: update proto/version to v2.3000.1035169204 (#284) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 23 +- WAProto/index.d.ts | 48 ++++- WAProto/index.js | 343 ++++++++++++++++++++++++++++++ src/Defaults/baileys-version.json | 2 +- 4 files changed, 412 insertions(+), 4 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index ae6ef6e0..f513d3d3 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1035083696 +/// WhatsApp Version: 2.3000.1035169204 message ADVDeviceIdentity { optional uint32 rawId = 1; @@ -415,6 +415,7 @@ message BotCapabilityMetadata { RICH_RESPONSE_INLINE_LINKS_ENABLED = 56; RICH_RESPONSE_UR_IMAGINE_VIDEO = 57; JSON_PATCH_STREAMING = 58; + AI_TAB_FORCE_CLIPPY = 59; } } @@ -1082,6 +1083,7 @@ message ClientPairingProps { optional bool isSyncdPureLidSession = 2; optional bool isSyncdSnapshotRecoveryEnabled = 3; optional bool isHsThumbnailSyncEnabled = 4; + optional bytes subscriptionSyncPayload = 5; } message ClientPayload { @@ -1616,6 +1618,7 @@ message Conversation { optional bool limitSharingInitiatedByMe = 53; optional bool maibaAiThreadEnabled = 54; optional bool isMarketingMessageThread = 55; + optional bool isSenderNewAccount = 56; enum EndOfHistoryTransferType { COMPLETE_BUT_MORE_MESSAGES_REMAIN_ON_PRIMARY = 0; COMPLETE_AND_NO_MORE_MESSAGE_REMAIN_ON_PRIMARY = 1; @@ -1914,6 +1917,8 @@ message HandshakeMessage { optional bytes static = 1; optional bytes payload = 2; optional bytes extendedCiphertext = 3; + optional bytes paddedBytes = 4; + optional bool simulateXxkemFs = 5; } message ClientHello { @@ -1922,6 +1927,9 @@ message HandshakeMessage { optional bytes payload = 3; optional bool useExtended = 4; optional bytes extendedCiphertext = 5; + optional bytes paddedBytes = 6; + optional bool sendServerHelloPaddedBytes = 7; + optional bool simulateXxkemFs = 8; } message ServerHello { @@ -1929,6 +1937,7 @@ message HandshakeMessage { optional bytes static = 2; optional bytes payload = 3; optional bytes extendedStatic = 4; + optional bytes paddingBytes = 5; } } @@ -3166,6 +3175,11 @@ message Message { optional int64 expiryTimestamp = 2; optional bool incentiveEligible = 3; optional string referralId = 4; + optional InviteType inviteType = 5; + enum InviteType { + DEFAULT = 0; + MAPPER = 1; + } enum ServiceType { UNKNOWN = 0; FBPAY = 1; @@ -4148,6 +4162,7 @@ enum MutationProps { BUSINESS_BROADCAST_INSIGHTS_ACTION = 82; CUSTOMER_DATA_ACTION = 83; SUBSCRIPTIONS_SYNC_V2_ACTION = 84; + THREAD_PIN_ACTION = 85; SHARE_OWN_PN = 10001; BUSINESS_BROADCAST_ACTION = 10002; AI_THREAD_DELETE_ACTION = 10003; @@ -4616,6 +4631,7 @@ message StatusAttribution { APPLE_MUSIC = 8; SHARECHAT = 9; GOOGLE_PHOTOS = 10; + SOUNDCLOUD = 11; } } @@ -4785,6 +4801,7 @@ message SyncActionValue { optional BusinessBroadcastInsightsAction businessBroadcastInsightsAction = 82; optional CustomerDataAction customerDataAction = 83; optional SubscriptionsSyncV2Action subscriptionsSyncV2Action = 84; + optional ThreadPinAction threadPinAction = 85; message AgentAction { optional string name = 1; optional int32 deviceID = 2; @@ -5334,6 +5351,10 @@ message SyncActionValue { repeated SyncActionValue.SyncActionMessage messages = 3; } + message ThreadPinAction { + optional bool pinned = 1; + } + message TimeFormatAction { optional bool isTwentyFourHourFormatEnabled = 1; } diff --git a/WAProto/index.d.ts b/WAProto/index.d.ts index e2bee454..c06faf32 100644 --- a/WAProto/index.d.ts +++ b/WAProto/index.d.ts @@ -1090,7 +1090,8 @@ export namespace proto { RICH_RESPONSE_UR_BLOKS_ENABLED = 55, RICH_RESPONSE_INLINE_LINKS_ENABLED = 56, RICH_RESPONSE_UR_IMAGINE_VIDEO = 57, - JSON_PATCH_STREAMING = 58 + JSON_PATCH_STREAMING = 58, + AI_TAB_FORCE_CLIPPY = 59 } } @@ -2801,6 +2802,7 @@ export namespace proto { isSyncdPureLidSession?: (boolean|null); isSyncdSnapshotRecoveryEnabled?: (boolean|null); isHsThumbnailSyncEnabled?: (boolean|null); + subscriptionSyncPayload?: (Uint8Array|null); } class ClientPairingProps implements IClientPairingProps { @@ -2809,6 +2811,7 @@ export namespace proto { public isSyncdPureLidSession?: (boolean|null); public isSyncdSnapshotRecoveryEnabled?: (boolean|null); public isHsThumbnailSyncEnabled?: (boolean|null); + public subscriptionSyncPayload?: (Uint8Array|null); public static create(properties?: proto.IClientPairingProps): proto.ClientPairingProps; public static encode(m: proto.IClientPairingProps, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.ClientPairingProps; @@ -3881,6 +3884,7 @@ export namespace proto { limitSharingInitiatedByMe?: (boolean|null); maibaAiThreadEnabled?: (boolean|null); isMarketingMessageThread?: (boolean|null); + isSenderNewAccount?: (boolean|null); } class Conversation implements IConversation { @@ -3940,6 +3944,7 @@ export namespace proto { public limitSharingInitiatedByMe?: (boolean|null); public maibaAiThreadEnabled?: (boolean|null); public isMarketingMessageThread?: (boolean|null); + public isSenderNewAccount?: (boolean|null); public static create(properties?: proto.IConversation): proto.Conversation; public static encode(m: proto.IConversation, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Conversation; @@ -4720,6 +4725,8 @@ export namespace proto { "static"?: (Uint8Array|null); payload?: (Uint8Array|null); extendedCiphertext?: (Uint8Array|null); + paddedBytes?: (Uint8Array|null); + simulateXxkemFs?: (boolean|null); } class ClientFinish implements IClientFinish { @@ -4727,6 +4734,8 @@ export namespace proto { public static?: (Uint8Array|null); public payload?: (Uint8Array|null); public extendedCiphertext?: (Uint8Array|null); + public paddedBytes?: (Uint8Array|null); + public simulateXxkemFs?: (boolean|null); public static create(properties?: proto.HandshakeMessage.IClientFinish): proto.HandshakeMessage.ClientFinish; public static encode(m: proto.HandshakeMessage.IClientFinish, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HandshakeMessage.ClientFinish; @@ -4742,6 +4751,9 @@ export namespace proto { payload?: (Uint8Array|null); useExtended?: (boolean|null); extendedCiphertext?: (Uint8Array|null); + paddedBytes?: (Uint8Array|null); + sendServerHelloPaddedBytes?: (boolean|null); + simulateXxkemFs?: (boolean|null); } class ClientHello implements IClientHello { @@ -4751,6 +4763,9 @@ export namespace proto { public payload?: (Uint8Array|null); public useExtended?: (boolean|null); public extendedCiphertext?: (Uint8Array|null); + public paddedBytes?: (Uint8Array|null); + public sendServerHelloPaddedBytes?: (boolean|null); + public simulateXxkemFs?: (boolean|null); public static create(properties?: proto.HandshakeMessage.IClientHello): proto.HandshakeMessage.ClientHello; public static encode(m: proto.HandshakeMessage.IClientHello, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HandshakeMessage.ClientHello; @@ -4765,6 +4780,7 @@ export namespace proto { "static"?: (Uint8Array|null); payload?: (Uint8Array|null); extendedStatic?: (Uint8Array|null); + paddingBytes?: (Uint8Array|null); } class ServerHello implements IServerHello { @@ -4773,6 +4789,7 @@ export namespace proto { public static?: (Uint8Array|null); public payload?: (Uint8Array|null); public extendedStatic?: (Uint8Array|null); + public paddingBytes?: (Uint8Array|null); public static create(properties?: proto.HandshakeMessage.IServerHello): proto.HandshakeMessage.ServerHello; public static encode(m: proto.HandshakeMessage.IServerHello, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HandshakeMessage.ServerHello; @@ -8054,6 +8071,7 @@ export namespace proto { expiryTimestamp?: (number|Long|null); incentiveEligible?: (boolean|null); referralId?: (string|null); + inviteType?: (proto.Message.PaymentInviteMessage.InviteType|null); } class PaymentInviteMessage implements IPaymentInviteMessage { @@ -8062,6 +8080,7 @@ export namespace proto { public expiryTimestamp?: (number|Long|null); public incentiveEligible?: (boolean|null); public referralId?: (string|null); + public inviteType?: (proto.Message.PaymentInviteMessage.InviteType|null); public static create(properties?: proto.Message.IPaymentInviteMessage): proto.Message.PaymentInviteMessage; public static encode(m: proto.Message.IPaymentInviteMessage, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Message.PaymentInviteMessage; @@ -8073,6 +8092,11 @@ export namespace proto { namespace PaymentInviteMessage { + enum InviteType { + DEFAULT = 0, + MAPPER = 1 + } + enum ServiceType { UNKNOWN = 0, FBPAY = 1, @@ -10533,6 +10557,7 @@ export namespace proto { BUSINESS_BROADCAST_INSIGHTS_ACTION = 82, CUSTOMER_DATA_ACTION = 83, SUBSCRIPTIONS_SYNC_V2_ACTION = 84, + THREAD_PIN_ACTION = 85, SHARE_OWN_PN = 10001, BUSINESS_BROADCAST_ACTION = 10002, AI_THREAD_DELETE_ACTION = 10003 @@ -11816,7 +11841,8 @@ export namespace proto { THREADS = 7, APPLE_MUSIC = 8, SHARECHAT = 9, - GOOGLE_PHOTOS = 10 + GOOGLE_PHOTOS = 10, + SOUNDCLOUD = 11 } } @@ -12128,6 +12154,7 @@ export namespace proto { businessBroadcastInsightsAction?: (proto.SyncActionValue.IBusinessBroadcastInsightsAction|null); customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null); subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null); + threadPinAction?: (proto.SyncActionValue.IThreadPinAction|null); } class SyncActionValue implements ISyncActionValue { @@ -12207,6 +12234,7 @@ export namespace proto { public businessBroadcastInsightsAction?: (proto.SyncActionValue.IBusinessBroadcastInsightsAction|null); public customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null); public subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null); + public threadPinAction?: (proto.SyncActionValue.IThreadPinAction|null); public static create(properties?: proto.ISyncActionValue): proto.SyncActionValue; public static encode(m: proto.ISyncActionValue, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue; @@ -13850,6 +13878,22 @@ export namespace proto { public static getTypeUrl(typeUrlPrefix?: string): string; } + interface IThreadPinAction { + pinned?: (boolean|null); + } + + class ThreadPinAction implements IThreadPinAction { + constructor(p?: proto.SyncActionValue.IThreadPinAction); + public pinned?: (boolean|null); + public static create(properties?: proto.SyncActionValue.IThreadPinAction): proto.SyncActionValue.ThreadPinAction; + public static encode(m: proto.SyncActionValue.IThreadPinAction, w?: $protobuf.Writer): $protobuf.Writer; + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.ThreadPinAction; + public static fromObject(d: { [k: string]: any }): proto.SyncActionValue.ThreadPinAction; + public static toObject(m: proto.SyncActionValue.ThreadPinAction, o?: $protobuf.IConversionOptions): { [k: string]: any }; + public toJSON(): { [k: string]: any }; + public static getTypeUrl(typeUrlPrefix?: string): string; + } + interface ITimeFormatAction { isTwentyFourHourFormatEnabled?: (boolean|null); } diff --git a/WAProto/index.js b/WAProto/index.js index c7fc0510..fc2022a6 100644 --- a/WAProto/index.js +++ b/WAProto/index.js @@ -6889,6 +6889,10 @@ export const proto = $root.proto = (() => { case 58: m.capabilities[i] = 58; break; + case "AI_TAB_FORCE_CLIPPY": + case 59: + m.capabilities[i] = 59; + break; } } } @@ -6983,6 +6987,7 @@ export const proto = $root.proto = (() => { values[valuesById[56] = "RICH_RESPONSE_INLINE_LINKS_ENABLED"] = 56; values[valuesById[57] = "RICH_RESPONSE_UR_IMAGINE_VIDEO"] = 57; values[valuesById[58] = "JSON_PATCH_STREAMING"] = 58; + values[valuesById[59] = "AI_TAB_FORCE_CLIPPY"] = 59; return values; })(); @@ -18028,6 +18033,7 @@ export const proto = $root.proto = (() => { ClientPairingProps.prototype.isSyncdPureLidSession = null; ClientPairingProps.prototype.isSyncdSnapshotRecoveryEnabled = null; ClientPairingProps.prototype.isHsThumbnailSyncEnabled = null; + ClientPairingProps.prototype.subscriptionSyncPayload = null; let $oneOfFields; @@ -18055,6 +18061,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(ClientPairingProps.prototype, "_subscriptionSyncPayload", { + get: $util.oneOfGetter($oneOfFields = ["subscriptionSyncPayload"]), + set: $util.oneOfSetter($oneOfFields) + }); + ClientPairingProps.create = function create(properties) { return new ClientPairingProps(properties); }; @@ -18070,6 +18082,8 @@ export const proto = $root.proto = (() => { w.uint32(24).bool(m.isSyncdSnapshotRecoveryEnabled); if (m.isHsThumbnailSyncEnabled != null && Object.hasOwnProperty.call(m, "isHsThumbnailSyncEnabled")) w.uint32(32).bool(m.isHsThumbnailSyncEnabled); + if (m.subscriptionSyncPayload != null && Object.hasOwnProperty.call(m, "subscriptionSyncPayload")) + w.uint32(42).bytes(m.subscriptionSyncPayload); return w; }; @@ -18098,6 +18112,10 @@ export const proto = $root.proto = (() => { m.isHsThumbnailSyncEnabled = r.bool(); break; } + case 5: { + m.subscriptionSyncPayload = r.bytes(); + break; + } default: r.skipType(t & 7); break; @@ -18122,6 +18140,12 @@ export const proto = $root.proto = (() => { if (d.isHsThumbnailSyncEnabled != null) { m.isHsThumbnailSyncEnabled = Boolean(d.isHsThumbnailSyncEnabled); } + if (d.subscriptionSyncPayload != null) { + if (typeof d.subscriptionSyncPayload === "string") + $util.base64.decode(d.subscriptionSyncPayload, m.subscriptionSyncPayload = $util.newBuffer($util.base64.length(d.subscriptionSyncPayload)), 0); + else if (d.subscriptionSyncPayload.length >= 0) + m.subscriptionSyncPayload = d.subscriptionSyncPayload; + } return m; }; @@ -18149,6 +18173,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._isHsThumbnailSyncEnabled = "isHsThumbnailSyncEnabled"; } + if (m.subscriptionSyncPayload != null && m.hasOwnProperty("subscriptionSyncPayload")) { + d.subscriptionSyncPayload = o.bytes === String ? $util.base64.encode(m.subscriptionSyncPayload, 0, m.subscriptionSyncPayload.length) : o.bytes === Array ? Array.prototype.slice.call(m.subscriptionSyncPayload) : m.subscriptionSyncPayload; + if (o.oneofs) + d._subscriptionSyncPayload = "subscriptionSyncPayload"; + } return d; }; @@ -25605,6 +25634,7 @@ export const proto = $root.proto = (() => { Conversation.prototype.limitSharingInitiatedByMe = null; Conversation.prototype.maibaAiThreadEnabled = null; Conversation.prototype.isMarketingMessageThread = null; + Conversation.prototype.isSenderNewAccount = null; let $oneOfFields; @@ -25926,6 +25956,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(Conversation.prototype, "_isSenderNewAccount", { + get: $util.oneOfGetter($oneOfFields = ["isSenderNewAccount"]), + set: $util.oneOfSetter($oneOfFields) + }); + Conversation.create = function create(properties) { return new Conversation(properties); }; @@ -26047,6 +26083,8 @@ export const proto = $root.proto = (() => { w.uint32(432).bool(m.maibaAiThreadEnabled); if (m.isMarketingMessageThread != null && Object.hasOwnProperty.call(m, "isMarketingMessageThread")) w.uint32(440).bool(m.isMarketingMessageThread); + if (m.isSenderNewAccount != null && Object.hasOwnProperty.call(m, "isSenderNewAccount")) + w.uint32(448).bool(m.isSenderNewAccount); return w; }; @@ -26283,6 +26321,10 @@ export const proto = $root.proto = (() => { m.isMarketingMessageThread = r.bool(); break; } + case 56: { + m.isSenderNewAccount = r.bool(); + break; + } default: r.skipType(t & 7); break; @@ -26620,6 +26662,9 @@ export const proto = $root.proto = (() => { if (d.isMarketingMessageThread != null) { m.isMarketingMessageThread = Boolean(d.isMarketingMessageThread); } + if (d.isSenderNewAccount != null) { + m.isSenderNewAccount = Boolean(d.isSenderNewAccount); + } return m; }; @@ -26932,6 +26977,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._isMarketingMessageThread = "isMarketingMessageThread"; } + if (m.isSenderNewAccount != null && m.hasOwnProperty("isSenderNewAccount")) { + d.isSenderNewAccount = m.isSenderNewAccount; + if (o.oneofs) + d._isSenderNewAccount = "isSenderNewAccount"; + } return d; }; @@ -32299,6 +32349,8 @@ export const proto = $root.proto = (() => { ClientFinish.prototype["static"] = null; ClientFinish.prototype.payload = null; ClientFinish.prototype.extendedCiphertext = null; + ClientFinish.prototype.paddedBytes = null; + ClientFinish.prototype.simulateXxkemFs = null; let $oneOfFields; @@ -32320,6 +32372,18 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(ClientFinish.prototype, "_paddedBytes", { + get: $util.oneOfGetter($oneOfFields = ["paddedBytes"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(ClientFinish.prototype, "_simulateXxkemFs", { + get: $util.oneOfGetter($oneOfFields = ["simulateXxkemFs"]), + set: $util.oneOfSetter($oneOfFields) + }); + ClientFinish.create = function create(properties) { return new ClientFinish(properties); }; @@ -32333,6 +32397,10 @@ export const proto = $root.proto = (() => { w.uint32(18).bytes(m.payload); if (m.extendedCiphertext != null && Object.hasOwnProperty.call(m, "extendedCiphertext")) w.uint32(26).bytes(m.extendedCiphertext); + if (m.paddedBytes != null && Object.hasOwnProperty.call(m, "paddedBytes")) + w.uint32(34).bytes(m.paddedBytes); + if (m.simulateXxkemFs != null && Object.hasOwnProperty.call(m, "simulateXxkemFs")) + w.uint32(40).bool(m.simulateXxkemFs); return w; }; @@ -32357,6 +32425,14 @@ export const proto = $root.proto = (() => { m.extendedCiphertext = r.bytes(); break; } + case 4: { + m.paddedBytes = r.bytes(); + break; + } + case 5: { + m.simulateXxkemFs = r.bool(); + break; + } default: r.skipType(t & 7); break; @@ -32387,6 +32463,15 @@ export const proto = $root.proto = (() => { else if (d.extendedCiphertext.length >= 0) m.extendedCiphertext = d.extendedCiphertext; } + if (d.paddedBytes != null) { + if (typeof d.paddedBytes === "string") + $util.base64.decode(d.paddedBytes, m.paddedBytes = $util.newBuffer($util.base64.length(d.paddedBytes)), 0); + else if (d.paddedBytes.length >= 0) + m.paddedBytes = d.paddedBytes; + } + if (d.simulateXxkemFs != null) { + m.simulateXxkemFs = Boolean(d.simulateXxkemFs); + } return m; }; @@ -32409,6 +32494,16 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._extendedCiphertext = "extendedCiphertext"; } + if (m.paddedBytes != null && m.hasOwnProperty("paddedBytes")) { + d.paddedBytes = o.bytes === String ? $util.base64.encode(m.paddedBytes, 0, m.paddedBytes.length) : o.bytes === Array ? Array.prototype.slice.call(m.paddedBytes) : m.paddedBytes; + if (o.oneofs) + d._paddedBytes = "paddedBytes"; + } + if (m.simulateXxkemFs != null && m.hasOwnProperty("simulateXxkemFs")) { + d.simulateXxkemFs = m.simulateXxkemFs; + if (o.oneofs) + d._simulateXxkemFs = "simulateXxkemFs"; + } return d; }; @@ -32440,6 +32535,9 @@ export const proto = $root.proto = (() => { ClientHello.prototype.payload = null; ClientHello.prototype.useExtended = null; ClientHello.prototype.extendedCiphertext = null; + ClientHello.prototype.paddedBytes = null; + ClientHello.prototype.sendServerHelloPaddedBytes = null; + ClientHello.prototype.simulateXxkemFs = null; let $oneOfFields; @@ -32473,6 +32571,24 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(ClientHello.prototype, "_paddedBytes", { + get: $util.oneOfGetter($oneOfFields = ["paddedBytes"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(ClientHello.prototype, "_sendServerHelloPaddedBytes", { + get: $util.oneOfGetter($oneOfFields = ["sendServerHelloPaddedBytes"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(ClientHello.prototype, "_simulateXxkemFs", { + get: $util.oneOfGetter($oneOfFields = ["simulateXxkemFs"]), + set: $util.oneOfSetter($oneOfFields) + }); + ClientHello.create = function create(properties) { return new ClientHello(properties); }; @@ -32490,6 +32606,12 @@ export const proto = $root.proto = (() => { w.uint32(32).bool(m.useExtended); if (m.extendedCiphertext != null && Object.hasOwnProperty.call(m, "extendedCiphertext")) w.uint32(42).bytes(m.extendedCiphertext); + if (m.paddedBytes != null && Object.hasOwnProperty.call(m, "paddedBytes")) + w.uint32(50).bytes(m.paddedBytes); + if (m.sendServerHelloPaddedBytes != null && Object.hasOwnProperty.call(m, "sendServerHelloPaddedBytes")) + w.uint32(56).bool(m.sendServerHelloPaddedBytes); + if (m.simulateXxkemFs != null && Object.hasOwnProperty.call(m, "simulateXxkemFs")) + w.uint32(64).bool(m.simulateXxkemFs); return w; }; @@ -32522,6 +32644,18 @@ export const proto = $root.proto = (() => { m.extendedCiphertext = r.bytes(); break; } + case 6: { + m.paddedBytes = r.bytes(); + break; + } + case 7: { + m.sendServerHelloPaddedBytes = r.bool(); + break; + } + case 8: { + m.simulateXxkemFs = r.bool(); + break; + } default: r.skipType(t & 7); break; @@ -32561,6 +32695,18 @@ export const proto = $root.proto = (() => { else if (d.extendedCiphertext.length >= 0) m.extendedCiphertext = d.extendedCiphertext; } + if (d.paddedBytes != null) { + if (typeof d.paddedBytes === "string") + $util.base64.decode(d.paddedBytes, m.paddedBytes = $util.newBuffer($util.base64.length(d.paddedBytes)), 0); + else if (d.paddedBytes.length >= 0) + m.paddedBytes = d.paddedBytes; + } + if (d.sendServerHelloPaddedBytes != null) { + m.sendServerHelloPaddedBytes = Boolean(d.sendServerHelloPaddedBytes); + } + if (d.simulateXxkemFs != null) { + m.simulateXxkemFs = Boolean(d.simulateXxkemFs); + } return m; }; @@ -32593,6 +32739,21 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._extendedCiphertext = "extendedCiphertext"; } + if (m.paddedBytes != null && m.hasOwnProperty("paddedBytes")) { + d.paddedBytes = o.bytes === String ? $util.base64.encode(m.paddedBytes, 0, m.paddedBytes.length) : o.bytes === Array ? Array.prototype.slice.call(m.paddedBytes) : m.paddedBytes; + if (o.oneofs) + d._paddedBytes = "paddedBytes"; + } + if (m.sendServerHelloPaddedBytes != null && m.hasOwnProperty("sendServerHelloPaddedBytes")) { + d.sendServerHelloPaddedBytes = m.sendServerHelloPaddedBytes; + if (o.oneofs) + d._sendServerHelloPaddedBytes = "sendServerHelloPaddedBytes"; + } + if (m.simulateXxkemFs != null && m.hasOwnProperty("simulateXxkemFs")) { + d.simulateXxkemFs = m.simulateXxkemFs; + if (o.oneofs) + d._simulateXxkemFs = "simulateXxkemFs"; + } return d; }; @@ -32623,6 +32784,7 @@ export const proto = $root.proto = (() => { ServerHello.prototype["static"] = null; ServerHello.prototype.payload = null; ServerHello.prototype.extendedStatic = null; + ServerHello.prototype.paddingBytes = null; let $oneOfFields; @@ -32650,6 +32812,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(ServerHello.prototype, "_paddingBytes", { + get: $util.oneOfGetter($oneOfFields = ["paddingBytes"]), + set: $util.oneOfSetter($oneOfFields) + }); + ServerHello.create = function create(properties) { return new ServerHello(properties); }; @@ -32665,6 +32833,8 @@ export const proto = $root.proto = (() => { w.uint32(26).bytes(m.payload); if (m.extendedStatic != null && Object.hasOwnProperty.call(m, "extendedStatic")) w.uint32(34).bytes(m.extendedStatic); + if (m.paddingBytes != null && Object.hasOwnProperty.call(m, "paddingBytes")) + w.uint32(42).bytes(m.paddingBytes); return w; }; @@ -32693,6 +32863,10 @@ export const proto = $root.proto = (() => { m.extendedStatic = r.bytes(); break; } + case 5: { + m.paddingBytes = r.bytes(); + break; + } default: r.skipType(t & 7); break; @@ -32729,6 +32903,12 @@ export const proto = $root.proto = (() => { else if (d.extendedStatic.length >= 0) m.extendedStatic = d.extendedStatic; } + if (d.paddingBytes != null) { + if (typeof d.paddingBytes === "string") + $util.base64.decode(d.paddingBytes, m.paddingBytes = $util.newBuffer($util.base64.length(d.paddingBytes)), 0); + else if (d.paddingBytes.length >= 0) + m.paddingBytes = d.paddingBytes; + } return m; }; @@ -32756,6 +32936,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._extendedStatic = "extendedStatic"; } + if (m.paddingBytes != null && m.hasOwnProperty("paddingBytes")) { + d.paddingBytes = o.bytes === String ? $util.base64.encode(m.paddingBytes, 0, m.paddingBytes.length) : o.bytes === Array ? Array.prototype.slice.call(m.paddingBytes) : m.paddingBytes; + if (o.oneofs) + d._paddingBytes = "paddingBytes"; + } return d; }; @@ -56527,6 +56712,7 @@ export const proto = $root.proto = (() => { PaymentInviteMessage.prototype.expiryTimestamp = null; PaymentInviteMessage.prototype.incentiveEligible = null; PaymentInviteMessage.prototype.referralId = null; + PaymentInviteMessage.prototype.inviteType = null; let $oneOfFields; @@ -56554,6 +56740,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(PaymentInviteMessage.prototype, "_inviteType", { + get: $util.oneOfGetter($oneOfFields = ["inviteType"]), + set: $util.oneOfSetter($oneOfFields) + }); + PaymentInviteMessage.create = function create(properties) { return new PaymentInviteMessage(properties); }; @@ -56569,6 +56761,8 @@ export const proto = $root.proto = (() => { w.uint32(24).bool(m.incentiveEligible); if (m.referralId != null && Object.hasOwnProperty.call(m, "referralId")) w.uint32(34).string(m.referralId); + if (m.inviteType != null && Object.hasOwnProperty.call(m, "inviteType")) + w.uint32(40).int32(m.inviteType); return w; }; @@ -56597,6 +56791,10 @@ export const proto = $root.proto = (() => { m.referralId = r.string(); break; } + case 5: { + m.inviteType = r.int32(); + break; + } default: r.skipType(t & 7); break; @@ -56649,6 +56847,22 @@ export const proto = $root.proto = (() => { if (d.referralId != null) { m.referralId = String(d.referralId); } + switch (d.inviteType) { + default: + if (typeof d.inviteType === "number") { + m.inviteType = d.inviteType; + break; + } + break; + case "DEFAULT": + case 0: + m.inviteType = 0; + break; + case "MAPPER": + case 1: + m.inviteType = 1; + break; + } return m; }; @@ -56679,6 +56893,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._referralId = "referralId"; } + if (m.inviteType != null && m.hasOwnProperty("inviteType")) { + d.inviteType = o.enums === String ? $root.proto.Message.PaymentInviteMessage.InviteType[m.inviteType] === undefined ? m.inviteType : $root.proto.Message.PaymentInviteMessage.InviteType[m.inviteType] : m.inviteType; + if (o.oneofs) + d._inviteType = "inviteType"; + } return d; }; @@ -56693,6 +56912,13 @@ export const proto = $root.proto = (() => { return typeUrlPrefix + "/proto.Message.PaymentInviteMessage"; }; + PaymentInviteMessage.InviteType = (function() { + const valuesById = {}, values = Object.create(valuesById); + values[valuesById[0] = "DEFAULT"] = 0; + values[valuesById[1] = "MAPPER"] = 1; + return values; + })(); + PaymentInviteMessage.ServiceType = (function() { const valuesById = {}, values = Object.create(valuesById); values[valuesById[0] = "UNKNOWN"] = 0; @@ -73887,6 +74113,7 @@ export const proto = $root.proto = (() => { values[valuesById[82] = "BUSINESS_BROADCAST_INSIGHTS_ACTION"] = 82; values[valuesById[83] = "CUSTOMER_DATA_ACTION"] = 83; values[valuesById[84] = "SUBSCRIPTIONS_SYNC_V2_ACTION"] = 84; + values[valuesById[85] = "THREAD_PIN_ACTION"] = 85; values[valuesById[10001] = "SHARE_OWN_PN"] = 10001; values[valuesById[10002] = "BUSINESS_BROADCAST_ACTION"] = 10002; values[valuesById[10003] = "AI_THREAD_DELETE_ACTION"] = 10003; @@ -82502,6 +82729,10 @@ export const proto = $root.proto = (() => { case 10: m.source = 10; break; + case "SOUNDCLOUD": + case 11: + m.source = 11; + break; } if (d.duration != null) { m.duration = d.duration | 0; @@ -82563,6 +82794,7 @@ export const proto = $root.proto = (() => { values[valuesById[8] = "APPLE_MUSIC"] = 8; values[valuesById[9] = "SHARECHAT"] = 9; values[valuesById[10] = "GOOGLE_PHOTOS"] = 10; + values[valuesById[11] = "SOUNDCLOUD"] = 11; return values; })(); @@ -84136,6 +84368,7 @@ export const proto = $root.proto = (() => { SyncActionValue.prototype.businessBroadcastInsightsAction = null; SyncActionValue.prototype.customerDataAction = null; SyncActionValue.prototype.subscriptionsSyncV2Action = null; + SyncActionValue.prototype.threadPinAction = null; let $oneOfFields; @@ -84589,6 +84822,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(SyncActionValue.prototype, "_threadPinAction", { + get: $util.oneOfGetter($oneOfFields = ["threadPinAction"]), + set: $util.oneOfSetter($oneOfFields) + }); + SyncActionValue.create = function create(properties) { return new SyncActionValue(properties); }; @@ -84746,6 +84985,8 @@ export const proto = $root.proto = (() => { $root.proto.SyncActionValue.CustomerDataAction.encode(m.customerDataAction, w.uint32(666).fork()).ldelim(); if (m.subscriptionsSyncV2Action != null && Object.hasOwnProperty.call(m, "subscriptionsSyncV2Action")) $root.proto.SyncActionValue.SubscriptionsSyncV2Action.encode(m.subscriptionsSyncV2Action, w.uint32(674).fork()).ldelim(); + if (m.threadPinAction != null && Object.hasOwnProperty.call(m, "threadPinAction")) + $root.proto.SyncActionValue.ThreadPinAction.encode(m.threadPinAction, w.uint32(682).fork()).ldelim(); return w; }; @@ -85058,6 +85299,10 @@ export const proto = $root.proto = (() => { m.subscriptionsSyncV2Action = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.decode(r, r.uint32()); break; } + case 85: { + m.threadPinAction = $root.proto.SyncActionValue.ThreadPinAction.decode(r, r.uint32()); + break; + } default: r.skipType(t & 7); break; @@ -85450,6 +85695,11 @@ export const proto = $root.proto = (() => { throw TypeError(".proto.SyncActionValue.subscriptionsSyncV2Action: object expected"); m.subscriptionsSyncV2Action = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.fromObject(d.subscriptionsSyncV2Action); } + if (d.threadPinAction != null) { + if (typeof d.threadPinAction !== "object") + throw TypeError(".proto.SyncActionValue.threadPinAction: object expected"); + m.threadPinAction = $root.proto.SyncActionValue.ThreadPinAction.fromObject(d.threadPinAction); + } return m; }; @@ -85835,6 +86085,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._subscriptionsSyncV2Action = "subscriptionsSyncV2Action"; } + if (m.threadPinAction != null && m.hasOwnProperty("threadPinAction")) { + d.threadPinAction = $root.proto.SyncActionValue.ThreadPinAction.toObject(m.threadPinAction, o); + if (o.oneofs) + d._threadPinAction = "threadPinAction"; + } return d; }; @@ -96080,6 +96335,94 @@ export const proto = $root.proto = (() => { return SyncActionMessageRange; })(); + SyncActionValue.ThreadPinAction = (function() { + + function ThreadPinAction(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + ThreadPinAction.prototype.pinned = null; + + let $oneOfFields; + + // Virtual OneOf for proto3 optional field + Object.defineProperty(ThreadPinAction.prototype, "_pinned", { + get: $util.oneOfGetter($oneOfFields = ["pinned"]), + set: $util.oneOfSetter($oneOfFields) + }); + + ThreadPinAction.create = function create(properties) { + return new ThreadPinAction(properties); + }; + + ThreadPinAction.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.pinned != null && Object.hasOwnProperty.call(m, "pinned")) + w.uint32(8).bool(m.pinned); + return w; + }; + + ThreadPinAction.decode = function decode(r, l, e) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.proto.SyncActionValue.ThreadPinAction(); + while (r.pos < c) { + var t = r.uint32(); + if (t === e) + break; + switch (t >>> 3) { + case 1: { + m.pinned = r.bool(); + break; + } + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + ThreadPinAction.fromObject = function fromObject(d) { + if (d instanceof $root.proto.SyncActionValue.ThreadPinAction) + return d; + var m = new $root.proto.SyncActionValue.ThreadPinAction(); + if (d.pinned != null) { + m.pinned = Boolean(d.pinned); + } + return m; + }; + + ThreadPinAction.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (m.pinned != null && m.hasOwnProperty("pinned")) { + d.pinned = m.pinned; + if (o.oneofs) + d._pinned = "pinned"; + } + return d; + }; + + ThreadPinAction.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + ThreadPinAction.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/proto.SyncActionValue.ThreadPinAction"; + }; + + return ThreadPinAction; + })(); + SyncActionValue.TimeFormatAction = (function() { function TimeFormatAction(p) { diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 263012eb..beffa30f 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1035102652]} +{"version": [2, 3000, 1035169204]} From b5464bec737accd60e5aa72cbe6aee7ac0e50b8c Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Sat, 14 Mar 2026 06:07:31 -0300 Subject: [PATCH 17/32] chore: update WhatsApp Web version to v2.3000.1035176028 (#285) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index beffa30f..49596037 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1035169204]} +{"version":[2,3000,1035176028]} From 64c73faab2302a4c60b17774d4d4be37078f1162 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 00:56:35 -0300 Subject: [PATCH 18/32] chore: update proto/version to v2.3000.1035198198 (#286) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 2 +- src/Defaults/baileys-version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index f513d3d3..23e678f4 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1035169204 +/// WhatsApp Version: 2.3000.1035198198 message ADVDeviceIdentity { optional uint32 rawId = 1; diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 49596037..6005fe05 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1035176028]} +{"version": [2, 3000, 1035198198]} From 986ac588a99f3a33130428ab6fe8a2439a5b4b41 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Sun, 15 Mar 2026 06:08:25 -0300 Subject: [PATCH 19/32] chore: update WhatsApp Web version to v2.3000.1035203283 (#287) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 6005fe05..0a0461b9 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1035198198]} +{"version":[2,3000,1035203283]} From a25e6740fd142a8aa88f26ddb6bc146c6f3f6064 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:58:19 -0300 Subject: [PATCH 20/32] chore: update proto/version to v2.3000.1035216863 (#288) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 6 +++- WAProto/index.d.ts | 10 ++++-- WAProto/index.js | 52 +++++++++++++++++++++++++++++++ src/Defaults/baileys-version.json | 2 +- 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index 23e678f4..2ee95777 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1035198198 +/// WhatsApp Version: 2.3000.1035216863 message ADVDeviceIdentity { optional uint32 rawId = 1; @@ -416,6 +416,7 @@ message BotCapabilityMetadata { RICH_RESPONSE_UR_IMAGINE_VIDEO = 57; JSON_PATCH_STREAMING = 58; AI_TAB_FORCE_CLIPPY = 59; + UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60; } } @@ -1464,6 +1465,7 @@ message ContextInfo { optional AdType adType = 25; optional string wtwaWebsiteUrl = 26; optional string adPreviewUrl = 27; + optional bool containsCtwaFlowsAutoReply = 28; enum AdType { CTWA = 0; CAWC = 1; @@ -1619,6 +1621,7 @@ message Conversation { optional bool maibaAiThreadEnabled = 54; optional bool isMarketingMessageThread = 55; optional bool isSenderNewAccount = 56; + optional uint32 afterReadDuration = 57; enum EndOfHistoryTransferType { COMPLETE_BUT_MORE_MESSAGES_REMAIN_ON_PRIMARY = 0; COMPLETE_AND_NO_MORE_MESSAGE_REMAIN_ON_PRIMARY = 1; @@ -3601,6 +3604,7 @@ message Message { GROUP_MEMBER_LABEL_CHANGE = 30; AI_MEDIA_COLLECTION_MESSAGE = 31; MESSAGE_UNSCHEDULE = 32; + BOT_UNLINK_MESSAGE = 33; } } diff --git a/WAProto/index.d.ts b/WAProto/index.d.ts index c06faf32..f0a659e6 100644 --- a/WAProto/index.d.ts +++ b/WAProto/index.d.ts @@ -1091,7 +1091,8 @@ export namespace proto { RICH_RESPONSE_INLINE_LINKS_ENABLED = 56, RICH_RESPONSE_UR_IMAGINE_VIDEO = 57, JSON_PATCH_STREAMING = 58, - AI_TAB_FORCE_CLIPPY = 59 + AI_TAB_FORCE_CLIPPY = 59, + UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60 } } @@ -3589,6 +3590,7 @@ export namespace proto { adType?: (proto.ContextInfo.ExternalAdReplyInfo.AdType|null); wtwaWebsiteUrl?: (string|null); adPreviewUrl?: (string|null); + containsCtwaFlowsAutoReply?: (boolean|null); } class ExternalAdReplyInfo implements IExternalAdReplyInfo { @@ -3620,6 +3622,7 @@ export namespace proto { public adType?: (proto.ContextInfo.ExternalAdReplyInfo.AdType|null); public wtwaWebsiteUrl?: (string|null); public adPreviewUrl?: (string|null); + public containsCtwaFlowsAutoReply?: (boolean|null); public static create(properties?: proto.ContextInfo.IExternalAdReplyInfo): proto.ContextInfo.ExternalAdReplyInfo; public static encode(m: proto.ContextInfo.IExternalAdReplyInfo, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.ContextInfo.ExternalAdReplyInfo; @@ -3885,6 +3888,7 @@ export namespace proto { maibaAiThreadEnabled?: (boolean|null); isMarketingMessageThread?: (boolean|null); isSenderNewAccount?: (boolean|null); + afterReadDuration?: (number|null); } class Conversation implements IConversation { @@ -3945,6 +3949,7 @@ export namespace proto { public maibaAiThreadEnabled?: (boolean|null); public isMarketingMessageThread?: (boolean|null); public isSenderNewAccount?: (boolean|null); + public afterReadDuration?: (number|null); public static create(properties?: proto.IConversation): proto.Conversation; public static encode(m: proto.IConversation, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.Conversation; @@ -9262,7 +9267,8 @@ export namespace proto { AI_QUERY_FANOUT = 29, GROUP_MEMBER_LABEL_CHANGE = 30, AI_MEDIA_COLLECTION_MESSAGE = 31, - MESSAGE_UNSCHEDULE = 32 + MESSAGE_UNSCHEDULE = 32, + BOT_UNLINK_MESSAGE = 33 } } diff --git a/WAProto/index.js b/WAProto/index.js index fc2022a6..804faf5a 100644 --- a/WAProto/index.js +++ b/WAProto/index.js @@ -6893,6 +6893,10 @@ export const proto = $root.proto = (() => { case 59: m.capabilities[i] = 59; break; + case "UNIFIED_RESPONSE_EMBEDDED_SCREENS": + case 60: + m.capabilities[i] = 60; + break; } } } @@ -6988,6 +6992,7 @@ export const proto = $root.proto = (() => { values[valuesById[57] = "RICH_RESPONSE_UR_IMAGINE_VIDEO"] = 57; values[valuesById[58] = "JSON_PATCH_STREAMING"] = 58; values[valuesById[59] = "AI_TAB_FORCE_CLIPPY"] = 59; + values[valuesById[60] = "UNIFIED_RESPONSE_EMBEDDED_SCREENS"] = 60; return values; })(); @@ -23995,6 +24000,7 @@ export const proto = $root.proto = (() => { ExternalAdReplyInfo.prototype.adType = null; ExternalAdReplyInfo.prototype.wtwaWebsiteUrl = null; ExternalAdReplyInfo.prototype.adPreviewUrl = null; + ExternalAdReplyInfo.prototype.containsCtwaFlowsAutoReply = null; let $oneOfFields; @@ -24160,6 +24166,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(ExternalAdReplyInfo.prototype, "_containsCtwaFlowsAutoReply", { + get: $util.oneOfGetter($oneOfFields = ["containsCtwaFlowsAutoReply"]), + set: $util.oneOfSetter($oneOfFields) + }); + ExternalAdReplyInfo.create = function create(properties) { return new ExternalAdReplyInfo(properties); }; @@ -24221,6 +24233,8 @@ export const proto = $root.proto = (() => { w.uint32(210).string(m.wtwaWebsiteUrl); if (m.adPreviewUrl != null && Object.hasOwnProperty.call(m, "adPreviewUrl")) w.uint32(218).string(m.adPreviewUrl); + if (m.containsCtwaFlowsAutoReply != null && Object.hasOwnProperty.call(m, "containsCtwaFlowsAutoReply")) + w.uint32(224).bool(m.containsCtwaFlowsAutoReply); return w; }; @@ -24341,6 +24355,10 @@ export const proto = $root.proto = (() => { m.adPreviewUrl = r.string(); break; } + case 28: { + m.containsCtwaFlowsAutoReply = r.bool(); + break; + } default: r.skipType(t & 7); break; @@ -24467,6 +24485,9 @@ export const proto = $root.proto = (() => { if (d.adPreviewUrl != null) { m.adPreviewUrl = String(d.adPreviewUrl); } + if (d.containsCtwaFlowsAutoReply != null) { + m.containsCtwaFlowsAutoReply = Boolean(d.containsCtwaFlowsAutoReply); + } return m; }; @@ -24609,6 +24630,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._adPreviewUrl = "adPreviewUrl"; } + if (m.containsCtwaFlowsAutoReply != null && m.hasOwnProperty("containsCtwaFlowsAutoReply")) { + d.containsCtwaFlowsAutoReply = m.containsCtwaFlowsAutoReply; + if (o.oneofs) + d._containsCtwaFlowsAutoReply = "containsCtwaFlowsAutoReply"; + } return d; }; @@ -25635,6 +25661,7 @@ export const proto = $root.proto = (() => { Conversation.prototype.maibaAiThreadEnabled = null; Conversation.prototype.isMarketingMessageThread = null; Conversation.prototype.isSenderNewAccount = null; + Conversation.prototype.afterReadDuration = null; let $oneOfFields; @@ -25962,6 +25989,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(Conversation.prototype, "_afterReadDuration", { + get: $util.oneOfGetter($oneOfFields = ["afterReadDuration"]), + set: $util.oneOfSetter($oneOfFields) + }); + Conversation.create = function create(properties) { return new Conversation(properties); }; @@ -26085,6 +26118,8 @@ export const proto = $root.proto = (() => { w.uint32(440).bool(m.isMarketingMessageThread); if (m.isSenderNewAccount != null && Object.hasOwnProperty.call(m, "isSenderNewAccount")) w.uint32(448).bool(m.isSenderNewAccount); + if (m.afterReadDuration != null && Object.hasOwnProperty.call(m, "afterReadDuration")) + w.uint32(456).uint32(m.afterReadDuration); return w; }; @@ -26325,6 +26360,10 @@ export const proto = $root.proto = (() => { m.isSenderNewAccount = r.bool(); break; } + case 57: { + m.afterReadDuration = r.uint32(); + break; + } default: r.skipType(t & 7); break; @@ -26665,6 +26704,9 @@ export const proto = $root.proto = (() => { if (d.isSenderNewAccount != null) { m.isSenderNewAccount = Boolean(d.isSenderNewAccount); } + if (d.afterReadDuration != null) { + m.afterReadDuration = d.afterReadDuration >>> 0; + } return m; }; @@ -26982,6 +27024,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._isSenderNewAccount = "isSenderNewAccount"; } + if (m.afterReadDuration != null && m.hasOwnProperty("afterReadDuration")) { + d.afterReadDuration = m.afterReadDuration; + if (o.oneofs) + d._afterReadDuration = "afterReadDuration"; + } return d; }; @@ -64691,6 +64738,10 @@ export const proto = $root.proto = (() => { case 32: m.type = 32; break; + case "BOT_UNLINK_MESSAGE": + case 33: + m.type = 33; + break; } if (d.ephemeralExpiration != null) { m.ephemeralExpiration = d.ephemeralExpiration >>> 0; @@ -65004,6 +65055,7 @@ export const proto = $root.proto = (() => { values[valuesById[30] = "GROUP_MEMBER_LABEL_CHANGE"] = 30; values[valuesById[31] = "AI_MEDIA_COLLECTION_MESSAGE"] = 31; values[valuesById[32] = "MESSAGE_UNSCHEDULE"] = 32; + values[valuesById[33] = "BOT_UNLINK_MESSAGE"] = 33; return values; })(); diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 0a0461b9..010f7776 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1035203283]} +{"version": [2, 3000, 1035216863]} From 7c095c84a59b28242515a919c9b7d406e8d42c02 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Mon, 16 Mar 2026 06:20:34 -0300 Subject: [PATCH 21/32] chore: update WhatsApp Web version to v2.3000.1035223526 (#289) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 010f7776..2e57a23a 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1035216863]} +{"version":[2,3000,1035223526]} From d991d0212d97a4a063225333329b6bfe3b1664b8 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Mon, 16 Mar 2026 21:25:29 -0300 Subject: [PATCH 22/32] fix: add biz node injection and quality_control for carousel WhatsApp Web rendering (#291) - Add interactive message detection helpers (getButtonType, isCarouselMessage, etc.) - Inject biz node with interactive/native_flow for non-carousel interactive messages - Inject biz + quality_control with decision_id for carousel messages - Skip bot node for native_flow/carousel/catalog (breaks Web rendering) - Force device-identity inclusion for carousel messages - Append deferred biz/bot/quality_control nodes after device-identity and tctoken - Store tctoken under both LID and PN for reliable lookup --- src/Socket/messages-send.ts | 197 +++++++++++++++++++++++++++++++++++- src/Utils/tc-token-utils.ts | 30 +++--- 2 files changed, 213 insertions(+), 14 deletions(-) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 9a63b668..5e6c2b64 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -1,3 +1,4 @@ +import { randomBytes } from 'crypto' import NodeCache from '@cacheable/node-cache' import { Boom } from '@hapi/boom' import { proto } from '../../WAProto/index.js' @@ -45,6 +46,7 @@ import { getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, + isJidBot, isJidGroup, isLidUser, isPnUser, @@ -66,7 +68,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, - maxMsgRetryCount + maxMsgRetryCount, + enableInteractiveMessages } = config const sock = makeNewsletterSocket(config) const { @@ -600,6 +603,85 @@ export const makeMessagesSocket = (config: SocketConfig) => { return { nodes, shouldIncludeDeviceIdentity } } + /** Detect interactive/button message type for biz node injection */ + const getButtonType = (message: proto.IMessage): string | undefined => { + if (message.buttonsMessage) return 'buttons' + if (message.templateMessage) return 'template' + if (message.listMessage) return 'list' + if (message.buttonsResponseMessage) return 'buttons_response' + if (message.listResponseMessage) return 'list_response' + if (message.templateButtonReplyMessage) return 'template_reply' + if (message.interactiveMessage) { + if (message.interactiveMessage.nativeFlowMessage) return 'native_flow' + if (message.interactiveMessage.carouselMessage?.cards?.length) { + const hasNativeFlow = message.interactiveMessage.carouselMessage.cards.some( + (card: proto.Message.IInteractiveMessage) => card?.nativeFlowMessage?.buttons?.length + ) + if (hasNativeFlow) return 'native_flow' + } + return 'interactive' + } + + const innerMessage = message.viewOnceMessage?.message || message.viewOnceMessageV2?.message + if (innerMessage) { + if (innerMessage.buttonsMessage) return 'buttons' + if (innerMessage.templateMessage) return 'template' + if (innerMessage.listMessage) return 'list' + if (innerMessage.buttonsResponseMessage) return 'buttons_response' + if (innerMessage.listResponseMessage) return 'list_response' + if (innerMessage.templateButtonReplyMessage) return 'template_reply' + if (innerMessage.interactiveMessage) { + if (innerMessage.interactiveMessage.nativeFlowMessage) return 'native_flow' + if (innerMessage.interactiveMessage.carouselMessage?.cards?.length) { + const hasNativeFlow = innerMessage.interactiveMessage.carouselMessage.cards.some( + (card: any) => card?.nativeFlowMessage?.buttons?.length + ) + if (hasNativeFlow) return 'native_flow' + } + return 'interactive' + } + } + + return undefined + } + + /** Check if message is a carousel (media or product carousel) */ + const isCarouselMessage = (message: proto.IMessage): boolean => { + const interactiveMsg = + message.interactiveMessage || + message.viewOnceMessage?.message?.interactiveMessage || + message.viewOnceMessageV2?.message?.interactiveMessage + return !!(interactiveMsg?.carouselMessage?.cards?.length) + } + + /** Check if message is a catalog/product message */ + const isCatalogMessage = (message: proto.IMessage): boolean => { + const interactiveMsg = + message.interactiveMessage || + message.viewOnceMessage?.message?.interactiveMessage || + message.viewOnceMessageV2?.message?.interactiveMessage + const nativeFlow = interactiveMsg?.nativeFlowMessage + if (nativeFlow?.buttons?.length) { + return nativeFlow.buttons.some( + (btn: any) => btn?.name === 'catalog_message' || btn?.name === 'single_product' || btn?.name === 'product_list' + ) + } + return false + } + + /** Check if nativeFlowMessage is a list (single_select button) */ + const isListNativeFlow = (message: proto.IMessage): boolean => { + const interactiveMsg = + message.interactiveMessage || + message.viewOnceMessage?.message?.interactiveMessage || + message.viewOnceMessageV2?.message?.interactiveMessage + const nativeFlow = interactiveMsg?.nativeFlowMessage + if (nativeFlow?.buttons?.length) { + return nativeFlow.buttons.some((btn: any) => btn?.name === 'single_select' || btn?.name === 'multi_select') + } + return false + } + const relayMessage = async ( jid: string, message: proto.IMessage, @@ -962,6 +1044,110 @@ export const makeMessagesSocket = (config: SocketConfig) => { content: binaryNodeContent } + // Detect interactive message type for biz node injection + const buttonType = getButtonType(message) + const isCatalog = isCatalogMessage(message) + const isCarousel = isCarouselMessage(message) + + // Collect biz/bot nodes to append AFTER device-identity and tctoken + // Stanza order: participants → device-identity → tctoken → biz + // Carousel messages must NOT have biz node (breaks WhatsApp Web rendering) + const deferredNodes: BinaryNode[] = [] + + if (buttonType && enableInteractiveMessages && !isCarousel) { + const interactiveMsg = + message.interactiveMessage || + message.viewOnceMessage?.message?.interactiveMessage || + message.viewOnceMessageV2?.message?.interactiveMessage + + let nativeFlowButtons = interactiveMsg?.nativeFlowMessage?.buttons || [] + if (nativeFlowButtons.length === 0 && interactiveMsg?.carouselMessage?.cards?.length) { + nativeFlowButtons = interactiveMsg.carouselMessage.cards.flatMap( + (card: proto.Message.IInteractiveMessage) => card?.nativeFlowMessage?.buttons || [] + ) + } + + const isListDetected = isListNativeFlow(message) + + logger.info( + { msgId, buttonType, to: destinationJid, isListDetected, isCatalog }, + '[Interactive] Preparing biz node' + ) + + try { + const allButtonNames = nativeFlowButtons.map((b: any) => b?.name).filter(Boolean) + const isNativeFlowButtons = buttonType === 'native_flow' + + if (buttonType === 'list') { + deferredNodes.push({ + tag: 'biz', + attrs: {}, + content: [ + { tag: 'list', attrs: { type: 'product_list', v: '2' } } + ] + }) + } else { + const SPECIAL_FLOW_NAMES: Record = { + review_and_pay: 'payment_info', + payment_info: 'payment_info', + mpm: 'mpm', + review_order: 'order_details' + } + const firstButtonName = allButtonNames[0] || '' + const nativeFlowName = SPECIAL_FLOW_NAMES[firstButtonName] || 'mixed' + + deferredNodes.push({ + tag: 'biz', + attrs: {}, + content: [ + { + tag: 'interactive', + attrs: { type: 'native_flow', v: '1' }, + content: [ + { tag: 'native_flow', attrs: { v: '9', name: nativeFlowName } } + ] + } + ] + }) + } + + // Bot node — skip for native_flow, carousel, catalog + const isPrivateUserChat = + (isPnUser(destinationJid) || isLidUser(destinationJid) || destinationJid?.endsWith('@c.us')) && + !isJidBot(destinationJid) + + if (isPrivateUserChat && !isCatalog && buttonType !== 'list' && !isNativeFlowButtons) { + deferredNodes.push({ tag: 'bot', attrs: { biz_bot: '1' } }) + } + } catch (error) { + logger.error({ error, msgId, buttonType }, '[BIZ NODE] Failed to inject biz node') + } + } else if (isCarousel && enableInteractiveMessages) { + // Carousel: inject quality_control with decision_id (required by WhatsApp Web) + const decisionId = randomBytes(20).toString('hex') + deferredNodes.push({ + tag: 'biz', + attrs: {}, + content: [ + { + tag: 'interactive', + attrs: { type: 'native_flow', v: '1' }, + content: [ + { tag: 'native_flow', attrs: { v: '9', name: 'mixed' } } + ] + } + ] + }) + deferredNodes.push({ + tag: 'quality_control', + attrs: { decision_id: decisionId }, + content: [ + { tag: 'decision_source', attrs: { value: 'df' } } + ] + }) + logger.info({ msgId, to: destinationJid, decisionId }, '[CAROUSEL] Injected biz + quality_control') + } + // if the participant to send to is explicitly specified (generally retry recp) // ensure the message is only sent to that person // if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg @@ -979,7 +1165,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { stanza.attrs.to = destinationJid } - if (shouldIncludeDeviceIdentity) { + // Force device-identity for carousel (ensures Web can decrypt) + if (shouldIncludeDeviceIdentity || isCarousel) { ;(stanza.content as BinaryNode[]).push({ tag: 'device-identity', attrs: {}, @@ -1030,6 +1217,12 @@ export const makeMessagesSocket = (config: SocketConfig) => { ;(stanza.content as BinaryNode[]).push(...additionalNodes) } + // Append deferred biz/bot/quality_control nodes LAST + // Stanza order: participants → device-identity → tctoken → biz → quality_control + if (deferredNodes.length > 0) { + ;(stanza.content as BinaryNode[]).push(...deferredNodes) + } + logger.debug({ msgId }, `sending message to ${participants.length} devices`) await sendNode(stanza) diff --git a/src/Utils/tc-token-utils.ts b/src/Utils/tc-token-utils.ts index bec77c18..c7d5d0e9 100644 --- a/src/Utils/tc-token-utils.ts +++ b/src/Utils/tc-token-utils.ts @@ -154,18 +154,24 @@ export async function storeTcTokensFromIqResult({ continue } - await keys.set({ - tctoken: { - [storageJid]: { - ...existingEntry, - token: Buffer.from(tokenNode.content), - timestamp: tokenNode.attrs.t, - // WABA Android: resets real_issue_timestamp to null when storing a new token - // (UPDATE wa_trusted_contacts_send SET real_issue_timestamp=null) - realIssueTimestamp: null - } - } - }) + const tokenEntry = { + ...existingEntry, + token: Buffer.from(tokenNode.content), + timestamp: tokenNode.attrs.t, + // Resets real_issue_timestamp to null when storing a new token + realIssueTimestamp: null + } + + // Store under resolved storageJid AND under fallbackJid (PN) for reliable lookup + const normalizedFallback = jidNormalizedUser(fallbackJid) + const keysToStore: Record = { + [storageJid]: tokenEntry + } + if (normalizedFallback !== storageJid) { + keysToStore[normalizedFallback] = tokenEntry + } + + await keys.set({ tctoken: keysToStore }) onNewJidStored?.(storageJid) } } From 50c13398c4d5761e9a7969df102a674a6ba2737b Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Mon, 16 Mar 2026 22:09:14 -0300 Subject: [PATCH 23/32] fix: harden carousel live rendering path (#292) * fix: harden carousel live rendering path * fix: flatten carousel thumbnail fallback --- src/Socket/messages-send.ts | 46 ++++++++++++++++++++++++++++++---- src/Utils/messages.ts | 49 ++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 5e6c2b64..992aa1f8 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -682,6 +682,33 @@ export const makeMessagesSocket = (config: SocketConfig) => { return false } + const canonicalizeCarouselRecipients = async (recipientJids: string[]) => { + const uniqueRecipients = [...new Set(recipientJids)] + const pnRecipients = uniqueRecipients.filter(jid => isPnUser(jid) || isHostedPnUser(jid)) + const lidMappings = pnRecipients.length > 0 + ? await signalRepository.lidMapping.getLIDsForPNs(pnRecipients) + : [] + + const lidByPn = new Map((lidMappings || []).map(({ pn, lid }) => [pn, lid])) + const canonicalRecipients: string[] = [] + + for (const jid of uniqueRecipients) { + const canonicalJid = lidByPn.get(jid) || jid + if (!canonicalRecipients.includes(canonicalJid)) { + canonicalRecipients.push(canonicalJid) + } + } + + if (canonicalRecipients.length !== uniqueRecipients.length || canonicalRecipients.some((jid, index) => jid !== uniqueRecipients[index])) { + logger.debug( + { before: uniqueRecipients, after: canonicalRecipients }, + '[CAROUSEL] Canonicalized recipient addressing for session reuse' + ) + } + + return canonicalRecipients + } + const relayMessage = async ( jid: string, message: proto.IMessage, @@ -968,21 +995,30 @@ export const makeMessagesSocket = (config: SocketConfig) => { allRecipients.push(jid) } - await assertSessions(allRecipients) + const isCarouselFanout = isCarouselMessage(message) + const effectiveMeRecipients = isCarouselFanout + ? await canonicalizeCarouselRecipients(meRecipients) + : meRecipients + const effectiveOtherRecipients = isCarouselFanout + ? await canonicalizeCarouselRecipients(otherRecipients) + : otherRecipients + const effectiveAllRecipients = [...effectiveMeRecipients, ...effectiveOtherRecipients] + + await assertSessions(effectiveAllRecipients) const [ { nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 } ] = await Promise.all([ // For own devices: use DSM if available (1:1 chats only) - createParticipantNodes(meRecipients, meMsg || message, extraAttrs), - createParticipantNodes(otherRecipients, message, extraAttrs, meMsg) + createParticipantNodes(effectiveMeRecipients, meMsg || message, extraAttrs), + createParticipantNodes(effectiveOtherRecipients, message, extraAttrs, meMsg) ]) participants.push(...meNodes) participants.push(...otherNodes) - if (meRecipients.length > 0 || otherRecipients.length > 0) { - extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]) + if (effectiveMeRecipients.length > 0 || effectiveOtherRecipients.length > 0) { + extraAttrs['phash'] = generateParticipantHashV2([...effectiveMeRecipients, ...effectiveOtherRecipients]) } shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2 diff --git a/src/Utils/messages.ts b/src/Utils/messages.ts index 006dbaae..468498df 100644 --- a/src/Utils/messages.ts +++ b/src/Utils/messages.ts @@ -41,10 +41,12 @@ import type { ILogger } from './logger' import { downloadContentFromMessage, encryptedStream, + extractImageThumb, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData, + getStream, type MediaDownloadOptions } from './messages-media' import { shouldIncludeReportingToken } from './reporting-utils' @@ -473,6 +475,48 @@ export const formatNativeFlowButton = (button: NativeButton): NativeFlowButton = } } +const recoverCarouselImageMetadata = async ( + card: CarouselMessageOptions['cards'][number], + imageMessage: proto.Message.IImageMessage, + mediaOptions: MessageContentGenerationOptions +) => { + if (imageMessage.jpegThumbnail && imageMessage.height && imageMessage.width) { + return + } + + try { + const { stream } = await getStream(card.image!, mediaOptions.options) + const thumb = await extractImageThumb(stream) + + if (!imageMessage.jpegThumbnail) { + imageMessage.jpegThumbnail = thumb.buffer + } + + if (!imageMessage.width && thumb.original.width) { + imageMessage.width = thumb.original.width + } + + if (!imageMessage.height && thumb.original.height) { + imageMessage.height = thumb.original.height + } + + mediaOptions.logger?.info( + { + cardTitle: card.title, + hasJpegThumbnail: !!imageMessage.jpegThumbnail, + width: imageMessage.width, + height: imageMessage.height + }, + '[CAROUSEL] Recovered image thumbnail/dimensions from source media' + ) + } catch (error) { + mediaOptions.logger?.warn( + { cardTitle: card.title, error }, + '[CAROUSEL] Failed to recover image thumbnail/dimensions from source media' + ) + } +} + /** * Generates a button message using Native Flow format wrapped in viewOnceMessage * This is the modern approach for button messages that works on iOS and Android @@ -648,7 +692,10 @@ export const generateCarouselMessage = async ( if (hasMedia && mediaOptions) { if (card.image) { const { imageMessage } = await prepareWAMessageMedia({ image: card.image }, mediaOptions) - // Validate image fields needed for WhatsApp rendering + if (imageMessage) { + await recoverCarouselImageMetadata(card, imageMessage, mediaOptions) + } + if (imageMessage && !imageMessage.jpegThumbnail) { mediaOptions.logger?.warn( { cardTitle: card.title }, From ff9b84c561b17a084a7bc1656ea5b662f37c0edf Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Mon, 16 Mar 2026 22:59:28 -0300 Subject: [PATCH 24/32] fix: unify PN and LID session reuse for 1:1 sends (#293) fix: unify PN and LID session reuse for 1:1 sends (#293) --- src/Socket/messages-send.ts | 89 +++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 992aa1f8..4ddae005 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -417,6 +417,39 @@ export const makeMessagesSocket = (config: SocketConfig) => { ) } + const resolveSessionJid = async (jid: string) => { + if (isLidUser(jid) || isHostedLidUser(jid)) { + return jid + } + + if (isPnUser(jid) || isHostedPnUser(jid)) { + return await signalRepository.lidMapping.getLIDForPN(jid) || jid + } + + return jid + } + + const canonicalizeSessionRecipients = async (recipientJids: string[]) => { + const uniqueRecipients = [...new Set(recipientJids)] + const canonicalRecipients: string[] = [] + + for (const jid of uniqueRecipients) { + const canonicalJid = await resolveSessionJid(jid) + if (!canonicalRecipients.includes(canonicalJid)) { + canonicalRecipients.push(canonicalJid) + } + } + + if (canonicalRecipients.length !== uniqueRecipients.length || canonicalRecipients.some((jid, index) => jid !== uniqueRecipients[index])) { + logger.debug( + { before: uniqueRecipients, after: canonicalRecipients }, + '[SESSION] Canonicalized recipient addressing for session reuse' + ) + } + + return canonicalRecipients + } + const assertSessions = async (jids: string[], force?: boolean) => { let didFetchNewSession = false const uniqueJids = [...new Set(jids)] // Deduplicate JIDs @@ -426,22 +459,32 @@ export const makeMessagesSocket = (config: SocketConfig) => { // Check peerSessionsCache and validate sessions using libsignal loadSession for (const jid of uniqueJids) { - const signalId = signalRepository.jidToSignalProtocolAddress(jid) - const cachedSession = peerSessionsCache.get(signalId) + const canonicalJid = await resolveSessionJid(jid) + const signalIds = [ + signalRepository.jidToSignalProtocolAddress(jid), + signalRepository.jidToSignalProtocolAddress(canonicalJid) + ] + const cachedSession = signalIds + .map(signalId => peerSessionsCache.get(signalId)) + .find(session => session !== undefined) + if (cachedSession !== undefined) { if (cachedSession && !force) { continue // Session exists in cache } } else { - const sessionValidation = await signalRepository.validateSession(jid) + const sessionValidation = await signalRepository.validateSession(canonicalJid) const hasSession = sessionValidation.exists - peerSessionsCache.set(signalId, hasSession) + for (const signalId of signalIds) { + peerSessionsCache.set(signalId, hasSession) + } + if (hasSession && !force) { continue } } - jidsRequiringFetch.push(jid) + jidsRequiringFetch.push(canonicalJid) } if (jidsRequiringFetch.length) { @@ -682,33 +725,6 @@ export const makeMessagesSocket = (config: SocketConfig) => { return false } - const canonicalizeCarouselRecipients = async (recipientJids: string[]) => { - const uniqueRecipients = [...new Set(recipientJids)] - const pnRecipients = uniqueRecipients.filter(jid => isPnUser(jid) || isHostedPnUser(jid)) - const lidMappings = pnRecipients.length > 0 - ? await signalRepository.lidMapping.getLIDsForPNs(pnRecipients) - : [] - - const lidByPn = new Map((lidMappings || []).map(({ pn, lid }) => [pn, lid])) - const canonicalRecipients: string[] = [] - - for (const jid of uniqueRecipients) { - const canonicalJid = lidByPn.get(jid) || jid - if (!canonicalRecipients.includes(canonicalJid)) { - canonicalRecipients.push(canonicalJid) - } - } - - if (canonicalRecipients.length !== uniqueRecipients.length || canonicalRecipients.some((jid, index) => jid !== uniqueRecipients[index])) { - logger.debug( - { before: uniqueRecipients, after: canonicalRecipients }, - '[CAROUSEL] Canonicalized recipient addressing for session reuse' - ) - } - - return canonicalRecipients - } - const relayMessage = async ( jid: string, message: proto.IMessage, @@ -995,13 +1011,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { allRecipients.push(jid) } - const isCarouselFanout = isCarouselMessage(message) - const effectiveMeRecipients = isCarouselFanout - ? await canonicalizeCarouselRecipients(meRecipients) - : meRecipients - const effectiveOtherRecipients = isCarouselFanout - ? await canonicalizeCarouselRecipients(otherRecipients) - : otherRecipients + const effectiveMeRecipients = await canonicalizeSessionRecipients(meRecipients) + const effectiveOtherRecipients = await canonicalizeSessionRecipients(otherRecipients) const effectiveAllRecipients = [...effectiveMeRecipients, ...effectiveOtherRecipients] await assertSessions(effectiveAllRecipients) From e152aee740fccec093542cee974a7697829d3d6c Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Mon, 16 Mar 2026 23:22:05 -0300 Subject: [PATCH 25/32] Fix/pn lid session reuse (#294) * fix: unify PN and LID session reuse for 1:1 sends * fix: limit PN/LID reuse changes to session cache --- src/Socket/messages-send.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 4ddae005..d78025d9 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -1011,6 +1011,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { allRecipients.push(jid) } + + await assertSessions(allRecipients) const effectiveMeRecipients = await canonicalizeSessionRecipients(meRecipients) const effectiveOtherRecipients = await canonicalizeSessionRecipients(otherRecipients) const effectiveAllRecipients = [...effectiveMeRecipients, ...effectiveOtherRecipients] @@ -1022,14 +1024,14 @@ export const makeMessagesSocket = (config: SocketConfig) => { { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 } ] = await Promise.all([ // For own devices: use DSM if available (1:1 chats only) - createParticipantNodes(effectiveMeRecipients, meMsg || message, extraAttrs), - createParticipantNodes(effectiveOtherRecipients, message, extraAttrs, meMsg) + createParticipantNodes(meRecipients, meMsg || message, extraAttrs), + createParticipantNodes(otherRecipients, message, extraAttrs, meMsg) ]) participants.push(...meNodes) participants.push(...otherNodes) - if (effectiveMeRecipients.length > 0 || effectiveOtherRecipients.length > 0) { - extraAttrs['phash'] = generateParticipantHashV2([...effectiveMeRecipients, ...effectiveOtherRecipients]) + if (meRecipients.length > 0 || otherRecipients.length > 0) { + extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]) } shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2 From dbc6dd6aed72a025fd0940cdbd7965f5d503205e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 00:46:29 -0300 Subject: [PATCH 26/32] chore: update proto/version to v2.3000.1035302375 (#295) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 2 +- src/Defaults/baileys-version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index 2ee95777..c9116099 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1035216863 +/// WhatsApp Version: 2.3000.1035302375 message ADVDeviceIdentity { optional uint32 rawId = 1; diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 2e57a23a..238eecb9 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1035223526]} +{"version": [2, 3000, 1035302375]} From 11eed3aedceaca01de56e9d2be9f4eed84f65205 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Tue, 17 Mar 2026 06:16:35 -0300 Subject: [PATCH 27/32] chore: update WhatsApp Web version to v2.3000.1035317910 (#296) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 238eecb9..a20fa7ea 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1035302375]} +{"version":[2,3000,1035317910]} From fc2122b11a054d1098dd7cd6b38dee4f931875ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 01:49:58 -0300 Subject: [PATCH 28/32] chore: update proto/version to v2.3000.1035370989 (#298) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 14 +++++- WAProto/index.d.ts | 14 ++++++ WAProto/index.js | 76 +++++++++++++++++++++++++++++++ src/Defaults/baileys-version.json | 2 +- 4 files changed, 104 insertions(+), 2 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index c9116099..65f2f544 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1035302375 +/// WhatsApp Version: 2.3000.1035370989 message ADVDeviceIdentity { optional uint32 rawId = 1; @@ -1933,8 +1933,20 @@ message HandshakeMessage { optional bytes paddedBytes = 6; optional bool sendServerHelloPaddedBytes = 7; optional bool simulateXxkemFs = 8; + optional HandshakeMessage.HandshakePqMode pqMode = 9; } + enum HandshakePqMode { + HANDSHAKE_PQ_MODE_UNKNOWN = 0; + XXKEM = 1; + XXKEM_FS = 2; + WA_CLASSICAL = 3; + WA_PQ = 4; + IKKEM = 5; + IKKEM_FS = 6; + XXKEM_2 = 7; + IKKEM_2 = 8; + } message ServerHello { optional bytes ephemeral = 1; optional bytes static = 2; diff --git a/WAProto/index.d.ts b/WAProto/index.d.ts index f0a659e6..772f0eee 100644 --- a/WAProto/index.d.ts +++ b/WAProto/index.d.ts @@ -4759,6 +4759,7 @@ export namespace proto { paddedBytes?: (Uint8Array|null); sendServerHelloPaddedBytes?: (boolean|null); simulateXxkemFs?: (boolean|null); + pqMode?: (proto.HandshakeMessage.HandshakePqMode|null); } class ClientHello implements IClientHello { @@ -4771,6 +4772,7 @@ export namespace proto { public paddedBytes?: (Uint8Array|null); public sendServerHelloPaddedBytes?: (boolean|null); public simulateXxkemFs?: (boolean|null); + public pqMode?: (proto.HandshakeMessage.HandshakePqMode|null); public static create(properties?: proto.HandshakeMessage.IClientHello): proto.HandshakeMessage.ClientHello; public static encode(m: proto.HandshakeMessage.IClientHello, w?: $protobuf.Writer): $protobuf.Writer; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HandshakeMessage.ClientHello; @@ -4780,6 +4782,18 @@ export namespace proto { public static getTypeUrl(typeUrlPrefix?: string): string; } + enum HandshakePqMode { + HANDSHAKE_PQ_MODE_UNKNOWN = 0, + XXKEM = 1, + XXKEM_FS = 2, + WA_CLASSICAL = 3, + WA_PQ = 4, + IKKEM = 5, + IKKEM_FS = 6, + XXKEM_2 = 7, + IKKEM_2 = 8 + } + interface IServerHello { ephemeral?: (Uint8Array|null); "static"?: (Uint8Array|null); diff --git a/WAProto/index.js b/WAProto/index.js index 804faf5a..3814a1b0 100644 --- a/WAProto/index.js +++ b/WAProto/index.js @@ -32585,6 +32585,7 @@ export const proto = $root.proto = (() => { ClientHello.prototype.paddedBytes = null; ClientHello.prototype.sendServerHelloPaddedBytes = null; ClientHello.prototype.simulateXxkemFs = null; + ClientHello.prototype.pqMode = null; let $oneOfFields; @@ -32636,6 +32637,12 @@ export const proto = $root.proto = (() => { set: $util.oneOfSetter($oneOfFields) }); + // Virtual OneOf for proto3 optional field + Object.defineProperty(ClientHello.prototype, "_pqMode", { + get: $util.oneOfGetter($oneOfFields = ["pqMode"]), + set: $util.oneOfSetter($oneOfFields) + }); + ClientHello.create = function create(properties) { return new ClientHello(properties); }; @@ -32659,6 +32666,8 @@ export const proto = $root.proto = (() => { w.uint32(56).bool(m.sendServerHelloPaddedBytes); if (m.simulateXxkemFs != null && Object.hasOwnProperty.call(m, "simulateXxkemFs")) w.uint32(64).bool(m.simulateXxkemFs); + if (m.pqMode != null && Object.hasOwnProperty.call(m, "pqMode")) + w.uint32(72).int32(m.pqMode); return w; }; @@ -32703,6 +32712,10 @@ export const proto = $root.proto = (() => { m.simulateXxkemFs = r.bool(); break; } + case 9: { + m.pqMode = r.int32(); + break; + } default: r.skipType(t & 7); break; @@ -32754,6 +32767,50 @@ export const proto = $root.proto = (() => { if (d.simulateXxkemFs != null) { m.simulateXxkemFs = Boolean(d.simulateXxkemFs); } + switch (d.pqMode) { + default: + if (typeof d.pqMode === "number") { + m.pqMode = d.pqMode; + break; + } + break; + case "HANDSHAKE_PQ_MODE_UNKNOWN": + case 0: + m.pqMode = 0; + break; + case "XXKEM": + case 1: + m.pqMode = 1; + break; + case "XXKEM_FS": + case 2: + m.pqMode = 2; + break; + case "WA_CLASSICAL": + case 3: + m.pqMode = 3; + break; + case "WA_PQ": + case 4: + m.pqMode = 4; + break; + case "IKKEM": + case 5: + m.pqMode = 5; + break; + case "IKKEM_FS": + case 6: + m.pqMode = 6; + break; + case "XXKEM_2": + case 7: + m.pqMode = 7; + break; + case "IKKEM_2": + case 8: + m.pqMode = 8; + break; + } return m; }; @@ -32801,6 +32858,11 @@ export const proto = $root.proto = (() => { if (o.oneofs) d._simulateXxkemFs = "simulateXxkemFs"; } + if (m.pqMode != null && m.hasOwnProperty("pqMode")) { + d.pqMode = o.enums === String ? $root.proto.HandshakeMessage.HandshakePqMode[m.pqMode] === undefined ? m.pqMode : $root.proto.HandshakeMessage.HandshakePqMode[m.pqMode] : m.pqMode; + if (o.oneofs) + d._pqMode = "pqMode"; + } return d; }; @@ -32818,6 +32880,20 @@ export const proto = $root.proto = (() => { return ClientHello; })(); + HandshakeMessage.HandshakePqMode = (function() { + const valuesById = {}, values = Object.create(valuesById); + values[valuesById[0] = "HANDSHAKE_PQ_MODE_UNKNOWN"] = 0; + values[valuesById[1] = "XXKEM"] = 1; + values[valuesById[2] = "XXKEM_FS"] = 2; + values[valuesById[3] = "WA_CLASSICAL"] = 3; + values[valuesById[4] = "WA_PQ"] = 4; + values[valuesById[5] = "IKKEM"] = 5; + values[valuesById[6] = "IKKEM_FS"] = 6; + values[valuesById[7] = "XXKEM_2"] = 7; + values[valuesById[8] = "IKKEM_2"] = 8; + return values; + })(); + HandshakeMessage.ServerHello = (function() { function ServerHello(p) { diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index a20fa7ea..568ede9a 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1035317910]} +{"version": [2, 3000, 1035370989]} From 21462c26d5be513e23b8ca4b3c7df4d5d846361b Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Wed, 18 Mar 2026 06:42:44 -0300 Subject: [PATCH 29/32] chore: update WhatsApp Web version to v2.3000.1035406580 (#299) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 568ede9a..ff7af088 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1035370989]} +{"version":[2,3000,1035406580]} From d04a3e1af84d4bc9ee5b57e7226d55008df5ca32 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Wed, 18 Mar 2026 20:30:18 -0300 Subject: [PATCH 30/32] fix: restore working carousel send/render path (#297) fix: restore working carousel send/render path (#297) --- src/Socket/messages-send.ts | 1317 +++++++++++++++++++++++++++++------ src/Utils/messages-media.ts | 2 +- src/Utils/messages.ts | 164 ++--- src/Utils/tc-token-utils.ts | 4 +- 4 files changed, 1171 insertions(+), 316 deletions(-) diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index d78025d9..cb2ec727 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -4,6 +4,10 @@ import { Boom } from '@hapi/boom' import { proto } from '../../WAProto/index.js' import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults' import type { + AlbumMediaItem, + AlbumMediaResult, + AlbumMessageOptions, + AlbumSendResult, AnyMessageContent, MediaConnInfo, MessageReceiptType, @@ -29,14 +33,23 @@ import { getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, + hasNonNullishProperty, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils' +import { logMessageSent, logTcToken } from '../Utils/baileys-logger' import { getUrlInfo } from '../Utils/link-preview' import { makeKeyedMutex } from '../Utils/make-mutex' +import { metrics, recordMessageFailure, recordMessageSent } from '../Utils/prometheus-metrics' import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils' +import { + isTcTokenExpired, + resolveTcTokenJid, + shouldSendNewTcToken, + storeTcTokensFromIqResult +} from '../Utils/tc-token-utils' import { areJidsSameUser, type BinaryNode, @@ -44,6 +57,8 @@ import { type FullJid, getBinaryNodeChild, getBinaryNodeChildren, + isAnyLidUser, + isAnyPnUser, isHostedLidUser, isHostedPnUser, isJidBot, @@ -77,6 +92,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { authState, messageMutex, signalRepository, + sessionActivityTracker, upsertMessage, query, fetchPrivacySettings, @@ -85,6 +101,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { groupToggleEphemeral } = sock + const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping) + const userDevicesCache = config.userDevicesCache || new NodeCache({ @@ -103,6 +121,9 @@ export const makeMessagesSocket = (config: SocketConfig) => { // Prevent race conditions in Signal session encryption by user const encryptionMutex = makeKeyedMutex() + // Tracks JIDs with an in-flight getPrivacyTokens IQ to avoid duplicate concurrent fetches + const tcTokenFetchingJids = new Set() + let mediaConn: Promise const refreshMediaConn = async (forceGet = false) => { const media = await mediaConn @@ -117,7 +138,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { }, content: [{ tag: 'media_conn', attrs: {} }] }) - const mediaConnNode = getBinaryNodeChild(result, 'media_conn')! + const mediaConnNode = getBinaryNodeChild(result, 'media_conn') + if (!mediaConnNode) throw new Boom('Missing media_conn node') // TODO: explore full length of data that whatsapp provides const node: MediaConnInfo = { hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({ @@ -162,8 +184,9 @@ export const makeMessagesSocket = (config: SocketConfig) => { } if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) { + if (!participant) throw new Boom('Missing participant for sender receipt') node.attrs.recipient = jid - node.attrs.to = participant! + node.attrs.to = participant } else { node.attrs.to = jid if (participant) { @@ -283,7 +306,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { const requestedLidUsers = new Set() for (const jid of toFetch) { - if (isLidUser(jid) || isHostedLidUser(jid)) { + if (isAnyLidUser(jid)) { const user = jidDecode(jid)?.user if (user) requestedLidUsers.add(user) } @@ -315,12 +338,11 @@ export const makeMessagesSocket = (config: SocketConfig) => { } } - const extracted = extractDeviceJids( - result?.list, - authState.creds.me!.id, - authState.creds.me!.lid!, - ignoreZeroDevices - ) + const meId = authState.creds.me?.id + if (!meId) throw new Boom('Not authenticated', { statusCode: 401 }) + const meLid = authState.creds.me?.lid || '' + + const extracted = extractDeviceJids(result?.list, meId, meLid, ignoreZeroDevices) const deviceMap: { [_: string]: FullJid[] } = {} for (const item of extracted) { @@ -417,39 +439,6 @@ export const makeMessagesSocket = (config: SocketConfig) => { ) } - const resolveSessionJid = async (jid: string) => { - if (isLidUser(jid) || isHostedLidUser(jid)) { - return jid - } - - if (isPnUser(jid) || isHostedPnUser(jid)) { - return await signalRepository.lidMapping.getLIDForPN(jid) || jid - } - - return jid - } - - const canonicalizeSessionRecipients = async (recipientJids: string[]) => { - const uniqueRecipients = [...new Set(recipientJids)] - const canonicalRecipients: string[] = [] - - for (const jid of uniqueRecipients) { - const canonicalJid = await resolveSessionJid(jid) - if (!canonicalRecipients.includes(canonicalJid)) { - canonicalRecipients.push(canonicalJid) - } - } - - if (canonicalRecipients.length !== uniqueRecipients.length || canonicalRecipients.some((jid, index) => jid !== uniqueRecipients[index])) { - logger.debug( - { before: uniqueRecipients, after: canonicalRecipients }, - '[SESSION] Canonicalized recipient addressing for session reuse' - ) - } - - return canonicalRecipients - } - const assertSessions = async (jids: string[], force?: boolean) => { let didFetchNewSession = false const uniqueJids = [...new Set(jids)] // Deduplicate JIDs @@ -459,42 +448,30 @@ export const makeMessagesSocket = (config: SocketConfig) => { // Check peerSessionsCache and validate sessions using libsignal loadSession for (const jid of uniqueJids) { - const canonicalJid = await resolveSessionJid(jid) - const signalIds = [ - signalRepository.jidToSignalProtocolAddress(jid), - signalRepository.jidToSignalProtocolAddress(canonicalJid) - ] - const cachedSession = signalIds - .map(signalId => peerSessionsCache.get(signalId)) - .find(session => session !== undefined) - + const signalId = signalRepository.jidToSignalProtocolAddress(jid) + const cachedSession = peerSessionsCache.get(signalId) if (cachedSession !== undefined) { if (cachedSession && !force) { continue // Session exists in cache } } else { - const sessionValidation = await signalRepository.validateSession(canonicalJid) + const sessionValidation = await signalRepository.validateSession(jid) const hasSession = sessionValidation.exists - for (const signalId of signalIds) { - peerSessionsCache.set(signalId, hasSession) - } - + peerSessionsCache.set(signalId, hasSession) if (hasSession && !force) { continue } } - jidsRequiringFetch.push(canonicalJid) + jidsRequiringFetch.push(jid) } if (jidsRequiringFetch.length) { // LID if mapped, otherwise original const wireJids = [ - ...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)), + ...jidsRequiringFetch.filter(jid => isAnyLidUser(jid)), ...( - (await signalRepository.lidMapping.getLIDsForPNs( - jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)) - )) || [] + (await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => isAnyPnUser(jid)))) || [] ).map(a => a.lid) ] @@ -531,6 +508,38 @@ export const makeMessagesSocket = (config: SocketConfig) => { return didFetchNewSession } + const canonicalizeCarouselRecipients = async (recipientJids: string[]): Promise => { + if (!recipientJids.length) { + return recipientJids + } + + const pnRecipients = [...new Set(recipientJids.filter(jid => isAnyPnUser(jid)))] + if (!pnRecipients.length) { + return recipientJids + } + + const mappings = (await signalRepository.lidMapping.getLIDsForPNs(pnRecipients)) || [] + if (!mappings.length) { + logger.debug({ recipientCount: recipientJids.length }, '[CAROUSEL] No PN→LID recipient mappings found') + return recipientJids + } + + const pnToLid = new Map(mappings.map(item => [item.pn, item.lid])) + const mapped = recipientJids.map(jid => pnToLid.get(jid) || jid) + const changed = mapped.filter((jid, index) => jid !== recipientJids[index]).length + + logger.info( + { + recipientCount: recipientJids.length, + pnRecipients: pnRecipients.length, + mappedRecipients: changed + }, + '[CAROUSEL] Canonicalized recipient addressing to LID before session assert/encrypt' + ) + + return mapped + } + const sendPeerDataOperationMessage = async ( pdoMessage: proto.Message.IPeerDataOperationRequestMessage ): Promise => { @@ -565,6 +574,82 @@ export const makeMessagesSocket = (config: SocketConfig) => { return msgId } + const resolveDsmMessageForRecipient = ( + jid: string, + patchedMessage: proto.IMessage, + dsmMessage: proto.IMessage | undefined, + meId: string, + meLid: string | undefined, + meLidUser: string | null | undefined + ) => { + if (!dsmMessage) { + return patchedMessage + } + + const { user: targetUser } = jidDecode(jid)! + const { user: ownPnUser } = jidDecode(meId)! + const isOwnUser = targetUser === ownPnUser || (meLidUser && targetUser === meLidUser) + const isExactSenderDevice = jid === meId || (meLid && jid === meLid) + + if (isOwnUser && !isExactSenderDevice) { + logger.debug({ jid, targetUser }, 'Using DSM for own device') + return dsmMessage + } + + return patchedMessage + } + + const encryptPatchedMessageForRecipient = async ({ + jid, + patchedMessage, + dsmMessage, + meId, + meLid, + meLidUser, + extraAttrs, + onPkmsg + }: { + jid: string + patchedMessage: proto.IMessage + dsmMessage: proto.IMessage | undefined + meId: string + meLid: string | undefined + meLidUser: string | null | undefined + extraAttrs: BinaryNode['attrs'] | undefined + onPkmsg: () => void + }) => { + if (!jid) return null + + try { + const msgToEncrypt = resolveDsmMessageForRecipient(jid, patchedMessage, dsmMessage, meId, meLid, meLidUser) + const bytes = encodeWAMessage(msgToEncrypt) + const mutexKey = jid + + return await encryptionMutex.mutex(mutexKey, async () => { + const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes }) + + if (type === 'pkmsg') { + onPkmsg() + } + + return { + tag: 'to', + attrs: { jid }, + content: [ + { + tag: 'enc', + attrs: { v: '2', type, ...(extraAttrs || {}) }, + content: ciphertext + } + ] + } as BinaryNode + }) + } catch (err) { + logger.error({ jid, err }, 'Failed to encrypt for recipient') + return null + } + } + const createParticipantNodes = async ( recipientJids: string[], message: proto.IMessage, @@ -581,106 +666,132 @@ export const makeMessagesSocket = (config: SocketConfig) => { : recipientJids.map(jid => ({ recipientJid: jid, message: patched })) let shouldIncludeDeviceIdentity = false - const meId = authState.creds.me!.id + const meId = authState.creds.me?.id + if (!meId) throw new Boom('Not authenticated', { statusCode: 401 }) const meLid = authState.creds.me?.lid - const meLidUser = meLid ? jidDecode(meLid)?.user : null + const meLidUser = meLid ? (jidDecode(meLid)?.user ?? null) : null + const onPkmsg = () => { + shouldIncludeDeviceIdentity = true + } - const encryptionPromises = (patchedMessages as any).map( - async ({ recipientJid: jid, message: patchedMessage }: any) => { - try { - if (!jid) return null - - let msgToEncrypt = patchedMessage - - if (dsmMessage) { - const { user: targetUser } = jidDecode(jid)! - const { user: ownPnUser } = jidDecode(meId)! - const ownLidUser = meLidUser - - const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser) - const isExactSenderDevice = jid === meId || (meLid && jid === meLid) - - if (isOwnUser && !isExactSenderDevice) { - msgToEncrypt = dsmMessage - logger.debug({ jid, targetUser }, 'Using DSM for own device') - } - } - - const bytes = encodeWAMessage(msgToEncrypt) - const mutexKey = jid - - const node = await encryptionMutex.mutex(mutexKey, async () => { - const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes }) - - if (type === 'pkmsg') { - shouldIncludeDeviceIdentity = true - } - - return { - tag: 'to', - attrs: { jid }, - content: [ - { - tag: 'enc', - attrs: { v: '2', type, ...(extraAttrs || {}) }, - content: ciphertext - } - ] - } - }) - - return node - } catch (err) { - logger.error({ jid, err }, 'Failed to encrypt for recipient') - return null - } - } + const encryptionPromises = (patchedMessages as { recipientJid: string; message: proto.IMessage }[]).map( + ({ recipientJid: jid, message: patchedMessage }: { recipientJid: string; message: proto.IMessage }) => + encryptPatchedMessageForRecipient({ + jid, + patchedMessage, + dsmMessage, + meId, + meLid, + meLidUser, + extraAttrs, + onPkmsg + }) ) const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null) as BinaryNode[] if (recipientJids.length > 0 && nodes.length === 0) { + recordMessageFailure('send', 'encryption_failed') throw new Boom('All encryptions failed', { statusCode: 500 }) } return { nodes, shouldIncludeDeviceIdentity } } - /** Detect interactive/button message type for biz node injection */ + // Interactive message detection and binary node injection + + /** + * Detects the type of interactive message and returns the appropriate binary node tag + * Returns 'native_flow' for modern nativeFlowMessage format (recommended) + * Returns legacy types for older button formats + */ const getButtonType = (message: proto.IMessage): string | undefined => { - if (message.buttonsMessage) return 'buttons' - if (message.templateMessage) return 'template' - if (message.listMessage) return 'list' - if (message.buttonsResponseMessage) return 'buttons_response' - if (message.listResponseMessage) return 'list_response' - if (message.templateButtonReplyMessage) return 'template_reply' - if (message.interactiveMessage) { - if (message.interactiveMessage.nativeFlowMessage) return 'native_flow' + // Check direct message types (legacy formats) + if (message.buttonsMessage) { + return 'buttons' + } else if (message.templateMessage) { + return 'template' + } else if (message.listMessage) { + // All listMessages (SINGLE_SELECT and PRODUCT_LIST) need biz > list node + return 'list' + } else if (message.buttonsResponseMessage) { + return 'buttons_response' + } else if (message.listResponseMessage) { + return 'list_response' + } else if (message.templateButtonReplyMessage) { + return 'template_reply' + } else if (message.interactiveMessage) { + // Check if it has nativeFlowMessage (modern format) + if (message.interactiveMessage.nativeFlowMessage) { + return 'native_flow' + } + + // Check if it's a carousel with nativeFlowMessage buttons in cards if (message.interactiveMessage.carouselMessage?.cards?.length) { - const hasNativeFlow = message.interactiveMessage.carouselMessage.cards.some( + const hasNativeFlowButtons = message.interactiveMessage.carouselMessage.cards.some( (card: proto.Message.IInteractiveMessage) => card?.nativeFlowMessage?.buttons?.length ) - if (hasNativeFlow) return 'native_flow' + if (hasNativeFlowButtons) { + return 'native_flow' + } } + + // Check if it's a collection/product carousel + if (message.interactiveMessage.carouselMessage?.cards?.length) { + const hasCollectionCards = message.interactiveMessage.carouselMessage.cards.some( + (card: any) => card?.collectionMessage + ) + if (hasCollectionCards) { + return 'native_flow' + } + } + return 'interactive' } + // Check inside viewOnceMessage/viewOnceMessageV2 wrapper (modern nativeFlowMessage format) + // V2 is the recommended format for interactive messages (carousel, buttons) const innerMessage = message.viewOnceMessage?.message || message.viewOnceMessageV2?.message if (innerMessage) { - if (innerMessage.buttonsMessage) return 'buttons' - if (innerMessage.templateMessage) return 'template' - if (innerMessage.listMessage) return 'list' - if (innerMessage.buttonsResponseMessage) return 'buttons_response' - if (innerMessage.listResponseMessage) return 'list_response' - if (innerMessage.templateButtonReplyMessage) return 'template_reply' - if (innerMessage.interactiveMessage) { - if (innerMessage.interactiveMessage.nativeFlowMessage) return 'native_flow' + if (innerMessage.buttonsMessage) { + return 'buttons' + } else if (innerMessage.templateMessage) { + return 'template' + } else if (innerMessage.listMessage) { + // All listMessages (SINGLE_SELECT and PRODUCT_LIST) need biz > list node + return 'list' + } else if (innerMessage.buttonsResponseMessage) { + return 'buttons_response' + } else if (innerMessage.listResponseMessage) { + return 'list_response' + } else if (innerMessage.templateButtonReplyMessage) { + return 'template_reply' + } else if (innerMessage.interactiveMessage) { + // Check if it has nativeFlowMessage (modern format) + if (innerMessage.interactiveMessage.nativeFlowMessage) { + return 'native_flow' + } + + // Check if it's a carousel with nativeFlowMessage buttons in cards if (innerMessage.interactiveMessage.carouselMessage?.cards?.length) { - const hasNativeFlow = innerMessage.interactiveMessage.carouselMessage.cards.some( + const hasNativeFlowButtons = innerMessage.interactiveMessage.carouselMessage.cards.some( (card: any) => card?.nativeFlowMessage?.buttons?.length ) - if (hasNativeFlow) return 'native_flow' + if (hasNativeFlowButtons) { + return 'native_flow' + } } + + // Check if it's a collection/product carousel + if (innerMessage.interactiveMessage.carouselMessage?.cards?.length) { + const hasCollectionCards = innerMessage.interactiveMessage.carouselMessage.cards.some( + (card: any) => card?.collectionMessage + ) + if (hasCollectionCards) { + return 'native_flow' + } + } + return 'interactive' } } @@ -688,40 +799,97 @@ export const makeMessagesSocket = (config: SocketConfig) => { return undefined } - /** Check if message is a carousel (media or product carousel) */ + /** + * Returns the attributes for the interactive binary node based on message type + * For native_flow: returns { v: '4', name: '' } or special attributes for payment flows + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _getButtonArgs = (message: proto.IMessage): BinaryNodeAttributes => { + const buttonType = getButtonType(message) + + // For native_flow messages, check for special button types that need specific attributes + if (buttonType === 'native_flow') { + const interactiveMsg = + message.interactiveMessage || + message.viewOnceMessage?.message?.interactiveMessage || + message.viewOnceMessageV2?.message?.interactiveMessage + + if (interactiveMsg?.nativeFlowMessage?.buttons?.[0]) { + const firstButtonName = interactiveMsg.nativeFlowMessage.buttons[0].name + + // Special button types that require specific attributes + // Based on official WhatsApp client traffic + if (firstButtonName === 'review_and_pay' || firstButtonName === 'payment_info') { + return { v: '4', name: 'payment_info' } + } else if (firstButtonName === 'mpm') { + return { v: '4', name: 'mpm' } + } else if (firstButtonName === 'review_order') { + return { v: '4', name: 'order_details' } + } + } + + // Default native_flow attributes + return { v: '4', name: '' } + } + + // For other button types, return empty attributes + return {} + } + + /** + * Checks if the message is a carousel (media carousel or product carousel) + * Carousels should NOT have the bot node injected as they are not bot messages + */ const isCarouselMessage = (message: proto.IMessage): boolean => { const interactiveMsg = message.interactiveMessage || message.viewOnceMessage?.message?.interactiveMessage || message.viewOnceMessageV2?.message?.interactiveMessage - return !!(interactiveMsg?.carouselMessage?.cards?.length) + + if (interactiveMsg?.carouselMessage?.cards?.length) { + return true + } + + return false } - /** Check if message is a catalog/product message */ + /** + * Checks if the message is a catalog/product message (catalog_message, single_product) + * These messages may need different biz node handling or no biz node at all + */ const isCatalogMessage = (message: proto.IMessage): boolean => { const interactiveMsg = message.interactiveMessage || message.viewOnceMessage?.message?.interactiveMessage || message.viewOnceMessageV2?.message?.interactiveMessage + const nativeFlow = interactiveMsg?.nativeFlowMessage if (nativeFlow?.buttons?.length) { + // Check if any button is a catalog-type button return nativeFlow.buttons.some( (btn: any) => btn?.name === 'catalog_message' || btn?.name === 'single_product' || btn?.name === 'product_list' ) } + return false } - /** Check if nativeFlowMessage is a list (single_select button) */ + /** + * Checks if the nativeFlowMessage is a list (single_select button) + * Lists need type='list' in the biz node instead of type='native_flow' + */ const isListNativeFlow = (message: proto.IMessage): boolean => { const interactiveMsg = message.interactiveMessage || message.viewOnceMessage?.message?.interactiveMessage || message.viewOnceMessageV2?.message?.interactiveMessage + const nativeFlow = interactiveMsg?.nativeFlowMessage if (nativeFlow?.buttons?.length) { + // Check if any button is a list-type button (single_select or multi_select) return nativeFlow.buttons.some((btn: any) => btn?.name === 'single_select' || btn?.name === 'multi_select') } + return false } @@ -738,13 +906,16 @@ export const makeMessagesSocket = (config: SocketConfig) => { statusJidList }: MessageRelayOptions ) => { - const meId = authState.creds.me!.id + const meId = authState.creds.me?.id + if (!meId) throw new Boom('Not authenticated', { statusCode: 401 }) const meLid = authState.creds.me?.lid const isRetryResend = Boolean(participant?.jid) let shouldIncludeDeviceIdentity = isRetryResend const statusJid = 'status@broadcast' - const { user, server } = jidDecode(jid)! + const jidDecoded = jidDecode(jid) + if (!jidDecoded) throw new Boom('Invalid JID') + const { user, server } = jidDecoded const isGroup = server === 'g.us' const isStatus = jid === statusJid const isLid = server === 'lid' @@ -756,6 +927,60 @@ export const makeMessagesSocket = (config: SocketConfig) => { useUserDevicesCache = useUserDevicesCache !== false useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus + // Convert nativeFlowMessage with single_select to direct listMessage (legacy format) + // This is required because WhatsApp expects listMessage format with biz > list node + // The viewOnceMessage > interactiveMessage > nativeFlowMessage wrapper causes error 479 + // Reference: direct listMessage works on phone + web + const innerMsg = message.viewOnceMessage?.message + const nativeFlow = innerMsg?.interactiveMessage?.nativeFlowMessage + if (nativeFlow?.buttons?.length) { + const singleSelectBtn = nativeFlow.buttons.find((btn: any) => btn?.name === 'single_select') + if (singleSelectBtn?.buttonParamsJson) { + try { + const params = JSON.parse(singleSelectBtn.buttonParamsJson) + const sections = params.sections?.map((section: any) => ({ + title: section.title, + rows: section.rows?.map((row: any) => ({ + rowId: row.id || row.rowId, + title: row.title, + description: row.description || '' + })) + })) + + if (sections?.length) { + // Build direct listMessage (legacy format that works) + const listMessage = proto.Message.ListMessage.fromObject({ + title: innerMsg?.interactiveMessage?.header?.title || '', + description: innerMsg?.interactiveMessage?.body?.text || '', + buttonText: params.title || 'Menu', + footerText: innerMsg?.interactiveMessage?.footer?.text || '', + listType: proto.Message.ListMessage.ListType.SINGLE_SELECT, + sections + }) + + // Mutate message in-place: remove viewOnceMessage, add listMessage + delete message.viewOnceMessage + message.listMessage = listMessage + // Keep messageContextInfo if it was nested + // eslint-disable-next-line max-depth + if (!message.messageContextInfo && innerMsg?.messageContextInfo) { + message.messageContextInfo = innerMsg.messageContextInfo + } + + logger.info( + { msgId, sectionsCount: sections.length, buttonText: params.title }, + '[LIST CONVERT] Converted nativeFlowMessage(single_select) to direct listMessage format' + ) + } + } catch (err) { + logger.warn( + { msgId, error: (err as Error).message }, + '[LIST CONVERT] Failed to convert nativeFlowMessage to listMessage, sending as-is' + ) + } + } + } + const participants: BinaryNode[] = [] const destinationJid = !isStatus ? finalJid : statusJid const binaryNodeContent: BinaryNode[] = [] @@ -777,7 +1002,9 @@ export const makeMessagesSocket = (config: SocketConfig) => { additionalAttributes = { ...additionalAttributes, device_fanout: 'false' } } - const { user, device } = jidDecode(participant.jid)! + const participantDecoded = jidDecode(participant.jid) + if (!participantDecoded) throw new Boom('Invalid participant JID') + const { user, device } = participantDecoded devices.push({ user, device, @@ -1003,6 +1230,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { const isMe = user === mePnUser || user === meLidUser if (isMe) { + // Send DSM to ALL own companion devices including carousel + // Error 479 on companion devices is non-fatal meRecipients.push(jid) } else { otherRecipients.push(jid) @@ -1011,10 +1240,13 @@ export const makeMessagesSocket = (config: SocketConfig) => { allRecipients.push(jid) } - - await assertSessions(allRecipients) - const effectiveMeRecipients = await canonicalizeSessionRecipients(meRecipients) - const effectiveOtherRecipients = await canonicalizeSessionRecipients(otherRecipients) + const isCarouselFanout = isCarouselMessage(message) + const effectiveMeRecipients = isCarouselFanout + ? await canonicalizeCarouselRecipients(meRecipients) + : meRecipients + const effectiveOtherRecipients = isCarouselFanout + ? await canonicalizeCarouselRecipients(otherRecipients) + : otherRecipients const effectiveAllRecipients = [...effectiveMeRecipients, ...effectiveOtherRecipients] await assertSessions(effectiveAllRecipients) @@ -1023,25 +1255,30 @@ export const makeMessagesSocket = (config: SocketConfig) => { { nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 } ] = await Promise.all([ - // For own devices: use DSM if available (1:1 chats only) - createParticipantNodes(meRecipients, meMsg || message, extraAttrs), - createParticipantNodes(otherRecipients, message, extraAttrs, meMsg) + // For own devices: use DSM (deviceSentMessage) wrapper + createParticipantNodes(effectiveMeRecipients, meMsg || message, extraAttrs), + createParticipantNodes(effectiveOtherRecipients, message, extraAttrs) ]) participants.push(...meNodes) participants.push(...otherNodes) - if (meRecipients.length > 0 || otherRecipients.length > 0) { - extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]) + if (effectiveMeRecipients.length > 0 || effectiveOtherRecipients.length > 0) { + extraAttrs['phash'] = generateParticipantHashV2(effectiveAllRecipients) } shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2 } if (isRetryResend) { - const isParticipantLid = isLidUser(participant!.jid) - const isMe = areJidsSameUser(participant!.jid, isParticipantLid ? meLid : meId) + if (!participant) throw new Boom('Missing participant for retry resend') + // Only check for regular LID users, NOT hosted LID users + // Hosted LID users should use meId for comparison, not meLid + const isParticipantLid = isLidUser(participant.jid) + const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId) - const encodedMessageToSend = isMe + // Send DSM for all message types including carousel + const usesDSM = isMe + const encodedMessageToSend = usesDSM ? encodeWAMessage({ deviceSentMessage: { destinationJid, @@ -1052,7 +1289,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({ data: encodedMessageToSend, - jid: participant!.jid + jid: participant.jid }) binaryNodeContent.push({ @@ -1060,7 +1297,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { attrs: { v: '2', type, - count: participant!.count.toString() + count: participant.count.toString() }, content: encryptedContent }) @@ -1093,22 +1330,32 @@ export const makeMessagesSocket = (config: SocketConfig) => { content: binaryNodeContent } - // Detect interactive message type for biz node injection + // Inject 'biz' node for interactive messages const buttonType = getButtonType(message) const isCatalog = isCatalogMessage(message) const isCarousel = isCarouselMessage(message) // Collect biz/bot nodes to append AFTER device-identity and tctoken - // Stanza order: participants → device-identity → tctoken → biz - // Carousel messages must NOT have biz node (breaks WhatsApp Web rendering) + // Stanza order: participants → device-identity → tctoken → biz (when applicable) + // CDP capture confirms Pastorini INCLUDES biz node for carousel (native_flow v=9, name=mixed) const deferredNodes: BinaryNode[] = [] - if (buttonType && enableInteractiveMessages && !isCarousel) { + // Inject biz node for interactive messages (including carousel — CDP evidence from Pastorini) + if ((buttonType || isCarousel) && enableInteractiveMessages) { + const startTime = Date.now() + // When entering via isCarousel, buttonType may be undefined — default to 'native_flow' + const effectiveButtonType = buttonType || 'native_flow' + + // Debug: Log message structure to diagnose list detection const interactiveMsg = message.interactiveMessage || message.viewOnceMessage?.message?.interactiveMessage || message.viewOnceMessageV2?.message?.interactiveMessage - + const listMsg = + message.listMessage || + message.viewOnceMessage?.message?.listMessage || + message.viewOnceMessageV2?.message?.listMessage + // For carousel messages, buttons are inside each card's nativeFlowMessage let nativeFlowButtons = interactiveMsg?.nativeFlowMessage?.buttons || [] if (nativeFlowButtons.length === 0 && interactiveMsg?.carouselMessage?.cards?.length) { nativeFlowButtons = interactiveMsg.carouselMessage.cards.flatMap( @@ -1119,22 +1366,49 @@ export const makeMessagesSocket = (config: SocketConfig) => { const isListDetected = isListNativeFlow(message) logger.info( - { msgId, buttonType, to: destinationJid, isListDetected, isCatalog }, + { + msgId, + buttonType, + to: destinationJid, + hasListMessage: !!listMsg, + hasInteractiveMessage: !!interactiveMsg, + hasNativeFlow: !!interactiveMsg?.nativeFlowMessage, + nativeFlowButtonNames: nativeFlowButtons.map((b: any) => b?.name), + isListDetected, + isCatalog, + isCarousel + }, '[Interactive] Preparing biz node' ) - try { - const allButtonNames = nativeFlowButtons.map((b: any) => b?.name).filter(Boolean) - const isNativeFlowButtons = buttonType === 'native_flow' + // Track that we're sending an interactive message + metrics.interactiveMessagesSent.inc({ type: effectiveButtonType }) + try { + // Classify button types for native_flow name and bot node decisions + const CTA_BUTTON_NAMES = new Set(['cta_url', 'cta_copy', 'cta_call']) + const allButtonNames = nativeFlowButtons.map((b: any) => b?.name).filter(Boolean) + const hasCTA = allButtonNames.some((name: string) => CTA_BUTTON_NAMES.has(name)) + const hasQuickReply = allButtonNames.some((name: string) => name === 'quick_reply') + const isCTAOnly = hasCTA && !hasQuickReply + + // For listMessage (legacy format), use direct tag + // This matches the known working implementation if (buttonType === 'list') { deferredNodes.push({ tag: 'biz', attrs: {}, content: [ - { tag: 'list', attrs: { type: 'product_list', v: '2' } } + { + tag: 'list', + attrs: { + type: 'product_list', + v: '2' + } + } ] }) + logger.info({ msgId, to: destinationJid }, '[BIZ NODE] Injected biz > list (product_list, v=2)') } else { const SPECIAL_FLOW_NAMES: Record = { review_and_pay: 'payment_info', @@ -1145,56 +1419,92 @@ export const makeMessagesSocket = (config: SocketConfig) => { const firstButtonName = allButtonNames[0] || '' const nativeFlowName = SPECIAL_FLOW_NAMES[firstButtonName] || 'mixed' + logger.info( + { msgId, buttonNames: allButtonNames, hasCTA, hasQuickReply, isCTAOnly, nativeFlowName }, + '[BIZ NODE] Injected biz > interactive(native_flow, v=1) > native_flow(v=9, name=' + nativeFlowName + ')' + ) + + const interactiveType = 'native_flow' + const bizContent: BinaryNode[] = [ + { + tag: 'interactive', + attrs: { + type: interactiveType, + v: '1' + }, + content: [ + { + tag: interactiveType, + attrs: { + v: '9', + name: nativeFlowName + } + } + ] + } + ] + + // CDP capture shows Pastorini includes quality_control inside biz for carousel + if (isCarousel) { + const decisionId = randomBytes(20).toString('hex') + bizContent.push({ + tag: 'quality_control', + attrs: { + decision_id: decisionId + }, + content: [ + { + tag: 'decision_source', + attrs: { + value: 'df' + } + } + ] + }) + logger.info({ msgId, decisionId }, '[BIZ NODE] Added quality_control for carousel') + } + deferredNodes.push({ tag: 'biz', attrs: {}, - content: [ - { - tag: 'interactive', - attrs: { type: 'native_flow', v: '1' }, - content: [ - { tag: 'native_flow', attrs: { v: '9', name: nativeFlowName } } - ] - } - ] + content: bizContent }) } - // Bot node — skip for native_flow, carousel, catalog + // Bot node — skip for native_flow buttons (breaks Web rendering) + const isNativeFlowButtons = buttonType === 'native_flow' + const isPrivateUserChat = (isPnUser(destinationJid) || isLidUser(destinationJid) || destinationJid?.endsWith('@c.us')) && !isJidBot(destinationJid) - if (isPrivateUserChat && !isCatalog && buttonType !== 'list' && !isNativeFlowButtons) { - deferredNodes.push({ tag: 'bot', attrs: { biz_bot: '1' } }) + if (isPrivateUserChat && !isCarousel && !isCatalog && buttonType !== 'list' && !isNativeFlowButtons) { + deferredNodes.push({ + tag: 'bot', + attrs: { biz_bot: '1' } + }) + logger.info({ msgId, to: destinationJid }, '[BOT NODE] Added bot node (biz_bot=1)') + } else if (isNativeFlowButtons) { + logger.debug({ msgId, to: destinationJid }, '[BOT NODE] Skipped — native_flow (Web compatibility)') + } else if (isCarousel) { + logger.debug({ msgId, to: destinationJid }, '[BOT NODE] Skipped — carousel message') + } else if (isCatalog) { + logger.debug({ msgId, to: destinationJid }, '[BOT NODE] Skipped — catalog message') } + + // Track success and latency after message is sent + metrics.interactiveMessagesSuccess.inc({ type: effectiveButtonType }) + metrics.interactiveMessagesLatency.observe({ type: effectiveButtonType }, Date.now() - startTime) } catch (error) { - logger.error({ error, msgId, buttonType }, '[BIZ NODE] Failed to inject biz node') + logger.error({ error, msgId, buttonType: effectiveButtonType }, '[BIZ NODE] Failed to inject biz node') + metrics.interactiveMessagesFailures.inc({ type: effectiveButtonType, reason: 'injection_failed' }) } - } else if (isCarousel && enableInteractiveMessages) { - // Carousel: inject quality_control with decision_id (required by WhatsApp Web) - const decisionId = randomBytes(20).toString('hex') - deferredNodes.push({ - tag: 'biz', - attrs: {}, - content: [ - { - tag: 'interactive', - attrs: { type: 'native_flow', v: '1' }, - content: [ - { tag: 'native_flow', attrs: { v: '9', name: 'mixed' } } - ] - } - ] - }) - deferredNodes.push({ - tag: 'quality_control', - attrs: { decision_id: decisionId }, - content: [ - { tag: 'decision_source', attrs: { value: 'df' } } - ] - }) - logger.info({ msgId, to: destinationJid, decisionId }, '[CAROUSEL] Injected biz + quality_control') + } else if (buttonType && !enableInteractiveMessages) { + logger.warn( + { msgId, buttonType }, + '[Interactive] Message detected but feature disabled (enableInteractiveMessages=false)' + ) + metrics.interactiveMessagesFailures.inc({ type: buttonType, reason: 'feature_disabled' }) } // if the participant to send to is explicitly specified (generally retry recp) @@ -1214,7 +1524,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { stanza.attrs.to = destinationJid } - // Force device-identity for carousel (ensures Web can decrypt) + // Always include device-identity for carousel (Pastorini stanza always has it) if (shouldIncludeDeviceIdentity || isCarousel) { ;(stanza.content as BinaryNode[]).push({ tag: 'device-identity', @@ -1249,36 +1559,281 @@ export const makeMessagesSocket = (config: SocketConfig) => { } } - const contactTcTokenData = - !isGroup && !isRetryResend && !isStatus ? await authState.keys.get('tctoken', [destinationJid]) : {} + // tctoken lifecycle: fetch, validate expiry, proactive re-fetch if missing/expired + // WA Web never attaches tctoken to peer (AppStateSync) messages — server + const isPeerMessage = additionalAttributes?.['category'] === 'peer' + const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage - const tcTokenBuffer = contactTcTokenData[destinationJid]?.token + // Resolve destination to LID for tctoken storage — matches Signal session key pattern + const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid + const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {} + const existingTokenEntry = contactTcTokenData[tcTokenJid] + let tcTokenBuffer = existingTokenEntry?.token - if (tcTokenBuffer) { + // Treat expired tokens the same as missing — re-fetch from server + if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) { + logTcToken('expired', { jid: destinationJid, timestamp: existingTokenEntry?.timestamp }) + tcTokenBuffer = undefined + // Opportunistic cleanup: remove expired token from store + try { + await authState.keys.set({ tctoken: { [tcTokenJid]: null } }) + } catch { + /* ignore cleanup errors */ + } + } + + // If tctoken is missing for a 1:1 send, fetch it + // CDP capture confirms Pastorini stanza includes tctoken for carousel + if (!tcTokenBuffer?.length && is1on1Send && !tcTokenFetchingJids.has(tcTokenJid)) { + tcTokenFetchingJids.add(tcTokenJid) + logTcToken('fetch', { jid: destinationJid }) + + if (isCarousel) { + // BLOCKING fetch for carousel — tctoken is required (Pastorini stanza has it) + try { + const fetchResult = await getPrivacyTokens([destinationJid]) + + // Direct extraction from IQ result — bypass store/read key mismatch + const tokensNode = getBinaryNodeChild(fetchResult, 'tokens') + if (tokensNode) { + const tokenNodes = getBinaryNodeChildren(tokensNode, 'token') + for (const tokenNode of tokenNodes) { + if (tokenNode.attrs.type === 'trusted_contact' && tokenNode.content instanceof Uint8Array) { + tcTokenBuffer = Buffer.from(tokenNode.content) + logger.info( + { + jid: destinationJid, + tokenLen: tcTokenBuffer.length, + tokenJid: tokenNode.attrs.jid, + timestamp: tokenNode.attrs.t + }, + '[CAROUSEL] tctoken extracted directly from IQ result' + ) + break + } + } + } + + if (!tcTokenBuffer?.length) { + // Debug: dump the IQ result structure + const childTags = Array.isArray(fetchResult.content) + ? (fetchResult.content as BinaryNode[]).map(n => `${n.tag}(${JSON.stringify(n.attrs)})`) + : [] + logger.warn( + { jid: destinationJid, resultTag: fetchResult.tag, resultAttrs: fetchResult.attrs, childTags }, + '[CAROUSEL] tctoken fetch completed but NO valid token in IQ result' + ) + } + + // Also store for future use + await storeTcTokensFromIqResult({ + result: fetchResult, + fallbackJid: destinationJid, + keys: authState.keys, + getLIDForPN + }).catch(() => {}) + } catch (err: any) { + logger.warn({ jid: destinationJid, err: err?.message }, '[CAROUSEL] Blocking tctoken fetch failed') + } finally { + tcTokenFetchingJids.delete(tcTokenJid) + } + } else { + // Fire-and-forget for non-carousel + getPrivacyTokens([destinationJid]) + .then(async fetchResult => { + await storeTcTokensFromIqResult({ + result: fetchResult, + fallbackJid: destinationJid, + keys: authState.keys, + getLIDForPN + }) + }) + .catch(err => { + logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken fetch failed') + }) + .finally(() => { + tcTokenFetchingJids.delete(tcTokenJid) + }) + } + } + + if (tcTokenBuffer?.length) { ;(stanza.content as BinaryNode[]).push({ tag: 'tctoken', attrs: {}, content: tcTokenBuffer }) + logTcToken('attached', { jid: destinationJid }) } if (additionalNodes && additionalNodes.length > 0) { ;(stanza.content as BinaryNode[]).push(...additionalNodes) } - // Append deferred biz/bot/quality_control nodes LAST - // Stanza order: participants → device-identity → tctoken → biz → quality_control + // Append deferred biz/bot nodes LAST (after device-identity, tctoken) + // Stanza order: participants → device-identity → tctoken → biz if (deferredNodes.length > 0) { ;(stanza.content as BinaryNode[]).push(...deferredNodes) } + // Log stanza structure for interactive messages + if (buttonType || isCarousel) { + const contentTags = Array.isArray(stanza.content) ? stanza.content.map((n: BinaryNode) => n.tag) : [] + logger.info({ msgId, to: destinationJid, contentTags }, '[STANZA] Content tags: ' + JSON.stringify(contentTags)) + } + logger.debug({ msgId }, `sending message to ${participants.length} devices`) + // ======= PROTOBUF ROUNDTRIP TEST: Verify encoding preserves carousel ======= + if (isCarousel) { + try { + const encoded = proto.Message.encode(message).finish() + const decoded = proto.Message.decode(encoded) + const decodedInteractive = decoded.interactiveMessage || decoded.viewOnceMessage?.message?.interactiveMessage + const cardsCount = decodedInteractive?.carouselMessage?.cards?.length || 0 + const card0 = decodedInteractive?.carouselMessage?.cards?.[0] + const card0Header = card0?.header + + logger.info( + { + msgId, + encodedSize: encoded.length, + messageKeys: Object.keys(message), + hasInteractive: !!message.interactiveMessage, + hasViewOnce: !!message.viewOnceMessage, + hasCarouselAfterDecode: !!decodedInteractive?.carouselMessage, + cardsCount, + card0Title: card0Header?.title, + card0HasImage: !!card0Header?.imageMessage, + card0Buttons: card0?.nativeFlowMessage?.buttons?.length || 0, + messageVersion: decodedInteractive?.carouselMessage?.messageVersion + }, + '[ROUNDTRIP] Protobuf encode→decode verification' + ) + } catch (err) { + logger.error({ msgId, err: (err as Error).message }, '[ROUNDTRIP] Failed to verify protobuf encoding') + } + } + + // ======= PROTOCOL INTERCEPTOR: Dump complete stanza for debugging ======= + if (buttonType || isCarousel) { + const dumpBinaryNode = (node: BinaryNode, indent = 0): string => { + if (!node) return '' + const pad = ' '.repeat(indent) + const tag = node.tag || '?' + const filteredAttrs = ([, v]: [string, unknown]) => v !== undefined && v !== null + const attrEntries = node.attrs ? Object.entries(node.attrs).filter(filteredAttrs) : [] + const attrStr = attrEntries.length > 0 ? ' ' + attrEntries.map(([k, v]) => `${k}="${v}"`).join(' ') : '' + + if (!node.content) return `${pad}<${tag}${attrStr}/>` + + if (Buffer.isBuffer(node.content) || node.content instanceof Uint8Array) { + return `${pad}<${tag}${attrStr}>[binary ${node.content.length} bytes]` + } + + if (Array.isArray(node.content)) { + const children = node.content.map((c: BinaryNode) => dumpBinaryNode(c, indent + 1)).join('\n') + return `${pad}<${tag}${attrStr}>\n${children}\n${pad}` + } + + return `${pad}<${tag}${attrStr}>${String(node.content).slice(0, 100)}` + } + + logger.debug( + { + msgId, + to: destinationJid, + buttonType: buttonType || 'carousel', + stanzaXML: '\n' + dumpBinaryNode(stanza) + }, + '[PROTOCOL-DUMP] Stanza structure before send' + ) + } + // ======= END PROTOCOL INTERCEPTOR ======= + await sendNode(stanza) + // Fire-and-forget: issue our token to the contact (like WA Web's sendTcToken). + // Gated only by shouldSendNewTcToken — removed tcTokenBuffer?.length guard so + // issuance fires even when we don't yet hold a token (bucket boundary crossed). + // IMPORTANT: must run AFTER sendNode — issuing before the message causes error 463. + if (is1on1Send && shouldSendNewTcToken(existingTokenEntry?.senderTimestamp)) { + const issueTimestamp = unixTimestampSeconds() + logTcToken('reissue', { jid: destinationJid }) + getPrivacyTokens([destinationJid], issueTimestamp) + .then(async result => { + // Store any tokens received in the IQ response. + // onNewJidStored not passed — pruning index lives in messages-recv (higher layer). + await storeTcTokensFromIqResult({ + result, + fallbackJid: tcTokenJid, + keys: authState.keys, + getLIDForPN + }) + + // Persist senderTimestamp unconditionally — WA Web stores it in the chat table + // regardless of whether a token exists. Spread preserves token+timestamp if present. + // WABA Android: INSERT INTO wa_trusted_contacts_send (jid, sent_tc_token_timestamp, real_issue_timestamp) + // VALUES (?, ?, 0) — realIssueTimestamp=0 means issued but not yet confirmed by server + const currentData = await authState.keys.get('tctoken', [tcTokenJid]) + const currentEntry = currentData[tcTokenJid] + await authState.keys.set({ + tctoken: { + [tcTokenJid]: { + ...currentEntry, + token: currentEntry?.token ?? Buffer.alloc(0), + senderTimestamp: issueTimestamp, + realIssueTimestamp: 0 + } + } + }) + + logTcToken('reissue_ok', { jid: destinationJid }) + }) + .catch(err => { + logTcToken('reissue_fail', { jid: destinationJid, error: err?.message }) + }) + } + + // Log with [BAILEYS] prefix + logMessageSent(msgId, destinationJid) + + // Record message sent metric + const msgType = message.conversation + ? 'text' + : message.imageMessage + ? 'image' + : message.videoMessage + ? 'video' + : message.audioMessage + ? 'audio' + : message.documentMessage + ? 'document' + : message.stickerMessage + ? 'sticker' + : message.stickerPackMessage + ? 'sticker_pack' + : message.reactionMessage + ? 'reaction' + : 'other' + recordMessageSent(msgType) + // Add message to retry cache if enabled if (messageRetryManager && !participant) { - messageRetryManager.addRecentMessage(destinationJid, msgId, message) + messageRetryManager.addRecentMessage(jidNormalizedUser(destinationJid), msgId, message) + } + + // Track session activity for cleanup (all target JIDs) + if (sessionActivityTracker) { + // Record activity for destination JID + sessionActivityTracker.recordActivity(destinationJid) + + // For groups, also record activity for all participants who received the message + if (isGroup || isStatus) { + for (const device of devices) { + sessionActivityTracker.recordActivity(device.jid) + } + } } }, meId) @@ -1310,11 +1865,6 @@ export const makeMessagesSocket = (config: SocketConfig) => { return 'media' } - // Carousel with images → media type for Web real-time rendering - if (normalizedMessage.interactiveMessage?.carouselMessage) { - return 'media' - } - return 'text' } @@ -1354,8 +1904,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { return '' } - const getPrivacyTokens = async (jids: string[], senderTs?: number) => { - const t = (senderTs || unixTimestampSeconds()).toString() + const getPrivacyTokens = async (jids: string[], timestamp?: number) => { + const t = (timestamp ?? unixTimestampSeconds()).toString() const result = await query({ tag: 'iq', attrs: { @@ -1404,7 +1954,11 @@ export const makeMessagesSocket = (config: SocketConfig) => { updateMemberLabel, updateMediaMessage: async (message: WAMessage) => { const content = assertMediaContent(message.message) - const mediaKey = content.mediaKey! + const mediaKey = content.mediaKey + if (!mediaKey) { + throw new Boom('Missing media key for update', { statusCode: 400 }) + } + const meId = authState.creds.me!.id const node = encryptMediaRetryRequest(message.key, mediaKey, meId) @@ -1449,8 +2003,325 @@ export const makeMessagesSocket = (config: SocketConfig) => { return message }, - sendMessage: async (jid: string, content: AnyMessageContent, options: MiscMessageGenerationOptions = {}) => { + + /** + * Send an album message (multiple images/videos grouped together) + * + * @param jid - The recipient JID + * @param album - Album configuration with media items + * @param options - Additional message generation options + * @returns Complete result with status of each media item + * + * @example + * ```typescript + * const result = await sock.sendAlbumMessage('1234567890@s.whatsapp.net', { + * medias: [ + * { image: { url: './photo1.jpg' }, caption: 'First photo' }, + * { image: { url: './photo2.jpg' }, caption: 'Second photo' }, + * { video: { url: './video.mp4' }, caption: 'A video' } + * ], + * delay: 'adaptive', // or fixed ms like 500 + * retryCount: 3, + * continueOnFailure: true + * }) + * + * if (!result.success) { + * console.log('Failed items:', result.failedIndices) + * } + * ``` + */ + sendAlbumMessage: async ( + jid: string, + album: AlbumMessageOptions, + options: MiscMessageGenerationOptions = {} + ): Promise => { + const startTime = Date.now() const userJid = authState.creds.me!.id + + const { medias, delay: delayConfig = 'adaptive', retryCount = 3, continueOnFailure = true } = album + + // Validation (also done in generateWAMessageContent, but double-check here) + if (!medias || medias.length < 2) { + throw new Boom('Album must have at least 2 media items', { statusCode: 400 }) + } + + if (medias.length > 10) { + throw new Boom('Album cannot have more than 10 media items (WhatsApp limit)', { statusCode: 400 }) + } + + // Count media types for album root + // Use hasNonNullishProperty for consistency with generateWAMessageContent validation + const imageCount = medias.filter(m => hasNonNullishProperty(m as AnyMessageContent, 'image')).length + const videoCount = medias.filter(m => hasNonNullishProperty(m as AnyMessageContent, 'video')).length + + logger.info( + { jid, totalItems: medias.length, imageCount, videoCount, delayConfig, retryCount }, + 'Starting album message send' + ) + + // Generate album root message first (with counts of expected media) + const albumRootMsg = await generateWAMessage( + jid, + { + album: { medias, delay: delayConfig, retryCount, continueOnFailure } + }, + { + logger, + userJid, + getUrlInfo: text => + getUrlInfo(text, { + thumbnailWidth: linkPreviewImageThumbnailWidth, + fetchOpts: { timeout: 3_000, ...(httpRequestOptions || {}) }, + logger, + uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined + }), + upload: waUploadToServer, + mediaCache: config.mediaCache, + // Don't spread options here to avoid messageId collision + timestamp: options.timestamp, + quoted: options.quoted, + ephemeralExpiration: options.ephemeralExpiration, + mediaUploadTimeoutMs: options.mediaUploadTimeoutMs + } + ) + + const albumKey = albumRootMsg.key + + // CRITICAL: Relay album root message to server first + // Without this, child media items reference a non-existent album key + await relayMessage(jid, albumRootMsg.message!, { + messageId: albumRootMsg.key.id!, + useCachedGroupMetadata: options.useCachedGroupMetadata + }) + + // Emit own event for album root if configured + if (config.emitOwnEvents) { + process.nextTick(async () => { + let mutexKey = albumRootMsg.key.remoteJid + if (!mutexKey) { + logger.warn( + { msgId: albumRootMsg.key.id }, + 'Missing remoteJid in albumRootMsg, using msg.key.id as fallback' + ) + mutexKey = albumRootMsg.key.id || 'unknown' + } + + await messageMutex.mutex(mutexKey, () => upsertMessage(albumRootMsg, 'append')) + }) + } + + logger.debug({ albumKeyId: albumKey.id }, 'Album root message relayed') + + const results: AlbumMediaResult[] = [] + + /** + * Calculate adaptive delay based on media characteristics + * Videos get more delay (2x), later items get slightly more delay, + * plus random jitter to prevent predictable patterns + */ + const calculateAdaptiveDelay = (media: AlbumMediaItem, index: number): number => { + const baseDelay = 500 // Base delay in ms + + // Videos get more delay + const isVideo = 'video' in media + const mediaTypeMultiplier = isVideo ? 2.0 : 1.0 + + // Later items in album get slightly more delay (cumulative load) + const positionMultiplier = 1 + index * 0.1 + + // Add some jitter to prevent predictable patterns + const jitter = Math.random() * 200 + + return Math.round(baseDelay * mediaTypeMultiplier * positionMultiplier + jitter) + } + + /** + * Get delay for this media item + */ + const getDelay = (media: AlbumMediaItem, index: number): number => { + if (delayConfig === 'adaptive') { + return calculateAdaptiveDelay(media, index) + } + + return delayConfig + } + + /** + * Send a single media item with retry logic + */ + const sendMediaWithRetry = async (media: AlbumMediaItem, index: number): Promise => { + const itemStartTime = Date.now() + let lastError: Error | undefined + let attempts = 0 + + for (let attempt = 0; attempt <= retryCount; attempt++) { + attempts = attempt + 1 + try { + // Generate message for this media item + // NOTE: Each item needs its own unique messageId, so we don't spread options.messageId + const mediaMsg = await generateWAMessage(jid, media as AnyMessageContent, { + logger, + userJid, + getUrlInfo: text => + getUrlInfo(text, { + thumbnailWidth: linkPreviewImageThumbnailWidth, + fetchOpts: { timeout: 3_000, ...(httpRequestOptions || {}) }, + logger, + uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined + }), + upload: waUploadToServer, + mediaCache: config.mediaCache, + // Don't spread ...options to avoid messageId collision + // Each item gets a fresh ID from generateWAMessage + timestamp: options.timestamp, + quoted: options.quoted, + ephemeralExpiration: options.ephemeralExpiration, + mediaUploadTimeoutMs: options.mediaUploadTimeoutMs + }) + + // Attach to parent album via messageAssociation (correct proto structure) + // Uses AssociationType.MEDIA_ALBUM and parentMessageKey as per WhatsApp protocol + if (!mediaMsg.message) { + throw new Boom('Missing message content for album media item') + } + + if (!mediaMsg.message.messageContextInfo) { + mediaMsg.message.messageContextInfo = {} + } + + mediaMsg.message.messageContextInfo.messageAssociation = { + associationType: proto.MessageAssociation.AssociationType.MEDIA_ALBUM, + parentMessageKey: albumKey + } + + // Relay the message + await relayMessage(jid, mediaMsg.message, { + messageId: mediaMsg.key.id!, + useCachedGroupMetadata: options.useCachedGroupMetadata + }) + + // Emit own event if configured + if (config.emitOwnEvents) { + process.nextTick(async () => { + let mutexKey = mediaMsg.key.remoteJid + if (!mutexKey) { + logger.warn({ msgId: mediaMsg.key.id }, 'Missing remoteJid in mediaMsg, using msg.key.id as fallback') + mutexKey = mediaMsg.key.id || 'unknown' + } + + await messageMutex.mutex(mutexKey, () => upsertMessage(mediaMsg, 'append')) + }) + } + + logger.debug({ index, msgId: mediaMsg.key.id, attempts }, 'Album media item sent successfully') + + return { + index, + success: true, + message: mediaMsg, + retryAttempts: attempts, + latencyMs: Date.now() - itemStartTime + } + } catch (error) { + lastError = error as Error + logger.warn( + { index, attempt: attempts, error: lastError.message }, + 'Album media item send failed, will retry' + ) + + // Exponential backoff for retries + if (attempt < retryCount) { + const backoffDelay = Math.min(1000 * Math.pow(2, attempt), 10000) + await new Promise(resolve => setTimeout(resolve, backoffDelay)) + } + } + } + + // All retries exhausted + logger.error({ index, attempts, error: lastError?.message }, 'Album media item failed after all retries') + + return { + index, + success: false, + error: lastError, + retryAttempts: attempts, + latencyMs: Date.now() - itemStartTime + } + } + + // Send each media item sequentially + for (let i = 0; i < medias.length; i++) { + const media = medias[i]! + + const result = await sendMediaWithRetry(media, i) + results.push(result) + + // Check if we should stop on failure + if (!result.success && !continueOnFailure) { + logger.warn( + { index: i, totalItems: medias.length }, + 'Album send stopped due to failure (continueOnFailure=false)' + ) + break + } + + // Apply delay before next item (except for last item) + if (i < medias.length - 1) { + const delay = getDelay(media, i) + logger.trace({ index: i, delayMs: delay }, 'Waiting before next album item') + await new Promise(resolve => setTimeout(resolve, delay)) + } + } + + // Calculate final results + const attemptedItems = results.length + const stoppedEarly = attemptedItems < medias.length + const successCount = results.filter(r => r.success).length + const failedCount = results.filter(r => !r.success).length + const failedIndices = results.filter(r => !r.success).map(r => r.index) + const totalLatencyMs = Date.now() - startTime + + const finalResult: AlbumSendResult = { + albumKey, + results, + totalItems: medias.length, + attemptedItems, + successCount, + failedCount, + failedIndices, + success: failedCount === 0 && !stoppedEarly, + stoppedEarly, + totalLatencyMs + } + + logger.info( + { + albumKeyId: albumKey.id, + totalItems: medias.length, + attemptedItems, + successCount, + failedCount, + stoppedEarly, + totalLatencyMs + }, + 'Album message send completed' + ) + + return finalResult + }, + + sendMessage: async (jid: string, content: AnyMessageContent, options: MiscMessageGenerationOptions = {}) => { + // Check for album misuse - must use sendAlbumMessage instead + if (typeof content === 'object' && 'album' in content) { + throw new Boom( + 'Cannot send album messages with sendMessage(). Use sendAlbumMessage() instead, ' + + 'which properly sends the album root and individual media items.', + { statusCode: 400 } + ) + } + + const userJid = authState.creds.me!.id + if ( typeof content === 'object' && 'disappearingMessagesInChat' in content && @@ -1532,7 +2403,13 @@ export const makeMessagesSocket = (config: SocketConfig) => { }) if (config.emitOwnEvents) { process.nextTick(async () => { - await messageMutex.mutex('upsert', () => upsertMessage(fullMsg, 'append')) + let mutexKey = fullMsg.key.remoteJid + if (!mutexKey) { + logger.warn({ msgId: fullMsg.key.id }, 'Missing remoteJid in fullMsg, using msg.key.id as fallback') + mutexKey = fullMsg.key.id || 'unknown' + } + + await messageMutex.mutex(mutexKey, () => upsertMessage(fullMsg, 'append')) }) } diff --git a/src/Utils/messages-media.ts b/src/Utils/messages-media.ts index fe10f21d..5d232796 100644 --- a/src/Utils/messages-media.ts +++ b/src/Utils/messages-media.ts @@ -160,7 +160,7 @@ export const extractImageThumb = async (bufferOrFilePath: Readable | Buffer | st height: dimensions.height } } - } else if ('jimp' in lib && (typeof lib.jimp?.Jimp === 'object' || typeof lib.jimp?.Jimp === 'function')) { + } else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'function') { const jimp = await (lib.jimp.Jimp as any).read(bufferOrFilePath) const dimensions = { width: jimp.width, diff --git a/src/Utils/messages.ts b/src/Utils/messages.ts index 468498df..782682db 100644 --- a/src/Utils/messages.ts +++ b/src/Utils/messages.ts @@ -45,8 +45,8 @@ import { generateThumbnail, getAudioDuration, getAudioWaveform, - getRawMediaUploadData, getStream, + getRawMediaUploadData, type MediaDownloadOptions } from './messages-media' import { shouldIncludeReportingToken } from './reporting-utils' @@ -475,48 +475,6 @@ export const formatNativeFlowButton = (button: NativeButton): NativeFlowButton = } } -const recoverCarouselImageMetadata = async ( - card: CarouselMessageOptions['cards'][number], - imageMessage: proto.Message.IImageMessage, - mediaOptions: MessageContentGenerationOptions -) => { - if (imageMessage.jpegThumbnail && imageMessage.height && imageMessage.width) { - return - } - - try { - const { stream } = await getStream(card.image!, mediaOptions.options) - const thumb = await extractImageThumb(stream) - - if (!imageMessage.jpegThumbnail) { - imageMessage.jpegThumbnail = thumb.buffer - } - - if (!imageMessage.width && thumb.original.width) { - imageMessage.width = thumb.original.width - } - - if (!imageMessage.height && thumb.original.height) { - imageMessage.height = thumb.original.height - } - - mediaOptions.logger?.info( - { - cardTitle: card.title, - hasJpegThumbnail: !!imageMessage.jpegThumbnail, - width: imageMessage.width, - height: imageMessage.height - }, - '[CAROUSEL] Recovered image thumbnail/dimensions from source media' - ) - } catch (error) { - mediaOptions.logger?.warn( - { cardTitle: card.title, error }, - '[CAROUSEL] Failed to recover image thumbnail/dimensions from source media' - ) - } -} - /** * Generates a button message using Native Flow format wrapped in viewOnceMessage * This is the modern approach for button messages that works on iOS and Android @@ -677,6 +635,59 @@ export const generateCarouselMessage = async ( ) } + const recoverCarouselImageMetadata = async ( + imageMessage: proto.Message.IImageMessage | null | undefined, + cardImage: WAMediaUpload, + cardTitle: string | undefined, + uploadOptions: MessageContentGenerationOptions + ) => { + if (!imageMessage) { + return + } + + const missingThumbnail = !imageMessage.jpegThumbnail + const missingWidth = !imageMessage.width + const missingHeight = !imageMessage.height + if (!missingThumbnail && !missingWidth && !missingHeight) { + return + } + + try { + const { stream } = await getStream(cardImage, uploadOptions.options) + const { buffer, original } = await extractImageThumb(stream) + + if (missingThumbnail) { + imageMessage.jpegThumbnail = buffer.toString('base64') + } + + if (missingWidth && original.width) { + imageMessage.width = original.width + } + + if (missingHeight && original.height) { + imageMessage.height = original.height + } + + uploadOptions.logger?.info( + { + cardTitle, + recoveredThumbnail: !!imageMessage.jpegThumbnail, + width: imageMessage.width, + height: imageMessage.height + }, + '[CAROUSEL] Recovered image metadata from source media' + ) + } catch (error) { + uploadOptions.logger?.warn( + { + cardTitle, + trace: error instanceof Error ? error.stack : String(error) + }, + '[CAROUSEL] Failed source-media thumbnail fallback' + ) + } + } + // Map cards to the carousel format (processing media) const carouselCards = await Promise.all( cards.map(async card => { @@ -692,10 +703,12 @@ export const generateCarouselMessage = async ( if (hasMedia && mediaOptions) { if (card.image) { const { imageMessage } = await prepareWAMessageMedia({ image: card.image }, mediaOptions) - if (imageMessage) { - await recoverCarouselImageMetadata(card, imageMessage, mediaOptions) - } + // Mirror the working Pastorini-style result: every carousel image card should + // carry a jpegThumbnail and dimensions before it reaches the Web live renderer. + await recoverCarouselImageMetadata(imageMessage, card.image, card.title, mediaOptions) + + // Validate image fields needed for WhatsApp rendering if (imageMessage && !imageMessage.jpegThumbnail) { mediaOptions.logger?.warn( { cardTitle: card.title }, @@ -1275,57 +1288,20 @@ export const generateWAMessageContent = async ( options.logger?.info('Sending CTA buttons as nativeFlowMessage with viewOnceMessage wrapper') } } - // Check for nativeCarousel — inline handler (validated on Android, iOS, Web) - // Direct interactiveMessage at root (field 45), NO viewOnceMessage wrapper, - // NO messageContextInfo, NO biz/bot stanza nodes needed + // Check for nativeCarousel else if (hasNonNullishProperty(message, 'nativeCarousel')) { const carouselMsg = message as any - const cards = carouselMsg.nativeCarousel.cards || [] - const title = carouselMsg.nativeCarousel.title || carouselMsg.title - const text = carouselMsg.text - const footer = carouselMsg.footer - - const carouselCards = await Promise.all( - cards.map(async (card: any) => { - const hasMedia = !!(card.image || card.video) - const header: any = { - title: card.title || '', - subtitle: card.footer || '', - hasMediaAttachment: hasMedia - } - if (hasMedia && card.image) { - const { imageMessage } = await prepareWAMessageMedia({ image: card.image }, options) - if (imageMessage && !imageMessage.height) imageMessage.height = 500 - if (imageMessage && !imageMessage.width) imageMessage.width = 500 - header.imageMessage = imageMessage - } - return { - header, - body: { text: card.body || '' }, - footer: card.footer ? { text: card.footer } : undefined, - nativeFlowMessage: { - buttons: (card.buttons || []).map((btn: any) => { - switch (btn.type) { - case 'url': return { name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: btn.text, url: btn.url, merchant_url: btn.url }) } - case 'copy': return { name: 'cta_copy', buttonParamsJson: JSON.stringify({ display_text: btn.text, copy_code: btn.copyText }) } - case 'call': return { name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: btn.text, phone_number: btn.phoneNumber }) } - default: return { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: btn.text, id: btn.id }) } - } - }) - } - } - }) - ) - - m.interactiveMessage = { - header: { title: title || ' ', hasMediaAttachment: false }, - body: { text: text || '' }, - footer: footer ? { text: footer } : undefined, - carouselMessage: { - cards: carouselCards, - messageVersion: 1 - } + const carouselOptions: CarouselMessageOptions = { + cards: carouselMsg.nativeCarousel.cards, + title: carouselMsg.nativeCarousel.title || carouselMsg.title, + text: carouselMsg.text, + footer: carouselMsg.footer } + // Pass options for media processing if cards have images/videos + const generated = await generateCarouselMessage(carouselOptions, options) + // Frida capture shows interactiveMessage DIRECT (field 45) in DSM — no viewOnceMessage wrapper + // Testing without wrapper: biz node + quality_control already match Pastorini CDP stanza + m.interactiveMessage = generated.interactiveMessage return m } // Check for nativeList diff --git a/src/Utils/tc-token-utils.ts b/src/Utils/tc-token-utils.ts index c7d5d0e9..b2a68fcc 100644 --- a/src/Utils/tc-token-utils.ts +++ b/src/Utils/tc-token-utils.ts @@ -158,11 +158,13 @@ export async function storeTcTokensFromIqResult({ ...existingEntry, token: Buffer.from(tokenNode.content), timestamp: tokenNode.attrs.t, - // Resets real_issue_timestamp to null when storing a new token + // WABA Android: resets real_issue_timestamp to null when storing a new token + // (UPDATE wa_trusted_contacts_send SET real_issue_timestamp=null) realIssueTimestamp: null } // Store under resolved storageJid AND under fallbackJid (PN) for reliable lookup + // The read path may resolve to a different LID than the store path const normalizedFallback = jidNormalizedUser(fallbackJid) const keysToStore: Record = { [storageJid]: tokenEntry From eecf6df2a2c08e9c4acd19996c3cc9ff71882f17 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 01:48:35 -0300 Subject: [PATCH 31/32] chore: update proto/version to v2.3000.1035484955 (#300) Co-authored-by: rsalcara --- WAProto/WAProto.proto | 3 ++- WAProto/index.d.ts | 3 ++- WAProto/index.js | 5 +++++ src/Defaults/baileys-version.json | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index 65f2f544..af98dad1 100644 --- a/WAProto/WAProto.proto +++ b/WAProto/WAProto.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package proto; -/// WhatsApp Version: 2.3000.1035370989 +/// WhatsApp Version: 2.3000.1035484955 message ADVDeviceIdentity { optional uint32 rawId = 1; @@ -417,6 +417,7 @@ message BotCapabilityMetadata { JSON_PATCH_STREAMING = 58; AI_TAB_FORCE_CLIPPY = 59; UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60; + AI_SUBSCRIPTION_ENABLED = 61; } } diff --git a/WAProto/index.d.ts b/WAProto/index.d.ts index 772f0eee..8d42202c 100644 --- a/WAProto/index.d.ts +++ b/WAProto/index.d.ts @@ -1092,7 +1092,8 @@ export namespace proto { RICH_RESPONSE_UR_IMAGINE_VIDEO = 57, JSON_PATCH_STREAMING = 58, AI_TAB_FORCE_CLIPPY = 59, - UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60 + UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60, + AI_SUBSCRIPTION_ENABLED = 61 } } diff --git a/WAProto/index.js b/WAProto/index.js index 3814a1b0..4daf4116 100644 --- a/WAProto/index.js +++ b/WAProto/index.js @@ -6897,6 +6897,10 @@ export const proto = $root.proto = (() => { case 60: m.capabilities[i] = 60; break; + case "AI_SUBSCRIPTION_ENABLED": + case 61: + m.capabilities[i] = 61; + break; } } } @@ -6993,6 +6997,7 @@ export const proto = $root.proto = (() => { values[valuesById[58] = "JSON_PATCH_STREAMING"] = 58; values[valuesById[59] = "AI_TAB_FORCE_CLIPPY"] = 59; values[valuesById[60] = "UNIFIED_RESPONSE_EMBEDDED_SCREENS"] = 60; + values[valuesById[61] = "AI_SUBSCRIPTION_ENABLED"] = 61; return values; })(); diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index ff7af088..1ced1a75 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1035406580]} +{"version": [2, 3000, 1035484955]} From 71fe69fd21d469e57c87e01efbdfed1f3c86b3b6 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Thu, 19 Mar 2026 06:33:42 -0300 Subject: [PATCH 32/32] chore: update WhatsApp Web version to v2.3000.1035504971 (#301) Co-authored-by: github-actions[bot] --- src/Defaults/baileys-version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 1ced1a75..a181c943 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version": [2, 3000, 1035484955]} +{"version":[2,3000,1035504971]}