Compare commits

...

23 Commits

Author SHA1 Message Date
Renato Alcara bd8465d9b8 fix: histsync LID improvements — raw_id mapping, prekeys, keepalive j… (#304)
* fix: histsync LID improvements — raw_id mapping, prekeys, keepalive jitter, CDN DELETE

* fix: prekey pool strategy — 800 initial, top-up to 800 when below 200

- INITIAL_PREKEY_COUNT: 812 → 800 (rounded, matches WA Business ~812 from CDP capture)
- MIN_PREKEY_COUNT: 25 → 200 (replenishment trigger threshold)
- uploadPreKeysToServerIfRequired: top-up to INITIAL_PREKEY_COUNT instead of
  uploading a flat MIN_PREKEY_COUNT — restores full 800-key pool on each replenish
- handleEncryptNotification: same top-up logic (INITIAL_PREKEY_COUNT - count)
  so server notification path also restores to 800, not just adds 200

uploadPreKeys(5) in error recovery path intentionally left unchanged.
2026-03-19 17:58:30 -03:00
Renato Alcara 6c52b01ea7 fix: add pastParticipants to HistorySync with LID normalization and deduplication (#303)
Two bugs fixed compared to prior implementation:

1. history.ts — normalize userJid LID→PN
   pastParticipants[].userJid may arrive as a LID identifier (e.g. "46802258641027@lid").
   The consumer expects a phone number. After building the lidPnMap from conversations
   and phoneNumberToLidMappings, each userJid is resolved to its PN equivalent.
   If the mapping is not available the original value is preserved (no data loss).

2. event-buffer.ts — deduplicate by groupJid + userJid across chunks
   Multiple HistorySync chunks can contain the same group. The previous approach
   concatenated blindly, producing duplicate entries. Now a keyed map
   { [groupJid]: IPastParticipant[] } is used so each group appears once and
   each participant within a group appears at most once.

Types updated (Events.ts):
   - messaging-history.set event includes pastParticipants?: IPastParticipants[]
   - BufferedEventData.historySets.pastParticipants typed as keyed map
2026-03-19 16:44:14 -03:00
Renato Alcara a9e926b907 fix: correct w:mex QueryIds and response fields for newsletter operations (#302)
Reverse-engineered from WA Web JS bundle and live CDP interception.
All QueryIds and XWAPaths were wrong; mute/unmute also had wrong variables structure.

- FOLLOW QueryId: 7871414976211147 → 24404358912487870
- UNFOLLOW QueryId: 7238632346214362 → 9767147403369991
- MUTE QueryId: 29766401636284406 → 31938993655691868
- UNMUTE QueryId: 9864994326891137 → 31938993655691868 (same mutation as MUTE)
- xwa2_newsletter_follow: 'xwa2_newsletter_follow' → 'xwa2_newsletter_join_v2'
- xwa2_newsletter_unfollow: 'xwa2_newsletter_unfollow' → 'xwa2_newsletter_leave_v2'
- xwa2_newsletter_mute_v2: 'xwa2_newsletter_mute_v2' → 'xwa2_newsletter_update_user_setting'
- xwa2_newsletter_unmute_v2: 'xwa2_newsletter_unmute_v2' → 'xwa2_newsletter_update_user_setting'
- newsletterMute/Unmute variables: flat {newsletter_id} → {input: {newsletter_id, type, value}}

Fixes #2346
2026-03-19 11:49:42 -03:00
Renato Alcara 71fe69fd21 chore: update WhatsApp Web version to v2.3000.1035504971 (#301)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-19 06:33:42 -03:00
github-actions[bot] eecf6df2a2 chore: update proto/version to v2.3000.1035484955 (#300)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-19 01:48:35 -03:00
Renato Alcara d04a3e1af8 fix: restore working carousel send/render path (#297)
fix: restore working carousel send/render path (#297)
2026-03-18 20:30:18 -03:00
Renato Alcara 21462c26d5 chore: update WhatsApp Web version to v2.3000.1035406580 (#299)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-18 06:42:44 -03:00
github-actions[bot] fc2122b11a chore: update proto/version to v2.3000.1035370989 (#298)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-18 01:49:58 -03:00
Renato Alcara 11eed3aedc chore: update WhatsApp Web version to v2.3000.1035317910 (#296)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-17 06:16:35 -03:00
github-actions[bot] dbc6dd6aed chore: update proto/version to v2.3000.1035302375 (#295)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-17 00:46:29 -03:00
Renato Alcara e152aee740 Fix/pn lid session reuse (#294)
* fix: unify PN and LID session reuse for 1:1 sends

* fix: limit PN/LID reuse changes to session cache
2026-03-16 23:22:05 -03:00
Renato Alcara ff9b84c561 fix: unify PN and LID session reuse for 1:1 sends (#293)
fix: unify PN and LID session reuse for 1:1 sends (#293)
2026-03-16 22:59:28 -03:00
Renato Alcara 50c13398c4 fix: harden carousel live rendering path (#292)
* fix: harden carousel live rendering path

* fix: flatten carousel thumbnail fallback
2026-03-16 22:09:14 -03:00
Renato Alcara d991d0212d fix: add biz node injection and quality_control for carousel WhatsApp Web rendering (#291)
- Add interactive message detection helpers (getButtonType, isCarouselMessage, etc.)
- Inject biz node with interactive/native_flow for non-carousel interactive messages
- Inject biz + quality_control with decision_id for carousel messages
- Skip bot node for native_flow/carousel/catalog (breaks Web rendering)
- Force device-identity inclusion for carousel messages
- Append deferred biz/bot/quality_control nodes after device-identity and tctoken
- Store tctoken under both LID and PN for reliable lookup
2026-03-16 21:25:29 -03:00
Renato Alcara 7c095c84a5 chore: update WhatsApp Web version to v2.3000.1035223526 (#289)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-16 06:20:34 -03:00
github-actions[bot] a25e6740fd chore: update proto/version to v2.3000.1035216863 (#288)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-16 00:58:19 -03:00
Renato Alcara 986ac588a9 chore: update WhatsApp Web version to v2.3000.1035203283 (#287)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-15 06:08:25 -03:00
github-actions[bot] 64c73faab2 chore: update proto/version to v2.3000.1035198198 (#286)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-15 00:56:35 -03:00
Renato Alcara b5464bec73 chore: update WhatsApp Web version to v2.3000.1035176028 (#285)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-14 06:07:31 -03:00
github-actions[bot] 9f8f74fc7a chore: update proto/version to v2.3000.1035169204 (#284)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-14 00:44:33 -03:00
Renato Alcara aaf3bd83eb chore: update WhatsApp Web version to v2.3000.1035102652 (#283)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-13 06:11:31 -03:00
github-actions[bot] 8fe71b03d5 chore: update proto/version to v2.3000.1035083696 (#282)
Co-authored-by: rsalcara <rsalcara@users.noreply.github.com>
2026-03-13 00:45:20 -03:00
Renato Alcara c2c156d707 chore(deps): update music-metadata from 11.12.0 to 11.12.3 (#281)
Includes security fix for CWE-85 (infinite loop in ASF parsing),
Ogg/Vorbis duration fix, and restored TypeScript declaration files.
2026-03-12 23:52:05 -03:00
19 changed files with 2051 additions and 203 deletions
+39 -1
View File
@@ -1,7 +1,7 @@
syntax = "proto3";
package proto;
/// WhatsApp Version: 2.3000.1034989030
/// WhatsApp Version: 2.3000.1035484955
message ADVDeviceIdentity {
optional uint32 rawId = 1;
@@ -415,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;
}
}
@@ -1082,6 +1085,7 @@ message ClientPairingProps {
optional bool isSyncdPureLidSession = 2;
optional bool isSyncdSnapshotRecoveryEnabled = 3;
optional bool isHsThumbnailSyncEnabled = 4;
optional bytes subscriptionSyncPayload = 5;
}
message ClientPayload {
@@ -1462,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;
@@ -1616,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;
@@ -1914,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 {
@@ -1922,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;
}
}
@@ -3166,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;
@@ -3587,6 +3617,7 @@ message Message {
GROUP_MEMBER_LABEL_CHANGE = 30;
AI_MEDIA_COLLECTION_MESSAGE = 31;
MESSAGE_UNSCHEDULE = 32;
BOT_UNLINK_MESSAGE = 33;
}
}
@@ -4148,6 +4179,7 @@ enum MutationProps {
BUSINESS_BROADCAST_INSIGHTS_ACTION = 82;
CUSTOMER_DATA_ACTION = 83;
SUBSCRIPTIONS_SYNC_V2_ACTION = 84;
THREAD_PIN_ACTION = 85;
SHARE_OWN_PN = 10001;
BUSINESS_BROADCAST_ACTION = 10002;
AI_THREAD_DELETE_ACTION = 10003;
@@ -4616,6 +4648,7 @@ message StatusAttribution {
APPLE_MUSIC = 8;
SHARECHAT = 9;
GOOGLE_PHOTOS = 10;
SOUNDCLOUD = 11;
}
}
@@ -4785,6 +4818,7 @@ message SyncActionValue {
optional BusinessBroadcastInsightsAction businessBroadcastInsightsAction = 82;
optional CustomerDataAction customerDataAction = 83;
optional SubscriptionsSyncV2Action subscriptionsSyncV2Action = 84;
optional ThreadPinAction threadPinAction = 85;
message AgentAction {
optional string name = 1;
optional int32 deviceID = 2;
@@ -5334,6 +5368,10 @@ message SyncActionValue {
repeated SyncActionValue.SyncActionMessage messages = 3;
}
message ThreadPinAction {
optional bool pinned = 1;
}
message TimeFormatAction {
optional bool isTwentyFourHourFormatEnabled = 1;
}
+68 -3
View File
@@ -1090,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
}
}
@@ -2801,6 +2804,7 @@ export namespace proto {
isSyncdPureLidSession?: (boolean|null);
isSyncdSnapshotRecoveryEnabled?: (boolean|null);
isHsThumbnailSyncEnabled?: (boolean|null);
subscriptionSyncPayload?: (Uint8Array|null);
}
class ClientPairingProps implements IClientPairingProps {
@@ -2809,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;
@@ -3586,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 {
@@ -3617,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;
@@ -3881,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 {
@@ -3940,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;
@@ -4720,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 {
@@ -4727,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;
@@ -4742,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 {
@@ -4751,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;
@@ -4760,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 {
@@ -4773,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;
@@ -8054,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 {
@@ -8062,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;
@@ -8073,6 +8112,11 @@ export namespace proto {
namespace PaymentInviteMessage {
enum InviteType {
DEFAULT = 0,
MAPPER = 1
}
enum ServiceType {
UNKNOWN = 0,
FBPAY = 1,
@@ -9238,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
}
}
@@ -10533,6 +10578,7 @@ export namespace proto {
BUSINESS_BROADCAST_INSIGHTS_ACTION = 82,
CUSTOMER_DATA_ACTION = 83,
SUBSCRIPTIONS_SYNC_V2_ACTION = 84,
THREAD_PIN_ACTION = 85,
SHARE_OWN_PN = 10001,
BUSINESS_BROADCAST_ACTION = 10002,
AI_THREAD_DELETE_ACTION = 10003
@@ -11816,7 +11862,8 @@ export namespace proto {
THREADS = 7,
APPLE_MUSIC = 8,
SHARECHAT = 9,
GOOGLE_PHOTOS = 10
GOOGLE_PHOTOS = 10,
SOUNDCLOUD = 11
}
}
@@ -12128,6 +12175,7 @@ export namespace proto {
businessBroadcastInsightsAction?: (proto.SyncActionValue.IBusinessBroadcastInsightsAction|null);
customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null);
subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null);
threadPinAction?: (proto.SyncActionValue.IThreadPinAction|null);
}
class SyncActionValue implements ISyncActionValue {
@@ -12207,6 +12255,7 @@ export namespace proto {
public businessBroadcastInsightsAction?: (proto.SyncActionValue.IBusinessBroadcastInsightsAction|null);
public customerDataAction?: (proto.SyncActionValue.ICustomerDataAction|null);
public subscriptionsSyncV2Action?: (proto.SyncActionValue.ISubscriptionsSyncV2Action|null);
public threadPinAction?: (proto.SyncActionValue.IThreadPinAction|null);
public static create(properties?: proto.ISyncActionValue): proto.SyncActionValue;
public static encode(m: proto.ISyncActionValue, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue;
@@ -13850,6 +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);
}
+476
View File
@@ -6889,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;
}
}
}
@@ -6983,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;
})();
@@ -18028,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;
@@ -18055,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);
};
@@ -18070,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;
};
@@ -18098,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;
@@ -18122,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;
};
@@ -18149,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;
};
@@ -23966,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;
@@ -24131,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);
};
@@ -24192,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;
};
@@ -24312,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;
@@ -24438,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;
};
@@ -24580,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;
};
@@ -25605,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;
@@ -25926,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);
};
@@ -26047,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;
};
@@ -26283,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;
@@ -26620,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;
};
@@ -26932,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;
};
@@ -32299,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;
@@ -32320,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);
};
@@ -32333,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;
};
@@ -32357,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;
@@ -32387,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;
};
@@ -32409,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;
};
@@ -32440,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;
@@ -32473,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);
};
@@ -32490,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;
};
@@ -32522,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;
@@ -32561,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;
};
@@ -32593,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;
};
@@ -32610,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) {
@@ -32623,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;
@@ -32650,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);
};
@@ -32665,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;
};
@@ -32693,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;
@@ -32729,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;
};
@@ -32756,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;
};
@@ -56527,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;
@@ -56554,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);
};
@@ -56569,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;
};
@@ -56597,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;
@@ -56649,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;
};
@@ -56679,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;
};
@@ -56693,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;
@@ -64465,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;
@@ -64778,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;
})();
@@ -73887,6 +74246,7 @@ export const proto = $root.proto = (() => {
values[valuesById[82] = "BUSINESS_BROADCAST_INSIGHTS_ACTION"] = 82;
values[valuesById[83] = "CUSTOMER_DATA_ACTION"] = 83;
values[valuesById[84] = "SUBSCRIPTIONS_SYNC_V2_ACTION"] = 84;
values[valuesById[85] = "THREAD_PIN_ACTION"] = 85;
values[valuesById[10001] = "SHARE_OWN_PN"] = 10001;
values[valuesById[10002] = "BUSINESS_BROADCAST_ACTION"] = 10002;
values[valuesById[10003] = "AI_THREAD_DELETE_ACTION"] = 10003;
@@ -82502,6 +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;
@@ -82563,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;
})();
@@ -84136,6 +84501,7 @@ export const proto = $root.proto = (() => {
SyncActionValue.prototype.businessBroadcastInsightsAction = null;
SyncActionValue.prototype.customerDataAction = null;
SyncActionValue.prototype.subscriptionsSyncV2Action = null;
SyncActionValue.prototype.threadPinAction = null;
let $oneOfFields;
@@ -84589,6 +84955,12 @@ export const proto = $root.proto = (() => {
set: $util.oneOfSetter($oneOfFields)
});
// Virtual OneOf for proto3 optional field
Object.defineProperty(SyncActionValue.prototype, "_threadPinAction", {
get: $util.oneOfGetter($oneOfFields = ["threadPinAction"]),
set: $util.oneOfSetter($oneOfFields)
});
SyncActionValue.create = function create(properties) {
return new SyncActionValue(properties);
};
@@ -84746,6 +85118,8 @@ export const proto = $root.proto = (() => {
$root.proto.SyncActionValue.CustomerDataAction.encode(m.customerDataAction, w.uint32(666).fork()).ldelim();
if (m.subscriptionsSyncV2Action != null && Object.hasOwnProperty.call(m, "subscriptionsSyncV2Action"))
$root.proto.SyncActionValue.SubscriptionsSyncV2Action.encode(m.subscriptionsSyncV2Action, w.uint32(674).fork()).ldelim();
if (m.threadPinAction != null && Object.hasOwnProperty.call(m, "threadPinAction"))
$root.proto.SyncActionValue.ThreadPinAction.encode(m.threadPinAction, w.uint32(682).fork()).ldelim();
return w;
};
@@ -85058,6 +85432,10 @@ export const proto = $root.proto = (() => {
m.subscriptionsSyncV2Action = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.decode(r, r.uint32());
break;
}
case 85: {
m.threadPinAction = $root.proto.SyncActionValue.ThreadPinAction.decode(r, r.uint32());
break;
}
default:
r.skipType(t & 7);
break;
@@ -85450,6 +85828,11 @@ export const proto = $root.proto = (() => {
throw TypeError(".proto.SyncActionValue.subscriptionsSyncV2Action: object expected");
m.subscriptionsSyncV2Action = $root.proto.SyncActionValue.SubscriptionsSyncV2Action.fromObject(d.subscriptionsSyncV2Action);
}
if (d.threadPinAction != null) {
if (typeof d.threadPinAction !== "object")
throw TypeError(".proto.SyncActionValue.threadPinAction: object expected");
m.threadPinAction = $root.proto.SyncActionValue.ThreadPinAction.fromObject(d.threadPinAction);
}
return m;
};
@@ -85835,6 +86218,11 @@ export const proto = $root.proto = (() => {
if (o.oneofs)
d._subscriptionsSyncV2Action = "subscriptionsSyncV2Action";
}
if (m.threadPinAction != null && m.hasOwnProperty("threadPinAction")) {
d.threadPinAction = $root.proto.SyncActionValue.ThreadPinAction.toObject(m.threadPinAction, o);
if (o.oneofs)
d._threadPinAction = "threadPinAction";
}
return d;
};
@@ -96080,6 +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) {
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -1 +1 @@
{"version":[2,3000,1035018135]}
{"version":[2,3000,1035504971]}
+5 -3
View File
@@ -188,10 +188,12 @@ export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP) as MediaType[]
/** 120s timeout for history sync stall detection, same as WA Web's handleChunkProgress / restartPausedTimer (g = 120) */
export const HISTORY_SYNC_PAUSED_TIMEOUT_MS = 120_000
export const MIN_PREKEY_COUNT = 5
// Replenishment threshold: when server count drops below this, top-up back to INITIAL_PREKEY_COUNT
export const MIN_PREKEY_COUNT = 200
// Moderate prekey count (upstream uses 812, reduced to balance rate limiting and availability)
export const INITIAL_PREKEY_COUNT = 200
// Initial pool size matching WA Business (CDP IDB capture: prekey-store = 812 on registration)
// Rounded to 800 for cleanliness; replenishment always tops up to this value
export const INITIAL_PREKEY_COUNT = 800
export const UPLOAD_TIMEOUT = 30000 // 30 seconds
// Moderate upload interval to balance rate limiting and responsiveness (was 5000)
+3 -1
View File
@@ -7,6 +7,7 @@ import { proto } from '../../WAProto/index.js'
import {
DEFAULT_CACHE_TTLS,
DEFAULT_SESSION_CLEANUP_CONFIG,
INITIAL_PREKEY_COUNT,
KEY_BUNDLE_TYPE,
MIN_PREKEY_COUNT,
PLACEHOLDER_MAX_AGE_SECONDS,
@@ -1440,7 +1441,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count')
if (shouldUploadMorePreKeys) {
await uploadPreKeys()
// Top-up back to INITIAL_PREKEY_COUNT so the pool is always restored to full size
await uploadPreKeys(Math.max(1, INITIAL_PREKEY_COUNT - count))
}
} else {
const result = await handleIdentityChange(node, {
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -123,11 +123,11 @@ export const makeNewsletterSocket = (config: SocketConfig) => {
},
newsletterMute: (jid: string) => {
return executeWMexQuery({ newsletter_id: jid }, QueryIds.MUTE, XWAPaths.xwa2_newsletter_mute_v2)
return executeWMexQuery({ input: { newsletter_id: jid, type: 'MUTE_ADMIN_ACTIVITY', value: 'OFF' } }, QueryIds.MUTE, XWAPaths.xwa2_newsletter_mute_v2)
},
newsletterUnmute: (jid: string) => {
return executeWMexQuery({ newsletter_id: jid }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2)
return executeWMexQuery({ input: { newsletter_id: jid, type: 'MUTE_ADMIN_ACTIVITY', value: 'ON' } }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2)
},
newsletterUpdateName: async (jid: string, name: string) => {
+19 -5
View File
@@ -785,7 +785,7 @@ export const makeSocket = (config: SocketConfig) => {
let count = 0
const preKeyCount = await getAvailablePreKeysOnServer()
if (preKeyCount === 0) count = INITIAL_PREKEY_COUNT
else count = MIN_PREKEY_COUNT
else count = Math.max(0, INITIAL_PREKEY_COUNT - preKeyCount)
const { exists: currentPreKeyExists, currentPreKeyId } = await verifyCurrentPreKeyExists()
logger.info(`${preKeyCount} pre-keys found on server`)
@@ -1151,7 +1151,7 @@ export const makeSocket = (config: SocketConfig) => {
// Decrement active connections
decrementActiveConnections()
clearInterval(keepAliveReq)
clearTimeout(keepAliveReq)
clearTimeout(qrTimer)
// Clear offline-buffer safety timer so its callback cannot call ev.flush()
@@ -1262,8 +1262,16 @@ export const makeSocket = (config: SocketConfig) => {
})
}
const startKeepAliveRequest = () =>
(keepAliveReq = setInterval(() => {
const startKeepAliveRequest = () => {
// Use recursive setTimeout with ±15% jitter to match WA Desktop behaviour
// (WA Business Desktop: ~25-30s intervals with natural variance)
const scheduleNextKeepAlive = () => {
const jitter = keepAliveIntervalMs * 0.15
const delay = keepAliveIntervalMs + Math.floor((Math.random() * 2 - 1) * jitter)
keepAliveReq = setTimeout(onKeepAliveTick, delay)
}
const onKeepAliveTick = () => {
if (!lastDateRecv) {
lastDateRecv = new Date()
}
@@ -1275,6 +1283,7 @@ export const makeSocket = (config: SocketConfig) => {
*/
if (diff > keepAliveIntervalMs + 5000) {
void end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
return // connection closing — do not reschedule
} else if (ws.isOpen) {
// Send keep-alive ping via sendNode() (fire-and-forget) instead of query().
// query() wraps the ping in the query circuit breaker — when that breaker is
@@ -1297,7 +1306,12 @@ export const makeSocket = (config: SocketConfig) => {
} else {
logger.warn('keep alive called when WS not open')
}
}, keepAliveIntervalMs))
scheduleNextKeepAlive()
}
scheduleNextKeepAlive()
}
/** i have no idea why this exists. pls enlighten me */
const sendPassiveIq = (tag: 'passive' | 'active') =>
query({
+4
View File
@@ -27,6 +27,8 @@ export type BaileysEventMap = {
chats: Chat[]
contacts: Contact[]
messages: WAMessage[]
/** Past participants for group chats (people who left/were removed). userJid is always a phone number (PN), never a LID. */
pastParticipants?: proto.IPastParticipants[] | null
isLatest?: boolean
progress?: number | null
syncType?: proto.HistorySync.HistorySyncType | null
@@ -185,6 +187,8 @@ export type BufferedEventData = {
chats: { [jid: string]: Chat }
contacts: { [jid: string]: Contact }
messages: { [uqId: string]: WAMessage }
/** Keyed by groupJid for O(1) deduplication across chunks */
pastParticipants: { [groupJid: string]: proto.IPastParticipant[] }
empty: boolean
isLatest: boolean
progress?: number | null
+8 -8
View File
@@ -4,10 +4,10 @@ export enum XWAPaths {
xwa2_newsletter_view = 'xwa2_newsletter_view',
xwa2_newsletter_metadata = 'xwa2_newsletter',
xwa2_newsletter_admin_count = 'xwa2_newsletter_admin',
xwa2_newsletter_mute_v2 = 'xwa2_newsletter_mute_v2',
xwa2_newsletter_unmute_v2 = 'xwa2_newsletter_unmute_v2',
xwa2_newsletter_follow = 'xwa2_newsletter_follow',
xwa2_newsletter_unfollow = 'xwa2_newsletter_unfollow',
xwa2_newsletter_mute_v2 = 'xwa2_newsletter_update_user_setting',
xwa2_newsletter_unmute_v2 = 'xwa2_newsletter_update_user_setting',
xwa2_newsletter_follow = 'xwa2_newsletter_join_v2',
xwa2_newsletter_unfollow = 'xwa2_newsletter_leave_v2',
xwa2_newsletter_change_owner = 'xwa2_newsletter_change_owner',
xwa2_newsletter_demote = 'xwa2_newsletter_demote',
xwa2_newsletter_delete_v2 = 'xwa2_newsletter_delete_v2'
@@ -17,10 +17,10 @@ export enum QueryIds {
UPDATE_METADATA = '24250201037901610',
METADATA = '6563316087068696',
SUBSCRIBERS = '9783111038412085',
FOLLOW = '7871414976211147',
UNFOLLOW = '7238632346214362',
MUTE = '29766401636284406',
UNMUTE = '9864994326891137',
FOLLOW = '24404358912487870',
UNFOLLOW = '9767147403369991',
MUTE = '31938993655691868',
UNMUTE = '31938993655691868',
ADMIN_COUNT = '7130823597031706',
CHANGE_OWNER = '7341777602580933',
DEMOTE = '6551828931592903',
+28
View File
@@ -1,4 +1,5 @@
import EventEmitter from 'events'
import { proto } from '../../WAProto/index.js'
import type {
BaileysEvent,
BaileysEventEmitter,
@@ -890,6 +891,7 @@ const makeBufferData = (): BufferedEventData => {
chats: {},
messages: {},
contacts: {},
pastParticipants: {},
isLatest: false,
empty: true
},
@@ -967,6 +969,28 @@ function append<E extends BufferableEvent>(
}
}
// Merge pastParticipants with deduplication by groupJid and by userJid within each group.
// Multiple HistorySync chunks can carry the same group -- we merge rather than concatenate
// to avoid duplicate entries in the final event delivered to the consumer.
for (const group of (eventData.pastParticipants ?? []) as proto.IPastParticipants[]) {
const groupJid = group.groupJid
if (!groupJid) continue
if (!data.historySets.pastParticipants[groupJid]) {
data.historySets.pastParticipants[groupJid] = []
}
const existing = data.historySets.pastParticipants[groupJid]
const seenJids = new Set(existing.map(p => p.userJid).filter(Boolean))
for (const participant of (group.pastParticipants ?? []) as proto.IPastParticipant[]) {
if (participant.userJid && !seenJids.has(participant.userJid)) {
existing.push(participant)
seenJids.add(participant.userJid)
}
}
}
data.historySets.empty = false
data.historySets.syncType = eventData.syncType
data.historySets.progress = eventData.progress
@@ -1292,6 +1316,10 @@ function consolidateEvents(data: BufferedEventData) {
chats: Object.values(data.historySets.chats),
messages: Object.values(data.historySets.messages),
contacts: Object.values(data.historySets.contacts),
// Convert dedup map back to array. Each entry has groupJid + participants (all unique userJids).
pastParticipants: Object.entries(data.historySets.pastParticipants).map(
([groupJid, participants]) => ({ groupJid, pastParticipants: participants })
),
syncType: data.historySets.syncType,
progress: data.historySets.progress,
isLatest: data.historySets.isLatest,
+29 -2
View File
@@ -15,7 +15,7 @@ import {
import { toNumber } from './generics'
import type { ILogger } from './logger.js'
import { normalizeMessageContent } from './messages'
import { downloadContentFromMessage } from './messages-media'
import { downloadContentFromMessage, getUrlFromDirectPath } from './messages-media'
const inflatePromise = promisify(inflate)
@@ -374,10 +374,25 @@ export const processHistoryMessage = (item: proto.IHistorySync, logger?: ILogger
// Convert Map back to array for return
const lidPnMappings = Array.from(lidPnMap.values())
// Normalize pastParticipants: resolve LID userJids → PN so the consumer always
// receives a phone number, never an opaque LID identifier.
// Uses the lidPnMap built above (populated from phoneNumberToLidMappings + conversations).
// If a LID cannot be resolved, the original value is kept rather than dropping the participant.
const pastParticipants: proto.IPastParticipants[] = (item.pastParticipants ?? []).map(group => ({
groupJid: group.groupJid,
pastParticipants: (group.pastParticipants ?? []).map(participant => {
const userJid = participant.userJid
if (!userJid || !isAnyLidUser(userJid)) return participant
const mapping = lidPnMap.get(jidNormalizedUser(userJid))
return mapping?.pn ? { ...participant, userJid: mapping.pn } : participant
})
}))
return {
chats,
contacts,
messages,
pastParticipants,
lidPnMappings,
syncType: item.syncType,
progress: item.progress
@@ -408,7 +423,19 @@ export const downloadAndProcessHistorySyncNotification = async (
historyMsg = await downloadHistory(msg, options)
}
return processHistoryMessage(historyMsg, logger)
const result = processHistoryMessage(historyMsg, logger)
// Mirror WA Desktop behaviour: DELETE the CDN blob only after processing succeeds.
// Doing this earlier (e.g. inside downloadHistory) risks permanent history loss if
// processing throws — the server copy would be gone and retry after reconnect would fail.
if (msg.directPath) {
const cdnUrl = getUrlFromDirectPath(msg.directPath)
fetch(cdnUrl, { ...options, method: 'DELETE' }).catch(() => {
// non-fatal — server will expire it anyway
})
}
return result
}
/**
+1 -1
View File
@@ -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,
+71 -48
View File
@@ -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
+20 -12
View File
@@ -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<string, typeof tokenEntry | null> = {
[storageJid]: tokenEntry
}
if (normalizedFallback !== storageJid) {
keysToStore[normalizedFallback] = tokenEntry
}
await keys.set({ tctoken: keysToStore })
onNewJidStored?.(storageJid)
}
}
+15 -8
View File
@@ -10,7 +10,7 @@ import {
} from './Protocols'
import { USyncUser } from './USyncUser'
export type USyncQueryResultList = { [protocol: string]: unknown; id: string }
export type USyncQueryResultList = { [protocol: string]: unknown; id: string; rawId?: number }
export type USyncQueryResult = {
list: USyncQueryResultList[]
@@ -68,10 +68,8 @@ export class USyncQuery {
//TODO: see if there are any errors in the result node
//const resultNode = getBinaryNodeChild(usyncNode, 'result')
const listNode = usyncNode ? getBinaryNodeChild(usyncNode, 'list') : undefined
if (listNode?.content && Array.isArray(listNode.content)) {
queryResult.list = listNode.content.reduce((acc: USyncQueryResultList[], node) => {
const parseNodeList = (content: BinaryNode[]): USyncQueryResultList[] =>
content.reduce((acc: USyncQueryResultList[], node) => {
const id = node?.attrs.jid
if (id) {
const data = Array.isArray(node?.content)
@@ -89,15 +87,24 @@ export class USyncQuery {
.filter(([, b]) => b !== null) as [string, unknown][]
)
: {}
acc.push({ ...data, id })
const rawIdAttr = node?.attrs?.['raw_id']
const rawId = rawIdAttr !== undefined ? Number(rawIdAttr) : undefined
acc.push({ ...data, id, ...(rawId !== undefined && !isNaN(rawId) ? { rawId } : {}) })
}
return acc
}, [])
const listNode = usyncNode ? getBinaryNodeChild(usyncNode, 'list') : undefined
if (listNode?.content && Array.isArray(listNode.content)) {
queryResult.list = parseNodeList(listNode.content)
}
const sideListNode = usyncNode ? getBinaryNodeChild(usyncNode, 'side_list') : undefined
if (sideListNode?.content && Array.isArray(sideListNode.content)) {
queryResult.sideList = parseNodeList(sideListNode.content)
}
//TODO: implement side list
//const sideListNode = getBinaryNodeChild(usyncNode, 'side_list')
return queryResult
}
+18 -11
View File
@@ -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