diff --git a/WAProto/WAProto.proto b/WAProto/WAProto.proto index 84d2500b..af98dad1 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.1035484955 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 { @@ -407,6 +415,9 @@ message BotCapabilityMetadata { RICH_RESPONSE_INLINE_LINKS_ENABLED = 56; RICH_RESPONSE_UR_IMAGINE_VIDEO = 57; JSON_PATCH_STREAMING = 58; + AI_TAB_FORCE_CLIPPY = 59; + UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60; + AI_SUBSCRIPTION_ENABLED = 61; } } @@ -1074,6 +1085,7 @@ message ClientPairingProps { optional bool isSyncdPureLidSession = 2; optional bool isSyncdSnapshotRecoveryEnabled = 3; optional bool isHsThumbnailSyncEnabled = 4; + optional bytes subscriptionSyncPayload = 5; } message ClientPayload { @@ -1454,6 +1466,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; @@ -1608,6 +1621,8 @@ message Conversation { optional bool limitSharingInitiatedByMe = 53; 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; @@ -1635,6 +1650,8 @@ message DeviceCapabilities { message BusinessBroadcast { optional bool importListEnabled = 1; optional bool companionSupportEnabled = 2; + optional bool campaignSyncEnabled = 3; + optional bool insightsSyncEnabled = 4; } enum ChatLockSupportLevel { @@ -1904,6 +1921,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 { @@ -1912,13 +1931,29 @@ 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; + 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; optional bytes payload = 3; optional bytes extendedStatic = 4; + optional bytes paddingBytes = 5; } } @@ -2275,6 +2310,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 +2591,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 +3127,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 { @@ -3144,6 +3191,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; @@ -3565,6 +3617,7 @@ message Message { GROUP_MEMBER_LABEL_CHANGE = 30; AI_MEDIA_COLLECTION_MESSAGE = 31; MESSAGE_UNSCHEDULE = 32; + BOT_UNLINK_MESSAGE = 33; } } @@ -3597,6 +3650,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 +4178,8 @@ enum MutationProps { BUSINESS_BROADCAST_CAMPAIGN_ACTION = 81; 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; @@ -4592,6 +4648,7 @@ message StatusAttribution { APPLE_MUSIC = 8; SHARECHAT = 9; GOOGLE_PHOTOS = 10; + SOUNDCLOUD = 11; } } @@ -4760,6 +4817,8 @@ message SyncActionValue { optional BusinessBroadcastCampaignAction businessBroadcastCampaignAction = 81; 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; @@ -5275,6 +5334,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; @@ -5286,6 +5368,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 0613088d..8d42202c 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); } @@ -1058,7 +1090,10 @@ 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, + UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60, + AI_SUBSCRIPTION_ENABLED = 61 } } @@ -2769,6 +2804,7 @@ export namespace proto { isSyncdPureLidSession?: (boolean|null); isSyncdSnapshotRecoveryEnabled?: (boolean|null); isHsThumbnailSyncEnabled?: (boolean|null); + subscriptionSyncPayload?: (Uint8Array|null); } class ClientPairingProps implements IClientPairingProps { @@ -2777,6 +2813,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; @@ -3554,6 +3591,7 @@ export namespace proto { adType?: (proto.ContextInfo.ExternalAdReplyInfo.AdType|null); wtwaWebsiteUrl?: (string|null); adPreviewUrl?: (string|null); + containsCtwaFlowsAutoReply?: (boolean|null); } class ExternalAdReplyInfo implements IExternalAdReplyInfo { @@ -3585,6 +3623,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; @@ -3849,6 +3888,8 @@ export namespace proto { limitSharingInitiatedByMe?: (boolean|null); maibaAiThreadEnabled?: (boolean|null); isMarketingMessageThread?: (boolean|null); + isSenderNewAccount?: (boolean|null); + afterReadDuration?: (number|null); } class Conversation implements IConversation { @@ -3908,6 +3949,8 @@ export namespace proto { public limitSharingInitiatedByMe?: (boolean|null); 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; @@ -3983,12 +4026,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; @@ -4684,6 +4731,8 @@ export namespace proto { "static"?: (Uint8Array|null); payload?: (Uint8Array|null); extendedCiphertext?: (Uint8Array|null); + paddedBytes?: (Uint8Array|null); + simulateXxkemFs?: (boolean|null); } class ClientFinish implements IClientFinish { @@ -4691,6 +4740,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; @@ -4706,6 +4757,10 @@ export namespace proto { payload?: (Uint8Array|null); useExtended?: (boolean|null); extendedCiphertext?: (Uint8Array|null); + paddedBytes?: (Uint8Array|null); + sendServerHelloPaddedBytes?: (boolean|null); + simulateXxkemFs?: (boolean|null); + pqMode?: (proto.HandshakeMessage.HandshakePqMode|null); } class ClientHello implements IClientHello { @@ -4715,6 +4770,10 @@ 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 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; @@ -4724,11 +4783,24 @@ 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); payload?: (Uint8Array|null); extendedStatic?: (Uint8Array|null); + paddingBytes?: (Uint8Array|null); } class ServerHello implements IServerHello { @@ -4737,6 +4809,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; @@ -5546,6 +5619,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 +5725,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 +6529,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 +7921,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; @@ -7986,6 +8091,7 @@ export namespace proto { expiryTimestamp?: (number|Long|null); incentiveEligible?: (boolean|null); referralId?: (string|null); + inviteType?: (proto.Message.PaymentInviteMessage.InviteType|null); } class PaymentInviteMessage implements IPaymentInviteMessage { @@ -7994,6 +8100,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; @@ -8005,6 +8112,11 @@ export namespace proto { namespace PaymentInviteMessage { + enum InviteType { + DEFAULT = 0, + MAPPER = 1 + } + enum ServiceType { UNKNOWN = 0, FBPAY = 1, @@ -9170,7 +9282,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 } } @@ -9261,12 +9374,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 +10577,8 @@ export namespace proto { BUSINESS_BROADCAST_CAMPAIGN_ACTION = 81, 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 @@ -11745,7 +11862,8 @@ export namespace proto { THREADS = 7, APPLE_MUSIC = 8, SHARECHAT = 9, - GOOGLE_PHOTOS = 10 + GOOGLE_PHOTOS = 10, + SOUNDCLOUD = 11 } } @@ -12056,6 +12174,8 @@ export namespace proto { businessBroadcastCampaignAction?: (proto.SyncActionValue.IBusinessBroadcastCampaignAction|null); businessBroadcastInsightsAction?: (proto.SyncActionValue.IBusinessBroadcastInsightsAction|null); customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null); + subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null); + threadPinAction?: (proto.SyncActionValue.IThreadPinAction|null); } class SyncActionValue implements ISyncActionValue { @@ -12134,6 +12254,8 @@ 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 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; @@ -13666,6 +13788,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); @@ -13704,6 +13899,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 8d6e2b07..4daf4116 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) { @@ -6711,6 +6889,18 @@ export const proto = $root.proto = (() => { case 58: m.capabilities[i] = 58; break; + case "AI_TAB_FORCE_CLIPPY": + case 59: + m.capabilities[i] = 59; + break; + case "UNIFIED_RESPONSE_EMBEDDED_SCREENS": + case 60: + m.capabilities[i] = 60; + break; + case "AI_SUBSCRIPTION_ENABLED": + case 61: + m.capabilities[i] = 61; + break; } } } @@ -6805,6 +6995,9 @@ 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; + values[valuesById[60] = "UNIFIED_RESPONSE_EMBEDDED_SCREENS"] = 60; + values[valuesById[61] = "AI_SUBSCRIPTION_ENABLED"] = 61; return values; })(); @@ -17850,6 +18043,7 @@ export const proto = $root.proto = (() => { ClientPairingProps.prototype.isSyncdPureLidSession = null; ClientPairingProps.prototype.isSyncdSnapshotRecoveryEnabled = null; ClientPairingProps.prototype.isHsThumbnailSyncEnabled = null; + ClientPairingProps.prototype.subscriptionSyncPayload = null; let $oneOfFields; @@ -17877,6 +18071,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); }; @@ -17892,6 +18092,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; }; @@ -17920,6 +18122,10 @@ export const proto = $root.proto = (() => { m.isHsThumbnailSyncEnabled = r.bool(); break; } + case 5: { + m.subscriptionSyncPayload = r.bytes(); + break; + } default: r.skipType(t & 7); break; @@ -17944,6 +18150,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; }; @@ -17971,6 +18183,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; }; @@ -23788,6 +24005,7 @@ export const proto = $root.proto = (() => { ExternalAdReplyInfo.prototype.adType = null; ExternalAdReplyInfo.prototype.wtwaWebsiteUrl = null; ExternalAdReplyInfo.prototype.adPreviewUrl = null; + ExternalAdReplyInfo.prototype.containsCtwaFlowsAutoReply = null; let $oneOfFields; @@ -23953,6 +24171,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); }; @@ -24014,6 +24238,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; }; @@ -24134,6 +24360,10 @@ export const proto = $root.proto = (() => { m.adPreviewUrl = r.string(); break; } + case 28: { + m.containsCtwaFlowsAutoReply = r.bool(); + break; + } default: r.skipType(t & 7); break; @@ -24260,6 +24490,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; }; @@ -24402,6 +24635,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; }; @@ -25427,6 +25665,8 @@ export const proto = $root.proto = (() => { Conversation.prototype.limitSharingInitiatedByMe = null; Conversation.prototype.maibaAiThreadEnabled = null; Conversation.prototype.isMarketingMessageThread = null; + Conversation.prototype.isSenderNewAccount = null; + Conversation.prototype.afterReadDuration = null; let $oneOfFields; @@ -25748,6 +25988,18 @@ 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) + }); + + // 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); }; @@ -25869,6 +26121,10 @@ 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); + if (m.afterReadDuration != null && Object.hasOwnProperty.call(m, "afterReadDuration")) + w.uint32(456).uint32(m.afterReadDuration); return w; }; @@ -26105,6 +26361,14 @@ export const proto = $root.proto = (() => { m.isMarketingMessageThread = r.bool(); break; } + case 56: { + m.isSenderNewAccount = r.bool(); + break; + } + case 57: { + m.afterReadDuration = r.uint32(); + break; + } default: r.skipType(t & 7); break; @@ -26442,6 +26706,12 @@ export const proto = $root.proto = (() => { if (d.isMarketingMessageThread != null) { m.isMarketingMessageThread = Boolean(d.isMarketingMessageThread); } + if (d.isSenderNewAccount != null) { + m.isSenderNewAccount = Boolean(d.isSenderNewAccount); + } + if (d.afterReadDuration != null) { + m.afterReadDuration = d.afterReadDuration >>> 0; + } return m; }; @@ -26754,6 +27024,16 @@ 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"; + } + if (m.afterReadDuration != null && m.hasOwnProperty("afterReadDuration")) { + d.afterReadDuration = m.afterReadDuration; + if (o.oneofs) + d._afterReadDuration = "afterReadDuration"; + } return d; }; @@ -27136,6 +27416,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 +27433,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 +27456,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 +27480,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 +27506,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 +27529,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; }; @@ -32079,6 +32401,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; @@ -32100,6 +32424,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); }; @@ -32113,6 +32449,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; }; @@ -32137,6 +32477,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; @@ -32167,6 +32515,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; }; @@ -32189,6 +32546,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; }; @@ -32220,6 +32587,10 @@ 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; + ClientHello.prototype.pqMode = null; let $oneOfFields; @@ -32253,6 +32624,30 @@ 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) + }); + + // 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); }; @@ -32270,6 +32665,14 @@ 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); + if (m.pqMode != null && Object.hasOwnProperty.call(m, "pqMode")) + w.uint32(72).int32(m.pqMode); return w; }; @@ -32302,6 +32705,22 @@ 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; + } + case 9: { + m.pqMode = r.int32(); + break; + } default: r.skipType(t & 7); break; @@ -32341,6 +32760,62 @@ 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); + } + 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; }; @@ -32373,6 +32848,26 @@ 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"; + } + 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; }; @@ -32390,6 +32885,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) { @@ -32403,6 +32912,7 @@ export const proto = $root.proto = (() => { ServerHello.prototype["static"] = null; ServerHello.prototype.payload = null; ServerHello.prototype.extendedStatic = null; + ServerHello.prototype.paddingBytes = null; let $oneOfFields; @@ -32430,6 +32940,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); }; @@ -32445,6 +32961,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; }; @@ -32473,6 +32991,10 @@ export const proto = $root.proto = (() => { m.extendedStatic = r.bytes(); break; } + case 5: { + m.paddingBytes = r.bytes(); + break; + } default: r.skipType(t & 7); break; @@ -32509,6 +33031,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; }; @@ -32536,6 +33064,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; }; @@ -37369,6 +37902,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 +38512,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 +38727,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 +39145,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 +39664,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 +40181,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 +45661,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 +55577,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 +55596,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 +55613,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 +55642,7 @@ export const proto = $root.proto = (() => { break; } case 2: { - m.oldestMessageTimestamp = r.int64(); + m.oldestMessageTimestampInWindow = r.int64(); break; } case 3: { @@ -54861,6 +55655,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 +55679,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 +55707,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 +55734,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 +55756,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; }; @@ -56024,6 +56840,7 @@ export const proto = $root.proto = (() => { PaymentInviteMessage.prototype.expiryTimestamp = null; PaymentInviteMessage.prototype.incentiveEligible = null; PaymentInviteMessage.prototype.referralId = null; + PaymentInviteMessage.prototype.inviteType = null; let $oneOfFields; @@ -56051,6 +56868,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); }; @@ -56066,6 +56889,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; }; @@ -56094,6 +56919,10 @@ export const proto = $root.proto = (() => { m.referralId = r.string(); break; } + case 5: { + m.inviteType = r.int32(); + break; + } default: r.skipType(t & 7); break; @@ -56146,6 +56975,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; }; @@ -56176,6 +57021,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; }; @@ -56190,6 +57040,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; @@ -63962,6 +64819,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; @@ -64275,6 +65136,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; })(); @@ -64896,6 +65758,7 @@ export const proto = $root.proto = (() => { RequestWelcomeMessageMetadata.prototype.localChatState = null; RequestWelcomeMessageMetadata.prototype.welcomeTrigger = null; + RequestWelcomeMessageMetadata.prototype.botAgentMetadata = null; let $oneOfFields; @@ -64911,6 +65774,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 +65791,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 +65813,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 +65861,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 +65883,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 +74245,8 @@ 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[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; @@ -81975,6 +82862,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; @@ -82036,6 +82927,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; })(); @@ -83608,6 +84500,8 @@ export const proto = $root.proto = (() => { SyncActionValue.prototype.businessBroadcastCampaignAction = null; SyncActionValue.prototype.businessBroadcastInsightsAction = null; SyncActionValue.prototype.customerDataAction = null; + SyncActionValue.prototype.subscriptionsSyncV2Action = null; + SyncActionValue.prototype.threadPinAction = null; let $oneOfFields; @@ -84055,6 +84949,18 @@ 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) + }); + + // 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); }; @@ -84210,6 +85116,10 @@ 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(); + if (m.threadPinAction != null && Object.hasOwnProperty.call(m, "threadPinAction")) + $root.proto.SyncActionValue.ThreadPinAction.encode(m.threadPinAction, w.uint32(682).fork()).ldelim(); return w; }; @@ -84518,6 +85428,14 @@ 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; + } + case 85: { + m.threadPinAction = $root.proto.SyncActionValue.ThreadPinAction.decode(r, r.uint32()); + break; + } default: r.skipType(t & 7); break; @@ -84905,6 +85823,16 @@ 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); + } + 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; }; @@ -85285,6 +86213,16 @@ 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"; + } + 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; }; @@ -94698,6 +95636,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) { @@ -94979,6 +96468,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/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/src/Defaults/baileys-version.json b/src/Defaults/baileys-version.json index 24477c3f..a181c943 100644 --- a/src/Defaults/baileys-version.json +++ b/src/Defaults/baileys-version.json @@ -1 +1 @@ -{"version":[2,3000,1034641024]} +{"version":[2,3000,1035504971]} diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 542587dc..cb2ec727 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -1,8 +1,13 @@ +import { randomBytes } from 'crypto' import NodeCache from '@cacheable/node-cache' 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, @@ -28,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, @@ -43,8 +57,11 @@ import { type FullJid, getBinaryNodeChild, getBinaryNodeChildren, + isAnyLidUser, + isAnyPnUser, isHostedLidUser, isHostedPnUser, + isJidBot, isJidGroup, isLidUser, isPnUser, @@ -66,7 +83,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, - maxMsgRetryCount + maxMsgRetryCount, + enableInteractiveMessages } = config const sock = makeNewsletterSocket(config) const { @@ -74,6 +92,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { authState, messageMutex, signalRepository, + sessionActivityTracker, upsertMessage, query, fetchPrivacySettings, @@ -82,6 +101,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { groupToggleEphemeral } = sock + const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping) + const userDevicesCache = config.userDevicesCache || new NodeCache({ @@ -100,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 @@ -114,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 }) => ({ @@ -159,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) { @@ -280,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) } @@ -312,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) { @@ -444,11 +469,9 @@ export const makeMessagesSocket = (config: SocketConfig) => { 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) ] @@ -485,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 => { @@ -519,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, @@ -535,71 +666,233 @@ 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 } } + // 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 => { + // 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 hasNativeFlowButtons = message.interactiveMessage.carouselMessage.cards.some( + (card: proto.Message.IInteractiveMessage) => card?.nativeFlowMessage?.buttons?.length + ) + 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' + } 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 hasNativeFlowButtons = innerMessage.interactiveMessage.carouselMessage.cards.some( + (card: any) => card?.nativeFlowMessage?.buttons?.length + ) + 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' + } + } + + return undefined + } + + /** + * 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 + + if (interactiveMsg?.carouselMessage?.cards?.length) { + return true + } + + return false + } + + /** + * 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 + } + + /** + * 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 + } + const relayMessage = async ( jid: string, message: proto.IMessage, @@ -613,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' @@ -631,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[] = [] @@ -652,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, @@ -878,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) @@ -886,31 +1240,45 @@ 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) + // 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, @@ -921,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({ @@ -929,7 +1297,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { attrs: { v: '2', type, - count: participant!.count.toString() + count: participant.count.toString() }, content: encryptedContent }) @@ -962,6 +1330,183 @@ export const makeMessagesSocket = (config: SocketConfig) => { content: binaryNodeContent } + // 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 (when applicable) + // CDP capture confirms Pastorini INCLUDES biz node for carousel (native_flow v=9, name=mixed) + const deferredNodes: BinaryNode[] = [] + + // 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( + (card: proto.Message.IInteractiveMessage) => card?.nativeFlowMessage?.buttons || [] + ) + } + + const isListDetected = isListNativeFlow(message) + + logger.info( + { + 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' + ) + + // 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' + } + } + ] + }) + 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', + payment_info: 'payment_info', + mpm: 'mpm', + review_order: 'order_details' + } + 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: bizContent + }) + } + + // 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 && !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: effectiveButtonType }, '[BIZ NODE] Failed to inject biz node') + metrics.interactiveMessagesFailures.inc({ type: effectiveButtonType, reason: 'injection_failed' }) + } + } 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) // 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 +1524,8 @@ export const makeMessagesSocket = (config: SocketConfig) => { stanza.attrs.to = destinationJid } - if (shouldIncludeDeviceIdentity) { + // Always include device-identity for carousel (Pastorini stanza always has it) + if (shouldIncludeDeviceIdentity || isCarousel) { ;(stanza.content as BinaryNode[]).push({ tag: 'device-identity', attrs: {}, @@ -1013,30 +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 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) @@ -1107,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: { @@ -1157,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) @@ -1202,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 && @@ -1285,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 006dbaae..782682db 100644 --- a/src/Utils/messages.ts +++ b/src/Utils/messages.ts @@ -41,9 +41,11 @@ import type { ILogger } from './logger' import { downloadContentFromMessage, encryptedStream, + extractImageThumb, generateThumbnail, getAudioDuration, getAudioWaveform, + getStream, getRawMediaUploadData, type MediaDownloadOptions } from './messages-media' @@ -633,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 => { @@ -648,6 +703,11 @@ export const generateCarouselMessage = async ( if (hasMedia && mediaOptions) { if (card.image) { const { imageMessage } = await prepareWAMessageMedia({ image: card.image }, 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( @@ -1228,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 bec77c18..b2a68fcc 100644 --- a/src/Utils/tc-token-utils.ts +++ b/src/Utils/tc-token-utils.ts @@ -154,18 +154,26 @@ 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, + // 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 + } + if (normalizedFallback !== storageJid) { + keysToStore[normalizedFallback] = tokenEntry + } + + await keys.set({ tctoken: keysToStore }) onNewJidStored?.(storageJid) } } 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