@symerian/symi 3.0.20 → 3.0.21
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/{audio-preflight-BaCdNfrk.js → audio-preflight-D7BVT-ls.js} +4 -4
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/{chrome-UfmVM0xR.js → chrome-B5CO2vB5.js} +7 -7
- package/dist/{deliver-BqXdac6W.js → deliver-CrwjsDwv.js} +1 -1
- package/dist/extensionAPI.js +7 -7
- package/dist/{image-DIWsXYcW.js → image-Csu7WcLW.js} +1 -1
- package/dist/{manager-DW3SxcPr.js → manager-BkkVjTO8.js} +1 -1
- package/dist/{pi-embedded-BNch0U5F.js → pi-embedded-Dhp64z5l.js} +16 -16
- package/dist/{pi-embedded-helpers-IkHl02JF.js → pi-embedded-helpers-840E4hop.js} +4 -4
- package/dist/{pw-ai-nMkA-oDJ.js → pw-ai-CBgJf_RR.js} +1 -1
- package/dist/{runner-DNEC58JI.js → runner-BbFKo1ne.js} +1 -1
- package/dist/{synthesis-BWAr0sZ9.js → synthesis-DoEM0E8_.js} +7 -7
- package/dist/{web-7a-m_UxL.js → web-BYXJn-Ps.js} +7 -7
- package/package.json +1 -1
- package/extensions/imessage/index.ts +0 -17
- package/extensions/imessage/node_modules/.bin/symi +0 -21
- package/extensions/imessage/package.json +0 -15
- package/extensions/imessage/src/channel.outbound.test.ts +0 -66
- package/extensions/imessage/src/channel.ts +0 -298
- package/extensions/imessage/src/runtime.ts +0 -14
- package/extensions/imessage/symi.plugin.json +0 -9
- package/extensions/line/index.ts +0 -19
- package/extensions/line/node_modules/.bin/symi +0 -21
- package/extensions/line/package.json +0 -30
- package/extensions/line/src/card-command.ts +0 -344
- package/extensions/line/src/channel.logout.test.ts +0 -133
- package/extensions/line/src/channel.sendPayload.test.ts +0 -312
- package/extensions/line/src/channel.startup.test.ts +0 -133
- package/extensions/line/src/channel.ts +0 -801
- package/extensions/line/src/runtime.ts +0 -14
- package/extensions/line/symi.plugin.json +0 -9
- package/extensions/signal/index.ts +0 -17
- package/extensions/signal/node_modules/.bin/symi +0 -21
- package/extensions/signal/package.json +0 -15
- package/extensions/signal/src/channel.ts +0 -302
- package/extensions/signal/src/runtime.ts +0 -14
- package/extensions/signal/symi.plugin.json +0 -9
- package/extensions/telegram/index.ts +0 -17
- package/extensions/telegram/node_modules/.bin/symi +0 -21
- package/extensions/telegram/package.json +0 -15
- package/extensions/telegram/src/channel.test.ts +0 -125
- package/extensions/telegram/src/channel.ts +0 -560
- package/extensions/telegram/src/runtime.ts +0 -14
- package/extensions/telegram/symi.plugin.json +0 -9
- package/extensions/whatsapp/index.ts +0 -17
- package/extensions/whatsapp/node_modules/.bin/symi +0 -21
- package/extensions/whatsapp/package.json +0 -15
- package/extensions/whatsapp/src/channel.ts +0 -465
- package/extensions/whatsapp/src/resolve-target.test.ts +0 -170
- package/extensions/whatsapp/src/runtime.ts +0 -14
- package/extensions/whatsapp/symi.plugin.json +0 -9
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
applyAccountNameToChannelSection,
|
|
3
|
-
buildChannelConfigSchema,
|
|
4
|
-
collectWhatsAppStatusIssues,
|
|
5
|
-
createActionGate,
|
|
6
|
-
DEFAULT_ACCOUNT_ID,
|
|
7
|
-
escapeRegExp,
|
|
8
|
-
formatPairingApproveHint,
|
|
9
|
-
getChatChannelMeta,
|
|
10
|
-
listWhatsAppAccountIds,
|
|
11
|
-
listWhatsAppDirectoryGroupsFromConfig,
|
|
12
|
-
listWhatsAppDirectoryPeersFromConfig,
|
|
13
|
-
looksLikeWhatsAppTargetId,
|
|
14
|
-
migrateBaseNameToDefaultAccount,
|
|
15
|
-
normalizeAccountId,
|
|
16
|
-
normalizeE164,
|
|
17
|
-
normalizeWhatsAppMessagingTarget,
|
|
18
|
-
normalizeWhatsAppTarget,
|
|
19
|
-
readStringParam,
|
|
20
|
-
resolveDefaultWhatsAppAccountId,
|
|
21
|
-
resolveWhatsAppOutboundTarget,
|
|
22
|
-
resolveWhatsAppAccount,
|
|
23
|
-
resolveWhatsAppGroupRequireMention,
|
|
24
|
-
resolveWhatsAppGroupToolPolicy,
|
|
25
|
-
resolveWhatsAppHeartbeatRecipients,
|
|
26
|
-
whatsappOnboardingAdapter,
|
|
27
|
-
WhatsAppConfigSchema,
|
|
28
|
-
type ChannelMessageActionName,
|
|
29
|
-
type ChannelPlugin,
|
|
30
|
-
type ResolvedWhatsAppAccount,
|
|
31
|
-
} from "symi/plugin-sdk";
|
|
32
|
-
import { getWhatsAppRuntime } from "./runtime.js";
|
|
33
|
-
|
|
34
|
-
const meta = getChatChannelMeta("whatsapp");
|
|
35
|
-
|
|
36
|
-
export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
|
37
|
-
id: "whatsapp",
|
|
38
|
-
meta: {
|
|
39
|
-
...meta,
|
|
40
|
-
showConfigured: false,
|
|
41
|
-
quickstartAllowFrom: true,
|
|
42
|
-
forceAccountBinding: true,
|
|
43
|
-
preferSessionLookupForAnnounceTarget: true,
|
|
44
|
-
},
|
|
45
|
-
onboarding: whatsappOnboardingAdapter,
|
|
46
|
-
agentTools: () => [getWhatsAppRuntime().channel.whatsapp.createLoginTool()],
|
|
47
|
-
pairing: {
|
|
48
|
-
idLabel: "whatsappSenderId",
|
|
49
|
-
},
|
|
50
|
-
capabilities: {
|
|
51
|
-
chatTypes: ["direct", "group"],
|
|
52
|
-
polls: true,
|
|
53
|
-
reactions: true,
|
|
54
|
-
media: true,
|
|
55
|
-
},
|
|
56
|
-
reload: { configPrefixes: ["web"], noopPrefixes: ["channels.whatsapp"] },
|
|
57
|
-
gatewayMethods: ["web.login.start", "web.login.wait"],
|
|
58
|
-
configSchema: buildChannelConfigSchema(WhatsAppConfigSchema),
|
|
59
|
-
config: {
|
|
60
|
-
listAccountIds: (cfg) => listWhatsAppAccountIds(cfg),
|
|
61
|
-
resolveAccount: (cfg, accountId) => resolveWhatsAppAccount({ cfg, accountId }),
|
|
62
|
-
defaultAccountId: (cfg) => resolveDefaultWhatsAppAccountId(cfg),
|
|
63
|
-
setAccountEnabled: ({ cfg, accountId, enabled }) => {
|
|
64
|
-
const accountKey = accountId || DEFAULT_ACCOUNT_ID;
|
|
65
|
-
const accounts = { ...cfg.channels?.whatsapp?.accounts };
|
|
66
|
-
const existing = accounts[accountKey] ?? {};
|
|
67
|
-
return {
|
|
68
|
-
...cfg,
|
|
69
|
-
channels: {
|
|
70
|
-
...cfg.channels,
|
|
71
|
-
whatsapp: {
|
|
72
|
-
...cfg.channels?.whatsapp,
|
|
73
|
-
accounts: {
|
|
74
|
-
...accounts,
|
|
75
|
-
[accountKey]: {
|
|
76
|
-
...existing,
|
|
77
|
-
enabled,
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
|
-
deleteAccount: ({ cfg, accountId }) => {
|
|
85
|
-
const accountKey = accountId || DEFAULT_ACCOUNT_ID;
|
|
86
|
-
const accounts = { ...cfg.channels?.whatsapp?.accounts };
|
|
87
|
-
delete accounts[accountKey];
|
|
88
|
-
return {
|
|
89
|
-
...cfg,
|
|
90
|
-
channels: {
|
|
91
|
-
...cfg.channels,
|
|
92
|
-
whatsapp: {
|
|
93
|
-
...cfg.channels?.whatsapp,
|
|
94
|
-
accounts: Object.keys(accounts).length ? accounts : undefined,
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
},
|
|
99
|
-
isEnabled: (account, cfg) => account.enabled && cfg.web?.enabled !== false,
|
|
100
|
-
disabledReason: () => "disabled",
|
|
101
|
-
isConfigured: async (account) =>
|
|
102
|
-
await getWhatsAppRuntime().channel.whatsapp.webAuthExists(account.authDir),
|
|
103
|
-
unconfiguredReason: () => "not linked",
|
|
104
|
-
describeAccount: (account) => ({
|
|
105
|
-
accountId: account.accountId,
|
|
106
|
-
name: account.name,
|
|
107
|
-
enabled: account.enabled,
|
|
108
|
-
configured: Boolean(account.authDir),
|
|
109
|
-
linked: Boolean(account.authDir),
|
|
110
|
-
dmPolicy: account.dmPolicy,
|
|
111
|
-
allowFrom: account.allowFrom,
|
|
112
|
-
}),
|
|
113
|
-
resolveAllowFrom: ({ cfg, accountId }) =>
|
|
114
|
-
resolveWhatsAppAccount({ cfg, accountId }).allowFrom ?? [],
|
|
115
|
-
formatAllowFrom: ({ allowFrom }) =>
|
|
116
|
-
allowFrom
|
|
117
|
-
.map((entry) => String(entry).trim())
|
|
118
|
-
.filter((entry): entry is string => Boolean(entry))
|
|
119
|
-
.map((entry) => (entry === "*" ? entry : normalizeWhatsAppTarget(entry)))
|
|
120
|
-
.filter((entry): entry is string => Boolean(entry)),
|
|
121
|
-
resolveDefaultTo: ({ cfg, accountId }) => {
|
|
122
|
-
const root = cfg.channels?.whatsapp;
|
|
123
|
-
const normalized = normalizeAccountId(accountId);
|
|
124
|
-
const account = root?.accounts?.[normalized];
|
|
125
|
-
return (account?.defaultTo ?? root?.defaultTo)?.trim() || undefined;
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
security: {
|
|
129
|
-
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
|
130
|
-
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
131
|
-
const useAccountPath = Boolean(cfg.channels?.whatsapp?.accounts?.[resolvedAccountId]);
|
|
132
|
-
const basePath = useAccountPath
|
|
133
|
-
? `channels.whatsapp.accounts.${resolvedAccountId}.`
|
|
134
|
-
: "channels.whatsapp.";
|
|
135
|
-
return {
|
|
136
|
-
policy: account.dmPolicy ?? "pairing",
|
|
137
|
-
allowFrom: account.allowFrom ?? [],
|
|
138
|
-
policyPath: `${basePath}dmPolicy`,
|
|
139
|
-
allowFromPath: basePath,
|
|
140
|
-
approveHint: formatPairingApproveHint("whatsapp"),
|
|
141
|
-
normalizeEntry: (raw) => normalizeE164(raw),
|
|
142
|
-
};
|
|
143
|
-
},
|
|
144
|
-
collectWarnings: ({ account, cfg }) => {
|
|
145
|
-
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
|
146
|
-
const groupPolicy = account.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
|
147
|
-
if (groupPolicy !== "open") {
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
const groupAllowlistConfigured =
|
|
151
|
-
Boolean(account.groups) && Object.keys(account.groups ?? {}).length > 0;
|
|
152
|
-
if (groupAllowlistConfigured) {
|
|
153
|
-
return [
|
|
154
|
-
`- WhatsApp groups: groupPolicy="open" allows any member in allowed groups to trigger (mention-gated). Set channels.whatsapp.groupPolicy="allowlist" + channels.whatsapp.groupAllowFrom to restrict senders.`,
|
|
155
|
-
];
|
|
156
|
-
}
|
|
157
|
-
return [
|
|
158
|
-
`- WhatsApp groups: groupPolicy="open" with no channels.whatsapp.groups allowlist; any group can add + ping (mention-gated). Set channels.whatsapp.groupPolicy="allowlist" + channels.whatsapp.groupAllowFrom or configure channels.whatsapp.groups.`,
|
|
159
|
-
];
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
setup: {
|
|
163
|
-
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
|
164
|
-
applyAccountName: ({ cfg, accountId, name }) =>
|
|
165
|
-
applyAccountNameToChannelSection({
|
|
166
|
-
cfg,
|
|
167
|
-
channelKey: "whatsapp",
|
|
168
|
-
accountId,
|
|
169
|
-
name,
|
|
170
|
-
alwaysUseAccounts: true,
|
|
171
|
-
}),
|
|
172
|
-
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
173
|
-
const namedConfig = applyAccountNameToChannelSection({
|
|
174
|
-
cfg,
|
|
175
|
-
channelKey: "whatsapp",
|
|
176
|
-
accountId,
|
|
177
|
-
name: input.name,
|
|
178
|
-
alwaysUseAccounts: true,
|
|
179
|
-
});
|
|
180
|
-
const next = migrateBaseNameToDefaultAccount({
|
|
181
|
-
cfg: namedConfig,
|
|
182
|
-
channelKey: "whatsapp",
|
|
183
|
-
alwaysUseAccounts: true,
|
|
184
|
-
});
|
|
185
|
-
const entry = {
|
|
186
|
-
...next.channels?.whatsapp?.accounts?.[accountId],
|
|
187
|
-
...(input.authDir ? { authDir: input.authDir } : {}),
|
|
188
|
-
enabled: true,
|
|
189
|
-
};
|
|
190
|
-
return {
|
|
191
|
-
...next,
|
|
192
|
-
channels: {
|
|
193
|
-
...next.channels,
|
|
194
|
-
whatsapp: {
|
|
195
|
-
...next.channels?.whatsapp,
|
|
196
|
-
accounts: {
|
|
197
|
-
...next.channels?.whatsapp?.accounts,
|
|
198
|
-
[accountId]: entry,
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
groups: {
|
|
206
|
-
resolveRequireMention: resolveWhatsAppGroupRequireMention,
|
|
207
|
-
resolveToolPolicy: resolveWhatsAppGroupToolPolicy,
|
|
208
|
-
resolveGroupIntroHint: () =>
|
|
209
|
-
"WhatsApp IDs: SenderId is the participant JID (group participant id).",
|
|
210
|
-
},
|
|
211
|
-
mentions: {
|
|
212
|
-
stripPatterns: ({ ctx }) => {
|
|
213
|
-
const selfE164 = (ctx.To ?? "").replace(/^whatsapp:/, "");
|
|
214
|
-
if (!selfE164) {
|
|
215
|
-
return [];
|
|
216
|
-
}
|
|
217
|
-
const escaped = escapeRegExp(selfE164);
|
|
218
|
-
return [escaped, `@${escaped}`];
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
commands: {
|
|
222
|
-
enforceOwnerForCommands: true,
|
|
223
|
-
skipWhenConfigEmpty: true,
|
|
224
|
-
},
|
|
225
|
-
messaging: {
|
|
226
|
-
normalizeTarget: normalizeWhatsAppMessagingTarget,
|
|
227
|
-
targetResolver: {
|
|
228
|
-
looksLikeId: looksLikeWhatsAppTargetId,
|
|
229
|
-
hint: "<E.164|group JID>",
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
directory: {
|
|
233
|
-
self: async ({ cfg, accountId }) => {
|
|
234
|
-
const account = resolveWhatsAppAccount({ cfg, accountId });
|
|
235
|
-
const { e164, jid } = getWhatsAppRuntime().channel.whatsapp.readWebSelfId(account.authDir);
|
|
236
|
-
const id = e164 ?? jid;
|
|
237
|
-
if (!id) {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
kind: "user",
|
|
242
|
-
id,
|
|
243
|
-
name: account.name,
|
|
244
|
-
raw: { e164, jid },
|
|
245
|
-
};
|
|
246
|
-
},
|
|
247
|
-
listPeers: async (params) => listWhatsAppDirectoryPeersFromConfig(params),
|
|
248
|
-
listGroups: async (params) => listWhatsAppDirectoryGroupsFromConfig(params),
|
|
249
|
-
},
|
|
250
|
-
actions: {
|
|
251
|
-
listActions: ({ cfg }) => {
|
|
252
|
-
if (!cfg.channels?.whatsapp) {
|
|
253
|
-
return [];
|
|
254
|
-
}
|
|
255
|
-
const gate = createActionGate(cfg.channels.whatsapp.actions);
|
|
256
|
-
const actions = new Set<ChannelMessageActionName>();
|
|
257
|
-
if (gate("reactions")) {
|
|
258
|
-
actions.add("react");
|
|
259
|
-
}
|
|
260
|
-
if (gate("polls")) {
|
|
261
|
-
actions.add("poll");
|
|
262
|
-
}
|
|
263
|
-
return Array.from(actions);
|
|
264
|
-
},
|
|
265
|
-
supportsAction: ({ action }) => action === "react",
|
|
266
|
-
handleAction: async ({ action, params, cfg, accountId }) => {
|
|
267
|
-
if (action !== "react") {
|
|
268
|
-
throw new Error(`Action ${action} is not supported for provider ${meta.id}.`);
|
|
269
|
-
}
|
|
270
|
-
const messageId = readStringParam(params, "messageId", {
|
|
271
|
-
required: true,
|
|
272
|
-
});
|
|
273
|
-
const emoji = readStringParam(params, "emoji", { allowEmpty: true });
|
|
274
|
-
const remove = typeof params.remove === "boolean" ? params.remove : undefined;
|
|
275
|
-
return await getWhatsAppRuntime().channel.whatsapp.handleWhatsAppAction(
|
|
276
|
-
{
|
|
277
|
-
action: "react",
|
|
278
|
-
chatJid:
|
|
279
|
-
readStringParam(params, "chatJid") ?? readStringParam(params, "to", { required: true }),
|
|
280
|
-
messageId,
|
|
281
|
-
emoji,
|
|
282
|
-
remove,
|
|
283
|
-
participant: readStringParam(params, "participant"),
|
|
284
|
-
accountId: accountId ?? undefined,
|
|
285
|
-
fromMe: typeof params.fromMe === "boolean" ? params.fromMe : undefined,
|
|
286
|
-
},
|
|
287
|
-
cfg,
|
|
288
|
-
);
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
outbound: {
|
|
292
|
-
deliveryMode: "gateway",
|
|
293
|
-
chunker: (text, limit) => getWhatsAppRuntime().channel.text.chunkText(text, limit),
|
|
294
|
-
chunkerMode: "text",
|
|
295
|
-
textChunkLimit: 4000,
|
|
296
|
-
pollMaxOptions: 12,
|
|
297
|
-
resolveTarget: ({ to, allowFrom, mode }) =>
|
|
298
|
-
resolveWhatsAppOutboundTarget({ to, allowFrom, mode }),
|
|
299
|
-
sendText: async ({ to, text, accountId, deps, gifPlayback }) => {
|
|
300
|
-
const send = deps?.sendWhatsApp ?? getWhatsAppRuntime().channel.whatsapp.sendMessageWhatsApp;
|
|
301
|
-
const result = await send(to, text, {
|
|
302
|
-
verbose: false,
|
|
303
|
-
accountId: accountId ?? undefined,
|
|
304
|
-
gifPlayback,
|
|
305
|
-
});
|
|
306
|
-
return { channel: "whatsapp", ...result };
|
|
307
|
-
},
|
|
308
|
-
sendMedia: async ({ to, text, mediaUrl, accountId, deps, gifPlayback }) => {
|
|
309
|
-
const send = deps?.sendWhatsApp ?? getWhatsAppRuntime().channel.whatsapp.sendMessageWhatsApp;
|
|
310
|
-
const result = await send(to, text, {
|
|
311
|
-
verbose: false,
|
|
312
|
-
mediaUrl,
|
|
313
|
-
accountId: accountId ?? undefined,
|
|
314
|
-
gifPlayback,
|
|
315
|
-
});
|
|
316
|
-
return { channel: "whatsapp", ...result };
|
|
317
|
-
},
|
|
318
|
-
sendPoll: async ({ to, poll, accountId }) =>
|
|
319
|
-
await getWhatsAppRuntime().channel.whatsapp.sendPollWhatsApp(to, poll, {
|
|
320
|
-
verbose: getWhatsAppRuntime().logging.shouldLogVerbose(),
|
|
321
|
-
accountId: accountId ?? undefined,
|
|
322
|
-
}),
|
|
323
|
-
},
|
|
324
|
-
auth: {
|
|
325
|
-
login: async ({ cfg, accountId, runtime, verbose }) => {
|
|
326
|
-
const resolvedAccountId = accountId?.trim() || resolveDefaultWhatsAppAccountId(cfg);
|
|
327
|
-
await getWhatsAppRuntime().channel.whatsapp.loginWeb(
|
|
328
|
-
Boolean(verbose),
|
|
329
|
-
undefined,
|
|
330
|
-
runtime,
|
|
331
|
-
resolvedAccountId,
|
|
332
|
-
);
|
|
333
|
-
},
|
|
334
|
-
},
|
|
335
|
-
heartbeat: {
|
|
336
|
-
checkReady: async ({ cfg, accountId, deps }) => {
|
|
337
|
-
if (cfg.web?.enabled === false) {
|
|
338
|
-
return { ok: false, reason: "whatsapp-disabled" };
|
|
339
|
-
}
|
|
340
|
-
const account = resolveWhatsAppAccount({ cfg, accountId });
|
|
341
|
-
const authExists = await (
|
|
342
|
-
deps?.webAuthExists ?? getWhatsAppRuntime().channel.whatsapp.webAuthExists
|
|
343
|
-
)(account.authDir);
|
|
344
|
-
if (!authExists) {
|
|
345
|
-
return { ok: false, reason: "whatsapp-not-linked" };
|
|
346
|
-
}
|
|
347
|
-
const listenerActive = deps?.hasActiveWebListener
|
|
348
|
-
? deps.hasActiveWebListener()
|
|
349
|
-
: Boolean(getWhatsAppRuntime().channel.whatsapp.getActiveWebListener());
|
|
350
|
-
if (!listenerActive) {
|
|
351
|
-
return { ok: false, reason: "whatsapp-not-running" };
|
|
352
|
-
}
|
|
353
|
-
return { ok: true, reason: "ok" };
|
|
354
|
-
},
|
|
355
|
-
resolveRecipients: ({ cfg, opts }) => resolveWhatsAppHeartbeatRecipients(cfg, opts),
|
|
356
|
-
},
|
|
357
|
-
status: {
|
|
358
|
-
defaultRuntime: {
|
|
359
|
-
accountId: DEFAULT_ACCOUNT_ID,
|
|
360
|
-
running: false,
|
|
361
|
-
connected: false,
|
|
362
|
-
reconnectAttempts: 0,
|
|
363
|
-
lastConnectedAt: null,
|
|
364
|
-
lastDisconnect: null,
|
|
365
|
-
lastMessageAt: null,
|
|
366
|
-
lastEventAt: null,
|
|
367
|
-
lastError: null,
|
|
368
|
-
},
|
|
369
|
-
collectStatusIssues: collectWhatsAppStatusIssues,
|
|
370
|
-
buildChannelSummary: async ({ account, snapshot }) => {
|
|
371
|
-
const authDir = account.authDir;
|
|
372
|
-
const linked =
|
|
373
|
-
typeof snapshot.linked === "boolean"
|
|
374
|
-
? snapshot.linked
|
|
375
|
-
: authDir
|
|
376
|
-
? await getWhatsAppRuntime().channel.whatsapp.webAuthExists(authDir)
|
|
377
|
-
: false;
|
|
378
|
-
const authAgeMs =
|
|
379
|
-
linked && authDir ? getWhatsAppRuntime().channel.whatsapp.getWebAuthAgeMs(authDir) : null;
|
|
380
|
-
const self =
|
|
381
|
-
linked && authDir
|
|
382
|
-
? getWhatsAppRuntime().channel.whatsapp.readWebSelfId(authDir)
|
|
383
|
-
: { e164: null, jid: null };
|
|
384
|
-
return {
|
|
385
|
-
configured: linked,
|
|
386
|
-
linked,
|
|
387
|
-
authAgeMs,
|
|
388
|
-
self,
|
|
389
|
-
running: snapshot.running ?? false,
|
|
390
|
-
connected: snapshot.connected ?? false,
|
|
391
|
-
lastConnectedAt: snapshot.lastConnectedAt ?? null,
|
|
392
|
-
lastDisconnect: snapshot.lastDisconnect ?? null,
|
|
393
|
-
reconnectAttempts: snapshot.reconnectAttempts,
|
|
394
|
-
lastMessageAt: snapshot.lastMessageAt ?? null,
|
|
395
|
-
lastEventAt: snapshot.lastEventAt ?? null,
|
|
396
|
-
lastError: snapshot.lastError ?? null,
|
|
397
|
-
};
|
|
398
|
-
},
|
|
399
|
-
buildAccountSnapshot: async ({ account, runtime }) => {
|
|
400
|
-
const linked = await getWhatsAppRuntime().channel.whatsapp.webAuthExists(account.authDir);
|
|
401
|
-
return {
|
|
402
|
-
accountId: account.accountId,
|
|
403
|
-
name: account.name,
|
|
404
|
-
enabled: account.enabled,
|
|
405
|
-
configured: true,
|
|
406
|
-
linked,
|
|
407
|
-
running: runtime?.running ?? false,
|
|
408
|
-
connected: runtime?.connected ?? false,
|
|
409
|
-
reconnectAttempts: runtime?.reconnectAttempts,
|
|
410
|
-
lastConnectedAt: runtime?.lastConnectedAt ?? null,
|
|
411
|
-
lastDisconnect: runtime?.lastDisconnect ?? null,
|
|
412
|
-
lastMessageAt: runtime?.lastMessageAt ?? null,
|
|
413
|
-
lastEventAt: runtime?.lastEventAt ?? null,
|
|
414
|
-
lastError: runtime?.lastError ?? null,
|
|
415
|
-
dmPolicy: account.dmPolicy,
|
|
416
|
-
allowFrom: account.allowFrom,
|
|
417
|
-
};
|
|
418
|
-
},
|
|
419
|
-
resolveAccountState: ({ configured }) => (configured ? "linked" : "not linked"),
|
|
420
|
-
logSelfId: ({ account, runtime, includeChannelPrefix }) => {
|
|
421
|
-
getWhatsAppRuntime().channel.whatsapp.logWebSelfId(
|
|
422
|
-
account.authDir,
|
|
423
|
-
runtime,
|
|
424
|
-
includeChannelPrefix,
|
|
425
|
-
);
|
|
426
|
-
},
|
|
427
|
-
},
|
|
428
|
-
gateway: {
|
|
429
|
-
startAccount: async (ctx) => {
|
|
430
|
-
const account = ctx.account;
|
|
431
|
-
const { e164, jid } = getWhatsAppRuntime().channel.whatsapp.readWebSelfId(account.authDir);
|
|
432
|
-
const identity = e164 ? e164 : jid ? `jid ${jid}` : "unknown";
|
|
433
|
-
ctx.log?.info(`[${account.accountId}] starting provider (${identity})`);
|
|
434
|
-
return getWhatsAppRuntime().channel.whatsapp.monitorWebChannel(
|
|
435
|
-
getWhatsAppRuntime().logging.shouldLogVerbose(),
|
|
436
|
-
undefined,
|
|
437
|
-
true,
|
|
438
|
-
undefined,
|
|
439
|
-
ctx.runtime,
|
|
440
|
-
ctx.abortSignal,
|
|
441
|
-
{
|
|
442
|
-
statusSink: (next) => ctx.setStatus({ accountId: ctx.accountId, ...next }),
|
|
443
|
-
accountId: account.accountId,
|
|
444
|
-
},
|
|
445
|
-
);
|
|
446
|
-
},
|
|
447
|
-
loginWithQrStart: async ({ accountId, force, timeoutMs, verbose }) =>
|
|
448
|
-
await getWhatsAppRuntime().channel.whatsapp.startWebLoginWithQr({
|
|
449
|
-
accountId,
|
|
450
|
-
force,
|
|
451
|
-
timeoutMs,
|
|
452
|
-
verbose,
|
|
453
|
-
}),
|
|
454
|
-
loginWithQrWait: async ({ accountId, timeoutMs }) =>
|
|
455
|
-
await getWhatsAppRuntime().channel.whatsapp.waitForWebLogin({ accountId, timeoutMs }),
|
|
456
|
-
logoutAccount: async ({ account, runtime }) => {
|
|
457
|
-
const cleared = await getWhatsAppRuntime().channel.whatsapp.logoutWeb({
|
|
458
|
-
authDir: account.authDir,
|
|
459
|
-
isLegacyAuthDir: account.isLegacyAuthDir,
|
|
460
|
-
runtime,
|
|
461
|
-
});
|
|
462
|
-
return { cleared, loggedOut: cleared };
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
};
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { installCommonResolveTargetErrorCases } from "../../shared/resolve-target-test-helpers.js";
|
|
3
|
-
|
|
4
|
-
vi.mock("symi/plugin-sdk", () => ({
|
|
5
|
-
getChatChannelMeta: () => ({ id: "whatsapp", label: "WhatsApp" }),
|
|
6
|
-
normalizeWhatsAppTarget: (value: string) => {
|
|
7
|
-
if (value === "invalid-target") return null;
|
|
8
|
-
// Simulate E.164 normalization: strip leading + and whatsapp: prefix
|
|
9
|
-
const stripped = value.replace(/^whatsapp:/i, "").replace(/^\+/, "");
|
|
10
|
-
return stripped.includes("@g.us") ? stripped : `${stripped}@s.whatsapp.net`;
|
|
11
|
-
},
|
|
12
|
-
isWhatsAppGroupJid: (value: string) => value.endsWith("@g.us"),
|
|
13
|
-
resolveWhatsAppOutboundTarget: ({
|
|
14
|
-
to,
|
|
15
|
-
allowFrom,
|
|
16
|
-
mode,
|
|
17
|
-
}: {
|
|
18
|
-
to?: string;
|
|
19
|
-
allowFrom: string[];
|
|
20
|
-
mode: "explicit" | "implicit";
|
|
21
|
-
}) => {
|
|
22
|
-
const raw = typeof to === "string" ? to.trim() : "";
|
|
23
|
-
if (!raw) {
|
|
24
|
-
return { ok: false, error: new Error("missing target") };
|
|
25
|
-
}
|
|
26
|
-
const normalizeWhatsAppTarget = (value: string) => {
|
|
27
|
-
if (value === "invalid-target") return null;
|
|
28
|
-
const stripped = value.replace(/^whatsapp:/i, "").replace(/^\+/, "");
|
|
29
|
-
return stripped.includes("@g.us") ? stripped : `${stripped}@s.whatsapp.net`;
|
|
30
|
-
};
|
|
31
|
-
const normalized = normalizeWhatsAppTarget(raw);
|
|
32
|
-
if (!normalized) {
|
|
33
|
-
return { ok: false, error: new Error("invalid target") };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (mode === "implicit" && !normalized.endsWith("@g.us")) {
|
|
37
|
-
const allowAll = allowFrom.includes("*");
|
|
38
|
-
const allowExact = allowFrom.some((entry) => {
|
|
39
|
-
if (!entry) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
const normalizedEntry = normalizeWhatsAppTarget(entry.trim());
|
|
43
|
-
return normalizedEntry?.toLowerCase() === normalized.toLowerCase();
|
|
44
|
-
});
|
|
45
|
-
if (!allowAll && !allowExact) {
|
|
46
|
-
return { ok: false, error: new Error("target not allowlisted") };
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return { ok: true, to: normalized };
|
|
51
|
-
},
|
|
52
|
-
missingTargetError: (provider: string, hint: string) =>
|
|
53
|
-
new Error(`Delivering to ${provider} requires target ${hint}`),
|
|
54
|
-
WhatsAppConfigSchema: {},
|
|
55
|
-
whatsappOnboardingAdapter: {},
|
|
56
|
-
resolveWhatsAppHeartbeatRecipients: vi.fn(),
|
|
57
|
-
buildChannelConfigSchema: vi.fn(),
|
|
58
|
-
collectWhatsAppStatusIssues: vi.fn(),
|
|
59
|
-
createActionGate: vi.fn(),
|
|
60
|
-
DEFAULT_ACCOUNT_ID: "default",
|
|
61
|
-
escapeRegExp: vi.fn(),
|
|
62
|
-
formatPairingApproveHint: vi.fn(),
|
|
63
|
-
listWhatsAppAccountIds: vi.fn(),
|
|
64
|
-
listWhatsAppDirectoryGroupsFromConfig: vi.fn(),
|
|
65
|
-
listWhatsAppDirectoryPeersFromConfig: vi.fn(),
|
|
66
|
-
looksLikeWhatsAppTargetId: vi.fn(),
|
|
67
|
-
migrateBaseNameToDefaultAccount: vi.fn(),
|
|
68
|
-
normalizeAccountId: vi.fn(),
|
|
69
|
-
normalizeE164: vi.fn(),
|
|
70
|
-
normalizeWhatsAppMessagingTarget: vi.fn(),
|
|
71
|
-
readStringParam: vi.fn(),
|
|
72
|
-
resolveDefaultWhatsAppAccountId: vi.fn(),
|
|
73
|
-
resolveWhatsAppAccount: vi.fn(),
|
|
74
|
-
resolveWhatsAppGroupRequireMention: vi.fn(),
|
|
75
|
-
resolveWhatsAppGroupToolPolicy: vi.fn(),
|
|
76
|
-
applyAccountNameToChannelSection: vi.fn(),
|
|
77
|
-
}));
|
|
78
|
-
|
|
79
|
-
vi.mock("./runtime.js", () => ({
|
|
80
|
-
getWhatsAppRuntime: vi.fn(() => ({
|
|
81
|
-
channel: {
|
|
82
|
-
text: { chunkText: vi.fn() },
|
|
83
|
-
whatsapp: {
|
|
84
|
-
sendMessageWhatsApp: vi.fn(),
|
|
85
|
-
createLoginTool: vi.fn(),
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
})),
|
|
89
|
-
}));
|
|
90
|
-
|
|
91
|
-
import { whatsappPlugin } from "./channel.js";
|
|
92
|
-
|
|
93
|
-
const resolveTarget = whatsappPlugin.outbound!.resolveTarget!;
|
|
94
|
-
|
|
95
|
-
describe("whatsapp resolveTarget", () => {
|
|
96
|
-
it("should resolve valid target in explicit mode", () => {
|
|
97
|
-
const result = resolveTarget({
|
|
98
|
-
to: "5511999999999",
|
|
99
|
-
mode: "explicit",
|
|
100
|
-
allowFrom: [],
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
expect(result.ok).toBe(true);
|
|
104
|
-
if (!result.ok) {
|
|
105
|
-
throw result.error;
|
|
106
|
-
}
|
|
107
|
-
expect(result.to).toBe("5511999999999@s.whatsapp.net");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("should resolve target in implicit mode with wildcard", () => {
|
|
111
|
-
const result = resolveTarget({
|
|
112
|
-
to: "5511999999999",
|
|
113
|
-
mode: "implicit",
|
|
114
|
-
allowFrom: ["*"],
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
expect(result.ok).toBe(true);
|
|
118
|
-
if (!result.ok) {
|
|
119
|
-
throw result.error;
|
|
120
|
-
}
|
|
121
|
-
expect(result.to).toBe("5511999999999@s.whatsapp.net");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("should resolve target in implicit mode when in allowlist", () => {
|
|
125
|
-
const result = resolveTarget({
|
|
126
|
-
to: "5511999999999",
|
|
127
|
-
mode: "implicit",
|
|
128
|
-
allowFrom: ["5511999999999"],
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
expect(result.ok).toBe(true);
|
|
132
|
-
if (!result.ok) {
|
|
133
|
-
throw result.error;
|
|
134
|
-
}
|
|
135
|
-
expect(result.to).toBe("5511999999999@s.whatsapp.net");
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("should allow group JID regardless of allowlist", () => {
|
|
139
|
-
const result = resolveTarget({
|
|
140
|
-
to: "120363123456789@g.us",
|
|
141
|
-
mode: "implicit",
|
|
142
|
-
allowFrom: ["5511999999999"],
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
expect(result.ok).toBe(true);
|
|
146
|
-
if (!result.ok) {
|
|
147
|
-
throw result.error;
|
|
148
|
-
}
|
|
149
|
-
expect(result.to).toBe("120363123456789@g.us");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("should error when target not in allowlist (implicit mode)", () => {
|
|
153
|
-
const result = resolveTarget({
|
|
154
|
-
to: "5511888888888",
|
|
155
|
-
mode: "implicit",
|
|
156
|
-
allowFrom: ["5511999999999", "5511777777777"],
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
expect(result.ok).toBe(false);
|
|
160
|
-
if (result.ok) {
|
|
161
|
-
throw new Error("expected resolution to fail");
|
|
162
|
-
}
|
|
163
|
-
expect(result.error).toBeDefined();
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
installCommonResolveTargetErrorCases({
|
|
167
|
-
resolveTarget,
|
|
168
|
-
implicitAllowFrom: ["5511999999999"],
|
|
169
|
-
});
|
|
170
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { PluginRuntime } from "symi/plugin-sdk";
|
|
2
|
-
|
|
3
|
-
let runtime: PluginRuntime | null = null;
|
|
4
|
-
|
|
5
|
-
export function setWhatsAppRuntime(next: PluginRuntime) {
|
|
6
|
-
runtime = next;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function getWhatsAppRuntime(): PluginRuntime {
|
|
10
|
-
if (!runtime) {
|
|
11
|
-
throw new Error("WhatsApp runtime not initialized");
|
|
12
|
-
}
|
|
13
|
-
return runtime;
|
|
14
|
-
}
|