Compare commits

..

20 Commits

Author SHA1 Message Date
Renato Alcara 871ad3711e chore: remove revert artifact file 2026-03-18 20:25:04 -03:00
Renato Alcara 3d8778561a fix(types): use proto.Message.IImageMessage in carousel helper 2026-03-18 19:23:10 -03:00
Renato Alcara 9d6ada6514 fix(lint): use project imageMessage type in carousel metadata helper 2026-03-18 19:20:25 -03:00
Renato Alcara 5e7c53eff7 fix(lint): flatten carousel image metadata fallback depth 2026-03-18 18:40:00 -03:00
Renato Alcara 0640de9a38 Reapply "refactor: flatten participant encryption flow for lint"
This reverts commit 7a66f8b3d6.
2026-03-17 23:01:20 -03:00
Renato Alcara 847e213443 fix: restore corrupted messages.ts from known-good carousel baseline 2026-03-17 22:57:34 -03:00
Renato Alcara 7a66f8b3d6 Revert "refactor: flatten participant encryption flow for lint"
This reverts commit a790bf616d.
2026-03-17 22:48:37 -03:00
Renato Alcara 5b42aaa642 Revert "Commit 5c6f38d by reverting its changes" 2026-03-17 22:41:30 -03:00
Renato Alcara 5c6f38d885 fix(lint): reduce max-depth in carousel image metadata recovery
- Refactor nested if blocks to single-line conditionals
- Fixes max-depth errors on lines 661, 665, 669
- No functional changes, only formatting
2026-03-17 22:38:51 -03:00
Renato Alcara a790bf616d refactor: flatten participant encryption flow for lint 2026-03-17 22:31:13 -03:00
Renato Alcara 259f9e3b68 chore: disable max-depth for messages-send 2026-03-17 21:20:05 -03:00
Renato Alcara 2ff8f6edba chore: scope max-depth lint suppression to participant fanout 2026-03-17 20:48:48 -03:00
Renato Alcara 0655d79e8b fix: move DSM helper out of participant loop 2026-03-17 20:32:42 -03:00
Renato Alcara 2363944bb3 fix: normalize meLidUser type for CI 2026-03-17 12:15:23 -03:00
Renato Alcara 7f1e9e67f7 fix: flatten DSM helper to satisfy lint 2026-03-17 12:06:15 -03:00
Renato Alcara acb8457ebc fix: flatten DSM selection in participant encryption 2026-03-17 11:33:20 -03:00
Renato Alcara 39d167168c fix: resolve lint issues in carousel send path 2026-03-17 11:27:51 -03:00
Renato Alcara 8340803377 chore: restore package files to master for CI 2026-03-17 11:14:21 -03:00
Renato Alcara aa37c8c19e Merge master into fix/carousel-send-render-clean 2026-03-17 11:10:33 -03:00
Renato Alcara d075195cda fix: restore working carousel send/render path 2026-03-17 11:03:06 -03:00
17 changed files with 98 additions and 391 deletions
+2 -15
View File
@@ -1,7 +1,7 @@
syntax = "proto3"; syntax = "proto3";
package proto; package proto;
/// WhatsApp Version: 2.3000.1035577069 /// WhatsApp Version: 2.3000.1035302375
message ADVDeviceIdentity { message ADVDeviceIdentity {
optional uint32 rawId = 1; optional uint32 rawId = 1;
@@ -417,7 +417,6 @@ message BotCapabilityMetadata {
JSON_PATCH_STREAMING = 58; JSON_PATCH_STREAMING = 58;
AI_TAB_FORCE_CLIPPY = 59; AI_TAB_FORCE_CLIPPY = 59;
UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60; UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60;
AI_SUBSCRIPTION_ENABLED = 61;
} }
} }
@@ -1934,20 +1933,8 @@ message HandshakeMessage {
optional bytes paddedBytes = 6; optional bytes paddedBytes = 6;
optional bool sendServerHelloPaddedBytes = 7; optional bool sendServerHelloPaddedBytes = 7;
optional bool simulateXxkemFs = 8; 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 { message ServerHello {
optional bytes ephemeral = 1; optional bytes ephemeral = 1;
optional bytes static = 2; optional bytes static = 2;
@@ -2149,6 +2136,7 @@ message LimitSharing {
CHAT_SETTING = 1; CHAT_SETTING = 1;
BIZ_SUPPORTS_FB_HOSTING = 2; BIZ_SUPPORTS_FB_HOSTING = 2;
UNKNOWN_GROUP = 3; UNKNOWN_GROUP = 3;
DEPRECATION = 4;
} }
} }
@@ -4892,7 +4880,6 @@ message SyncActionValue {
repeated SyncActionValue.BroadcastListParticipant participants = 2; repeated SyncActionValue.BroadcastListParticipant participants = 2;
optional string listName = 3; optional string listName = 3;
repeated string labelIds = 4; repeated string labelIds = 4;
optional string audienceExpression = 5;
} }
message CallLogAction { message CallLogAction {
+3 -19
View File
@@ -1092,8 +1092,7 @@ export namespace proto {
RICH_RESPONSE_UR_IMAGINE_VIDEO = 57, RICH_RESPONSE_UR_IMAGINE_VIDEO = 57,
JSON_PATCH_STREAMING = 58, JSON_PATCH_STREAMING = 58,
AI_TAB_FORCE_CLIPPY = 59, AI_TAB_FORCE_CLIPPY = 59,
UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60, UNIFIED_RESPONSE_EMBEDDED_SCREENS = 60
AI_SUBSCRIPTION_ENABLED = 61
} }
} }
@@ -4760,7 +4759,6 @@ export namespace proto {
paddedBytes?: (Uint8Array|null); paddedBytes?: (Uint8Array|null);
sendServerHelloPaddedBytes?: (boolean|null); sendServerHelloPaddedBytes?: (boolean|null);
simulateXxkemFs?: (boolean|null); simulateXxkemFs?: (boolean|null);
pqMode?: (proto.HandshakeMessage.HandshakePqMode|null);
} }
class ClientHello implements IClientHello { class ClientHello implements IClientHello {
@@ -4773,7 +4771,6 @@ export namespace proto {
public paddedBytes?: (Uint8Array|null); public paddedBytes?: (Uint8Array|null);
public sendServerHelloPaddedBytes?: (boolean|null); public sendServerHelloPaddedBytes?: (boolean|null);
public simulateXxkemFs?: (boolean|null); public simulateXxkemFs?: (boolean|null);
public pqMode?: (proto.HandshakeMessage.HandshakePqMode|null);
public static create(properties?: proto.HandshakeMessage.IClientHello): proto.HandshakeMessage.ClientHello; public static create(properties?: proto.HandshakeMessage.IClientHello): proto.HandshakeMessage.ClientHello;
public static encode(m: proto.HandshakeMessage.IClientHello, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.HandshakeMessage.IClientHello, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HandshakeMessage.ClientHello; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.HandshakeMessage.ClientHello;
@@ -4783,18 +4780,6 @@ export namespace proto {
public static getTypeUrl(typeUrlPrefix?: string): string; 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 { interface IServerHello {
ephemeral?: (Uint8Array|null); ephemeral?: (Uint8Array|null);
"static"?: (Uint8Array|null); "static"?: (Uint8Array|null);
@@ -5356,7 +5341,8 @@ export namespace proto {
UNKNOWN = 0, UNKNOWN = 0,
CHAT_SETTING = 1, CHAT_SETTING = 1,
BIZ_SUPPORTS_FB_HOSTING = 2, BIZ_SUPPORTS_FB_HOSTING = 2,
UNKNOWN_GROUP = 3 UNKNOWN_GROUP = 3,
DEPRECATION = 4
} }
} }
@@ -12482,7 +12468,6 @@ export namespace proto {
participants?: (proto.SyncActionValue.IBroadcastListParticipant[]|null); participants?: (proto.SyncActionValue.IBroadcastListParticipant[]|null);
listName?: (string|null); listName?: (string|null);
labelIds?: (string[]|null); labelIds?: (string[]|null);
audienceExpression?: (string|null);
} }
class BusinessBroadcastListAction implements IBusinessBroadcastListAction { class BusinessBroadcastListAction implements IBusinessBroadcastListAction {
@@ -12491,7 +12476,6 @@ export namespace proto {
public participants: proto.SyncActionValue.IBroadcastListParticipant[]; public participants: proto.SyncActionValue.IBroadcastListParticipant[];
public listName?: (string|null); public listName?: (string|null);
public labelIds: string[]; public labelIds: string[];
public audienceExpression?: (string|null);
public static create(properties?: proto.SyncActionValue.IBusinessBroadcastListAction): proto.SyncActionValue.BusinessBroadcastListAction; public static create(properties?: proto.SyncActionValue.IBusinessBroadcastListAction): proto.SyncActionValue.BusinessBroadcastListAction;
public static encode(m: proto.SyncActionValue.IBusinessBroadcastListAction, w?: $protobuf.Writer): $protobuf.Writer; public static encode(m: proto.SyncActionValue.IBusinessBroadcastListAction, w?: $protobuf.Writer): $protobuf.Writer;
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.BusinessBroadcastListAction; public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): proto.SyncActionValue.BusinessBroadcastListAction;
+9 -102
View File
@@ -6897,10 +6897,6 @@ export const proto = $root.proto = (() => {
case 60: case 60:
m.capabilities[i] = 60; m.capabilities[i] = 60;
break; break;
case "AI_SUBSCRIPTION_ENABLED":
case 61:
m.capabilities[i] = 61;
break;
} }
} }
} }
@@ -6997,7 +6993,6 @@ export const proto = $root.proto = (() => {
values[valuesById[58] = "JSON_PATCH_STREAMING"] = 58; values[valuesById[58] = "JSON_PATCH_STREAMING"] = 58;
values[valuesById[59] = "AI_TAB_FORCE_CLIPPY"] = 59; values[valuesById[59] = "AI_TAB_FORCE_CLIPPY"] = 59;
values[valuesById[60] = "UNIFIED_RESPONSE_EMBEDDED_SCREENS"] = 60; values[valuesById[60] = "UNIFIED_RESPONSE_EMBEDDED_SCREENS"] = 60;
values[valuesById[61] = "AI_SUBSCRIPTION_ENABLED"] = 61;
return values; return values;
})(); })();
@@ -26692,6 +26687,10 @@ export const proto = $root.proto = (() => {
case 3: case 3:
m.limitSharingTrigger = 3; m.limitSharingTrigger = 3;
break; break;
case "DEPRECATION":
case 4:
m.limitSharingTrigger = 4;
break;
} }
if (d.limitSharingInitiatedByMe != null) { if (d.limitSharingInitiatedByMe != null) {
m.limitSharingInitiatedByMe = Boolean(d.limitSharingInitiatedByMe); m.limitSharingInitiatedByMe = Boolean(d.limitSharingInitiatedByMe);
@@ -32586,7 +32585,6 @@ export const proto = $root.proto = (() => {
ClientHello.prototype.paddedBytes = null; ClientHello.prototype.paddedBytes = null;
ClientHello.prototype.sendServerHelloPaddedBytes = null; ClientHello.prototype.sendServerHelloPaddedBytes = null;
ClientHello.prototype.simulateXxkemFs = null; ClientHello.prototype.simulateXxkemFs = null;
ClientHello.prototype.pqMode = null;
let $oneOfFields; let $oneOfFields;
@@ -32638,12 +32636,6 @@ export const proto = $root.proto = (() => {
set: $util.oneOfSetter($oneOfFields) 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) { ClientHello.create = function create(properties) {
return new ClientHello(properties); return new ClientHello(properties);
}; };
@@ -32667,8 +32659,6 @@ export const proto = $root.proto = (() => {
w.uint32(56).bool(m.sendServerHelloPaddedBytes); w.uint32(56).bool(m.sendServerHelloPaddedBytes);
if (m.simulateXxkemFs != null && Object.hasOwnProperty.call(m, "simulateXxkemFs")) if (m.simulateXxkemFs != null && Object.hasOwnProperty.call(m, "simulateXxkemFs"))
w.uint32(64).bool(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; return w;
}; };
@@ -32713,10 +32703,6 @@ export const proto = $root.proto = (() => {
m.simulateXxkemFs = r.bool(); m.simulateXxkemFs = r.bool();
break; break;
} }
case 9: {
m.pqMode = r.int32();
break;
}
default: default:
r.skipType(t & 7); r.skipType(t & 7);
break; break;
@@ -32768,50 +32754,6 @@ export const proto = $root.proto = (() => {
if (d.simulateXxkemFs != null) { if (d.simulateXxkemFs != null) {
m.simulateXxkemFs = Boolean(d.simulateXxkemFs); 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; return m;
}; };
@@ -32859,11 +32801,6 @@ export const proto = $root.proto = (() => {
if (o.oneofs) if (o.oneofs)
d._simulateXxkemFs = "simulateXxkemFs"; 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; return d;
}; };
@@ -32881,20 +32818,6 @@ export const proto = $root.proto = (() => {
return ClientHello; 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() { HandshakeMessage.ServerHello = (function() {
function ServerHello(p) { function ServerHello(p) {
@@ -36774,6 +36697,10 @@ export const proto = $root.proto = (() => {
case 3: case 3:
m.trigger = 3; m.trigger = 3;
break; break;
case "DEPRECATION":
case 4:
m.trigger = 4;
break;
} }
if (d.limitSharingSettingTimestamp != null) { if (d.limitSharingSettingTimestamp != null) {
if ($util.Long) if ($util.Long)
@@ -36838,6 +36765,7 @@ export const proto = $root.proto = (() => {
values[valuesById[1] = "CHAT_SETTING"] = 1; values[valuesById[1] = "CHAT_SETTING"] = 1;
values[valuesById[2] = "BIZ_SUPPORTS_FB_HOSTING"] = 2; values[valuesById[2] = "BIZ_SUPPORTS_FB_HOSTING"] = 2;
values[valuesById[3] = "UNKNOWN_GROUP"] = 3; values[valuesById[3] = "UNKNOWN_GROUP"] = 3;
values[valuesById[4] = "DEPRECATION"] = 4;
return values; return values;
})(); })();
@@ -87572,7 +87500,6 @@ export const proto = $root.proto = (() => {
BusinessBroadcastListAction.prototype.participants = $util.emptyArray; BusinessBroadcastListAction.prototype.participants = $util.emptyArray;
BusinessBroadcastListAction.prototype.listName = null; BusinessBroadcastListAction.prototype.listName = null;
BusinessBroadcastListAction.prototype.labelIds = $util.emptyArray; BusinessBroadcastListAction.prototype.labelIds = $util.emptyArray;
BusinessBroadcastListAction.prototype.audienceExpression = null;
let $oneOfFields; let $oneOfFields;
@@ -87588,12 +87515,6 @@ export const proto = $root.proto = (() => {
set: $util.oneOfSetter($oneOfFields) set: $util.oneOfSetter($oneOfFields)
}); });
// Virtual OneOf for proto3 optional field
Object.defineProperty(BusinessBroadcastListAction.prototype, "_audienceExpression", {
get: $util.oneOfGetter($oneOfFields = ["audienceExpression"]),
set: $util.oneOfSetter($oneOfFields)
});
BusinessBroadcastListAction.create = function create(properties) { BusinessBroadcastListAction.create = function create(properties) {
return new BusinessBroadcastListAction(properties); return new BusinessBroadcastListAction(properties);
}; };
@@ -87613,8 +87534,6 @@ export const proto = $root.proto = (() => {
for (var i = 0; i < m.labelIds.length; ++i) for (var i = 0; i < m.labelIds.length; ++i)
w.uint32(34).string(m.labelIds[i]); w.uint32(34).string(m.labelIds[i]);
} }
if (m.audienceExpression != null && Object.hasOwnProperty.call(m, "audienceExpression"))
w.uint32(42).string(m.audienceExpression);
return w; return w;
}; };
@@ -87647,10 +87566,6 @@ export const proto = $root.proto = (() => {
m.labelIds.push(r.string()); m.labelIds.push(r.string());
break; break;
} }
case 5: {
m.audienceExpression = r.string();
break;
}
default: default:
r.skipType(t & 7); r.skipType(t & 7);
break; break;
@@ -87687,9 +87602,6 @@ export const proto = $root.proto = (() => {
m.labelIds[i] = String(d.labelIds[i]); m.labelIds[i] = String(d.labelIds[i]);
} }
} }
if (d.audienceExpression != null) {
m.audienceExpression = String(d.audienceExpression);
}
return m; return m;
}; };
@@ -87723,11 +87635,6 @@ export const proto = $root.proto = (() => {
d.labelIds[j] = m.labelIds[j]; d.labelIds[j] = m.labelIds[j];
} }
} }
if (m.audienceExpression != null && m.hasOwnProperty("audienceExpression")) {
d.audienceExpression = m.audienceExpression;
if (o.oneofs)
d._audienceExpression = "audienceExpression";
}
return d; return d;
}; };
+1 -1
View File
@@ -1 +1 @@
{"version":[2,3000,1035595667]} {"version":[2,3000,1035317910]}
+3 -14
View File
@@ -157,15 +157,6 @@ export const MEDIA_PATH_MAP: { [T in MediaType]?: string } = {
'thumbnail-sticker-pack': '/mms/thumbnail-sticker-pack' 'thumbnail-sticker-pack': '/mms/thumbnail-sticker-pack'
} }
export const NEWSLETTER_MEDIA_PATH_MAP: { [T in MediaType]?: string } = {
image: '/newsletter/newsletter-image',
video: '/newsletter/newsletter-video',
document: '/newsletter/newsletter-document',
audio: '/newsletter/newsletter-audio',
sticker: '/newsletter/newsletter-image',
'thumbnail-link': '/newsletter/newsletter-image'
}
export const MEDIA_HKDF_KEY_MAPPING = { export const MEDIA_HKDF_KEY_MAPPING = {
audio: 'Audio', audio: 'Audio',
document: 'Document', document: 'Document',
@@ -197,12 +188,10 @@ 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) */ /** 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 HISTORY_SYNC_PAUSED_TIMEOUT_MS = 120_000
// Replenishment threshold: when server count drops below this, top-up back to INITIAL_PREKEY_COUNT export const MIN_PREKEY_COUNT = 5
export const MIN_PREKEY_COUNT = 200
// Initial pool size matching WA Business (CDP IDB capture: prekey-store = 812 on registration) // Moderate prekey count (upstream uses 812, reduced to balance rate limiting and availability)
// Rounded to 800 for cleanliness; replenishment always tops up to this value export const INITIAL_PREKEY_COUNT = 200
export const INITIAL_PREKEY_COUNT = 800
export const UPLOAD_TIMEOUT = 30000 // 30 seconds export const UPLOAD_TIMEOUT = 30000 // 30 seconds
// Moderate upload interval to balance rate limiting and responsiveness (was 5000) // Moderate upload interval to balance rate limiting and responsiveness (was 5000)
+40 -44
View File
@@ -7,7 +7,6 @@ import { proto } from '../../WAProto/index.js'
import { import {
DEFAULT_CACHE_TTLS, DEFAULT_CACHE_TTLS,
DEFAULT_SESSION_CLEANUP_CONFIG, DEFAULT_SESSION_CLEANUP_CONFIG,
INITIAL_PREKEY_COUNT,
KEY_BUNDLE_TYPE, KEY_BUNDLE_TYPE,
MIN_PREKEY_COUNT, MIN_PREKEY_COUNT,
PLACEHOLDER_MAX_AGE_SECONDS, PLACEHOLDER_MAX_AGE_SECONDS,
@@ -49,12 +48,9 @@ import {
getStatusFromReceiptType, getStatusFromReceiptType,
handleIdentityChange, handleIdentityChange,
hkdf, hkdf,
BAD_MAC_ERROR_TEXT,
DECRYPTION_RETRY_CONFIG,
MISSING_KEYS_ERROR_TEXT, MISSING_KEYS_ERROR_TEXT,
NACK_REASONS, NACK_REASONS,
NO_MESSAGE_FOUND_ERROR_TEXT, NO_MESSAGE_FOUND_ERROR_TEXT,
RetryReason,
normalizeKeyLidToPn, normalizeKeyLidToPn,
normalizeMessageJids, normalizeMessageJids,
resolveLidToPn, resolveLidToPn,
@@ -1216,7 +1212,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
return await query(stanza) return await query(stanza)
} }
const sendRetryRequest = async (node: BinaryNode, forceIncludeKeys = false, decryptionError?: string) => { const sendRetryRequest = async (node: BinaryNode, forceIncludeKeys = false) => {
const { fullMessage } = decodeMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '') const { fullMessage } = decodeMessageNode(node, authState.creds.me!.id, authState.creds.me!.lid || '')
const { key: msgKey } = fullMessage const { key: msgKey } = fullMessage
const msgId = msgKey.id! const msgId = msgKey.id!
@@ -1314,39 +1310,39 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
const fromJid = node.attrs.from! const fromJid = node.attrs.from!
// Derive the Signal error code from the actual decryption failure message. // Check if we should recreate the session
// Sent in the retry receipt so the peer (even another InfiniteAPI instance) let shouldRecreateSession = false
// knows the exact failure type and can recreate the session immediately let recreateReason = ''
// instead of falling back to the 1-hour timeout.
// if (enableAutoSessionRecreation && messageRetryManager && retryCount >= 1) {
// Codes mirror RetryReason enum in message-retry-manager.ts: try {
// 0 = UnknownError | 1 = NoSession | 2 = InvalidKey // Check if we have a session with this JID
// 3 = InvalidKeyId | 4 = InvalidMessage | 7 = BadMac const sessionId = signalRepository.jidToSignalProtocolAddress(fromJid)
// const hasSession = await signalRepository.validateSession(fromJid)
// Uses DECRYPTION_RETRY_CONFIG error lists (single source of truth in
// decode-wa-message.ts) so additions to those lists are picked up here // Extract error code from retry node if present (for MAC error detection)
// automatically. const retryNode = getBinaryNodeChild(node, 'retry')
// const errorAttr = retryNode?.attrs?.error
// NOTE: We do NOT delete the session here (receiver side). The Signal Protocol const errorCode = messageRetryManager.parseRetryErrorCode(errorAttr)
// recovers automatically when the sender's pkmsg arrives — it overwrites the
// corrupted session. Deleting prematurely creates a race window where no session const result = messageRetryManager.shouldRecreateSession(fromJid, hasSession.exists, errorCode)
// exists, which can cause "No Session" errors on concurrent messages. shouldRecreateSession = result.recreate
const retryErrorCode = (() => { recreateReason = result.reason
if (!decryptionError) return RetryReason.UnknownError
// Bad MAC must be checked first — it is also in corruptedSessionErrors if (shouldRecreateSession) {
// but warrants the more specific code 7 over the generic code 4. logger.debug({ fromJid, retryCount, reason: recreateReason, errorCode }, 'recreating session for retry')
if (decryptionError.includes(BAD_MAC_ERROR_TEXT)) return RetryReason.SignalErrorBadMac // Delete existing session to force recreation
// MessageCounterError and other corrupted-session variants // CRITICAL: Use same transaction key as encrypt/decrypt operations to prevent race
if (DECRYPTION_RETRY_CONFIG.corruptedSessionErrors.some(e => decryptionError.includes(e))) return RetryReason.SignalErrorInvalidMessage // Using meId ensures this delete serializes with sendMessage() and other session operations
// Missing / invalid session record await authState.keys.transaction(async () => {
if (DECRYPTION_RETRY_CONFIG.sessionRecordErrors.some(e => decryptionError.includes(e)) || await authState.keys.set({ session: { [sessionId]: null } })
/no\s+(open\s+)?sessions?/i.test(decryptionError)) return RetryReason.SignalErrorNoSession }, authState.creds.me?.id || 'session-operation')
// PreKey / key-id errors forceIncludeKeys = true
if (/pre\s*key/i.test(decryptionError)) return RetryReason.SignalErrorInvalidKeyId }
// Identity / key errors } catch (error) {
if (/invalid\s*key|untrusted\s*identity/i.test(decryptionError)) return RetryReason.SignalErrorInvalidKey logger.warn({ error, fromJid }, 'failed to check session recreation')
return RetryReason.UnknownError }
})() }
if (retryCount <= 2) { if (retryCount <= 2) {
// Use new retry manager for phone requests if available // Use new retry manager for phone requests if available
@@ -1387,7 +1383,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
id: node.attrs.id!, id: node.attrs.id!,
t: node.attrs.t!, t: node.attrs.t!,
v: '1', v: '1',
error: retryErrorCode.toString() // ADD ERROR FIELD
error: '0'
} }
}, },
{ {
@@ -1406,7 +1403,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
receipt.attrs.participant = node.attrs.participant receipt.attrs.participant = node.attrs.participant
} }
if (retryCount > 1 || forceIncludeKeys) { if (retryCount > 1 || forceIncludeKeys || shouldRecreateSession) {
const { update, preKeys } = await getNextPreKeys(authState, 1) const { update, preKeys } = await getNextPreKeys(authState, 1)
const [keyId] = Object.keys(preKeys) const [keyId] = Object.keys(preKeys)
@@ -1443,8 +1440,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count') logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count')
if (shouldUploadMorePreKeys) { if (shouldUploadMorePreKeys) {
// Top-up back to INITIAL_PREKEY_COUNT so the pool is always restored to full size await uploadPreKeys()
await uploadPreKeys(Math.max(1, INITIAL_PREKEY_COUNT - count))
} }
} else { } else {
const result = await handleIdentityChange(node, { const result = await handleIdentityChange(node, {
@@ -2509,7 +2505,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
} }
const encNode = getBinaryNodeChild(node, 'enc') const encNode = getBinaryNodeChild(node, 'enc')
await sendRetryRequest(node, !encNode, errorMessage) await sendRetryRequest(node, !encNode)
if (retryRequestDelayMs) { if (retryRequestDelayMs) {
await delay(retryRequestDelayMs) await delay(retryRequestDelayMs)
} }
@@ -2518,7 +2514,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
// Still attempt retry even if pre-key upload failed // Still attempt retry even if pre-key upload failed
try { try {
const encNode = getBinaryNodeChild(node, 'enc') const encNode = getBinaryNodeChild(node, 'enc')
await sendRetryRequest(node, !encNode, errorMessage) await sendRetryRequest(node, !encNode)
} catch (retryErr) { } catch (retryErr) {
logger.error({ retryErr }, 'Failed to send retry after error handling') logger.error({ retryErr }, 'Failed to send retry after error handling')
} }
+1 -29
View File
@@ -9,7 +9,6 @@ import type {
AlbumMessageOptions, AlbumMessageOptions,
AlbumSendResult, AlbumSendResult,
AnyMessageContent, AnyMessageContent,
LIDMapping,
MediaConnInfo, MediaConnInfo,
MessageReceiptType, MessageReceiptType,
MessageRelayOptions, MessageRelayOptions,
@@ -339,33 +338,6 @@ export const makeMessagesSocket = (config: SocketConfig) => {
} }
} }
// 4th LID→PN source: device-list entries sharing the same raw_id
// WA Business uses this for accounts where HistorySync sends zero phoneNumberToLidMappings
const allEntries = [...result.list, ...result.sideList]
const rawIdMap = new Map<number, { pn?: string; lid?: string }>()
for (const item of allEntries) {
if (typeof item.rawId !== 'number' || isNaN(item.rawId)) continue
const decoded = jidDecode(item.id)
if (!decoded) continue
const entry = rawIdMap.get(item.rawId) || {}
if (decoded.server === 'lid' || decoded.server === 'hosted.lid') {
entry.lid = item.id
} else if (decoded.server === 's.whatsapp.net' || decoded.server === 'c.us') {
entry.pn = item.id
}
rawIdMap.set(item.rawId, entry)
}
const rawIdMappings: LIDMapping[] = []
for (const { pn, lid } of rawIdMap.values()) {
if (pn && lid) {
rawIdMappings.push({ lid: jidNormalizedUser(lid), pn: jidNormalizedUser(pn) })
}
}
if (rawIdMappings.length > 0) {
await signalRepository.lidMapping.storeLIDPNMappings(rawIdMappings)
logger.debug({ count: rawIdMappings.length }, 'stored LID-PN mappings from raw_id pairing')
}
const meId = authState.creds.me?.id const meId = authState.creds.me?.id
if (!meId) throw new Boom('Not authenticated', { statusCode: 401 }) if (!meId) throw new Boom('Not authenticated', { statusCode: 401 })
const meLid = authState.creds.me?.lid || '' const meLid = authState.creds.me?.lid || ''
@@ -1051,7 +1023,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const bytes = encodeNewsletterMessage(patched as proto.IMessage) const bytes = encodeNewsletterMessage(patched as proto.IMessage)
binaryNodeContent.push({ binaryNodeContent.push({
tag: 'plaintext', tag: 'plaintext',
attrs: mediaType ? { mediatype: mediaType } : {}, attrs: {},
content: bytes content: bytes
}) })
const stanza: BinaryNode = { const stanza: BinaryNode = {
+2 -2
View File
@@ -123,11 +123,11 @@ export const makeNewsletterSocket = (config: SocketConfig) => {
}, },
newsletterMute: (jid: string) => { newsletterMute: (jid: string) => {
return executeWMexQuery({ input: { newsletter_id: jid, type: 'MUTE_ADMIN_ACTIVITY', value: 'OFF' } }, QueryIds.MUTE, XWAPaths.xwa2_newsletter_mute_v2) return executeWMexQuery({ newsletter_id: jid }, QueryIds.MUTE, XWAPaths.xwa2_newsletter_mute_v2)
}, },
newsletterUnmute: (jid: string) => { newsletterUnmute: (jid: string) => {
return executeWMexQuery({ input: { newsletter_id: jid, type: 'MUTE_ADMIN_ACTIVITY', value: 'ON' } }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2) return executeWMexQuery({ newsletter_id: jid }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2)
}, },
newsletterUpdateName: async (jid: string, name: string) => { newsletterUpdateName: async (jid: string, name: string) => {
+8 -28
View File
@@ -701,12 +701,10 @@ export const makeSocket = (config: SocketConfig) => {
} }
} }
// Prevent multiple concurrent uploads — if one is already running, wait for it and return: // Prevent multiple concurrent uploads
// the concurrent upload already replenished the pool, so there is nothing left to do.
if (uploadPreKeysPromise) { if (uploadPreKeysPromise) {
logger.debug('Pre-key upload already in progress, waiting for completion') logger.debug('Pre-key upload already in progress, waiting for completion')
await uploadPreKeysPromise await uploadPreKeysPromise
return
} }
const uploadLogic = async () => { const uploadLogic = async () => {
@@ -786,15 +784,14 @@ export const makeSocket = (config: SocketConfig) => {
try { try {
let count = 0 let count = 0
const preKeyCount = await getAvailablePreKeysOnServer() const preKeyCount = await getAvailablePreKeysOnServer()
// How many to upload: top-up to INITIAL_PREKEY_COUNT from whatever remains on server if (preKeyCount === 0) count = INITIAL_PREKEY_COUNT
count = Math.max(0, INITIAL_PREKEY_COUNT - preKeyCount) else count = MIN_PREKEY_COUNT
const { exists: currentPreKeyExists, currentPreKeyId } = await verifyCurrentPreKeyExists() const { exists: currentPreKeyExists, currentPreKeyId } = await verifyCurrentPreKeyExists()
logger.info(`${preKeyCount} pre-keys found on server`) logger.info(`${preKeyCount} pre-keys found on server`)
logger.info(`Current prekey ID: ${currentPreKeyId}, exists in storage: ${currentPreKeyExists}`) logger.info(`Current prekey ID: ${currentPreKeyId}, exists in storage: ${currentPreKeyExists}`)
// Trigger upload when below the replenishment threshold, not when count < topUp amount const lowServerCount = preKeyCount <= count
const lowServerCount = preKeyCount < MIN_PREKEY_COUNT
const missingCurrentPreKey = !currentPreKeyExists && currentPreKeyId > 0 const missingCurrentPreKey = !currentPreKeyExists && currentPreKeyId > 0
const shouldUpload = lowServerCount || missingCurrentPreKey const shouldUpload = lowServerCount || missingCurrentPreKey
@@ -1154,7 +1151,7 @@ export const makeSocket = (config: SocketConfig) => {
// Decrement active connections // Decrement active connections
decrementActiveConnections() decrementActiveConnections()
clearTimeout(keepAliveReq) clearInterval(keepAliveReq)
clearTimeout(qrTimer) clearTimeout(qrTimer)
// Clear offline-buffer safety timer so its callback cannot call ev.flush() // Clear offline-buffer safety timer so its callback cannot call ev.flush()
@@ -1265,16 +1262,8 @@ export const makeSocket = (config: SocketConfig) => {
}) })
} }
const startKeepAliveRequest = () => { const startKeepAliveRequest = () =>
// Use recursive setTimeout with ±15% jitter to match WA Desktop behaviour (keepAliveReq = setInterval(() => {
// (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) { if (!lastDateRecv) {
lastDateRecv = new Date() lastDateRecv = new Date()
} }
@@ -1286,7 +1275,6 @@ export const makeSocket = (config: SocketConfig) => {
*/ */
if (diff > keepAliveIntervalMs + 5000) { if (diff > keepAliveIntervalMs + 5000) {
void end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost })) void end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
return // connection closing — do not reschedule
} else if (ws.isOpen) { } else if (ws.isOpen) {
// Send keep-alive ping via sendNode() (fire-and-forget) instead of query(). // 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 // query() wraps the ping in the query circuit breaker — when that breaker is
@@ -1309,15 +1297,7 @@ export const makeSocket = (config: SocketConfig) => {
} else { } else {
logger.warn('keep alive called when WS not open') logger.warn('keep alive called when WS not open')
} }
}, keepAliveIntervalMs))
// Do not reschedule once shutdown has started (closed set by end() on any concurrent path)
if (!closed) {
scheduleNextKeepAlive()
}
}
scheduleNextKeepAlive()
}
/** i have no idea why this exists. pls enlighten me */ /** i have no idea why this exists. pls enlighten me */
const sendPassiveIq = (tag: 'passive' | 'active') => const sendPassiveIq = (tag: 'passive' | 'active') =>
query({ query({
-4
View File
@@ -27,8 +27,6 @@ export type BaileysEventMap = {
chats: Chat[] chats: Chat[]
contacts: Contact[] contacts: Contact[]
messages: WAMessage[] 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 isLatest?: boolean
progress?: number | null progress?: number | null
syncType?: proto.HistorySync.HistorySyncType | null syncType?: proto.HistorySync.HistorySyncType | null
@@ -187,8 +185,6 @@ export type BufferedEventData = {
chats: { [jid: string]: Chat } chats: { [jid: string]: Chat }
contacts: { [jid: string]: Contact } contacts: { [jid: string]: Contact }
messages: { [uqId: string]: WAMessage } messages: { [uqId: string]: WAMessage }
/** Keyed by groupJid for O(1) deduplication across chunks */
pastParticipants: { [groupJid: string]: proto.IPastParticipant[] }
empty: boolean empty: boolean
isLatest: boolean isLatest: boolean
progress?: number | null progress?: number | null
+2 -10
View File
@@ -990,16 +990,8 @@ export type MessageGenerationOptionsFromContent = MiscMessageGenerationOptions &
export type WAMediaUploadFunction = ( export type WAMediaUploadFunction = (
encFilePath: string, encFilePath: string,
opts: { fileEncSha256B64: string; mediaType: MediaType; timeoutMs?: number; newsletter?: boolean } opts: { fileEncSha256B64: string; mediaType: MediaType; timeoutMs?: number }
) => Promise<{ ) => Promise<{ mediaUrl: string; directPath: string; meta_hmac?: string; ts?: number; fbid?: number }>
mediaUrl: string | undefined
directPath: string
meta_hmac?: string
ts?: number
fbid?: number
thumbnailDirectPath?: string
thumbnailSha256?: string
}>
export type MediaGenerationOptions = { export type MediaGenerationOptions = {
logger?: ILogger logger?: ILogger
+8 -8
View File
@@ -4,10 +4,10 @@ export enum XWAPaths {
xwa2_newsletter_view = 'xwa2_newsletter_view', xwa2_newsletter_view = 'xwa2_newsletter_view',
xwa2_newsletter_metadata = 'xwa2_newsletter', xwa2_newsletter_metadata = 'xwa2_newsletter',
xwa2_newsletter_admin_count = 'xwa2_newsletter_admin', xwa2_newsletter_admin_count = 'xwa2_newsletter_admin',
xwa2_newsletter_mute_v2 = 'xwa2_newsletter_update_user_setting', xwa2_newsletter_mute_v2 = 'xwa2_newsletter_mute_v2',
xwa2_newsletter_unmute_v2 = 'xwa2_newsletter_update_user_setting', xwa2_newsletter_unmute_v2 = 'xwa2_newsletter_unmute_v2',
xwa2_newsletter_follow = 'xwa2_newsletter_join_v2', xwa2_newsletter_follow = 'xwa2_newsletter_follow',
xwa2_newsletter_unfollow = 'xwa2_newsletter_leave_v2', xwa2_newsletter_unfollow = 'xwa2_newsletter_unfollow',
xwa2_newsletter_change_owner = 'xwa2_newsletter_change_owner', xwa2_newsletter_change_owner = 'xwa2_newsletter_change_owner',
xwa2_newsletter_demote = 'xwa2_newsletter_demote', xwa2_newsletter_demote = 'xwa2_newsletter_demote',
xwa2_newsletter_delete_v2 = 'xwa2_newsletter_delete_v2' xwa2_newsletter_delete_v2 = 'xwa2_newsletter_delete_v2'
@@ -17,10 +17,10 @@ export enum QueryIds {
UPDATE_METADATA = '24250201037901610', UPDATE_METADATA = '24250201037901610',
METADATA = '6563316087068696', METADATA = '6563316087068696',
SUBSCRIBERS = '9783111038412085', SUBSCRIBERS = '9783111038412085',
FOLLOW = '24404358912487870', FOLLOW = '7871414976211147',
UNFOLLOW = '9767147403369991', UNFOLLOW = '7238632346214362',
MUTE = '31938993655691868', MUTE = '29766401636284406',
UNMUTE = '31938993655691868', UNMUTE = '9864994326891137',
ADMIN_COUNT = '7130823597031706', ADMIN_COUNT = '7130823597031706',
CHANGE_OWNER = '7341777602580933', CHANGE_OWNER = '7341777602580933',
DEMOTE = '6551828931592903', DEMOTE = '6551828931592903',
-28
View File
@@ -1,5 +1,4 @@
import EventEmitter from 'events' import EventEmitter from 'events'
import { proto } from '../../WAProto/index.js'
import type { import type {
BaileysEvent, BaileysEvent,
BaileysEventEmitter, BaileysEventEmitter,
@@ -891,7 +890,6 @@ const makeBufferData = (): BufferedEventData => {
chats: {}, chats: {},
messages: {}, messages: {},
contacts: {}, contacts: {},
pastParticipants: {},
isLatest: false, isLatest: false,
empty: true empty: true
}, },
@@ -969,28 +967,6 @@ 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.empty = false
data.historySets.syncType = eventData.syncType data.historySets.syncType = eventData.syncType
data.historySets.progress = eventData.progress data.historySets.progress = eventData.progress
@@ -1316,10 +1292,6 @@ function consolidateEvents(data: BufferedEventData) {
chats: Object.values(data.historySets.chats), chats: Object.values(data.historySets.chats),
messages: Object.values(data.historySets.messages), messages: Object.values(data.historySets.messages),
contacts: Object.values(data.historySets.contacts), 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, syncType: data.historySets.syncType,
progress: data.historySets.progress, progress: data.historySets.progress,
isLatest: data.historySets.isLatest, isLatest: data.historySets.isLatest,
+2 -34
View File
@@ -15,8 +15,7 @@ import {
import { toNumber } from './generics' import { toNumber } from './generics'
import type { ILogger } from './logger.js' import type { ILogger } from './logger.js'
import { normalizeMessageContent } from './messages' import { normalizeMessageContent } from './messages'
import { DEFAULT_ORIGIN } from '../Defaults' import { downloadContentFromMessage } from './messages-media'
import { downloadContentFromMessage, getUrlFromDirectPath } from './messages-media'
const inflatePromise = promisify(inflate) const inflatePromise = promisify(inflate)
@@ -375,25 +374,10 @@ export const processHistoryMessage = (item: proto.IHistorySync, logger?: ILogger
// Convert Map back to array for return // Convert Map back to array for return
const lidPnMappings = Array.from(lidPnMap.values()) 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 { return {
chats, chats,
contacts, contacts,
messages, messages,
pastParticipants,
lidPnMappings, lidPnMappings,
syncType: item.syncType, syncType: item.syncType,
progress: item.progress progress: item.progress
@@ -424,23 +408,7 @@ export const downloadAndProcessHistorySyncNotification = async (
historyMsg = await downloadHistory(msg, options) historyMsg = await downloadHistory(msg, options)
} }
const result = processHistoryMessage(historyMsg, logger) return 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',
headers: { ...((options as RequestInit).headers ?? {}), Origin: DEFAULT_ORIGIN }
}).catch(() => {
// non-fatal — server will expire it anyway
})
}
return result
} }
/** /**
+6 -32
View File
@@ -10,13 +10,7 @@ import { join } from 'path'
import { Readable, Transform } from 'stream' import { Readable, Transform } from 'stream'
import { URL } from 'url' import { URL } from 'url'
import { proto } from '../../WAProto/index.js' import { proto } from '../../WAProto/index.js'
import { import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP, type MediaType } from '../Defaults'
DEFAULT_ORIGIN,
MEDIA_HKDF_KEY_MAPPING,
MEDIA_PATH_MAP,
type MediaType,
NEWSLETTER_MEDIA_PATH_MAP
} from '../Defaults'
import type { import type {
BaileysEventMap, BaileysEventMap,
DownloadableMessage, DownloadableMessage,
@@ -687,10 +681,6 @@ type MediaUploadResult = {
meta_hmac?: string meta_hmac?: string
ts?: number ts?: number
fbid?: number fbid?: number
thumbnail_info?: {
thumbnail_sha256?: string
thumbnail_direct_path?: string
}
} }
export type UploadParams = { export type UploadParams = {
@@ -835,21 +825,11 @@ export const getWAUploadToServer = (
{ customUploadHosts, fetchAgent, logger, options }: SocketConfig, { customUploadHosts, fetchAgent, logger, options }: SocketConfig,
refreshMediaConn: (force: boolean) => Promise<MediaConnInfo> refreshMediaConn: (force: boolean) => Promise<MediaConnInfo>
): WAMediaUploadFunction => { ): WAMediaUploadFunction => {
return async (filePath, { mediaType, fileEncSha256B64, timeoutMs, newsletter }) => { return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
// send a query JSON to obtain the url & auth token to upload our media // send a query JSON to obtain the url & auth token to upload our media
let uploadInfo = await refreshMediaConn(false) let uploadInfo = await refreshMediaConn(false)
let urls: let urls: { mediaUrl: string; directPath: string; meta_hmac?: string; ts?: number; fbid?: number } | undefined
| {
mediaUrl: string
directPath: string
meta_hmac?: string
ts?: number
fbid?: number
thumbnailDirectPath?: string
thumbnailSha256?: string
}
| undefined
const hosts = [...customUploadHosts, ...uploadInfo.hosts] const hosts = [...customUploadHosts, ...uploadInfo.hosts]
fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64) fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64)
@@ -871,11 +851,7 @@ export const getWAUploadToServer = (
logger.debug(`uploading to "${hostname}"`) logger.debug(`uploading to "${hostname}"`)
const auth = encodeURIComponent(uploadInfo.auth) const auth = encodeURIComponent(uploadInfo.auth)
const mediaPath = (newsletter ? NEWSLETTER_MEDIA_PATH_MAP[mediaType] : undefined) || MEDIA_PATH_MAP[mediaType] const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
let url = `https://${hostname}${mediaPath}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
if (newsletter) {
url += '&server_thumb_gen=1'
}
let result: MediaUploadResult | undefined let result: MediaUploadResult | undefined
try { try {
@@ -892,13 +868,11 @@ export const getWAUploadToServer = (
if (result?.url || result?.direct_path) { if (result?.url || result?.direct_path) {
urls = { urls = {
mediaUrl: result.url || result.direct_path!, mediaUrl: result.url!,
directPath: result.direct_path!, directPath: result.direct_path!,
meta_hmac: result.meta_hmac, meta_hmac: result.meta_hmac,
fbid: result.fbid, fbid: result.fbid,
ts: result.ts, ts: result.ts
thumbnailDirectPath: result.thumbnail_info?.thumbnail_direct_path,
thumbnailSha256: result.thumbnail_info?.thumbnail_sha256
} }
break break
} else { } else {
+3 -6
View File
@@ -196,11 +196,10 @@ export const prepareWAMessageMedia = async (
) )
const fileSha256B64 = fileSha256.toString('base64') const fileSha256B64 = fileSha256.toString('base64')
const { directPath, thumbnailDirectPath, thumbnailSha256 } = await options.upload(filePath, { const { mediaUrl, directPath } = await options.upload(filePath, {
fileEncSha256B64: fileSha256B64, fileEncSha256B64: fileSha256B64,
mediaType: mediaType, mediaType: mediaType,
timeoutMs: options.mediaUploadTimeoutMs, timeoutMs: options.mediaUploadTimeoutMs
newsletter: true
}) })
await fs.unlink(filePath) await fs.unlink(filePath)
@@ -208,12 +207,10 @@ export const prepareWAMessageMedia = async (
const obj = WAProto.Message.fromObject({ const obj = WAProto.Message.fromObject({
// todo: add more support here // todo: add more support here
[`${mediaType}Message`]: (MessageTypeProto as any)[mediaType].fromObject({ [`${mediaType}Message`]: (MessageTypeProto as any)[mediaType].fromObject({
// url intentionally omitted — newsletters use directPath only url: mediaUrl,
directPath, directPath,
fileSha256, fileSha256,
fileLength, fileLength,
thumbnailDirectPath,
thumbnailSha256: thumbnailSha256 ? Buffer.from(thumbnailSha256, 'base64') : undefined,
...uploadData, ...uploadData,
media: undefined media: undefined
}) })
+8 -15
View File
@@ -10,7 +10,7 @@ import {
} from './Protocols' } from './Protocols'
import { USyncUser } from './USyncUser' import { USyncUser } from './USyncUser'
export type USyncQueryResultList = { [protocol: string]: unknown; id: string; rawId?: number } export type USyncQueryResultList = { [protocol: string]: unknown; id: string }
export type USyncQueryResult = { export type USyncQueryResult = {
list: USyncQueryResultList[] list: USyncQueryResultList[]
@@ -68,8 +68,10 @@ export class USyncQuery {
//TODO: see if there are any errors in the result node //TODO: see if there are any errors in the result node
//const resultNode = getBinaryNodeChild(usyncNode, 'result') //const resultNode = getBinaryNodeChild(usyncNode, 'result')
const parseNodeList = (content: BinaryNode[]): USyncQueryResultList[] => const listNode = usyncNode ? getBinaryNodeChild(usyncNode, 'list') : undefined
content.reduce((acc: USyncQueryResultList[], node) => {
if (listNode?.content && Array.isArray(listNode.content)) {
queryResult.list = listNode.content.reduce((acc: USyncQueryResultList[], node) => {
const id = node?.attrs.jid const id = node?.attrs.jid
if (id) { if (id) {
const data = Array.isArray(node?.content) const data = Array.isArray(node?.content)
@@ -87,24 +89,15 @@ export class USyncQuery {
.filter(([, b]) => b !== null) as [string, unknown][] .filter(([, b]) => b !== null) as [string, unknown][]
) )
: {} : {}
const rawIdAttr = node?.attrs?.['raw_id'] acc.push({ ...data, id })
const rawId = rawIdAttr !== undefined ? Number(rawIdAttr) : undefined
acc.push({ ...data, id, ...(rawId !== undefined && !isNaN(rawId) ? { rawId } : {}) })
} }
return acc 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 return queryResult
} }