@openclaw/whatsapp 2026.7.2-beta.7 → 2026.8.1-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{accounts-CSqLvCY5.js → accounts-BX2fZUyR.js} +1 -1
- package/dist/action-runtime-api.js +1 -1
- package/dist/{action-runtime-BzfDFPbH.js → action-runtime-pM-ExU0l.js} +2 -2
- package/dist/action-runtime.runtime.js +1 -1
- package/dist/{agent-tools-login-DE9MU98T.js → agent-tools-login-BjRpD-JQ.js} +5 -4
- package/dist/api.js +69 -127
- package/dist/{approval-handler.runtime-DrA59QSA.js → approval-handler.runtime-CkMMvUjk.js} +4 -4
- package/dist/{approval-reactions-Cu66aYdq.js → approval-reactions-Jyr1JaAK.js} +29 -81
- package/dist/auth-presence.js +1 -1
- package/dist/{auth-store-DcEpIOmE.js → auth-store-CtpBqH5S.js} +82 -18
- package/dist/call-tool-api.js +1 -1
- package/dist/{channel-Dg62olhy.js → channel-DRMYRfZ8.js} +56 -160
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-react-action-J_YtyMGX.js → channel-react-action-CstrR0CT.js} +14 -12
- package/dist/{channel.runtime-BP0xNmLi.js → channel.runtime-X3JC2gaq.js} +4 -4
- package/dist/channel.setup-DhBBhbQv.js +14 -0
- package/dist/{connection-controller-BOB2MVKY.js → connection-controller-CIYuBJrs.js} +2 -3
- package/dist/contract-api.js +2 -3
- package/dist/{creds-files-B1kSWtBg.js → creds-files-W6B3EMZ8.js} +17 -1
- package/dist/{directory-config-DalGrfGN.js → directory-config-P7_3U73Z.js} +3 -3
- package/dist/directory-contract-api.js +1 -1
- package/dist/doctor-contract-api.js +37 -2
- package/dist/{group-session-key-DVMZlO3x.js → group-session-key-DmFc_Cqi.js} +23 -4
- package/dist/light-runtime-api.js +2 -2
- package/dist/{login-CvY2Dhch.js → login-BkpKpndm.js} +4 -4
- package/dist/{login-qr-Bncpb6js.js → login-qr-1dpv6jjT.js} +4 -4
- package/dist/login-qr-runtime.js +1 -1
- package/dist/{monitor-CfkaUSL2.js → monitor-IzwQKizW.js} +545 -549
- package/dist/{question-reactions-CRsnyoMK.js → question-reactions-D3eAeB0i.js} +29 -78
- package/dist/runtime-api.js +9 -9
- package/dist/runtime-group-policy-DeSRFXEV.js +11 -0
- package/dist/{send-D37ddk22.js → send-CwvTU4RI.js} +298 -23
- package/dist/{send-api-7wk6eQyW.js → send-api-obOSm2cC.js} +126 -137
- package/dist/setup-entry.js +1 -8
- package/dist/{setup-finalize-BZucx24W.js → setup-finalize-n4K2I2gJ.js} +4 -4
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-BkVcd769.js → setup-surface-Cwn3zyxK.js} +3 -3
- package/dist/{shared-CAxd15bp.js → shared-wg42NKFB.js} +13 -8
- package/dist/{socket-close-DridEuzz.js → socket-close-2T0_jZTN.js} +4 -10
- package/openclaw.plugin.json +4 -0
- package/package.json +4 -5
- package/skills/wacli/SKILL.md +8 -1
- package/dist/access-control-pZcxTmd5.js +0 -363
- package/dist/approval-resolver-DoeBBt22.js +0 -16
- package/dist/channel.setup-pMwNVkCA.js +0 -19
- package/dist/identity-CPrT5aEU.js +0 -76
- package/dist/legacy-state-migrations-api.js +0 -2
- package/dist/state-migrations-CAMTrs2U.js +0 -33
|
@@ -1,62 +1,248 @@
|
|
|
1
|
-
import { a as resolveWhatsAppAccount, s as resolveWhatsAppMediaMaxBytes } from "./accounts-
|
|
2
|
-
import {
|
|
1
|
+
import { a as resolveWhatsAppAccount, s as resolveWhatsAppMediaMaxBytes } from "./accounts-BX2fZUyR.js";
|
|
2
|
+
import { c as normalizeWhatsAppPayloadTextPreservingIndentation, d as listWhatsAppSendResultMessageIds, f as mergeWhatsAppAcceptedSendError, g as withWhatsAppLogicalDeliveryActivity, h as rememberWhatsAppPartialSend, l as prepareWhatsAppOutboundMedia, m as rememberWhatsAppAcceptedSend, o as normalizeWhatsAppOutboundPayload, p as normalizeWhatsAppSendResult, r as sendReactionWhatsApp, v as resolveWhatsAppReactionLevel } from "./send-CwvTU4RI.js";
|
|
3
3
|
import { i as getWhatsAppRuntime } from "./runtime-BLlToOi6.js";
|
|
4
4
|
import { r as getWhatsAppConnectionController } from "./connection-controller-runtime-context-DG0tuYuw.js";
|
|
5
5
|
import { a as markdownToWhatsAppChunks, c as toWhatsappJid, l as toWhatsappJidWithLid, n as isSelfChatMode, o as resolveEquivalentWhatsAppDirectChatJids, r as jidToE164, s as resolveJidToE164 } from "./targets-runtime-TMk0lGuy.js";
|
|
6
6
|
import { t as normalizeE164 } from "./text-runtime-CaKLhowd.js";
|
|
7
|
-
import { a as cacheInboundMessageMeta, i as buildQuotedMessageOptions, n as resolveWhatsAppGroupSessionRoute, o as lookupInboundMessageMeta, r as resolveWhatsAppLegacyGroupSessionKey } from "./group-session-key-
|
|
8
|
-
import { g as readWebSelfId, o as getWebAuthAgeMs, r as WhatsAppAuthUnstableError, v as readWebSelfIdentityForDecision } from "./auth-store-
|
|
9
|
-
import { a as getSelfIdentity, c as resolveComparableIdentity, i as getReplyContext, n as getMentionIdentities, o as getSenderIdentity, r as getPrimaryIdentityId, s as identitiesOverlap, t as getComparableIdentityValues } from "./identity-CPrT5aEU.js";
|
|
7
|
+
import { a as cacheInboundMessageMeta, i as buildQuotedMessageOptions, n as resolveWhatsAppGroupSessionRoute, o as lookupInboundMessageMeta, r as resolveWhatsAppLegacyGroupSessionKey } from "./group-session-key-DmFc_Cqi.js";
|
|
8
|
+
import { C as getMentionIdentities, D as getSenderIdentity, E as getSelfIdentity, O as identitiesOverlap, S as getComparableIdentityValues, T as getReplyContext, g as readWebSelfId, k as resolveComparableIdentity, o as getWebAuthAgeMs, r as WhatsAppAuthUnstableError, v as readWebSelfIdentityForDecision, w as getPrimaryIdentityId } from "./auth-store-CtpBqH5S.js";
|
|
10
9
|
import { n as resolveWhatsAppGroupsConfigPath } from "./group-config-path-BGyzT9Lg.js";
|
|
11
|
-
import { a as waitForWaConnection, c as isWhatsAppSocketOperationTimeoutError, d as withWhatsAppSocketOperationTimeout, l as resolveWhatsAppSocketOperationTimeoutMs, r as createWaSocket, s as createWhatsAppSocketOperationTimeoutAdapter, u as resolveWhatsAppSocketTiming } from "./socket-close-
|
|
10
|
+
import { a as waitForWaConnection, c as isWhatsAppSocketOperationTimeoutError, d as withWhatsAppSocketOperationTimeout, l as resolveWhatsAppSocketOperationTimeoutMs, r as createWaSocket, s as createWhatsAppSocketOperationTimeoutAdapter, u as resolveWhatsAppSocketTiming } from "./socket-close-2T0_jZTN.js";
|
|
12
11
|
import { n as getStatusCode, t as formatError } from "./session-errors-JuazczrA.js";
|
|
13
12
|
import { t as BufferJSON } from "./session.runtime-Nsqaehbd.js";
|
|
14
|
-
import { c as
|
|
15
|
-
import {
|
|
16
|
-
import { c as computeBackoff, d as resolveReconnectPolicy, f as sleepWithAbort, l as newConnectionId, n as WHATSAPP_WATCHDOG_TIMEOUT_ERROR, r as WhatsAppConnectionController, s as DEFAULT_RECONNECT_POLICY, u as resolveHeartbeatSeconds } from "./connection-controller-
|
|
17
|
-
import { n as maybeResolveWhatsAppApprovalReaction } from "./approval-reactions-
|
|
18
|
-
import { t as maybeResolveWhatsAppQuestionReaction } from "./question-reactions-
|
|
19
|
-
import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
|
+
import { _ as resolveInboundMediaMimetype, a as addWhatsAppImagePreviewFields, c as extractContextInfo, d as extractMediaKind, f as extractMentionedJids, g as projectWhatsAppInboundMessage, h as hasInboundUserContent, i as resolveWhatsAppOutboundMentions, l as extractExternalAdReplyContext, n as addWhatsAppOutboundMentionsToContent, o as describeReplyContext, p as extractText, r as mayContainWhatsAppOutboundMention, s as extractContactContext, t as createWebSendApi, u as extractLocationData } from "./send-api-obOSm2cC.js";
|
|
14
|
+
import { t as resolveWhatsAppRuntimeGroupPolicy } from "./runtime-group-policy-DeSRFXEV.js";
|
|
15
|
+
import { c as computeBackoff, d as resolveReconnectPolicy, f as sleepWithAbort, l as newConnectionId, n as WHATSAPP_WATCHDOG_TIMEOUT_ERROR, r as WhatsAppConnectionController, s as DEFAULT_RECONNECT_POLICY, u as resolveHeartbeatSeconds } from "./connection-controller-CIYuBJrs.js";
|
|
16
|
+
import { n as maybeResolveWhatsAppApprovalReaction } from "./approval-reactions-Jyr1JaAK.js";
|
|
17
|
+
import { t as maybeResolveWhatsAppQuestionReaction } from "./question-reactions-D3eAeB0i.js";
|
|
18
|
+
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
20
19
|
import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
|
|
21
|
-
import { createAckReactionHandle, createStatusReactionController, logAckFailure, removeAckReactionHandleAfterReply,
|
|
20
|
+
import { createAckReactionHandle, createStatusReactionController, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
|
|
22
21
|
import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
|
|
23
|
-
import {
|
|
22
|
+
import { PlatformMessageNotDispatchedError } from "openclaw/plugin-sdk/error-runtime";
|
|
23
|
+
import { createSubsystemLogger, getChildLogger, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
24
24
|
import { resolveMarkdownTableMode as resolveMarkdownTableMode$1 } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
25
|
-
import { createSubsystemLogger, defaultRuntime, formatDurationPrecise, getChildLogger as getChildLogger$1, logVerbose, logVerbose as logVerbose$1, registerUnhandledRejectionHandler, shouldLogVerbose, shouldLogVerbose as shouldLogVerbose$1, warn } from "openclaw/plugin-sdk/runtime-env";
|
|
25
|
+
import { createSubsystemLogger as createSubsystemLogger$1, defaultRuntime, formatDurationPrecise, getChildLogger as getChildLogger$1, logVerbose, logVerbose as logVerbose$1, registerUnhandledRejectionHandler, shouldLogVerbose, shouldLogVerbose as shouldLogVerbose$1, warn } from "openclaw/plugin-sdk/runtime-env";
|
|
26
26
|
import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
|
|
27
|
+
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
|
28
|
+
import { buildChannelInboundEventContext, createChannelPartialDeliveryError, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundEnvelope as formatInboundEnvelope$1, formatInboundMediaUnavailableText, formatLocationText, formatMediaPlaceholderText, hasVisibleInboundReplyDispatch, isChannelPartialDeliveryError, readAgentRunTerminalOutcome, resolveInboundSessionEnvelopeContext, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFactsWithMetadata, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
27
29
|
import { bindIngressLifecycleToReplyOptions, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, listMessageReceiptPlatformIds, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
28
30
|
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
|
29
31
|
import { isReasoningReplyPayload, resolveSendableOutboundReplyParts, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
|
|
30
32
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
31
33
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
32
|
-
import {
|
|
34
|
+
import { DisconnectReason, downloadMediaMessage, isHostedLidUser, isHostedPnUser, isJidGroup, isJidGroup as isJidGroup$1, jidDecode, jidEncode, jidNormalizedUser, normalizeMessageContent as normalizeMessageContent$1 } from "baileys";
|
|
35
|
+
import { resolveChannelGroupPolicy, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
|
|
33
36
|
import { DEFAULT_ACCOUNT_ID, DEFAULT_MAIN_KEY, buildAgentMainSessionKey, buildAgentSessionKey, buildGroupHistoryKey, deriveLastRoutePolicy, normalizeAccountId, normalizeAgentId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
34
37
|
import { asDateTimestampMs, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
38
|
+
import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
|
|
35
39
|
import { createHash } from "node:crypto";
|
|
36
|
-
import {
|
|
37
|
-
import { getSessionEntry, patchSessionEntry, resolveStorePath, resolveStorePath as resolveStorePath$1, updateLastRoute } from "openclaw/plugin-sdk/session-store-runtime";
|
|
38
|
-
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
|
40
|
+
import { resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
39
41
|
import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
|
40
42
|
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
|
|
41
43
|
import { createInboundDebouncer, resolveInboundDebounceMs } from "openclaw/plugin-sdk/channel-inbound-debounce";
|
|
42
44
|
import { drainPendingDeliveries } from "openclaw/plugin-sdk/delivery-queue-runtime";
|
|
43
45
|
import { DEFAULT_GROUP_HISTORY_LIMIT, buildHistoryContextFromEntries, buildInboundHistoryFromEntries, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
44
46
|
import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
|
|
47
|
+
import { fanInChannelIngressLifecycles, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
48
|
+
import { getSessionEntry, patchSessionEntry, resolveGroupSessionKey, resolveStorePath, resolveStorePath as resolveStorePath$1, updateLastRoute } from "openclaw/plugin-sdk/session-store-runtime";
|
|
45
49
|
import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
|
|
50
|
+
import { upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
|
|
46
51
|
import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
|
|
47
52
|
import { getRuntimeConfig as getRuntimeConfig$1 } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
48
53
|
import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
|
|
49
54
|
import { buildMentionRegexes, implicitMentionKindWhen, normalizeMentionText, resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-mention-gating";
|
|
50
|
-
import {
|
|
55
|
+
import { channelReadyPatch, channelStoppedPatch, createTransportActivityStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
|
51
56
|
import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute } from "openclaw/plugin-sdk/conversation-binding-runtime";
|
|
52
57
|
import { resolveAgentIdentity, resolveIdentityNamePrefix as resolveIdentityNamePrefix$1 } from "openclaw/plugin-sdk/agent-runtime";
|
|
53
58
|
import { normalizeGroupActivation, parseActivationCommand } from "openclaw/plugin-sdk/group-activation";
|
|
59
|
+
import { formatAudioTranscriptForAgent } from "openclaw/plugin-sdk/media-understanding-runtime";
|
|
54
60
|
import { hasControlCommand, isControlCommandMessage, shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-detection";
|
|
55
61
|
import { createInternalHookEvent, deriveInboundMessageHookContext, fireAndForgetBoundedHook, toInternalMessageReceivedContext, toPluginMessageContext, toPluginMessageReceivedEvent, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
|
|
56
62
|
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
57
63
|
import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
|
|
58
64
|
import { LocalMediaAccessError, getDefaultLocalRoots, loadWebMedia, loadWebMediaRaw, optimizeImageToJpeg, optimizeImageToPng } from "openclaw/plugin-sdk/web-media";
|
|
59
65
|
import { createChannelApiRetryRunner } from "openclaw/plugin-sdk/retry-runtime";
|
|
66
|
+
import { resolveAgentConfig } from "openclaw/plugin-sdk/agent-scope-runtime";
|
|
67
|
+
//#region extensions/whatsapp/src/inbound/group-conversation.ts
|
|
68
|
+
function resolveWhatsAppGroupConversationId(conversationId) {
|
|
69
|
+
return resolveGroupSessionKey({
|
|
70
|
+
From: conversationId,
|
|
71
|
+
ChatType: "group",
|
|
72
|
+
Provider: "whatsapp"
|
|
73
|
+
})?.id ?? conversationId;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region extensions/whatsapp/src/inbound/admission.ts
|
|
77
|
+
const ingressResolverByAdmission = /* @__PURE__ */ new WeakMap();
|
|
78
|
+
function copyAccount(account) {
|
|
79
|
+
const copied = {
|
|
80
|
+
accountId: account.accountId,
|
|
81
|
+
enabled: account.enabled,
|
|
82
|
+
sendReadReceipts: account.sendReadReceipts
|
|
83
|
+
};
|
|
84
|
+
if (account.name) copied.name = account.name;
|
|
85
|
+
if (typeof account.selfChatMode === "boolean") copied.selfChatMode = account.selfChatMode;
|
|
86
|
+
if (account.replyToMode) copied.replyToMode = account.replyToMode;
|
|
87
|
+
return copied;
|
|
88
|
+
}
|
|
89
|
+
function buildWhatsAppInboundAdmission(params) {
|
|
90
|
+
const admission = {
|
|
91
|
+
channelIngress: params.channelIngress,
|
|
92
|
+
accountId: params.policy.account.accountId,
|
|
93
|
+
isSelfChat: params.policy.isSelfChat,
|
|
94
|
+
account: copyAccount(params.policy.account),
|
|
95
|
+
conversation: {
|
|
96
|
+
kind: params.isGroup ? "group" : "direct",
|
|
97
|
+
id: params.conversationId,
|
|
98
|
+
groupSessionId: resolveWhatsAppGroupConversationId(params.conversationId)
|
|
99
|
+
},
|
|
100
|
+
sender: {
|
|
101
|
+
id: params.senderId,
|
|
102
|
+
isSamePhone: params.policy.isSamePhone(params.senderId)
|
|
103
|
+
},
|
|
104
|
+
ingress: {
|
|
105
|
+
admission: params.access.ingress.admission,
|
|
106
|
+
decision: params.access.ingress.decision,
|
|
107
|
+
decisiveGateId: params.access.ingress.decisiveGateId,
|
|
108
|
+
reasonCode: params.access.ingress.reasonCode
|
|
109
|
+
},
|
|
110
|
+
senderAccess: {
|
|
111
|
+
allowed: params.access.senderAccess.allowed,
|
|
112
|
+
decision: params.access.senderAccess.decision,
|
|
113
|
+
reasonCode: params.access.senderAccess.reasonCode,
|
|
114
|
+
providerMissingFallbackApplied: params.access.senderAccess.providerMissingFallbackApplied
|
|
115
|
+
},
|
|
116
|
+
commandAccess: {
|
|
117
|
+
requested: params.access.commandAccess.requested,
|
|
118
|
+
authorized: params.access.commandAccess.authorized,
|
|
119
|
+
shouldBlockControlCommand: params.access.commandAccess.shouldBlockControlCommand,
|
|
120
|
+
reasonCode: params.access.commandAccess.reasonCode
|
|
121
|
+
},
|
|
122
|
+
activationAccess: {
|
|
123
|
+
ran: params.access.activationAccess.ran,
|
|
124
|
+
allowed: params.access.activationAccess.allowed,
|
|
125
|
+
shouldSkip: params.access.activationAccess.shouldSkip,
|
|
126
|
+
reasonCode: params.access.activationAccess.reasonCode
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
if (params.resolveChannelIngress) ingressResolverByAdmission.set(admission, params.resolveChannelIngress);
|
|
130
|
+
return admission;
|
|
131
|
+
}
|
|
132
|
+
async function resolveWhatsAppAdmissionChannelIngress(admission, contextBinding) {
|
|
133
|
+
return await ingressResolverByAdmission.get(admission)?.(contextBinding);
|
|
134
|
+
}
|
|
135
|
+
function requireWhatsAppInboundAdmission(params) {
|
|
136
|
+
if (!params.admission) throw new Error("WhatsApp inbound message is missing admission facts");
|
|
137
|
+
return params.admission;
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region extensions/whatsapp/src/inbound-policy.ts
|
|
141
|
+
function normalizeWhatsAppIngressPhone(value) {
|
|
142
|
+
const trimmed = value.trim();
|
|
143
|
+
if (!trimmed) return null;
|
|
144
|
+
return normalizeE164(trimmed);
|
|
145
|
+
}
|
|
146
|
+
function buildResolvedWhatsAppGroupConfig(params) {
|
|
147
|
+
return { channels: { whatsapp: {
|
|
148
|
+
groupPolicy: params.groupPolicy,
|
|
149
|
+
groups: params.groups
|
|
150
|
+
} } };
|
|
151
|
+
}
|
|
152
|
+
function resolveWhatsAppInboundPolicy(params) {
|
|
153
|
+
const account = resolveWhatsAppAccount({
|
|
154
|
+
cfg: params.cfg,
|
|
155
|
+
accountId: params.accountId
|
|
156
|
+
});
|
|
157
|
+
const configuredAllowFrom = account.allowFrom ?? [];
|
|
158
|
+
const dmPolicy = account.dmPolicy ?? "pairing";
|
|
159
|
+
const dmAllowFrom = configuredAllowFrom.length > 0 ? configuredAllowFrom : params.selfE164 ? [params.selfE164] : [];
|
|
160
|
+
const groupAllowFrom = (Array.isArray(account.groupAllowFrom) && account.groupAllowFrom.length > 0 ? account.groupAllowFrom : void 0) ?? (configuredAllowFrom.length > 0 ? configuredAllowFrom : void 0) ?? [];
|
|
161
|
+
const defaultGroupPolicy = resolveDefaultGroupPolicy(params.cfg);
|
|
162
|
+
const { groupPolicy, providerMissingFallbackApplied } = resolveWhatsAppRuntimeGroupPolicy({
|
|
163
|
+
providerConfigPresent: params.cfg.channels?.whatsapp !== void 0,
|
|
164
|
+
groupPolicy: account.groupPolicy,
|
|
165
|
+
defaultGroupPolicy
|
|
166
|
+
});
|
|
167
|
+
const resolvedGroupCfg = buildResolvedWhatsAppGroupConfig({
|
|
168
|
+
groupPolicy,
|
|
169
|
+
groups: account.groups
|
|
170
|
+
});
|
|
171
|
+
const isSamePhone = (value) => typeof value === "string" && typeof params.selfE164 === "string" && value === params.selfE164;
|
|
172
|
+
return {
|
|
173
|
+
account,
|
|
174
|
+
dmPolicy,
|
|
175
|
+
groupPolicy,
|
|
176
|
+
configuredAllowFrom,
|
|
177
|
+
dmAllowFrom,
|
|
178
|
+
groupAllowFrom,
|
|
179
|
+
isSelfChat: account.selfChatMode ?? isSelfChatMode(params.selfE164, configuredAllowFrom),
|
|
180
|
+
providerMissingFallbackApplied,
|
|
181
|
+
isSamePhone,
|
|
182
|
+
resolveConversationGroupPolicy: (conversationId) => resolveChannelGroupPolicy({
|
|
183
|
+
cfg: resolvedGroupCfg,
|
|
184
|
+
channel: "whatsapp",
|
|
185
|
+
groupId: resolveWhatsAppGroupConversationId(conversationId),
|
|
186
|
+
hasGroupAllowFrom: groupAllowFrom.length > 0
|
|
187
|
+
}),
|
|
188
|
+
resolveConversationRequireMention: (conversationId) => resolveChannelGroupRequireMention({
|
|
189
|
+
cfg: resolvedGroupCfg,
|
|
190
|
+
channel: "whatsapp",
|
|
191
|
+
groupId: resolveWhatsAppGroupConversationId(conversationId)
|
|
192
|
+
})
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async function resolveWhatsAppIngressAccess(params) {
|
|
196
|
+
return await resolveStableChannelMessageIngress({
|
|
197
|
+
channelId: "whatsapp",
|
|
198
|
+
accountId: params.policy.account.accountId,
|
|
199
|
+
identity: {
|
|
200
|
+
key: "whatsapp-sender-phone",
|
|
201
|
+
kind: "phone",
|
|
202
|
+
normalize: normalizeWhatsAppIngressPhone,
|
|
203
|
+
sensitivity: "pii",
|
|
204
|
+
entryIdPrefix: "whatsapp-entry"
|
|
205
|
+
},
|
|
206
|
+
cfg: params.cfg,
|
|
207
|
+
useDefaultPairingStore: true,
|
|
208
|
+
subject: { stableId: params.senderId ?? "" },
|
|
209
|
+
conversation: {
|
|
210
|
+
kind: params.isGroup ? "group" : "direct",
|
|
211
|
+
id: params.conversationId
|
|
212
|
+
},
|
|
213
|
+
contextBinding: params.contextBinding,
|
|
214
|
+
dmPolicy: params.policy.dmPolicy,
|
|
215
|
+
groupPolicy: params.policy.groupPolicy,
|
|
216
|
+
policy: { groupAllowFromFallbackToAllowFrom: false },
|
|
217
|
+
providerMissingFallbackApplied: params.policy.providerMissingFallbackApplied,
|
|
218
|
+
allowFrom: !params.isGroup && params.policy.account.selfChatMode !== false && params.senderId && params.policy.isSamePhone(params.senderId) ? [...params.policy.dmAllowFrom, params.senderId] : params.policy.dmAllowFrom,
|
|
219
|
+
groupAllowFrom: params.policy.groupAllowFrom,
|
|
220
|
+
command: params.includeCommand === true ? {} : void 0
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async function resolveWhatsAppCommandAuthorized(params) {
|
|
224
|
+
const self = getSelfIdentity(params.msg, params.authDir);
|
|
225
|
+
const admission = requireWhatsAppInboundAdmission(params.msg);
|
|
226
|
+
const policy = params.policy ?? resolveWhatsAppInboundPolicy({
|
|
227
|
+
cfg: params.cfg,
|
|
228
|
+
accountId: admission.accountId,
|
|
229
|
+
selfE164: self.e164 ?? null
|
|
230
|
+
});
|
|
231
|
+
const isGroup = admission.conversation.kind === "group";
|
|
232
|
+
const sender = getSenderIdentity(params.msg, params.authDir);
|
|
233
|
+
const dmSender = sender.e164 ?? admission.conversation.id;
|
|
234
|
+
const groupSender = sender.e164 ?? "";
|
|
235
|
+
if (!normalizeE164(isGroup ? groupSender : dmSender)) return false;
|
|
236
|
+
return (await resolveWhatsAppIngressAccess({
|
|
237
|
+
cfg: params.cfg,
|
|
238
|
+
policy,
|
|
239
|
+
isGroup,
|
|
240
|
+
conversationId: admission.conversation.id,
|
|
241
|
+
senderId: isGroup ? groupSender : dmSender,
|
|
242
|
+
includeCommand: true
|
|
243
|
+
})).commandAccess.authorized;
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
60
246
|
//#region extensions/whatsapp/src/inbound/baileys-cache.ts
|
|
61
247
|
const WHATSAPP_BAILEYS_CACHE_MAX_ENTRIES = 500;
|
|
62
248
|
function rememberWhatsAppBaileysCacheEntry(cache, key, value, ttlMs) {
|
|
@@ -84,353 +270,6 @@ function readWhatsAppBaileysCacheEntry(cache, key) {
|
|
|
84
270
|
return entry.value;
|
|
85
271
|
}
|
|
86
272
|
//#endregion
|
|
87
|
-
//#region extensions/whatsapp/src/inbound/message-aliases.ts
|
|
88
|
-
function normalizeQuoteSender(sender) {
|
|
89
|
-
if (!sender?.displayName && !sender?.jid && !sender?.e164) return;
|
|
90
|
-
return sender;
|
|
91
|
-
}
|
|
92
|
-
function buildQuoteFromFlatAliases(msg) {
|
|
93
|
-
if (msg.replyTo) return {
|
|
94
|
-
context: msg.replyTo,
|
|
95
|
-
id: msg.replyTo.id,
|
|
96
|
-
body: msg.replyTo.body,
|
|
97
|
-
sender: normalizeQuoteSender({
|
|
98
|
-
displayName: msg.replyTo.sender?.label ?? msg.replyToSender,
|
|
99
|
-
jid: msg.replyTo.sender?.jid ?? msg.replyToSenderJid,
|
|
100
|
-
e164: msg.replyTo.sender?.e164 ?? msg.replyToSenderE164
|
|
101
|
-
})
|
|
102
|
-
};
|
|
103
|
-
if (!msg.replyToId && !msg.replyToBody && !msg.replyToSender && !msg.replyToSenderJid && !msg.replyToSenderE164) return;
|
|
104
|
-
return {
|
|
105
|
-
id: msg.replyToId,
|
|
106
|
-
body: msg.replyToBody,
|
|
107
|
-
sender: normalizeQuoteSender({
|
|
108
|
-
displayName: msg.replyToSender,
|
|
109
|
-
jid: msg.replyToSenderJid,
|
|
110
|
-
e164: msg.replyToSenderE164
|
|
111
|
-
})
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function buildGroupFromFlatAliases(msg) {
|
|
115
|
-
const mentionJids = msg.mentions ?? msg.mentionedJids;
|
|
116
|
-
if (!msg.groupSubject && !msg.groupParticipants?.length && !mentionJids?.length) return;
|
|
117
|
-
return {
|
|
118
|
-
subject: msg.groupSubject,
|
|
119
|
-
participants: msg.groupParticipants,
|
|
120
|
-
mentions: mentionJids?.length ? { jids: mentionJids } : void 0
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
function ensureQuote(msg) {
|
|
124
|
-
return msg.quote ??= {};
|
|
125
|
-
}
|
|
126
|
-
function ensureQuoteSender(msg) {
|
|
127
|
-
const quote = ensureQuote(msg);
|
|
128
|
-
return quote.sender ??= {};
|
|
129
|
-
}
|
|
130
|
-
function ensureGroup(msg) {
|
|
131
|
-
return msg.group ??= {};
|
|
132
|
-
}
|
|
133
|
-
function ensureGroupMentions(msg) {
|
|
134
|
-
const group = ensureGroup(msg);
|
|
135
|
-
return group.mentions ??= {};
|
|
136
|
-
}
|
|
137
|
-
function ensureMedia(msg) {
|
|
138
|
-
return msg.payload.media ??= {};
|
|
139
|
-
}
|
|
140
|
-
function setMediaField(msg, key, value) {
|
|
141
|
-
if (value === void 0 && !msg.payload.media) return;
|
|
142
|
-
ensureMedia(msg)[key] = value;
|
|
143
|
-
}
|
|
144
|
-
function defineDeprecatedAccessors(msg, descriptors) {
|
|
145
|
-
Object.defineProperties(msg, Object.fromEntries(Object.entries(descriptors).map(([key, descriptor]) => [key, {
|
|
146
|
-
configurable: true,
|
|
147
|
-
enumerable: true,
|
|
148
|
-
get: descriptor.get,
|
|
149
|
-
set: descriptor.set
|
|
150
|
-
}])));
|
|
151
|
-
return msg;
|
|
152
|
-
}
|
|
153
|
-
function defineDeprecatedAliasAccessors(msg, descriptors) {
|
|
154
|
-
defineDeprecatedAccessors(msg, descriptors);
|
|
155
|
-
return msg;
|
|
156
|
-
}
|
|
157
|
-
function defineDeprecatedStructuredContextPayloadAlias(msg) {
|
|
158
|
-
const channelStructuredContext = msg.payload.channelStructuredContext ?? msg.payload.untrustedStructuredContext;
|
|
159
|
-
if (channelStructuredContext !== void 0) msg.payload.channelStructuredContext = channelStructuredContext;
|
|
160
|
-
Object.defineProperty(msg.payload, "untrustedStructuredContext", {
|
|
161
|
-
configurable: true,
|
|
162
|
-
enumerable: true,
|
|
163
|
-
get: () => msg.payload.channelStructuredContext,
|
|
164
|
-
set: (value) => {
|
|
165
|
-
msg.payload.channelStructuredContext = value;
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
function defineDeprecatedAdmissionTopLevelAccessors(msg) {
|
|
170
|
-
let fallbackConversationId = msg.conversationId || msg.from;
|
|
171
|
-
let fallbackAccountId = msg.accountId;
|
|
172
|
-
let fallbackAccessControlPassed = msg.accessControlPassed;
|
|
173
|
-
let fallbackChatType = msg.chatType;
|
|
174
|
-
const conversationId = () => msg.admission?.conversation.id ?? fallbackConversationId;
|
|
175
|
-
const setConversationId = (value) => {
|
|
176
|
-
const next = value;
|
|
177
|
-
fallbackConversationId = next;
|
|
178
|
-
if (msg.admission) {
|
|
179
|
-
msg.admission.conversation.id = next;
|
|
180
|
-
msg.admission.conversation.groupSessionId = resolveWhatsAppGroupConversationId(next);
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
return defineDeprecatedAccessors(msg, {
|
|
184
|
-
from: {
|
|
185
|
-
get: conversationId,
|
|
186
|
-
set: setConversationId
|
|
187
|
-
},
|
|
188
|
-
conversationId: {
|
|
189
|
-
get: conversationId,
|
|
190
|
-
set: setConversationId
|
|
191
|
-
},
|
|
192
|
-
accountId: {
|
|
193
|
-
get: () => msg.admission?.accountId ?? fallbackAccountId,
|
|
194
|
-
set: (value) => {
|
|
195
|
-
const next = value;
|
|
196
|
-
fallbackAccountId = next;
|
|
197
|
-
if (msg.admission) {
|
|
198
|
-
msg.admission.accountId = next;
|
|
199
|
-
msg.admission.account.accountId = next;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
accessControlPassed: {
|
|
204
|
-
get: () => {
|
|
205
|
-
if (msg.admission?.ingress.decisiveGateId === "legacy-flat-compat") return fallbackAccessControlPassed;
|
|
206
|
-
return msg.admission ? msg.admission.ingress.decision === "allow" : fallbackAccessControlPassed;
|
|
207
|
-
},
|
|
208
|
-
set: (value) => {
|
|
209
|
-
if (!msg.admission) fallbackAccessControlPassed = value;
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
chatType: {
|
|
213
|
-
get: () => msg.admission?.conversation.kind ?? fallbackChatType,
|
|
214
|
-
set: (value) => {
|
|
215
|
-
const next = value;
|
|
216
|
-
fallbackChatType = next;
|
|
217
|
-
if (msg.admission) msg.admission.conversation.kind = next;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
function withDeprecatedWebInboundMessageFlatAliases(msg) {
|
|
223
|
-
defineDeprecatedStructuredContextPayloadAlias(msg);
|
|
224
|
-
return defineDeprecatedAliasAccessors(defineDeprecatedAdmissionTopLevelAccessors(msg), {
|
|
225
|
-
id: {
|
|
226
|
-
get: () => msg.event.id,
|
|
227
|
-
set: (value) => msg.event.id = value
|
|
228
|
-
},
|
|
229
|
-
to: {
|
|
230
|
-
get: () => msg.platform.recipientJid,
|
|
231
|
-
set: (value) => msg.platform.recipientJid = value
|
|
232
|
-
},
|
|
233
|
-
body: {
|
|
234
|
-
get: () => msg.payload.body,
|
|
235
|
-
set: (value) => msg.payload.body = value
|
|
236
|
-
},
|
|
237
|
-
pushName: {
|
|
238
|
-
get: () => msg.platform.pushName,
|
|
239
|
-
set: (value) => msg.platform.pushName = value
|
|
240
|
-
},
|
|
241
|
-
timestamp: {
|
|
242
|
-
get: () => msg.event.timestamp,
|
|
243
|
-
set: (value) => msg.event.timestamp = value
|
|
244
|
-
},
|
|
245
|
-
chatId: {
|
|
246
|
-
get: () => msg.platform.chatJid,
|
|
247
|
-
set: (value) => msg.platform.chatJid = value
|
|
248
|
-
},
|
|
249
|
-
sender: {
|
|
250
|
-
get: () => msg.platform.sender,
|
|
251
|
-
set: (value) => msg.platform.sender = value
|
|
252
|
-
},
|
|
253
|
-
senderJid: {
|
|
254
|
-
get: () => msg.platform.senderJid,
|
|
255
|
-
set: (value) => msg.platform.senderJid = value
|
|
256
|
-
},
|
|
257
|
-
senderE164: {
|
|
258
|
-
get: () => msg.platform.senderE164,
|
|
259
|
-
set: (value) => msg.platform.senderE164 = value
|
|
260
|
-
},
|
|
261
|
-
senderName: {
|
|
262
|
-
get: () => msg.platform.senderName,
|
|
263
|
-
set: (value) => msg.platform.senderName = value
|
|
264
|
-
},
|
|
265
|
-
replyTo: {
|
|
266
|
-
get: () => msg.quote?.context,
|
|
267
|
-
set: (value) => ensureQuote(msg).context = value
|
|
268
|
-
},
|
|
269
|
-
replyToId: {
|
|
270
|
-
get: () => msg.quote?.id ?? msg.quote?.context?.id,
|
|
271
|
-
set: (value) => ensureQuote(msg).id = value
|
|
272
|
-
},
|
|
273
|
-
replyToBody: {
|
|
274
|
-
get: () => msg.quote?.body ?? msg.quote?.context?.body,
|
|
275
|
-
set: (value) => ensureQuote(msg).body = value
|
|
276
|
-
},
|
|
277
|
-
replyToSender: {
|
|
278
|
-
get: () => msg.quote?.context?.sender?.label ?? msg.quote?.sender?.displayName,
|
|
279
|
-
set: (value) => {
|
|
280
|
-
const sender = ensureQuoteSender(msg);
|
|
281
|
-
sender.displayName = value;
|
|
282
|
-
if (msg.quote?.context?.sender) msg.quote.context.sender.label = value;
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
replyToSenderJid: {
|
|
286
|
-
get: () => msg.quote?.context?.sender?.jid ?? msg.quote?.sender?.jid,
|
|
287
|
-
set: (value) => {
|
|
288
|
-
const jid = value;
|
|
289
|
-
ensureQuoteSender(msg).jid = jid;
|
|
290
|
-
if (msg.quote?.context?.sender) msg.quote.context.sender.jid = jid;
|
|
291
|
-
}
|
|
292
|
-
},
|
|
293
|
-
replyToSenderE164: {
|
|
294
|
-
get: () => msg.quote?.context?.sender?.e164 ?? msg.quote?.sender?.e164,
|
|
295
|
-
set: (value) => {
|
|
296
|
-
const e164 = value;
|
|
297
|
-
ensureQuoteSender(msg).e164 = e164;
|
|
298
|
-
if (msg.quote?.context?.sender) msg.quote.context.sender.e164 = e164;
|
|
299
|
-
}
|
|
300
|
-
},
|
|
301
|
-
groupSubject: {
|
|
302
|
-
get: () => msg.group?.subject,
|
|
303
|
-
set: (value) => ensureGroup(msg).subject = value
|
|
304
|
-
},
|
|
305
|
-
groupParticipants: {
|
|
306
|
-
get: () => msg.group?.participants,
|
|
307
|
-
set: (value) => ensureGroup(msg).participants = value
|
|
308
|
-
},
|
|
309
|
-
mentions: {
|
|
310
|
-
get: () => msg.group?.mentions?.jids,
|
|
311
|
-
set: (value) => ensureGroupMentions(msg).jids = value
|
|
312
|
-
},
|
|
313
|
-
mentionedJids: {
|
|
314
|
-
get: () => msg.group?.mentions?.jids,
|
|
315
|
-
set: (value) => ensureGroupMentions(msg).jids = value
|
|
316
|
-
},
|
|
317
|
-
self: {
|
|
318
|
-
get: () => msg.platform.self,
|
|
319
|
-
set: (value) => msg.platform.self = value
|
|
320
|
-
},
|
|
321
|
-
selfJid: {
|
|
322
|
-
get: () => msg.platform.selfJid,
|
|
323
|
-
set: (value) => msg.platform.selfJid = value
|
|
324
|
-
},
|
|
325
|
-
selfLid: {
|
|
326
|
-
get: () => msg.platform.selfLid,
|
|
327
|
-
set: (value) => msg.platform.selfLid = value
|
|
328
|
-
},
|
|
329
|
-
selfE164: {
|
|
330
|
-
get: () => msg.platform.selfE164,
|
|
331
|
-
set: (value) => msg.platform.selfE164 = value
|
|
332
|
-
},
|
|
333
|
-
fromMe: {
|
|
334
|
-
get: () => msg.platform.fromMe,
|
|
335
|
-
set: (value) => msg.platform.fromMe = value
|
|
336
|
-
},
|
|
337
|
-
location: {
|
|
338
|
-
get: () => msg.payload.location,
|
|
339
|
-
set: (value) => msg.payload.location = value
|
|
340
|
-
},
|
|
341
|
-
sendComposing: {
|
|
342
|
-
get: () => msg.platform.sendComposing,
|
|
343
|
-
set: (value) => msg.platform.sendComposing = value
|
|
344
|
-
},
|
|
345
|
-
reply: {
|
|
346
|
-
get: () => msg.platform.reply,
|
|
347
|
-
set: (value) => msg.platform.reply = value
|
|
348
|
-
},
|
|
349
|
-
sendMedia: {
|
|
350
|
-
get: () => msg.platform.sendMedia,
|
|
351
|
-
set: (value) => msg.platform.sendMedia = value
|
|
352
|
-
},
|
|
353
|
-
mediaPath: {
|
|
354
|
-
get: () => msg.payload.media?.path,
|
|
355
|
-
set: (value) => setMediaField(msg, "path", value)
|
|
356
|
-
},
|
|
357
|
-
mediaType: {
|
|
358
|
-
get: () => msg.payload.media?.type,
|
|
359
|
-
set: (value) => setMediaField(msg, "type", value)
|
|
360
|
-
},
|
|
361
|
-
mediaFileName: {
|
|
362
|
-
get: () => msg.payload.media?.fileName,
|
|
363
|
-
set: (value) => setMediaField(msg, "fileName", value)
|
|
364
|
-
},
|
|
365
|
-
mediaUrl: {
|
|
366
|
-
get: () => msg.payload.media?.url,
|
|
367
|
-
set: (value) => setMediaField(msg, "url", value)
|
|
368
|
-
},
|
|
369
|
-
channelStructuredContext: {
|
|
370
|
-
get: () => msg.payload.channelStructuredContext,
|
|
371
|
-
set: (value) => msg.payload.channelStructuredContext = value
|
|
372
|
-
},
|
|
373
|
-
untrustedStructuredContext: {
|
|
374
|
-
get: () => msg.payload.channelStructuredContext,
|
|
375
|
-
set: (value) => msg.payload.channelStructuredContext = value
|
|
376
|
-
},
|
|
377
|
-
isBatched: {
|
|
378
|
-
get: () => msg.event.isBatched,
|
|
379
|
-
set: (value) => msg.event.isBatched = value
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
function normalizeLegacyFlatWebInboundMessage(msg) {
|
|
384
|
-
const media = msg.mediaPath || msg.mediaType || msg.mediaFileName || msg.mediaUrl ? {
|
|
385
|
-
path: msg.mediaPath,
|
|
386
|
-
type: msg.mediaType,
|
|
387
|
-
fileName: msg.mediaFileName,
|
|
388
|
-
url: msg.mediaUrl
|
|
389
|
-
} : void 0;
|
|
390
|
-
return withDeprecatedWebInboundMessageFlatAliases({
|
|
391
|
-
...msg,
|
|
392
|
-
admission: msg.admission ?? buildDeprecatedFlatWhatsAppInboundAdmission(msg),
|
|
393
|
-
event: {
|
|
394
|
-
id: msg.id,
|
|
395
|
-
timestamp: msg.timestamp,
|
|
396
|
-
isBatched: msg.isBatched
|
|
397
|
-
},
|
|
398
|
-
payload: {
|
|
399
|
-
body: msg.body,
|
|
400
|
-
media,
|
|
401
|
-
location: msg.location,
|
|
402
|
-
channelStructuredContext: msg.channelStructuredContext ?? msg.untrustedStructuredContext
|
|
403
|
-
},
|
|
404
|
-
platform: {
|
|
405
|
-
chatJid: msg.chatId,
|
|
406
|
-
recipientJid: msg.to,
|
|
407
|
-
sender: msg.sender,
|
|
408
|
-
senderJid: msg.senderJid,
|
|
409
|
-
senderE164: msg.senderE164,
|
|
410
|
-
senderName: msg.senderName,
|
|
411
|
-
pushName: msg.pushName,
|
|
412
|
-
self: msg.self,
|
|
413
|
-
selfJid: msg.selfJid,
|
|
414
|
-
selfLid: msg.selfLid,
|
|
415
|
-
selfE164: msg.selfE164,
|
|
416
|
-
fromMe: msg.fromMe,
|
|
417
|
-
sendComposing: msg.sendComposing,
|
|
418
|
-
reply: msg.reply,
|
|
419
|
-
sendMedia: msg.sendMedia
|
|
420
|
-
},
|
|
421
|
-
quote: buildQuoteFromFlatAliases(msg),
|
|
422
|
-
group: buildGroupFromFlatAliases(msg)
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
function normalizeWebInboundMessage(msg) {
|
|
426
|
-
if (msg.event && msg.payload && msg.platform) return withDeprecatedWebInboundMessageFlatAliases(msg);
|
|
427
|
-
if (msg.event || msg.payload || msg.platform || msg.quote || msg.group) throw new Error("WhatsApp inbound messages must be either legacy flat or canonical nested; partial nested contexts are not supported.");
|
|
428
|
-
return normalizeLegacyFlatWebInboundMessage(msg);
|
|
429
|
-
}
|
|
430
|
-
function normalizeAdmittedWebInboundMessage(msg) {
|
|
431
|
-
return requireAdmittedWhatsAppInboundMessage(normalizeWebInboundMessage(msg));
|
|
432
|
-
}
|
|
433
|
-
//#endregion
|
|
434
273
|
//#region extensions/whatsapp/src/inbound/durable-payload.ts
|
|
435
274
|
var WhatsAppIngressPermanentError = class extends Error {
|
|
436
275
|
constructor(reason, message, options) {
|
|
@@ -625,10 +464,14 @@ function createWhatsAppGroupMetadataCacheOwner(params) {
|
|
|
625
464
|
const cached = readGroupMetadataCacheEntry(localCache, jid);
|
|
626
465
|
if (cached) return cached;
|
|
627
466
|
try {
|
|
628
|
-
const
|
|
629
|
-
|
|
467
|
+
const hydratedEntry = params.baileysCache?.get(jid);
|
|
468
|
+
const providerMetadata = params.baileysCache ? readWhatsAppBaileysCacheEntry(params.baileysCache, jid) : void 0;
|
|
469
|
+
const hydratedMetadata = providerMetadata?.participants?.length ? providerMetadata : void 0;
|
|
470
|
+
const meta = hydratedMetadata ?? await (params.getCurrentSock() ?? params.sock).groupMetadata(jid);
|
|
471
|
+
if (!hydratedMetadata) rememberWhatsAppBaileysCacheEntry(params.baileysCache, jid, meta, GROUP_META_TTL_MS);
|
|
630
472
|
publishedJids.add(jid);
|
|
631
473
|
const entry = await summarize(meta);
|
|
474
|
+
if (hydratedMetadata && hydratedEntry) entry.expires = hydratedEntry.expiresAt;
|
|
632
475
|
rememberGroupMetadataCacheEntry(reconnectCache, jid, {
|
|
633
476
|
subject: entry.subject,
|
|
634
477
|
expires: entry.expires
|
|
@@ -647,7 +490,7 @@ function createWhatsAppGroupMetadataCacheOwner(params) {
|
|
|
647
490
|
}
|
|
648
491
|
};
|
|
649
492
|
const resolveOutboundMentions = async (jid, text) => {
|
|
650
|
-
if (isJidGroup(jid) !== true || !mayContainWhatsAppOutboundMention(text)) return {
|
|
493
|
+
if (isJidGroup$1(jid) !== true || !mayContainWhatsAppOutboundMention(text)) return {
|
|
651
494
|
text,
|
|
652
495
|
mentionedJids: []
|
|
653
496
|
};
|
|
@@ -819,7 +662,7 @@ function createWhatsAppInboundMessageDebouncer(options) {
|
|
|
819
662
|
...last.group,
|
|
820
663
|
mentions: combinedMentions
|
|
821
664
|
} : void 0;
|
|
822
|
-
const combinedMessage = attachWhatsAppIngressLifecycle(
|
|
665
|
+
const combinedMessage = attachWhatsAppIngressLifecycle({
|
|
823
666
|
...last,
|
|
824
667
|
turnAdoptionLifecycle: admissionLifecycle,
|
|
825
668
|
payload: {
|
|
@@ -832,7 +675,7 @@ function createWhatsAppInboundMessageDebouncer(options) {
|
|
|
832
675
|
...last.event,
|
|
833
676
|
isBatched: true
|
|
834
677
|
}
|
|
835
|
-
}
|
|
678
|
+
}, admissionLifecycle);
|
|
836
679
|
await options.onMessage(combinedMessage);
|
|
837
680
|
await settle();
|
|
838
681
|
await Promise.all(orderedEntries.map((entry) => options.markRead(entry.readReceipt)));
|
|
@@ -898,27 +741,21 @@ function createWhatsAppInboundMessageDebouncer(options) {
|
|
|
898
741
|
}
|
|
899
742
|
//#endregion
|
|
900
743
|
//#region extensions/whatsapp/src/inbound/media.ts
|
|
901
|
-
var WhatsAppInboundMediaLimitExceededError = class extends Error {
|
|
902
|
-
constructor(maxBytes) {
|
|
903
|
-
super(`Media exceeds ${Math.round(maxBytes / (1024 * 1024))}MB limit`);
|
|
904
|
-
this.name = "WhatsAppInboundMediaLimitExceededError";
|
|
905
|
-
}
|
|
906
|
-
};
|
|
907
744
|
function unwrapMessage(message) {
|
|
908
|
-
return normalizeMessageContent(message);
|
|
745
|
+
return normalizeMessageContent$1(message);
|
|
909
746
|
}
|
|
910
|
-
async function downloadInboundMedia(msg, sock, maxBytes = 50 * 1024 * 1024) {
|
|
911
|
-
const message = unwrapMessage(msg.message);
|
|
747
|
+
async function downloadInboundMedia(msg, sock, maxBytes = 50 * 1024 * 1024, normalizedMessage) {
|
|
748
|
+
const message = normalizedMessage ?? unwrapMessage(msg.message);
|
|
912
749
|
if (!message) return;
|
|
913
750
|
const mimetype = resolveInboundMediaMimetype(message);
|
|
914
751
|
const fileName = message.documentMessage?.fileName ?? void 0;
|
|
915
|
-
if (!message.imageMessage && !message.videoMessage && !message.documentMessage && !message.audioMessage && !message.stickerMessage) return;
|
|
752
|
+
if (!message.imageMessage && !message.videoMessage && !message.ptvMessage && !message.documentMessage && !message.audioMessage && !message.stickerMessage) return;
|
|
916
753
|
return {
|
|
917
754
|
saved: await saveMediaStream(await downloadMediaMessage(msg, "stream", {}, {
|
|
918
755
|
reuploadRequest: sock.updateMediaMessage,
|
|
919
756
|
logger: sock.logger
|
|
920
757
|
}), mimetype, "inbound", maxBytes, fileName).catch((err) => {
|
|
921
|
-
if (err instanceof Error && /Media exceeds/i.test(err.message)) throw new
|
|
758
|
+
if (err instanceof Error && /Media exceeds/i.test(err.message)) throw new Error(`Media exceeds ${Math.round(maxBytes / (1024 * 1024))}MB limit`);
|
|
922
759
|
throw err;
|
|
923
760
|
}),
|
|
924
761
|
mimetype,
|
|
@@ -929,12 +766,18 @@ async function downloadQuotedInboundMedia(msg, sock, maxBytes = 50 * 1024 * 1024
|
|
|
929
766
|
const contextInfo = extractContextInfo(unwrapMessage(msg.message));
|
|
930
767
|
if (!contextInfo?.quotedMessage) return;
|
|
931
768
|
const quotedMessage = contextInfo.quotedMessage;
|
|
769
|
+
const self = sock.user;
|
|
770
|
+
const quotedFromMe = identitiesOverlap({ jid: contextInfo.participant }, {
|
|
771
|
+
jid: self?.id,
|
|
772
|
+
lid: self?.lid,
|
|
773
|
+
e164: self?.phoneNumber
|
|
774
|
+
});
|
|
932
775
|
return downloadInboundMedia({
|
|
933
776
|
key: {
|
|
934
777
|
id: contextInfo?.stanzaId || void 0,
|
|
935
778
|
remoteJid: contextInfo.remoteJid ?? msg.key?.remoteJid ?? void 0,
|
|
936
779
|
participant: contextInfo?.participant ?? void 0,
|
|
937
|
-
fromMe:
|
|
780
|
+
fromMe: quotedFromMe
|
|
938
781
|
},
|
|
939
782
|
message: quotedMessage,
|
|
940
783
|
messageTimestamp: msg.messageTimestamp
|
|
@@ -942,19 +785,46 @@ async function downloadQuotedInboundMedia(msg, sock, maxBytes = 50 * 1024 * 1024
|
|
|
942
785
|
}
|
|
943
786
|
//#endregion
|
|
944
787
|
//#region extensions/whatsapp/src/inbound/message-enrichment.ts
|
|
788
|
+
const inboundMediaLogger = createSubsystemLogger("gateway/channels/whatsapp").child("inbound");
|
|
789
|
+
const MAX_MEDIA_ERROR_MESSAGE_CHARS = 256;
|
|
790
|
+
function sanitizeMediaErrorMessage(error) {
|
|
791
|
+
return truncateUtf16Safe(redactToolPayloadText(error instanceof Error ? error.message : String(error)).replace(/https?:\/\/\S+/giu, "[redacted-url]").replace(/\b[^@\s]+@[a-z][a-z\d.-]*\b/giu, "[redacted-jid]").replace(/\+?\d[\d ().-]{6,}\d/gu, "[redacted-phone]").split("\n", 1)[0]?.trim() || "unknown error", MAX_MEDIA_ERROR_MESSAGE_CHARS);
|
|
792
|
+
}
|
|
793
|
+
function logMediaMaterializationFailure(params) {
|
|
794
|
+
const errorClass = params.error instanceof Error && /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/u.test(params.error.name) ? params.error.name : "Error";
|
|
795
|
+
const statusCode = getStatusCode(params.error);
|
|
796
|
+
const errorMessage = sanitizeMediaErrorMessage(params.error);
|
|
797
|
+
const failureDetails = [
|
|
798
|
+
`stage=${params.failureStage}`,
|
|
799
|
+
...params.mediaKind ? [`kind=${params.mediaKind}`] : [],
|
|
800
|
+
...typeof statusCode === "number" ? [`status=${statusCode}`] : []
|
|
801
|
+
].join(" ");
|
|
802
|
+
inboundMediaLogger.warn("WhatsApp inbound media materialization failed", {
|
|
803
|
+
channel: "whatsapp",
|
|
804
|
+
messageId: params.messageId ?? void 0,
|
|
805
|
+
mediaKind: params.mediaKind,
|
|
806
|
+
mimeType: params.mimeType,
|
|
807
|
+
failureStage: params.failureStage,
|
|
808
|
+
errorClass,
|
|
809
|
+
...typeof statusCode === "number" ? { statusCode } : {},
|
|
810
|
+
errorMessage,
|
|
811
|
+
consoleMessage: `WhatsApp inbound media materialization failed (${failureDetails}): ${errorMessage}`
|
|
812
|
+
});
|
|
813
|
+
}
|
|
945
814
|
async function enrichWhatsAppInboundMessage(params) {
|
|
946
815
|
const { msg, sock } = params;
|
|
947
|
-
const
|
|
816
|
+
const messageProjection = projectWhatsAppInboundMessage(msg.message ?? void 0);
|
|
817
|
+
const location = extractLocationData(messageProjection);
|
|
948
818
|
const locationText = location ? formatLocationText(location) : void 0;
|
|
949
|
-
const contactContext = extractContactContext(
|
|
950
|
-
const externalAdReplyContext = extractExternalAdReplyContext(
|
|
951
|
-
let mediaKind = extractMediaKind(
|
|
952
|
-
let body = extractText(
|
|
819
|
+
const contactContext = extractContactContext(messageProjection);
|
|
820
|
+
const externalAdReplyContext = extractExternalAdReplyContext(messageProjection);
|
|
821
|
+
let mediaKind = extractMediaKind(messageProjection);
|
|
822
|
+
let body = extractText(messageProjection);
|
|
953
823
|
if (locationText) body = [body, locationText].filter(Boolean).join("\n").trim();
|
|
954
824
|
if (!body && !mediaKind) return null;
|
|
955
825
|
body = body ?? "";
|
|
956
826
|
const commandBody = body;
|
|
957
|
-
const replyContext = describeReplyContext(
|
|
827
|
+
const replyContext = describeReplyContext(messageProjection);
|
|
958
828
|
let mediaPath;
|
|
959
829
|
let mediaType = mediaKind ? resolveInboundMediaMimetype(msg.message) : void 0;
|
|
960
830
|
const nativeMedia = mediaKind ? {
|
|
@@ -970,8 +840,15 @@ async function enrichWhatsAppInboundMessage(params) {
|
|
|
970
840
|
mediaFileName = inboundMedia.fileName;
|
|
971
841
|
};
|
|
972
842
|
try {
|
|
973
|
-
await saveInboundMedia(await downloadInboundMedia(msg, sock, maxBytes));
|
|
843
|
+
await saveInboundMedia(await downloadInboundMedia(msg, sock, maxBytes, messageProjection[0]));
|
|
974
844
|
} catch (error) {
|
|
845
|
+
logMediaMaterializationFailure({
|
|
846
|
+
messageId: msg.key?.id,
|
|
847
|
+
mediaKind,
|
|
848
|
+
mimeType: mediaType,
|
|
849
|
+
failureStage: "direct",
|
|
850
|
+
error
|
|
851
|
+
});
|
|
975
852
|
params.logVerbose(`Inbound media download failed: ${String(error)}`);
|
|
976
853
|
body = formatInboundMediaUnavailableText({
|
|
977
854
|
body,
|
|
@@ -983,6 +860,13 @@ async function enrichWhatsAppInboundMessage(params) {
|
|
|
983
860
|
mediaKind = replyContext.media.kind ?? void 0;
|
|
984
861
|
mediaType = mediaType ?? replyContext.media.contentType ?? void 0;
|
|
985
862
|
} catch (error) {
|
|
863
|
+
logMediaMaterializationFailure({
|
|
864
|
+
messageId: msg.key?.id,
|
|
865
|
+
mediaKind: replyContext.media.kind ?? void 0,
|
|
866
|
+
mimeType: replyContext.media.contentType ?? void 0,
|
|
867
|
+
failureStage: "quoted",
|
|
868
|
+
error
|
|
869
|
+
});
|
|
986
870
|
params.logVerbose(`Quoted media download failed: ${String(error)}`);
|
|
987
871
|
body = formatInboundMediaUnavailableText({
|
|
988
872
|
body,
|
|
@@ -1000,7 +884,114 @@ async function enrichWhatsAppInboundMessage(params) {
|
|
|
1000
884
|
mediaType,
|
|
1001
885
|
mediaFileName,
|
|
1002
886
|
mediaKind,
|
|
1003
|
-
nativeMedia
|
|
887
|
+
nativeMedia,
|
|
888
|
+
mentionedJids: extractMentionedJids(messageProjection)
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
//#endregion
|
|
892
|
+
//#region extensions/whatsapp/src/inbound/access-control.ts
|
|
893
|
+
const PAIRING_REPLY_HISTORY_GRACE_MS = 3e4;
|
|
894
|
+
function logWhatsAppVerbose$2(enabled, message) {
|
|
895
|
+
if (!enabled) return;
|
|
896
|
+
defaultRuntime.log(message);
|
|
897
|
+
}
|
|
898
|
+
function blockedInboundAccess(policy) {
|
|
899
|
+
return {
|
|
900
|
+
allowed: false,
|
|
901
|
+
shouldMarkRead: false,
|
|
902
|
+
isSelfChat: policy.isSelfChat,
|
|
903
|
+
resolvedAccountId: policy.account.accountId
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
async function checkInboundAccessControl(params) {
|
|
907
|
+
const policy = resolveWhatsAppInboundPolicy({
|
|
908
|
+
cfg: params.cfg,
|
|
909
|
+
accountId: params.accountId,
|
|
910
|
+
selfE164: params.selfE164
|
|
911
|
+
});
|
|
912
|
+
const pairingGraceMs = typeof params.pairingGraceMs === "number" && params.pairingGraceMs > 0 ? params.pairingGraceMs : PAIRING_REPLY_HISTORY_GRACE_MS;
|
|
913
|
+
const suppressPairingReply = typeof params.connectedAtMs === "number" && typeof params.messageTimestampMs === "number" && params.messageTimestampMs < params.connectedAtMs - pairingGraceMs;
|
|
914
|
+
warnMissingProviderGroupPolicyFallbackOnce({
|
|
915
|
+
providerMissingFallbackApplied: policy.providerMissingFallbackApplied,
|
|
916
|
+
providerKey: "whatsapp",
|
|
917
|
+
accountId: policy.account.accountId,
|
|
918
|
+
log: (message) => logWhatsAppVerbose$2(params.verbose, message)
|
|
919
|
+
});
|
|
920
|
+
const conversationId = params.group ? params.remoteJid : params.from;
|
|
921
|
+
const accessSenderId = params.group ? params.senderE164 : params.from;
|
|
922
|
+
const admissionSenderId = params.group ? params.senderE164 ?? params.senderJid ?? params.from : params.from;
|
|
923
|
+
const resolveChannelIngress = async (contextBinding) => await resolveWhatsAppIngressAccess({
|
|
924
|
+
cfg: params.cfg,
|
|
925
|
+
policy,
|
|
926
|
+
isGroup: params.group,
|
|
927
|
+
conversationId,
|
|
928
|
+
senderId: accessSenderId,
|
|
929
|
+
contextBinding
|
|
930
|
+
});
|
|
931
|
+
const access = await resolveChannelIngress();
|
|
932
|
+
const { senderAccess } = access;
|
|
933
|
+
if (params.group && senderAccess.decision !== "allow") {
|
|
934
|
+
if (senderAccess.reasonCode === "group_policy_disabled") logWhatsAppVerbose$2(params.verbose, "Blocked group message (groupPolicy: disabled)");
|
|
935
|
+
else if (senderAccess.reasonCode === "group_policy_empty_allowlist") logWhatsAppVerbose$2(params.verbose, "Blocked group message (groupPolicy: allowlist, no groupAllowFrom)");
|
|
936
|
+
else logWhatsAppVerbose$2(params.verbose, `Blocked group message from ${params.senderE164 ?? "unknown sender"} (groupPolicy: allowlist)`);
|
|
937
|
+
return blockedInboundAccess(policy);
|
|
938
|
+
}
|
|
939
|
+
if (!params.group) {
|
|
940
|
+
if (params.isFromMe && (policy.account.selfChatMode === false || !policy.isSamePhone(params.from))) {
|
|
941
|
+
logWhatsAppVerbose$2(params.verbose, "Skipping outbound DM (fromMe); no pairing reply needed.");
|
|
942
|
+
return blockedInboundAccess(policy);
|
|
943
|
+
}
|
|
944
|
+
if (senderAccess.decision === "block" && senderAccess.reasonCode === "dm_policy_disabled") {
|
|
945
|
+
logWhatsAppVerbose$2(params.verbose, "Blocked dm (dmPolicy: disabled)");
|
|
946
|
+
return blockedInboundAccess(policy);
|
|
947
|
+
}
|
|
948
|
+
if (senderAccess.decision === "pairing" && !policy.isSamePhone(params.from)) {
|
|
949
|
+
const candidate = params.from;
|
|
950
|
+
if (suppressPairingReply) logWhatsAppVerbose$2(params.verbose, `Skipping pairing reply for historical DM from ${candidate}.`);
|
|
951
|
+
else await createChannelPairingChallengeIssuer({
|
|
952
|
+
channel: "whatsapp",
|
|
953
|
+
accountId: policy.account.accountId,
|
|
954
|
+
upsertPairingRequest: async ({ id, meta }) => await upsertChannelPairingRequest({
|
|
955
|
+
channel: "whatsapp",
|
|
956
|
+
id,
|
|
957
|
+
accountId: policy.account.accountId,
|
|
958
|
+
meta
|
|
959
|
+
})
|
|
960
|
+
})({
|
|
961
|
+
senderId: candidate,
|
|
962
|
+
senderIdLine: `Your WhatsApp phone number: ${candidate}`,
|
|
963
|
+
meta: { name: (params.pushName ?? "").trim() || void 0 },
|
|
964
|
+
onCreated: () => {
|
|
965
|
+
logWhatsAppVerbose$2(params.verbose, `whatsapp pairing request sender=${candidate} name=${params.pushName ?? "unknown"}`);
|
|
966
|
+
},
|
|
967
|
+
sendPairingReply: async (text) => {
|
|
968
|
+
await params.sock.sendMessage(params.remoteJid, { text });
|
|
969
|
+
},
|
|
970
|
+
onReplyError: (err) => {
|
|
971
|
+
logWhatsAppVerbose$2(params.verbose, `whatsapp pairing reply failed for ${candidate}: ${String(err)}`);
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
return blockedInboundAccess(policy);
|
|
975
|
+
}
|
|
976
|
+
if (senderAccess.decision !== "allow") {
|
|
977
|
+
logWhatsAppVerbose$2(params.verbose, `Blocked unauthorized sender ${params.from} (dmPolicy=${policy.dmPolicy})`);
|
|
978
|
+
return blockedInboundAccess(policy);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return {
|
|
982
|
+
allowed: true,
|
|
983
|
+
shouldMarkRead: true,
|
|
984
|
+
isSelfChat: policy.isSelfChat,
|
|
985
|
+
resolvedAccountId: policy.account.accountId,
|
|
986
|
+
admission: buildWhatsAppInboundAdmission({
|
|
987
|
+
policy,
|
|
988
|
+
access,
|
|
989
|
+
channelIngress: access,
|
|
990
|
+
resolveChannelIngress,
|
|
991
|
+
isGroup: params.group,
|
|
992
|
+
conversationId,
|
|
993
|
+
senderId: admissionSenderId
|
|
994
|
+
})
|
|
1004
995
|
};
|
|
1005
996
|
}
|
|
1006
997
|
//#endregion
|
|
@@ -1011,7 +1002,14 @@ const recentOutboundMessages = createDedupeCache({
|
|
|
1011
1002
|
});
|
|
1012
1003
|
function buildMessageKey(params) {
|
|
1013
1004
|
const accountId = params.accountId.trim();
|
|
1014
|
-
const
|
|
1005
|
+
const rawRemoteJid = params.remoteJid.trim();
|
|
1006
|
+
const hostedPhone = isHostedPnUser(rawRemoteJid);
|
|
1007
|
+
let remoteJid;
|
|
1008
|
+
if (hostedPhone || isHostedLidUser(rawRemoteJid)) {
|
|
1009
|
+
const decodedJid = jidDecode(rawRemoteJid);
|
|
1010
|
+
if (!decodedJid) return null;
|
|
1011
|
+
remoteJid = jidEncode(decodedJid.user, hostedPhone ? "s.whatsapp.net" : "lid");
|
|
1012
|
+
} else remoteJid = jidNormalizedUser(rawRemoteJid);
|
|
1015
1013
|
const messageId = params.messageId.trim();
|
|
1016
1014
|
if (!accountId || !remoteJid || !messageId || messageId === "unknown") return null;
|
|
1017
1015
|
return `${accountId}:${remoteJid}:${messageId}`;
|
|
@@ -1027,7 +1025,13 @@ function rememberRecentOutboundMessage(params) {
|
|
|
1027
1025
|
function isRecentOutboundMessage(params) {
|
|
1028
1026
|
const key = buildMessageKey(params);
|
|
1029
1027
|
if (!key) return false;
|
|
1030
|
-
|
|
1028
|
+
if (recentOutboundMessages.peek(key)) return true;
|
|
1029
|
+
if (!params.alternateRemoteJid || isJidGroup(params.remoteJid)) return false;
|
|
1030
|
+
const alternateKey = buildMessageKey({
|
|
1031
|
+
...params,
|
|
1032
|
+
remoteJid: params.alternateRemoteJid
|
|
1033
|
+
});
|
|
1034
|
+
return alternateKey !== null && recentOutboundMessages.peek(alternateKey);
|
|
1031
1035
|
}
|
|
1032
1036
|
//#endregion
|
|
1033
1037
|
//#region extensions/whatsapp/src/inbound/message-normalization.ts
|
|
@@ -1039,6 +1043,7 @@ function createWhatsAppInboundMessageNormalizer(options) {
|
|
|
1039
1043
|
if (!msg.key?.fromMe || !id || !remoteJid || !isRecentOutboundMessage({
|
|
1040
1044
|
accountId: options.accountId,
|
|
1041
1045
|
remoteJid,
|
|
1046
|
+
alternateRemoteJid: msg.key?.remoteJidAlt,
|
|
1042
1047
|
messageId: id
|
|
1043
1048
|
})) return false;
|
|
1044
1049
|
options.logVerbose(`Skipping recent outbound WhatsApp echo ${id} for ${remoteJid}`);
|
|
@@ -1048,7 +1053,7 @@ function createWhatsAppInboundMessageNormalizer(options) {
|
|
|
1048
1053
|
const id = msg.key?.id ?? void 0;
|
|
1049
1054
|
const remoteJid = msg.key?.remoteJid;
|
|
1050
1055
|
if (!remoteJid || remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) return null;
|
|
1051
|
-
const group = isJidGroup(remoteJid) === true;
|
|
1056
|
+
const group = isJidGroup$1(remoteJid) === true;
|
|
1052
1057
|
if (shouldSkipRecentOutboundEcho(msg)) return null;
|
|
1053
1058
|
if (!hasInboundUserContent(msg.message ?? void 0)) return null;
|
|
1054
1059
|
const participantJid = msg.key?.participant ?? void 0;
|
|
@@ -1122,7 +1127,7 @@ function recordAcceptedInboundActivity(accountId) {
|
|
|
1122
1127
|
}
|
|
1123
1128
|
function createWhatsAppMessageDeliveryCoordinator(options) {
|
|
1124
1129
|
const inboundLogger = getChildLogger({ module: "web-inbound" });
|
|
1125
|
-
const inboundConsoleLog = createSubsystemLogger("gateway/channels/whatsapp").child("inbound");
|
|
1130
|
+
const inboundConsoleLog = createSubsystemLogger$1("gateway/channels/whatsapp").child("inbound");
|
|
1126
1131
|
const sock = options.sock;
|
|
1127
1132
|
const socketSession = options.socketSession;
|
|
1128
1133
|
const groupMetadata = options.groupMetadata;
|
|
@@ -1213,7 +1218,7 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
|
|
|
1213
1218
|
return normalizeWhatsAppSendResult(await sendTrackedMessage(chatJid, await groupMetadata.applyOutboundMentions(chatJid, previewPayload), optionsValue), "media");
|
|
1214
1219
|
};
|
|
1215
1220
|
const timestamp = inbound.messageTimestampMs;
|
|
1216
|
-
const mentionedJids =
|
|
1221
|
+
const mentionedJids = enriched.mentionedJids;
|
|
1217
1222
|
const senderName = msg.pushName ?? void 0;
|
|
1218
1223
|
inboundLogger.info({
|
|
1219
1224
|
from: inbound.from,
|
|
@@ -1256,7 +1261,7 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
|
|
|
1256
1261
|
payload: enriched.externalAdReplyContext
|
|
1257
1262
|
}] : []
|
|
1258
1263
|
];
|
|
1259
|
-
const inboundMessage =
|
|
1264
|
+
const inboundMessage = {
|
|
1260
1265
|
admission: inbound.access.admission,
|
|
1261
1266
|
event: {
|
|
1262
1267
|
id: inbound.id,
|
|
@@ -1305,7 +1310,7 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
|
|
|
1305
1310
|
turnAdoptionLifecycle: durable.turnAdoptionLifecycle,
|
|
1306
1311
|
readReceipt: durable.readReceipt,
|
|
1307
1312
|
receiveOrder: durable.receiveOrder
|
|
1308
|
-
}
|
|
1313
|
+
};
|
|
1309
1314
|
if (inboundMessage.event.id) {
|
|
1310
1315
|
const admission = requireWhatsAppInboundAdmission(inboundMessage);
|
|
1311
1316
|
cacheInboundMessageMeta(admission.accountId, inboundMessage.platform.chatJid, inboundMessage.event.id, {
|
|
@@ -1634,7 +1639,10 @@ async function createWhatsAppAttachedSocketSession(options) {
|
|
|
1634
1639
|
if (readinessOptions?.useVerifiedReady && consumeVerifiedSendReady(jid, currentSock)) return;
|
|
1635
1640
|
const state = getActiveReachoutTimelock(reachoutTimeLock) ?? await fetchReachoutTimeLock(currentSock);
|
|
1636
1641
|
const activeState = getActiveReachoutTimelock(state);
|
|
1637
|
-
if (activeState)
|
|
1642
|
+
if (activeState) {
|
|
1643
|
+
const error = new Error(formatReachoutTimelockError(activeState));
|
|
1644
|
+
throw readinessOptions?.rememberReady ? new PlatformMessageNotDispatchedError(error.message, { cause: error }) : error;
|
|
1645
|
+
}
|
|
1638
1646
|
if (readinessOptions?.rememberReady && state) rememberVerifiedSendReady(jid, currentSock);
|
|
1639
1647
|
};
|
|
1640
1648
|
const assertSendReady = async (to) => {
|
|
@@ -1757,7 +1765,7 @@ function logWhatsAppVerbose(enabled, message) {
|
|
|
1757
1765
|
}
|
|
1758
1766
|
async function attachWebInboxToSocket(options) {
|
|
1759
1767
|
const inboundLogger = getChildLogger({ module: "web-inbound" });
|
|
1760
|
-
const inboundConsoleLog = createSubsystemLogger("gateway/channels/whatsapp").child("inbound");
|
|
1768
|
+
const inboundConsoleLog = createSubsystemLogger$1("gateway/channels/whatsapp").child("inbound");
|
|
1761
1769
|
const socketSession = await createWhatsAppAttachedSocketSession({
|
|
1762
1770
|
sock: options.sock,
|
|
1763
1771
|
socketRef: options.socketRef,
|
|
@@ -1856,10 +1864,6 @@ async function monitorWebInbox(options) {
|
|
|
1856
1864
|
}
|
|
1857
1865
|
return attachWebInboxToSocket({
|
|
1858
1866
|
...options,
|
|
1859
|
-
onMessage: async (msg) => {
|
|
1860
|
-
await options.onMessage(normalizeAdmittedWebInboundMessage(msg));
|
|
1861
|
-
},
|
|
1862
|
-
shouldDebounce: options.shouldDebounce ? (msg) => options.shouldDebounce?.(normalizeAdmittedWebInboundMessage(msg)) ?? true : void 0,
|
|
1863
1867
|
socketTiming,
|
|
1864
1868
|
sock,
|
|
1865
1869
|
recentMessageKeys,
|
|
@@ -1868,7 +1872,7 @@ async function monitorWebInbox(options) {
|
|
|
1868
1872
|
}
|
|
1869
1873
|
//#endregion
|
|
1870
1874
|
//#region extensions/whatsapp/src/auto-reply/loggers.ts
|
|
1871
|
-
const whatsappLog = createSubsystemLogger("gateway/channels/whatsapp");
|
|
1875
|
+
const whatsappLog = createSubsystemLogger$1("gateway/channels/whatsapp");
|
|
1872
1876
|
const whatsappInboundLog = whatsappLog.child("inbound");
|
|
1873
1877
|
const whatsappOutboundLog = whatsappLog.child("outbound");
|
|
1874
1878
|
const whatsappHeartbeatLog = whatsappLog.child("heartbeat");
|
|
@@ -1932,6 +1936,15 @@ function resolveOwnerList(mentionCfg, selfE164) {
|
|
|
1932
1936
|
}
|
|
1933
1937
|
//#endregion
|
|
1934
1938
|
//#region extensions/whatsapp/src/auto-reply/monitor-state.ts
|
|
1939
|
+
const LIFECYCLE_BY_HEALTH_STATE = {
|
|
1940
|
+
starting: "starting",
|
|
1941
|
+
healthy: "ready",
|
|
1942
|
+
stale: "recovering",
|
|
1943
|
+
reconnecting: "recovering",
|
|
1944
|
+
conflict: "blocked",
|
|
1945
|
+
"logged-out": "blocked",
|
|
1946
|
+
stopped: "blocked"
|
|
1947
|
+
};
|
|
1935
1948
|
function cloneStatus(status) {
|
|
1936
1949
|
return {
|
|
1937
1950
|
...status,
|
|
@@ -1955,7 +1968,8 @@ function createWebChannelStatusController(statusSink) {
|
|
|
1955
1968
|
lastError: null,
|
|
1956
1969
|
busy: false,
|
|
1957
1970
|
lastRunActivityAt: null,
|
|
1958
|
-
healthState: "starting"
|
|
1971
|
+
healthState: "starting",
|
|
1972
|
+
lifecycle: "starting"
|
|
1959
1973
|
};
|
|
1960
1974
|
const emit = () => {
|
|
1961
1975
|
statusSink?.(cloneStatus(status));
|
|
@@ -1964,16 +1978,17 @@ function createWebChannelStatusController(statusSink) {
|
|
|
1964
1978
|
emit,
|
|
1965
1979
|
snapshot: () => status,
|
|
1966
1980
|
noteConnected(at = Date.now()) {
|
|
1967
|
-
Object.assign(status,
|
|
1981
|
+
Object.assign(status, channelReadyPatch({
|
|
1982
|
+
lastConnectedAt: at,
|
|
1983
|
+
lastEventAt: at
|
|
1984
|
+
}));
|
|
1968
1985
|
Object.assign(status, createTransportActivityStatusPatch(at));
|
|
1969
1986
|
if (lastDisconnectWasWatchdogRecovery) {
|
|
1970
1987
|
status.lastDisconnect = null;
|
|
1971
1988
|
status.reconnectAttempts = 0;
|
|
1972
1989
|
lastDisconnectWasWatchdogRecovery = false;
|
|
1973
1990
|
}
|
|
1974
|
-
status.lastError = null;
|
|
1975
1991
|
status.healthState = "healthy";
|
|
1976
|
-
status.terminalDisconnect = void 0;
|
|
1977
1992
|
emit();
|
|
1978
1993
|
},
|
|
1979
1994
|
noteInbound(at = Date.now()) {
|
|
@@ -1981,7 +1996,10 @@ function createWebChannelStatusController(statusSink) {
|
|
|
1981
1996
|
status.lastMessageAt = at;
|
|
1982
1997
|
status.lastEventAt = at;
|
|
1983
1998
|
Object.assign(status, createTransportActivityStatusPatch(at));
|
|
1984
|
-
if (status.connected)
|
|
1999
|
+
if (status.connected) {
|
|
2000
|
+
status.healthState = "healthy";
|
|
2001
|
+
status.lifecycle = "ready";
|
|
2002
|
+
}
|
|
1985
2003
|
emit();
|
|
1986
2004
|
},
|
|
1987
2005
|
noteTransportActivity(at = Date.now()) {
|
|
@@ -1993,12 +2011,18 @@ function createWebChannelStatusController(statusSink) {
|
|
|
1993
2011
|
if (status.busy === busy && status.lastRunActivityAt === at) return;
|
|
1994
2012
|
status.busy = busy;
|
|
1995
2013
|
status.lastRunActivityAt = at;
|
|
1996
|
-
if (status.connected && busy)
|
|
2014
|
+
if (status.connected && busy) {
|
|
2015
|
+
status.healthState = "healthy";
|
|
2016
|
+
status.lifecycle = "ready";
|
|
2017
|
+
}
|
|
1997
2018
|
emit();
|
|
1998
2019
|
},
|
|
1999
2020
|
noteWatchdogStale(at = Date.now()) {
|
|
2000
2021
|
status.lastEventAt = at;
|
|
2001
|
-
if (status.connected)
|
|
2022
|
+
if (status.connected) {
|
|
2023
|
+
status.healthState = "stale";
|
|
2024
|
+
status.lifecycle = "recovering";
|
|
2025
|
+
}
|
|
2002
2026
|
emit();
|
|
2003
2027
|
},
|
|
2004
2028
|
noteReconnectAttempts(reconnectAttempts) {
|
|
@@ -2019,52 +2043,28 @@ function createWebChannelStatusController(statusSink) {
|
|
|
2019
2043
|
status.lastError = params.error ?? null;
|
|
2020
2044
|
status.reconnectAttempts = params.reconnectAttempts;
|
|
2021
2045
|
status.healthState = params.healthState;
|
|
2046
|
+
status.lifecycle = LIFECYCLE_BY_HEALTH_STATE[params.healthState];
|
|
2022
2047
|
emit();
|
|
2023
2048
|
},
|
|
2024
2049
|
markStopped(at = Date.now()) {
|
|
2025
|
-
status.
|
|
2026
|
-
status.
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2050
|
+
const terminalDisconnect = status.lifecycle === "blocked";
|
|
2051
|
+
if (!isTerminalHealthState(status.healthState)) {
|
|
2052
|
+
Object.assign(status, channelStoppedPatch({
|
|
2053
|
+
lastEventAt: at,
|
|
2054
|
+
terminalDisconnect
|
|
2055
|
+
}));
|
|
2056
|
+
status.healthState = "stopped";
|
|
2057
|
+
} else Object.assign(status, {
|
|
2058
|
+
running: false,
|
|
2059
|
+
connected: false,
|
|
2060
|
+
lastEventAt: at,
|
|
2061
|
+
terminalDisconnect
|
|
2062
|
+
});
|
|
2030
2063
|
emit();
|
|
2031
2064
|
}
|
|
2032
2065
|
};
|
|
2033
2066
|
}
|
|
2034
2067
|
//#endregion
|
|
2035
|
-
//#region extensions/whatsapp/src/auto-reply/monitor/echo.ts
|
|
2036
|
-
function createEchoTracker(params) {
|
|
2037
|
-
const recentlySent = /* @__PURE__ */ new Set();
|
|
2038
|
-
const maxItems = Math.max(1, params.maxItems ?? 100);
|
|
2039
|
-
const buildCombinedKey = (p) => `combined:${p.sessionKey}:${p.combinedBody}`;
|
|
2040
|
-
const buildTextKey = (text, conversationId) => conversationId ? `${conversationId}\0${text}` : text;
|
|
2041
|
-
const trim = () => {
|
|
2042
|
-
while (recentlySent.size > maxItems) {
|
|
2043
|
-
const firstKey = recentlySent.values().next().value;
|
|
2044
|
-
if (!firstKey) break;
|
|
2045
|
-
recentlySent.delete(firstKey);
|
|
2046
|
-
}
|
|
2047
|
-
};
|
|
2048
|
-
const rememberText = (text, opts) => {
|
|
2049
|
-
if (!text) return;
|
|
2050
|
-
recentlySent.add(buildTextKey(text, opts.conversationId));
|
|
2051
|
-
if (opts.combinedBody && opts.combinedBodySessionKey) recentlySent.add(buildCombinedKey({
|
|
2052
|
-
sessionKey: opts.combinedBodySessionKey,
|
|
2053
|
-
combinedBody: opts.combinedBody
|
|
2054
|
-
}));
|
|
2055
|
-
if (opts.logVerboseMessage) params.logVerbose?.(`Added to echo detection set (size now: ${recentlySent.size}): ${truncateUtf16Safe(text, 50)}...`);
|
|
2056
|
-
trim();
|
|
2057
|
-
};
|
|
2058
|
-
return {
|
|
2059
|
-
rememberText,
|
|
2060
|
-
has: (key, conversationId) => recentlySent.has(buildTextKey(key, conversationId)),
|
|
2061
|
-
forget: (key, conversationId) => {
|
|
2062
|
-
recentlySent.delete(buildTextKey(key, conversationId));
|
|
2063
|
-
},
|
|
2064
|
-
buildCombinedKey
|
|
2065
|
-
};
|
|
2066
|
-
}
|
|
2067
|
-
//#endregion
|
|
2068
2068
|
//#region extensions/whatsapp/src/auto-reply/monitor/listener-log.ts
|
|
2069
2069
|
function formatWhatsAppInboundListeningLog(account) {
|
|
2070
2070
|
if (account.groupPolicy === "disabled") return "Listening for WhatsApp inbound messages (DM + groups disabled by groupPolicy).";
|
|
@@ -2157,8 +2157,6 @@ async function resolveWhatsAppReactionEligibility(params) {
|
|
|
2157
2157
|
agentId: params.agentId,
|
|
2158
2158
|
ackConfig: params.cfg.messages?.ackReaction
|
|
2159
2159
|
});
|
|
2160
|
-
const directEnabled = scope === "all" || scope === "direct";
|
|
2161
|
-
const groupMode = scope === "all" || scope === "group-all" ? "always" : scope === "direct" ? "never" : "mentions";
|
|
2162
2160
|
const isGroup = admission.conversation.kind === "group";
|
|
2163
2161
|
const activation = isGroup ? await resolveGroupActivationFor({
|
|
2164
2162
|
cfg: params.cfg,
|
|
@@ -2167,14 +2165,14 @@ async function resolveWhatsAppReactionEligibility(params) {
|
|
|
2167
2165
|
sessionKey: params.sessionKey,
|
|
2168
2166
|
conversationId: admission.conversation.id
|
|
2169
2167
|
}) : null;
|
|
2170
|
-
if (!
|
|
2171
|
-
|
|
2168
|
+
if (!emoji || !shouldAckReaction({
|
|
2169
|
+
scope,
|
|
2172
2170
|
isDirect: admission.conversation.kind === "direct",
|
|
2173
2171
|
isGroup,
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2172
|
+
isMentionableGroup: isGroup,
|
|
2173
|
+
canDetectMention: isGroup,
|
|
2174
|
+
effectiveWasMentioned: (params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned) === true,
|
|
2175
|
+
shouldBypassMention: activation === "always"
|
|
2178
2176
|
})) return DISABLED_REACTION;
|
|
2179
2177
|
const participant = resolveReactionParticipant(params.msg);
|
|
2180
2178
|
return {
|
|
@@ -2184,7 +2182,7 @@ async function resolveWhatsAppReactionEligibility(params) {
|
|
|
2184
2182
|
emoji,
|
|
2185
2183
|
reactionOptions: {
|
|
2186
2184
|
verbose: params.verbose,
|
|
2187
|
-
fromMe:
|
|
2185
|
+
fromMe: params.msg.platform.fromMe === true,
|
|
2188
2186
|
...participant ? { participant } : {},
|
|
2189
2187
|
accountId,
|
|
2190
2188
|
cfg: params.cfg
|
|
@@ -2375,7 +2373,7 @@ function recordPendingGroupHistoryEntry(params) {
|
|
|
2375
2373
|
timestamp: params.msg.event.timestamp,
|
|
2376
2374
|
id: params.msg.event.id,
|
|
2377
2375
|
senderJid: senderIdentity.jid ?? params.msg.platform.senderJid,
|
|
2378
|
-
...params.
|
|
2376
|
+
...params.msg.payload.media ? { media: [{
|
|
2379
2377
|
path: params.msg.payload.media.path,
|
|
2380
2378
|
url: params.msg.payload.media.url ?? params.msg.payload.media.path,
|
|
2381
2379
|
contentType: params.msg.payload.media.type,
|
|
@@ -2495,7 +2493,8 @@ async function applyGroupGating(params) {
|
|
|
2495
2493
|
needsMentionText: true
|
|
2496
2494
|
};
|
|
2497
2495
|
}
|
|
2498
|
-
|
|
2496
|
+
const pendingHistoryBody = params.mentionText === void 0 ? void 0 : formatAudioTranscriptForAgent(params.mentionText);
|
|
2497
|
+
return skipGroupMessageAndStoreHistory(params, `Group message stored for context (no mention detected) in ${conversationId}: ${mentionMsg.payload.body}`, pendingHistoryBody);
|
|
2499
2498
|
}
|
|
2500
2499
|
return { shouldProcess: true };
|
|
2501
2500
|
}
|
|
@@ -2571,7 +2570,7 @@ var WhatsAppOutboundRetryError = class extends Error {
|
|
|
2571
2570
|
}
|
|
2572
2571
|
};
|
|
2573
2572
|
function isRetryableWhatsAppOutboundError(error) {
|
|
2574
|
-
if (isWhatsAppSocketOperationTimeoutError(error)) return false;
|
|
2573
|
+
if (isChannelPartialDeliveryError(error) || isWhatsAppSocketOperationTimeoutError(error)) return false;
|
|
2575
2574
|
return WHATSAPP_RETRYABLE_OUTBOUND_ERROR_PATTERN.test(formatError(error));
|
|
2576
2575
|
}
|
|
2577
2576
|
async function sendWhatsAppOutboundWithRetry(params) {
|
|
@@ -2706,14 +2705,14 @@ function createWhatsAppReplyDeliveryReceipt(results) {
|
|
|
2706
2705
|
});
|
|
2707
2706
|
}
|
|
2708
2707
|
async function deliverWebReply(params) {
|
|
2708
|
+
return await withWhatsAppLogicalDeliveryActivity(() => deliverWebReplyInActivityScope(params));
|
|
2709
|
+
}
|
|
2710
|
+
async function deliverWebReplyInActivityScope(params) {
|
|
2709
2711
|
const { replyResult, transport, maxMediaBytes, textLimit, replyLogger, connectionId, skipLog } = params;
|
|
2710
2712
|
const conversationId = transport.conversationId;
|
|
2711
2713
|
const isGroupConversation = transport.conversationKind === "group";
|
|
2712
2714
|
const replyStarted = Date.now();
|
|
2713
2715
|
const sendResults = [];
|
|
2714
|
-
const rememberSendResult = (result) => {
|
|
2715
|
-
if (result) sendResults.push(result);
|
|
2716
|
-
};
|
|
2717
2716
|
const finishDelivery = () => {
|
|
2718
2717
|
const receipt = createWhatsAppReplyDeliveryReceipt(sendResults);
|
|
2719
2718
|
return {
|
|
@@ -2722,6 +2721,32 @@ async function deliverWebReply(params) {
|
|
|
2722
2721
|
providerAccepted: sendResults.some((result) => result.providerAccepted)
|
|
2723
2722
|
};
|
|
2724
2723
|
};
|
|
2724
|
+
const preserveAcceptedDeliveryError = (error, kind) => {
|
|
2725
|
+
const sendKind = kind ?? sendResults[0]?.kind ?? "text";
|
|
2726
|
+
if (isChannelPartialDeliveryError(error)) rememberWhatsAppPartialSend({
|
|
2727
|
+
error,
|
|
2728
|
+
kind: sendKind,
|
|
2729
|
+
results: sendResults
|
|
2730
|
+
});
|
|
2731
|
+
return mergeWhatsAppAcceptedSendError({
|
|
2732
|
+
error,
|
|
2733
|
+
kind: sendKind,
|
|
2734
|
+
results: sendResults
|
|
2735
|
+
});
|
|
2736
|
+
};
|
|
2737
|
+
const rememberSendResult = (result) => {
|
|
2738
|
+
if (!result) return;
|
|
2739
|
+
try {
|
|
2740
|
+
rememberWhatsAppAcceptedSend({
|
|
2741
|
+
accountId: transport.accountId,
|
|
2742
|
+
result,
|
|
2743
|
+
results: sendResults
|
|
2744
|
+
});
|
|
2745
|
+
} catch (error) {
|
|
2746
|
+
if (sendResults.some((accepted) => accepted.providerAccepted)) throw preserveAcceptedDeliveryError(error, result.kind);
|
|
2747
|
+
throw error;
|
|
2748
|
+
}
|
|
2749
|
+
};
|
|
2725
2750
|
if (isReasoningReplyPayload(replyResult)) {
|
|
2726
2751
|
whatsappOutboundLog.debug(`Suppressed reasoning payload to ${conversationId}`);
|
|
2727
2752
|
return finishDelivery();
|
|
@@ -2743,7 +2768,7 @@ async function deliverWebReply(params) {
|
|
|
2743
2768
|
media: cached?.media
|
|
2744
2769
|
});
|
|
2745
2770
|
};
|
|
2746
|
-
const sendWithRetry = async (fn, label) => {
|
|
2771
|
+
const sendWithRetry = async (fn, label, kind) => {
|
|
2747
2772
|
try {
|
|
2748
2773
|
return await sendWhatsAppOutboundWithRetry({
|
|
2749
2774
|
send: fn,
|
|
@@ -2752,7 +2777,7 @@ async function deliverWebReply(params) {
|
|
|
2752
2777
|
}
|
|
2753
2778
|
});
|
|
2754
2779
|
} catch (error) {
|
|
2755
|
-
if (sendResults.some((result) => result.providerAccepted)) throw
|
|
2780
|
+
if (isChannelPartialDeliveryError(error) || sendResults.some((result) => result.providerAccepted)) throw preserveAcceptedDeliveryError(error, kind);
|
|
2756
2781
|
throw error;
|
|
2757
2782
|
}
|
|
2758
2783
|
};
|
|
@@ -2761,7 +2786,7 @@ async function deliverWebReply(params) {
|
|
|
2761
2786
|
for (const [index, chunk] of textChunks.entries()) {
|
|
2762
2787
|
const chunkStarted = Date.now();
|
|
2763
2788
|
const quote = getQuote();
|
|
2764
|
-
rememberSendResult(await sendWithRetry(() => transport.reply(chunk, quote), "text"));
|
|
2789
|
+
rememberSendResult(await sendWithRetry(() => transport.reply(chunk, quote), "text", "text"));
|
|
2765
2790
|
if (!skipLog) {
|
|
2766
2791
|
const durationMs = Date.now() - chunkStarted;
|
|
2767
2792
|
whatsappOutboundLog.debug(`Sent chunk ${index + 1}/${totalChunks} to ${conversationId} (${durationMs.toFixed(0)}ms)`);
|
|
@@ -2784,10 +2809,13 @@ async function deliverWebReply(params) {
|
|
|
2784
2809
|
return delivery;
|
|
2785
2810
|
}
|
|
2786
2811
|
const remainingText = [...textChunks];
|
|
2812
|
+
const leadingCaption = remainingText.shift() || "";
|
|
2813
|
+
let acceptedIdsBeforeCurrentMedia = /* @__PURE__ */ new Set();
|
|
2787
2814
|
await sendMediaWithLeadingCaption({
|
|
2788
2815
|
mediaUrls: mediaList,
|
|
2789
|
-
caption:
|
|
2816
|
+
caption: leadingCaption,
|
|
2790
2817
|
send: async ({ mediaUrl, caption }) => {
|
|
2818
|
+
acceptedIdsBeforeCurrentMedia = new Set(sendResults.flatMap(listWhatsAppSendResultMessageIds));
|
|
2791
2819
|
const media = await prepareWhatsAppOutboundMedia(await loadWebMedia(mediaUrl, {
|
|
2792
2820
|
maxBytes: maxMediaBytes,
|
|
2793
2821
|
localRoots: params.mediaLocalRoots
|
|
@@ -2802,22 +2830,22 @@ async function deliverWebReply(params) {
|
|
|
2802
2830
|
image: media.buffer,
|
|
2803
2831
|
caption,
|
|
2804
2832
|
mimetype: media.mimetype
|
|
2805
|
-
}, quote), "media:image"));
|
|
2833
|
+
}, quote), "media:image", "media"));
|
|
2806
2834
|
} else if (media.kind === "audio") {
|
|
2807
2835
|
const quote = getQuote();
|
|
2808
2836
|
rememberSendResult(await sendWithRetry(() => transport.sendMedia({
|
|
2809
2837
|
audio: media.buffer,
|
|
2810
2838
|
ptt: true,
|
|
2811
2839
|
mimetype: media.mimetype
|
|
2812
|
-
}, quote), "media:audio"));
|
|
2813
|
-
if (caption) rememberSendResult(await sendWithRetry(() => transport.reply(caption, quote), "media:audio-text"));
|
|
2840
|
+
}, quote), "media:audio", "media"));
|
|
2841
|
+
if (caption) rememberSendResult(await sendWithRetry(() => transport.reply(caption, quote), "media:audio-text", "text"));
|
|
2814
2842
|
} else if (media.kind === "video") {
|
|
2815
2843
|
const quote = getQuote();
|
|
2816
2844
|
rememberSendResult(await sendWithRetry(() => transport.sendMedia({
|
|
2817
2845
|
video: media.buffer,
|
|
2818
2846
|
caption,
|
|
2819
2847
|
mimetype: media.mimetype
|
|
2820
|
-
}, quote), "media:video"));
|
|
2848
|
+
}, quote), "media:video", "media"));
|
|
2821
2849
|
} else {
|
|
2822
2850
|
const quote = getQuote();
|
|
2823
2851
|
rememberSendResult(await sendWithRetry(() => transport.sendMedia({
|
|
@@ -2825,7 +2853,7 @@ async function deliverWebReply(params) {
|
|
|
2825
2853
|
fileName: media.fileName,
|
|
2826
2854
|
caption,
|
|
2827
2855
|
mimetype: media.mimetype
|
|
2828
|
-
}, quote), "media:document"));
|
|
2856
|
+
}, quote), "media:document", "media"));
|
|
2829
2857
|
}
|
|
2830
2858
|
whatsappOutboundLog.info(`Sent media reply to ${conversationId} (${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB)`);
|
|
2831
2859
|
replyLogger.info({
|
|
@@ -2841,6 +2869,7 @@ async function deliverWebReply(params) {
|
|
|
2841
2869
|
}, "auto-reply sent (media)");
|
|
2842
2870
|
},
|
|
2843
2871
|
onError: async ({ error, mediaUrl, caption, isFirst }) => {
|
|
2872
|
+
if (sendResults.some((result) => listWhatsAppSendResultMessageIds(result).some((messageId) => !acceptedIdsBeforeCurrentMedia.has(messageId)))) throw preserveAcceptedDeliveryError(error);
|
|
2844
2873
|
whatsappOutboundLog.error(`Failed sending web media to ${conversationId}: ${formatError(error)}`);
|
|
2845
2874
|
replyLogger.warn({
|
|
2846
2875
|
err: error,
|
|
@@ -2848,16 +2877,16 @@ async function deliverWebReply(params) {
|
|
|
2848
2877
|
}, "failed to send web media reply");
|
|
2849
2878
|
if (!isFirst) {
|
|
2850
2879
|
whatsappOutboundLog.warn(`Trailing media failed; sent warning to ${conversationId}`);
|
|
2851
|
-
rememberSendResult(await sendWithRetry(() => transport.reply("⚠️ Media unavailable.", getQuote()), "media:fallback-unavailable"));
|
|
2880
|
+
rememberSendResult(await sendWithRetry(() => transport.reply("⚠️ Media unavailable.", getQuote()), "media:fallback-unavailable", "text"));
|
|
2852
2881
|
return;
|
|
2853
2882
|
}
|
|
2854
2883
|
const fallbackText = [caption ?? "", "⚠️ Media failed."].filter(Boolean).join("\n");
|
|
2855
2884
|
if (!fallbackText) return;
|
|
2856
2885
|
whatsappOutboundLog.warn(`Media skipped; sent text-only to ${conversationId}`);
|
|
2857
|
-
rememberSendResult(await sendWithRetry(() => transport.reply(fallbackText, getQuote()), "media:fallback-text"));
|
|
2886
|
+
rememberSendResult(await sendWithRetry(() => transport.reply(fallbackText, getQuote()), "media:fallback-text", "text"));
|
|
2858
2887
|
}
|
|
2859
2888
|
});
|
|
2860
|
-
for (const chunk of remainingText) rememberSendResult(await sendWithRetry(() => transport.reply(chunk, getQuote()), "media:text"));
|
|
2889
|
+
for (const chunk of remainingText) rememberSendResult(await sendWithRetry(() => transport.reply(chunk, getQuote()), "media:text", "text"));
|
|
2861
2890
|
return finishDelivery();
|
|
2862
2891
|
}
|
|
2863
2892
|
//#endregion
|
|
@@ -2941,7 +2970,7 @@ function projectPreparedChannelInbound(params) {
|
|
|
2941
2970
|
textForCommands: inbound.message.commandBody,
|
|
2942
2971
|
raw: inbound
|
|
2943
2972
|
},
|
|
2944
|
-
context:
|
|
2973
|
+
context: params.buildContext({
|
|
2945
2974
|
...inbound,
|
|
2946
2975
|
messageId: inbound.event.id,
|
|
2947
2976
|
messageIdFull: inbound.event.fullId,
|
|
@@ -3003,7 +3032,7 @@ function isWhatsAppVisibleDeliveryError(error) {
|
|
|
3003
3032
|
return typeof error === "object" && error !== null && !Array.isArray(error) && error.visibleReplySent === true || isChannelPartialDeliveryError(error) && error.deliveryResult.visibleReplySent;
|
|
3004
3033
|
}
|
|
3005
3034
|
function readTrimmedString(value) {
|
|
3006
|
-
return
|
|
3035
|
+
return normalizeOptionalString(value) ?? "";
|
|
3007
3036
|
}
|
|
3008
3037
|
function markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult) {
|
|
3009
3038
|
if (flushResult.delivered === 0) return error;
|
|
@@ -3128,6 +3157,13 @@ async function prepareWhatsAppInboundContext(params) {
|
|
|
3128
3157
|
const admission = requireWhatsAppInboundAdmission(params.msg);
|
|
3129
3158
|
const conversationId = admission.conversation.id;
|
|
3130
3159
|
const conversationKind = admission.conversation.kind;
|
|
3160
|
+
const eventId = params.msg.event.id ?? `${conversationId}:${newConnectionId()}`;
|
|
3161
|
+
const channelIngress = await resolveWhatsAppAdmissionChannelIngress(admission, {
|
|
3162
|
+
agentId: params.route.agentId,
|
|
3163
|
+
sessionKey: params.route.sessionKey,
|
|
3164
|
+
messageId: eventId,
|
|
3165
|
+
inboundEventKind: "user_request"
|
|
3166
|
+
}) ?? admission.channelIngress;
|
|
3131
3167
|
const wasMentioned = params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned;
|
|
3132
3168
|
const inboundHistory = conversationKind === "group" ? buildInboundHistoryFromEntries({
|
|
3133
3169
|
entries: (params.groupHistory ?? []).map((entry) => ({
|
|
@@ -3147,6 +3183,7 @@ async function prepareWhatsAppInboundContext(params) {
|
|
|
3147
3183
|
}] : void 0, { transcribed: (_entry, index) => params.mediaTranscribedIndexes?.includes(index) === true });
|
|
3148
3184
|
const control = { messageReceivedHooks: params.suppressMessageReceivedHooks ? "channel" : "core" };
|
|
3149
3185
|
const inbound = {
|
|
3186
|
+
channelIngress,
|
|
3150
3187
|
channel: "whatsapp",
|
|
3151
3188
|
supplemental: {
|
|
3152
3189
|
quote: params.visibleReplyTo ? {
|
|
@@ -3159,7 +3196,7 @@ async function prepareWhatsAppInboundContext(params) {
|
|
|
3159
3196
|
},
|
|
3160
3197
|
media,
|
|
3161
3198
|
event: {
|
|
3162
|
-
id:
|
|
3199
|
+
id: eventId,
|
|
3163
3200
|
timestamp: params.msg.event.timestamp
|
|
3164
3201
|
},
|
|
3165
3202
|
from: conversationId,
|
|
@@ -3212,7 +3249,8 @@ async function prepareWhatsAppInboundContext(params) {
|
|
|
3212
3249
|
};
|
|
3213
3250
|
const projected = projectPreparedChannelInbound({
|
|
3214
3251
|
inbound,
|
|
3215
|
-
control
|
|
3252
|
+
control,
|
|
3253
|
+
buildContext: params.buildContext ?? buildChannelInboundEventContext
|
|
3216
3254
|
});
|
|
3217
3255
|
return {
|
|
3218
3256
|
inbound,
|
|
@@ -3271,13 +3309,6 @@ function createWhatsAppReplyPlan(params) {
|
|
|
3271
3309
|
let didLogHeartbeatStrip = false;
|
|
3272
3310
|
const recordDeliveredPayload = (payload) => {
|
|
3273
3311
|
didSendReply = true;
|
|
3274
|
-
const shouldLog = payload.text ? true : void 0;
|
|
3275
|
-
params.rememberSentText(payload.text, {
|
|
3276
|
-
combinedBody: params.context.Body,
|
|
3277
|
-
combinedBodySessionKey: params.route.sessionKey,
|
|
3278
|
-
conversationId,
|
|
3279
|
-
logVerboseMessage: shouldLog
|
|
3280
|
-
});
|
|
3281
3312
|
if (shouldLogVerbose$1()) {
|
|
3282
3313
|
const reply = resolveSendableOutboundReplyParts(payload);
|
|
3283
3314
|
logVerbose$1(`Reply body: ${payload.text != null ? reply.text : "<media>"}${reply.hasMedia ? " (media)" : ""} -> ${conversationId}`);
|
|
@@ -3324,14 +3355,7 @@ function createWhatsAppReplyPlan(params) {
|
|
|
3324
3355
|
}, "auto-reply was not accepted by WhatsApp provider");
|
|
3325
3356
|
return result;
|
|
3326
3357
|
}
|
|
3327
|
-
if (options?.recordDelivery !== false)
|
|
3328
|
-
recordDeliveredPayload(normalizedDeliveryPayload);
|
|
3329
|
-
} catch (error) {
|
|
3330
|
-
throw createChannelPartialDeliveryError(error, {
|
|
3331
|
-
...result,
|
|
3332
|
-
visibleReplySent: true
|
|
3333
|
-
});
|
|
3334
|
-
}
|
|
3358
|
+
if (options?.recordDelivery !== false) recordDeliveredPayload(normalizedDeliveryPayload);
|
|
3335
3359
|
return result;
|
|
3336
3360
|
};
|
|
3337
3361
|
const mediaOnlyCoalescer = createWhatsAppMediaOnlyReplyCoalescer({ deliver: async (pending) => {
|
|
@@ -3448,13 +3472,16 @@ function createWhatsAppReplyPlan(params) {
|
|
|
3448
3472
|
onToolStart: async (payload) => {
|
|
3449
3473
|
const toolName = payload.name?.trim();
|
|
3450
3474
|
if (toolName) await statusReactionController.setTool(toolName);
|
|
3475
|
+
return false;
|
|
3451
3476
|
},
|
|
3452
3477
|
onCompactionStart: async () => {
|
|
3453
3478
|
await statusReactionController.setCompacting();
|
|
3479
|
+
return false;
|
|
3454
3480
|
},
|
|
3455
3481
|
onCompactionEnd: async () => {
|
|
3456
3482
|
statusReactionController.cancelPending();
|
|
3457
3483
|
await statusReactionController.setThinking();
|
|
3484
|
+
return false;
|
|
3458
3485
|
}
|
|
3459
3486
|
} : {}
|
|
3460
3487
|
},
|
|
@@ -3473,7 +3500,7 @@ function createWhatsAppReplyPlan(params) {
|
|
|
3473
3500
|
}
|
|
3474
3501
|
if (statusReactionController) finalizeWhatsAppStatusReaction({
|
|
3475
3502
|
controller: statusReactionController,
|
|
3476
|
-
outcome: didDeliverVisibleReply ? "
|
|
3503
|
+
outcome: readAgentRunTerminalOutcome(dispatchResult) === "failed" || !didDeliverVisibleReply ? "error" : "done"
|
|
3477
3504
|
});
|
|
3478
3505
|
if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
|
|
3479
3506
|
return didDeliverVisibleReply;
|
|
@@ -3487,12 +3514,8 @@ async function finalizeWhatsAppStatusReaction(params) {
|
|
|
3487
3514
|
}
|
|
3488
3515
|
//#endregion
|
|
3489
3516
|
//#region extensions/whatsapp/src/auto-reply/monitor/message-line.runtime.ts
|
|
3490
|
-
function normalizeAgentId$1(agentId) {
|
|
3491
|
-
return agentId.trim().toLowerCase() || "main";
|
|
3492
|
-
}
|
|
3493
3517
|
function resolveIdentityNamePrefix(cfg, agentId) {
|
|
3494
|
-
const
|
|
3495
|
-
const identityName = cfg.agents?.list?.find((agent) => normalizeAgentId$1(agent.id ?? "") === normalizedAgentId)?.identity?.name?.trim();
|
|
3518
|
+
const identityName = resolveAgentConfig(cfg, agentId)?.identity?.name?.trim();
|
|
3496
3519
|
return identityName ? `[${identityName}]` : void 0;
|
|
3497
3520
|
}
|
|
3498
3521
|
function resolveMessagePrefix(cfg, agentId, opts) {
|
|
@@ -3660,7 +3683,7 @@ async function processMessage(params) {
|
|
|
3660
3683
|
...params.msg,
|
|
3661
3684
|
payload: {
|
|
3662
3685
|
...params.msg.payload,
|
|
3663
|
-
body: audioTranscript
|
|
3686
|
+
body: formatAudioTranscriptForAgent(audioTranscript)
|
|
3664
3687
|
}
|
|
3665
3688
|
} : params.msg;
|
|
3666
3689
|
const visibleReplyTo = resolveVisibleWhatsAppReplyContext({
|
|
@@ -3711,15 +3734,6 @@ async function processMessage(params) {
|
|
|
3711
3734
|
});
|
|
3712
3735
|
shouldClearGroupHistory = !(params.suppressGroupHistoryClear ?? false);
|
|
3713
3736
|
}
|
|
3714
|
-
const combinedEchoKey = params.buildCombinedEchoKey({
|
|
3715
|
-
sessionKey: params.route.sessionKey,
|
|
3716
|
-
combinedBody
|
|
3717
|
-
});
|
|
3718
|
-
if (params.echoHas(combinedEchoKey)) {
|
|
3719
|
-
logVerbose$1("Skipping auto-reply: detected echo for combined message");
|
|
3720
|
-
params.echoForget(combinedEchoKey);
|
|
3721
|
-
return false;
|
|
3722
|
-
}
|
|
3723
3737
|
const statusReactionController = params.statusReactionController ?? (params.cfg.messages?.statusReactions?.enabled === true && !params.ackAlreadySent ? await createWhatsAppStatusReactionController({
|
|
3724
3738
|
cfg: params.cfg,
|
|
3725
3739
|
msg: params.msg,
|
|
@@ -3803,6 +3817,7 @@ async function processMessage(params) {
|
|
|
3803
3817
|
msg: params.msg,
|
|
3804
3818
|
rawBody: commandBody,
|
|
3805
3819
|
route: params.route,
|
|
3820
|
+
buildContext: params.buildContext,
|
|
3806
3821
|
sender: {
|
|
3807
3822
|
id: getPrimaryIdentityId(sender) ?? void 0,
|
|
3808
3823
|
name: sender.name ?? void 0,
|
|
@@ -3877,7 +3892,6 @@ async function processMessage(params) {
|
|
|
3877
3892
|
maxMediaTextChunkLimit: params.maxMediaTextChunkLimit,
|
|
3878
3893
|
inbound,
|
|
3879
3894
|
onModelSelected,
|
|
3880
|
-
rememberSentText: params.rememberSentText,
|
|
3881
3895
|
replyLogger: params.replyLogger,
|
|
3882
3896
|
replyPipeline: {
|
|
3883
3897
|
...replyPipeline,
|
|
@@ -3900,6 +3914,7 @@ async function processMessage(params) {
|
|
|
3900
3914
|
sessionKey: params.route.sessionKey
|
|
3901
3915
|
},
|
|
3902
3916
|
ctxPayload,
|
|
3917
|
+
dispatchReplyFromConfig: params.dispatchReplyFromConfig,
|
|
3903
3918
|
record: {
|
|
3904
3919
|
onRecordError: (err) => {
|
|
3905
3920
|
params.replyLogger.warn({
|
|
@@ -3934,19 +3949,13 @@ async function processMessage(params) {
|
|
|
3934
3949
|
}
|
|
3935
3950
|
//#endregion
|
|
3936
3951
|
//#region extensions/whatsapp/src/auto-reply/monitor/on-message.ts
|
|
3937
|
-
function readDeprecatedAccessControlPassed(msg) {
|
|
3938
|
-
return msg.accessControlPassed;
|
|
3939
|
-
}
|
|
3940
3952
|
function createWebOnMessageHandler(params) {
|
|
3941
|
-
const hasExplicitlyPassedInboundAccess = (msg) =>
|
|
3942
|
-
if (msg.admission.ingress.decisiveGateId === "legacy-flat-compat") return readDeprecatedAccessControlPassed(msg) === true;
|
|
3943
|
-
return msg.admission.ingress.decision === "allow";
|
|
3944
|
-
};
|
|
3953
|
+
const hasExplicitlyPassedInboundAccess = (msg) => msg.admission.ingress.decision === "allow";
|
|
3945
3954
|
const withDirectSenderPeer = (msg, peerId) => {
|
|
3946
3955
|
if (requireWhatsAppInboundAdmission(msg).conversation.kind === "group" || msg.platform.sender?.e164 || msg.platform.senderE164 || !peerId.startsWith("+")) return msg;
|
|
3947
3956
|
const normalized = normalizeE164(peerId);
|
|
3948
3957
|
if (!normalized) return msg;
|
|
3949
|
-
return
|
|
3958
|
+
return {
|
|
3950
3959
|
...msg,
|
|
3951
3960
|
platform: {
|
|
3952
3961
|
...msg.platform,
|
|
@@ -3956,7 +3965,7 @@ function createWebOnMessageHandler(params) {
|
|
|
3956
3965
|
},
|
|
3957
3966
|
senderE164: normalized
|
|
3958
3967
|
}
|
|
3959
|
-
}
|
|
3968
|
+
};
|
|
3960
3969
|
};
|
|
3961
3970
|
const processForRoute = async (cfg, msg, route, groupHistoryKey, opts) => {
|
|
3962
3971
|
const processParams = {
|
|
@@ -3973,10 +3982,8 @@ function createWebOnMessageHandler(params) {
|
|
|
3973
3982
|
replyResolver: params.replyResolver,
|
|
3974
3983
|
replyLogger: params.replyLogger,
|
|
3975
3984
|
backgroundTasks: params.backgroundTasks,
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
echoForget: params.echoTracker.forget,
|
|
3979
|
-
buildCombinedEchoKey: params.echoTracker.buildCombinedKey
|
|
3985
|
+
buildContext: params.buildContext,
|
|
3986
|
+
dispatchReplyFromConfig: params.dispatchReplyFromConfig
|
|
3980
3987
|
};
|
|
3981
3988
|
if (opts?.groupHistory !== void 0) processParams.groupHistory = opts.groupHistory;
|
|
3982
3989
|
if (opts?.suppressGroupHistoryClear !== void 0) processParams.suppressGroupHistoryClear = opts.suppressGroupHistoryClear;
|
|
@@ -4012,11 +4019,6 @@ function createWebOnMessageHandler(params) {
|
|
|
4012
4019
|
});
|
|
4013
4020
|
const baseMentionConfig = buildMentionConfig(cfg);
|
|
4014
4021
|
if (conversationId === msg.platform.recipientJid) logVerbose(`📱 Same-phone mode detected (from === to: ${conversationId})`);
|
|
4015
|
-
if (params.echoTracker.has(msg.payload.body, conversationId)) {
|
|
4016
|
-
logVerbose("Skipping auto-reply: detected echo (message matches recently sent text)");
|
|
4017
|
-
params.echoTracker.forget(msg.payload.body, conversationId);
|
|
4018
|
-
return;
|
|
4019
|
-
}
|
|
4020
4022
|
const configuredRoute = resolveConfiguredBindingRoute({
|
|
4021
4023
|
cfg,
|
|
4022
4024
|
route: baseConversationRoute,
|
|
@@ -4292,10 +4294,6 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
4292
4294
|
const groupMetadataCache = /* @__PURE__ */ new Map();
|
|
4293
4295
|
const recentMessageKeys = /* @__PURE__ */ new Map();
|
|
4294
4296
|
const baileysGroupMetaCache = /* @__PURE__ */ new Map();
|
|
4295
|
-
const echoTracker = createEchoTracker({
|
|
4296
|
-
maxItems: 100,
|
|
4297
|
-
logVerbose
|
|
4298
|
-
});
|
|
4299
4297
|
const sleep = tuning.sleep ?? ((ms, signal) => sleepWithAbort(ms, signal ?? abortSignal));
|
|
4300
4298
|
const stopRequested = () => abortSignal?.aborted === true;
|
|
4301
4299
|
const currentMaxListeners = process.getMaxListeners?.() ?? 10;
|
|
@@ -4334,15 +4332,12 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
4334
4332
|
cfg,
|
|
4335
4333
|
channel: "whatsapp"
|
|
4336
4334
|
});
|
|
4337
|
-
const shouldDebounce = (msg) => {
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
allowDebounce: !(admitted.payload.location || admitted.quote?.id || admitted.quote?.body)
|
|
4344
|
-
});
|
|
4345
|
-
};
|
|
4335
|
+
const shouldDebounce = (msg) => shouldDebounceTextInbound({
|
|
4336
|
+
text: msg.payload.commandBody ?? msg.payload.body,
|
|
4337
|
+
cfg,
|
|
4338
|
+
hasMedia: Boolean(msg.payload.media?.path || msg.payload.media?.type),
|
|
4339
|
+
allowDebounce: !(msg.payload.location || msg.quote?.id || msg.quote?.body)
|
|
4340
|
+
});
|
|
4346
4341
|
let connection;
|
|
4347
4342
|
try {
|
|
4348
4343
|
connection = await controller.openConnection({
|
|
@@ -4353,6 +4348,7 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
4353
4348
|
return meta?.participants?.length ? meta : void 0;
|
|
4354
4349
|
},
|
|
4355
4350
|
createListener: async ({ sock, connection: connectionLocal }) => {
|
|
4351
|
+
const pluginChannelRuntime = tuning.channelRuntime;
|
|
4356
4352
|
const onMessage = createWebOnMessageHandler({
|
|
4357
4353
|
cfg,
|
|
4358
4354
|
loadConfig: loadCurrentMonitorConfig,
|
|
@@ -4362,12 +4358,13 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
4362
4358
|
groupHistoryLimit,
|
|
4363
4359
|
groupHistories,
|
|
4364
4360
|
groupMemberNames,
|
|
4365
|
-
echoTracker,
|
|
4366
4361
|
backgroundTasks: connectionLocal.backgroundTasks,
|
|
4367
4362
|
replyResolver: activeReplyResolver,
|
|
4368
4363
|
replyLogger,
|
|
4369
4364
|
baseMentionConfig,
|
|
4370
|
-
account
|
|
4365
|
+
account,
|
|
4366
|
+
buildContext: pluginChannelRuntime?.inbound.buildContext,
|
|
4367
|
+
dispatchReplyFromConfig: pluginChannelRuntime?.reply?.dispatchReplyFromConfig
|
|
4371
4368
|
});
|
|
4372
4369
|
return await (listenerFactory ?? attachWebInboxToSocket)({
|
|
4373
4370
|
cfg,
|
|
@@ -4394,11 +4391,10 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
4394
4391
|
recentMessageKeys,
|
|
4395
4392
|
baileysGroupMetaCache,
|
|
4396
4393
|
onMessage: async (msg) => {
|
|
4397
|
-
const admitted = normalizeAdmittedWebInboundMessage(msg);
|
|
4398
4394
|
const inboundAt = Date.now();
|
|
4399
4395
|
controller.noteInbound(inboundAt);
|
|
4400
4396
|
statusController.noteInbound(inboundAt);
|
|
4401
|
-
await onMessage(
|
|
4397
|
+
await onMessage(msg);
|
|
4402
4398
|
},
|
|
4403
4399
|
onPendingWorkChanged: (pendingWorkCount, at) => {
|
|
4404
4400
|
statusController.noteBusy(pendingWorkCount > 0, at);
|