@gakr-gakr/whatsapp 0.1.0
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/action-runtime-api.ts +1 -0
- package/action-runtime.runtime.ts +1 -0
- package/api.ts +67 -0
- package/auth-presence.ts +80 -0
- package/autobot.plugin.json +23 -0
- package/channel-config-api.ts +1 -0
- package/channel-plugin-api.ts +3 -0
- package/config-api.ts +4 -0
- package/constants.ts +1 -0
- package/contract-api.ts +29 -0
- package/directory-contract-api.ts +4 -0
- package/doctor-contract-api.ts +8 -0
- package/index.ts +16 -0
- package/legacy-session-surface-api.ts +6 -0
- package/legacy-state-migrations-api.ts +1 -0
- package/light-runtime-api.ts +12 -0
- package/login-qr-api.ts +1 -0
- package/login-qr-runtime.ts +23 -0
- package/outbound-payload-test-api.ts +1 -0
- package/package.json +76 -0
- package/runtime-api.ts +84 -0
- package/secret-contract-api.ts +4 -0
- package/security-contract-api.ts +4 -0
- package/setup-entry.ts +21 -0
- package/setup-plugin-api.ts +3 -0
- package/src/account-config.ts +77 -0
- package/src/account-ids.ts +17 -0
- package/src/account-types.ts +5 -0
- package/src/accounts.ts +176 -0
- package/src/action-runtime-target-auth.ts +27 -0
- package/src/action-runtime.ts +76 -0
- package/src/active-listener.ts +17 -0
- package/src/agent-tools-login.ts +113 -0
- package/src/approval-auth.ts +27 -0
- package/src/auth-store.runtime.ts +1 -0
- package/src/auth-store.ts +494 -0
- package/src/auto-reply/config.runtime.ts +16 -0
- package/src/auto-reply/constants.ts +1 -0
- package/src/auto-reply/deliver-reply.ts +332 -0
- package/src/auto-reply/loggers.ts +6 -0
- package/src/auto-reply/mentions.ts +131 -0
- package/src/auto-reply/monitor/ack-reaction.ts +99 -0
- package/src/auto-reply/monitor/audio-preflight.runtime.ts +9 -0
- package/src/auto-reply/monitor/broadcast.ts +153 -0
- package/src/auto-reply/monitor/commands.ts +19 -0
- package/src/auto-reply/monitor/echo.ts +64 -0
- package/src/auto-reply/monitor/group-activation.runtime.ts +1 -0
- package/src/auto-reply/monitor/group-activation.ts +73 -0
- package/src/auto-reply/monitor/group-gating.runtime.ts +8 -0
- package/src/auto-reply/monitor/group-gating.ts +218 -0
- package/src/auto-reply/monitor/group-members.ts +65 -0
- package/src/auto-reply/monitor/inbound-context.ts +92 -0
- package/src/auto-reply/monitor/inbound-dispatch.runtime.ts +22 -0
- package/src/auto-reply/monitor/inbound-dispatch.ts +749 -0
- package/src/auto-reply/monitor/last-route.ts +61 -0
- package/src/auto-reply/monitor/listener-log.ts +28 -0
- package/src/auto-reply/monitor/message-line.runtime.ts +38 -0
- package/src/auto-reply/monitor/message-line.ts +54 -0
- package/src/auto-reply/monitor/on-message.ts +333 -0
- package/src/auto-reply/monitor/peer.ts +17 -0
- package/src/auto-reply/monitor/process-message.ts +584 -0
- package/src/auto-reply/monitor/runtime-api.ts +36 -0
- package/src/auto-reply/monitor/status-reaction.ts +108 -0
- package/src/auto-reply/monitor-state.ts +114 -0
- package/src/auto-reply/monitor.ts +720 -0
- package/src/auto-reply/reply-resolver.runtime.ts +1 -0
- package/src/auto-reply/types.ts +48 -0
- package/src/auto-reply/util.ts +62 -0
- package/src/auto-reply.impl.ts +6 -0
- package/src/auto-reply.ts +1 -0
- package/src/channel-actions.runtime.ts +7 -0
- package/src/channel-actions.ts +85 -0
- package/src/channel-outbound.ts +87 -0
- package/src/channel-react-action.runtime.ts +10 -0
- package/src/channel-react-action.ts +247 -0
- package/src/channel.runtime.ts +117 -0
- package/src/channel.setup.ts +32 -0
- package/src/channel.ts +356 -0
- package/src/command-policy.ts +7 -0
- package/src/config-accessors.ts +22 -0
- package/src/config-schema.ts +6 -0
- package/src/config-ui-hints.ts +24 -0
- package/src/connection-controller-registry.ts +49 -0
- package/src/connection-controller.ts +680 -0
- package/src/creds-files.ts +19 -0
- package/src/creds-persistence.ts +71 -0
- package/src/directory-config.ts +40 -0
- package/src/doctor-contract.ts +11 -0
- package/src/doctor.ts +56 -0
- package/src/document-filename.ts +17 -0
- package/src/group-intro.ts +15 -0
- package/src/group-policy.ts +40 -0
- package/src/group-session-contract.ts +20 -0
- package/src/group-session-key.ts +42 -0
- package/src/heartbeat.ts +34 -0
- package/src/identity.ts +164 -0
- package/src/inbound/access-control.ts +187 -0
- package/src/inbound/dedupe.ts +132 -0
- package/src/inbound/extract.ts +484 -0
- package/src/inbound/lifecycle.ts +39 -0
- package/src/inbound/media.ts +128 -0
- package/src/inbound/monitor.ts +1042 -0
- package/src/inbound/outbound-mentions.ts +260 -0
- package/src/inbound/runtime-api.ts +7 -0
- package/src/inbound/save-media.runtime.ts +1 -0
- package/src/inbound/send-api.ts +203 -0
- package/src/inbound/send-result.ts +109 -0
- package/src/inbound/types.ts +107 -0
- package/src/inbound-policy.ts +215 -0
- package/src/inbound.ts +9 -0
- package/src/login-qr.ts +542 -0
- package/src/login.ts +83 -0
- package/src/media.ts +10 -0
- package/src/monitor-inbox.allows-messages-from-senders-allowfrom-list.test-support.ts +417 -0
- package/src/monitor-inbox.append-upsert.test-support.ts +133 -0
- package/src/monitor-inbox.blocks-messages-from-unauthorized-senders-not-allowfrom.test-support.ts +418 -0
- package/src/monitor-inbox.captures-media-path-image-messages.test-support.ts +308 -0
- package/src/monitor-inbox.streams-inbound-messages.test-support.ts +824 -0
- package/src/normalize-target.ts +148 -0
- package/src/normalize.ts +8 -0
- package/src/outbound-adapter.ts +36 -0
- package/src/outbound-base.ts +256 -0
- package/src/outbound-media-contract.ts +307 -0
- package/src/outbound-media.runtime.ts +41 -0
- package/src/outbound-send-deps.ts +1 -0
- package/src/outbound-test-support.ts +16 -0
- package/src/qa-driver.runtime.ts +189 -0
- package/src/qr-image.ts +1 -0
- package/src/qr-terminal.ts +1 -0
- package/src/quoted-message.ts +184 -0
- package/src/reaction-level.ts +24 -0
- package/src/reconnect.ts +55 -0
- package/src/resolve-outbound-target.ts +58 -0
- package/src/runtime-api.ts +59 -0
- package/src/runtime-group-policy.ts +16 -0
- package/src/runtime.ts +9 -0
- package/src/security-contract.ts +47 -0
- package/src/security-fix.ts +71 -0
- package/src/send.ts +342 -0
- package/src/session-contract.ts +43 -0
- package/src/session-errors.ts +125 -0
- package/src/session-route.ts +32 -0
- package/src/session.runtime.ts +8 -0
- package/src/session.ts +327 -0
- package/src/setup-core.ts +52 -0
- package/src/setup-finalize.ts +450 -0
- package/src/setup-surface.ts +71 -0
- package/src/setup-test-helpers.ts +217 -0
- package/src/shared.ts +291 -0
- package/src/socket-timing.ts +38 -0
- package/src/state-migrations.ts +55 -0
- package/src/status-issues.ts +185 -0
- package/src/system-prompt.ts +31 -0
- package/src/targets-runtime.ts +221 -0
- package/src/text-runtime.ts +18 -0
- package/src/vcard.ts +84 -0
- package/targets.ts +5 -0
- package/test-api.ts +2 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { normalizeE164 } from "autobot/plugin-sdk/account-resolution";
|
|
2
|
+
import { normalizeLowercaseStringOrEmpty } from "autobot/plugin-sdk/string-coerce-runtime";
|
|
3
|
+
|
|
4
|
+
const WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i;
|
|
5
|
+
const WHATSAPP_LEGACY_USER_JID_RE = /^(\d+)@c\.us$/i;
|
|
6
|
+
const WHATSAPP_LID_RE = /^(\d+)@lid$/i;
|
|
7
|
+
const NON_WHATSAPP_PROVIDER_PREFIX_RE = /^[a-z][a-z0-9-]*:/i;
|
|
8
|
+
const WHATSAPP_NEWSLETTER_JID_RE = /^([0-9]+)@newsletter$/i;
|
|
9
|
+
|
|
10
|
+
function stripWhatsAppTargetPrefixes(value: string): string {
|
|
11
|
+
let candidate = value.trim();
|
|
12
|
+
for (;;) {
|
|
13
|
+
const before = candidate;
|
|
14
|
+
candidate = candidate.replace(/^whatsapp:/i, "").trim();
|
|
15
|
+
if (candidate === before) {
|
|
16
|
+
return candidate;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeWhatsAppGroupJid(value: string): string | null {
|
|
22
|
+
const candidate = stripWhatsAppTargetPrefixes(value)
|
|
23
|
+
.replace(/^group:/i, "")
|
|
24
|
+
.trim();
|
|
25
|
+
const lower = normalizeLowercaseStringOrEmpty(candidate);
|
|
26
|
+
if (!lower.endsWith("@g.us")) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const localPart = candidate.slice(0, candidate.length - "@g.us".length);
|
|
30
|
+
if (!localPart || localPart.includes("@")) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return /^[0-9]+(-[0-9]+)*$/.test(localPart) ? `${localPart}@g.us` : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isWhatsAppGroupJid(value: string): boolean {
|
|
37
|
+
return normalizeWhatsAppGroupJid(value) !== null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isWhatsAppNewsletterJid(value: string): boolean {
|
|
41
|
+
const candidate = stripWhatsAppTargetPrefixes(value);
|
|
42
|
+
return WHATSAPP_NEWSLETTER_JID_RE.test(candidate);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isWhatsAppUserTarget(value: string): boolean {
|
|
46
|
+
const candidate = stripWhatsAppTargetPrefixes(value);
|
|
47
|
+
return (
|
|
48
|
+
WHATSAPP_USER_JID_RE.test(candidate) ||
|
|
49
|
+
WHATSAPP_LEGACY_USER_JID_RE.test(candidate) ||
|
|
50
|
+
WHATSAPP_LID_RE.test(candidate)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function extractUserJidPhone(jid: string): string | null {
|
|
55
|
+
const userMatch = jid.match(WHATSAPP_USER_JID_RE);
|
|
56
|
+
if (userMatch) {
|
|
57
|
+
return userMatch[1];
|
|
58
|
+
}
|
|
59
|
+
const legacyUserMatch = jid.match(WHATSAPP_LEGACY_USER_JID_RE);
|
|
60
|
+
if (legacyUserMatch) {
|
|
61
|
+
return legacyUserMatch[1];
|
|
62
|
+
}
|
|
63
|
+
const lidMatch = jid.match(WHATSAPP_LID_RE);
|
|
64
|
+
if (lidMatch) {
|
|
65
|
+
return lidMatch[1];
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function normalizeWhatsAppTarget(value: string): string | null {
|
|
71
|
+
const candidate = stripWhatsAppTargetPrefixes(value);
|
|
72
|
+
if (!candidate) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const groupJid = normalizeWhatsAppGroupJid(candidate);
|
|
76
|
+
if (groupJid) {
|
|
77
|
+
return groupJid;
|
|
78
|
+
}
|
|
79
|
+
if (isWhatsAppNewsletterJid(candidate)) {
|
|
80
|
+
const match = candidate.match(WHATSAPP_NEWSLETTER_JID_RE);
|
|
81
|
+
return match ? `${match[1]}@newsletter` : null;
|
|
82
|
+
}
|
|
83
|
+
if (isWhatsAppUserTarget(candidate)) {
|
|
84
|
+
const phone = extractUserJidPhone(candidate);
|
|
85
|
+
if (!phone) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const normalized = normalizeE164(phone);
|
|
89
|
+
return normalized.length > 1 ? normalized : null;
|
|
90
|
+
}
|
|
91
|
+
if (candidate.includes("@")) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (NON_WHATSAPP_PROVIDER_PREFIX_RE.test(candidate)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const normalized = normalizeE164(candidate);
|
|
98
|
+
return normalized.length > 1 ? normalized : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function normalizeWhatsAppMessagingTarget(raw: string): string | undefined {
|
|
102
|
+
const trimmed = raw.trim();
|
|
103
|
+
if (!trimmed) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return normalizeWhatsAppTarget(trimmed) ?? undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function normalizeWhatsAppAllowFromEntries(allowFrom: Array<string | number>): string[] {
|
|
110
|
+
const seen = new Set<string>();
|
|
111
|
+
const normalized = allowFrom
|
|
112
|
+
.map((entry) => String(entry).trim())
|
|
113
|
+
.filter((entry): entry is string => Boolean(entry))
|
|
114
|
+
.map(normalizeWhatsAppAllowFromEntry)
|
|
115
|
+
.filter((entry): entry is string => Boolean(entry));
|
|
116
|
+
return normalized.filter((entry) => {
|
|
117
|
+
if (seen.has(entry)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
seen.add(entry);
|
|
121
|
+
return true;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function normalizeWhatsAppAllowFromEntry(entry: string): string | null {
|
|
126
|
+
if (entry === "*") {
|
|
127
|
+
return entry;
|
|
128
|
+
}
|
|
129
|
+
const normalized = normalizeWhatsAppTarget(entry);
|
|
130
|
+
if (!normalized) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
return normalized.startsWith("+") ? normalized.slice(1) : normalized;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function looksLikeWhatsAppTargetId(raw: string): boolean {
|
|
137
|
+
const trimmed = raw.trim();
|
|
138
|
+
if (!trimmed) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
return (
|
|
142
|
+
/^whatsapp:/i.test(trimmed) ||
|
|
143
|
+
isWhatsAppGroupJid(trimmed) ||
|
|
144
|
+
isWhatsAppNewsletterJid(trimmed) ||
|
|
145
|
+
isWhatsAppUserTarget(trimmed) ||
|
|
146
|
+
normalizeWhatsAppTarget(trimmed) !== null
|
|
147
|
+
);
|
|
148
|
+
}
|
package/src/normalize.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ChannelOutboundAdapter } from "autobot/plugin-sdk/channel-send-result";
|
|
2
|
+
import { chunkText } from "autobot/plugin-sdk/reply-chunking";
|
|
3
|
+
import { shouldLogVerbose } from "autobot/plugin-sdk/runtime-env";
|
|
4
|
+
import { createWhatsAppOutboundBase } from "./outbound-base.js";
|
|
5
|
+
import { normalizeWhatsAppPayloadText } from "./outbound-media-contract.js";
|
|
6
|
+
import { resolveWhatsAppOutboundTarget } from "./resolve-outbound-target.js";
|
|
7
|
+
|
|
8
|
+
type WhatsAppSendModule = typeof import("./send.js");
|
|
9
|
+
|
|
10
|
+
let whatsAppSendModulePromise: Promise<WhatsAppSendModule> | undefined;
|
|
11
|
+
|
|
12
|
+
function loadWhatsAppSendModule(): Promise<WhatsAppSendModule> {
|
|
13
|
+
whatsAppSendModulePromise ??= import("./send.js");
|
|
14
|
+
return whatsAppSendModulePromise;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeOutboundText(text: string | undefined): string {
|
|
18
|
+
return normalizeWhatsAppPayloadText(text);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const whatsappOutbound: ChannelOutboundAdapter = createWhatsAppOutboundBase({
|
|
22
|
+
chunker: chunkText,
|
|
23
|
+
sendMessageWhatsApp: async (to, text, options) =>
|
|
24
|
+
await (
|
|
25
|
+
await loadWhatsAppSendModule()
|
|
26
|
+
).sendMessageWhatsApp(to, normalizeOutboundText(text), {
|
|
27
|
+
...options,
|
|
28
|
+
}),
|
|
29
|
+
sendPollWhatsApp: async (to, poll, options) =>
|
|
30
|
+
await (await loadWhatsAppSendModule()).sendPollWhatsApp(to, poll, options),
|
|
31
|
+
shouldLogVerbose: () => shouldLogVerbose(),
|
|
32
|
+
resolveTarget: ({ to, allowFrom, mode }) =>
|
|
33
|
+
resolveWhatsAppOutboundTarget({ to, allowFrom, mode }),
|
|
34
|
+
normalizeText: normalizeOutboundText,
|
|
35
|
+
skipEmptyText: true,
|
|
36
|
+
});
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ACCOUNT_ID,
|
|
3
|
+
listCombinedAccountIds,
|
|
4
|
+
normalizeOptionalAccountId,
|
|
5
|
+
resolveListedDefaultAccountId,
|
|
6
|
+
} from "autobot/plugin-sdk/account-core";
|
|
7
|
+
import {
|
|
8
|
+
createAttachedChannelResultAdapter,
|
|
9
|
+
type ChannelOutboundAdapter,
|
|
10
|
+
} from "autobot/plugin-sdk/channel-send-result";
|
|
11
|
+
import type { AutoBotConfig } from "autobot/plugin-sdk/config-contracts";
|
|
12
|
+
import { resolveOutboundSendDep } from "autobot/plugin-sdk/outbound-send-deps";
|
|
13
|
+
import { sendTextMediaPayload } from "autobot/plugin-sdk/reply-payload";
|
|
14
|
+
import {
|
|
15
|
+
normalizeWhatsAppOutboundPayload,
|
|
16
|
+
normalizeWhatsAppPayloadText,
|
|
17
|
+
} from "./outbound-media-contract.js";
|
|
18
|
+
import { WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS } from "./outbound-send-deps.js";
|
|
19
|
+
import { lookupInboundMessageMetaForTarget } from "./quoted-message.js";
|
|
20
|
+
import { toWhatsappJid } from "./text-runtime.js";
|
|
21
|
+
|
|
22
|
+
type WhatsAppChunker = NonNullable<ChannelOutboundAdapter["chunker"]>;
|
|
23
|
+
type WhatsAppSendTextOptions = {
|
|
24
|
+
verbose: boolean;
|
|
25
|
+
cfg: AutoBotConfig;
|
|
26
|
+
mediaUrl?: string;
|
|
27
|
+
mediaAccess?: {
|
|
28
|
+
localRoots?: readonly string[];
|
|
29
|
+
readFile?: (filePath: string) => Promise<Buffer>;
|
|
30
|
+
};
|
|
31
|
+
mediaLocalRoots?: readonly string[];
|
|
32
|
+
mediaReadFile?: (filePath: string) => Promise<Buffer>;
|
|
33
|
+
gifPlayback?: boolean;
|
|
34
|
+
audioAsVoice?: boolean;
|
|
35
|
+
forceDocument?: boolean;
|
|
36
|
+
accountId?: string;
|
|
37
|
+
quotedMessageKey?: {
|
|
38
|
+
id: string;
|
|
39
|
+
remoteJid: string;
|
|
40
|
+
fromMe: boolean;
|
|
41
|
+
participant?: string;
|
|
42
|
+
messageText?: string;
|
|
43
|
+
};
|
|
44
|
+
preserveLeadingWhitespace?: boolean;
|
|
45
|
+
};
|
|
46
|
+
type WhatsAppSendMessage = (
|
|
47
|
+
to: string,
|
|
48
|
+
body: string,
|
|
49
|
+
options: WhatsAppSendTextOptions,
|
|
50
|
+
) => Promise<{ messageId: string; toJid: string }>;
|
|
51
|
+
type WhatsAppSendPoll = (
|
|
52
|
+
to: string,
|
|
53
|
+
poll: Parameters<NonNullable<ChannelOutboundAdapter["sendPoll"]>>[0]["poll"],
|
|
54
|
+
options: { verbose: boolean; accountId?: string; cfg: AutoBotConfig },
|
|
55
|
+
) => Promise<{ messageId: string; toJid: string }>;
|
|
56
|
+
|
|
57
|
+
type CreateWhatsAppOutboundBaseParams = {
|
|
58
|
+
chunker: WhatsAppChunker;
|
|
59
|
+
sendMessageWhatsApp: WhatsAppSendMessage;
|
|
60
|
+
sendPollWhatsApp: WhatsAppSendPoll;
|
|
61
|
+
shouldLogVerbose: () => boolean;
|
|
62
|
+
resolveTarget: ChannelOutboundAdapter["resolveTarget"];
|
|
63
|
+
normalizeText?: (text: string | undefined) => string;
|
|
64
|
+
skipEmptyText?: boolean;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function resolveQuoteLookupAccountId(cfg?: AutoBotConfig, accountId?: string | null): string {
|
|
68
|
+
const explicitAccountId = normalizeOptionalAccountId(accountId);
|
|
69
|
+
if (explicitAccountId) {
|
|
70
|
+
return explicitAccountId;
|
|
71
|
+
}
|
|
72
|
+
const channelCfg = cfg?.channels?.whatsapp;
|
|
73
|
+
const configuredIds = listCombinedAccountIds({
|
|
74
|
+
configuredAccountIds:
|
|
75
|
+
channelCfg?.accounts && typeof channelCfg.accounts === "object"
|
|
76
|
+
? Object.keys(channelCfg.accounts).filter(Boolean)
|
|
77
|
+
: [],
|
|
78
|
+
fallbackAccountIdWhenEmpty: DEFAULT_ACCOUNT_ID,
|
|
79
|
+
});
|
|
80
|
+
return resolveListedDefaultAccountId({
|
|
81
|
+
accountIds: configuredIds,
|
|
82
|
+
configuredDefaultAccountId: normalizeOptionalAccountId(channelCfg?.defaultAccount),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type WhatsAppOutboundBaseCore = Pick<
|
|
87
|
+
ChannelOutboundAdapter,
|
|
88
|
+
| "deliveryMode"
|
|
89
|
+
| "chunker"
|
|
90
|
+
| "chunkerMode"
|
|
91
|
+
| "textChunkLimit"
|
|
92
|
+
| "sanitizeText"
|
|
93
|
+
| "deliveryCapabilities"
|
|
94
|
+
| "pollMaxOptions"
|
|
95
|
+
| "resolveTarget"
|
|
96
|
+
| "sendText"
|
|
97
|
+
| "sendMedia"
|
|
98
|
+
| "sendPoll"
|
|
99
|
+
>;
|
|
100
|
+
|
|
101
|
+
export function createWhatsAppOutboundBase({
|
|
102
|
+
chunker,
|
|
103
|
+
sendMessageWhatsApp,
|
|
104
|
+
sendPollWhatsApp,
|
|
105
|
+
shouldLogVerbose,
|
|
106
|
+
resolveTarget,
|
|
107
|
+
normalizeText = normalizeWhatsAppPayloadText,
|
|
108
|
+
skipEmptyText = true,
|
|
109
|
+
}: CreateWhatsAppOutboundBaseParams): Pick<
|
|
110
|
+
ChannelOutboundAdapter,
|
|
111
|
+
| "deliveryMode"
|
|
112
|
+
| "chunker"
|
|
113
|
+
| "chunkerMode"
|
|
114
|
+
| "textChunkLimit"
|
|
115
|
+
| "sanitizeText"
|
|
116
|
+
| "deliveryCapabilities"
|
|
117
|
+
| "pollMaxOptions"
|
|
118
|
+
| "resolveTarget"
|
|
119
|
+
| "sendPayload"
|
|
120
|
+
| "sendText"
|
|
121
|
+
| "sendMedia"
|
|
122
|
+
| "sendPoll"
|
|
123
|
+
> {
|
|
124
|
+
const resolveQuotedMessageKey = (params: {
|
|
125
|
+
accountId: string;
|
|
126
|
+
to: string;
|
|
127
|
+
replyToId?: string | null;
|
|
128
|
+
}) => {
|
|
129
|
+
const replyToId = params.replyToId?.trim();
|
|
130
|
+
if (!replyToId) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
const targetJid = toWhatsappJid(params.to);
|
|
134
|
+
const cachedMeta = lookupInboundMessageMetaForTarget(params.accountId, targetJid, replyToId);
|
|
135
|
+
return {
|
|
136
|
+
id: replyToId,
|
|
137
|
+
remoteJid: cachedMeta?.remoteJid ?? targetJid,
|
|
138
|
+
fromMe: cachedMeta?.fromMe ?? false,
|
|
139
|
+
participant: cachedMeta?.participant,
|
|
140
|
+
messageText: cachedMeta?.body,
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const outbound: WhatsAppOutboundBaseCore = {
|
|
145
|
+
deliveryMode: "gateway",
|
|
146
|
+
chunker,
|
|
147
|
+
chunkerMode: "text",
|
|
148
|
+
textChunkLimit: 4000,
|
|
149
|
+
sanitizeText: ({ text }) => normalizeText(text),
|
|
150
|
+
deliveryCapabilities: {
|
|
151
|
+
durableFinal: {
|
|
152
|
+
text: true,
|
|
153
|
+
replyTo: true,
|
|
154
|
+
messageSendingHooks: true,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
pollMaxOptions: 12,
|
|
158
|
+
resolveTarget,
|
|
159
|
+
...createAttachedChannelResultAdapter({
|
|
160
|
+
channel: "whatsapp",
|
|
161
|
+
sendText: async ({ cfg, to, text, accountId, deps, gifPlayback, replyToId }) => {
|
|
162
|
+
const normalizedText = normalizeText(text);
|
|
163
|
+
if (skipEmptyText && !normalizedText) {
|
|
164
|
+
return { messageId: "" };
|
|
165
|
+
}
|
|
166
|
+
const send =
|
|
167
|
+
resolveOutboundSendDep<WhatsAppSendMessage>(deps, "whatsapp", {
|
|
168
|
+
legacyKeys: WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS,
|
|
169
|
+
}) ?? sendMessageWhatsApp;
|
|
170
|
+
const lookupAccountId = resolveQuoteLookupAccountId(cfg, accountId);
|
|
171
|
+
const quotedMessageKey = resolveQuotedMessageKey({
|
|
172
|
+
accountId: lookupAccountId,
|
|
173
|
+
to,
|
|
174
|
+
replyToId,
|
|
175
|
+
});
|
|
176
|
+
return await send(to, normalizedText, {
|
|
177
|
+
verbose: false,
|
|
178
|
+
cfg,
|
|
179
|
+
accountId: accountId ?? undefined,
|
|
180
|
+
gifPlayback,
|
|
181
|
+
quotedMessageKey,
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
sendMedia: async ({
|
|
185
|
+
cfg,
|
|
186
|
+
to,
|
|
187
|
+
text,
|
|
188
|
+
mediaUrl,
|
|
189
|
+
mediaAccess,
|
|
190
|
+
mediaLocalRoots,
|
|
191
|
+
mediaReadFile,
|
|
192
|
+
audioAsVoice,
|
|
193
|
+
accountId,
|
|
194
|
+
deps,
|
|
195
|
+
gifPlayback,
|
|
196
|
+
forceDocument,
|
|
197
|
+
replyToId,
|
|
198
|
+
}) => {
|
|
199
|
+
const send =
|
|
200
|
+
resolveOutboundSendDep<WhatsAppSendMessage>(deps, "whatsapp", {
|
|
201
|
+
legacyKeys: WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS,
|
|
202
|
+
}) ?? sendMessageWhatsApp;
|
|
203
|
+
const lookupAccountId = resolveQuoteLookupAccountId(cfg, accountId);
|
|
204
|
+
const quotedMessageKey = resolveQuotedMessageKey({
|
|
205
|
+
accountId: lookupAccountId,
|
|
206
|
+
to,
|
|
207
|
+
replyToId,
|
|
208
|
+
});
|
|
209
|
+
return await send(to, normalizeText(text), {
|
|
210
|
+
verbose: false,
|
|
211
|
+
cfg,
|
|
212
|
+
mediaUrl,
|
|
213
|
+
mediaAccess,
|
|
214
|
+
mediaLocalRoots,
|
|
215
|
+
mediaReadFile,
|
|
216
|
+
...(audioAsVoice === undefined ? {} : { audioAsVoice }),
|
|
217
|
+
accountId: accountId ?? undefined,
|
|
218
|
+
gifPlayback,
|
|
219
|
+
forceDocument,
|
|
220
|
+
quotedMessageKey,
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
sendPoll: async ({ cfg, to, poll, accountId }) =>
|
|
224
|
+
await sendPollWhatsApp(to, poll, {
|
|
225
|
+
verbose: shouldLogVerbose(),
|
|
226
|
+
accountId: accountId ?? undefined,
|
|
227
|
+
cfg,
|
|
228
|
+
}),
|
|
229
|
+
}),
|
|
230
|
+
};
|
|
231
|
+
return {
|
|
232
|
+
...outbound,
|
|
233
|
+
sendPayload: async (ctx) => {
|
|
234
|
+
if (ctx.payload.isError === true) {
|
|
235
|
+
return { channel: "whatsapp", messageId: "" };
|
|
236
|
+
}
|
|
237
|
+
const payload = normalizeWhatsAppOutboundPayload(ctx.payload, { normalizeText });
|
|
238
|
+
if (!payload.text && !(payload.mediaUrl || payload.mediaUrls?.length)) {
|
|
239
|
+
if (ctx.payload.interactive || ctx.payload.presentation || ctx.payload.channelData) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
"WhatsApp sendPayload does not support structured-only payloads without text or media.",
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return { channel: "whatsapp", messageId: "" };
|
|
245
|
+
}
|
|
246
|
+
return await sendTextMediaPayload({
|
|
247
|
+
channel: "whatsapp",
|
|
248
|
+
ctx: {
|
|
249
|
+
...ctx,
|
|
250
|
+
payload,
|
|
251
|
+
},
|
|
252
|
+
adapter: outbound,
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|