@openclaw/zalouser 2026.3.2 → 2026.3.7
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/CHANGELOG.md +12 -0
- package/index.ts +2 -2
- package/package.json +3 -2
- package/src/accounts.test.ts +1 -1
- package/src/accounts.ts +7 -37
- package/src/channel.sendpayload.test.ts +1 -1
- package/src/channel.ts +59 -152
- package/src/config-schema.ts +1 -1
- package/src/monitor.account-scope.test.ts +3 -13
- package/src/monitor.group-gating.test.ts +8 -13
- package/src/monitor.send-mocks.ts +20 -0
- package/src/monitor.ts +46 -51
- package/src/onboarding.ts +19 -52
- package/src/probe.ts +1 -1
- package/src/qr-temp-file.ts +22 -0
- package/src/runtime.ts +1 -1
- package/src/status-issues.ts +1 -1
- package/src/zalo-js.ts +1 -1
- package/src/zca-client.ts +16 -49
package/CHANGELOG.md
CHANGED
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
-
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/zalouser";
|
|
2
|
+
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/zalouser";
|
|
3
3
|
import { zalouserDock, zalouserPlugin } from "./src/channel.js";
|
|
4
4
|
import { setZalouserRuntime } from "./src/runtime.js";
|
|
5
5
|
import { ZalouserToolSchema, executeZalouserTool } from "./src/tool.js";
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/zalouser",
|
|
3
|
-
"version": "2026.3.
|
|
3
|
+
"version": "2026.3.7",
|
|
4
4
|
"description": "OpenClaw Zalo Personal Account plugin via native zca-js integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@sinclair/typebox": "0.34.48",
|
|
8
|
-
"zca-js": "2.1.1"
|
|
8
|
+
"zca-js": "2.1.1",
|
|
9
|
+
"zod": "^4.3.6"
|
|
9
10
|
},
|
|
10
11
|
"openclaw": {
|
|
11
12
|
"extensions": [
|
package/src/accounts.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
1
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
2
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/zalouser";
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import {
|
|
5
5
|
getZcaUserInfo,
|
package/src/accounts.ts
CHANGED
|
@@ -1,43 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_ACCOUNT_ID,
|
|
4
|
-
normalizeAccountId,
|
|
5
|
-
normalizeOptionalAccountId,
|
|
6
|
-
} from "openclaw/plugin-sdk/account-id";
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
|
2
|
+
import { createAccountListHelpers, type OpenClawConfig } from "openclaw/plugin-sdk/zalouser";
|
|
7
3
|
import type { ResolvedZalouserAccount, ZalouserAccountConfig, ZalouserConfig } from "./types.js";
|
|
8
4
|
import { checkZaloAuthenticated, getZaloUserInfo } from "./zalo-js.js";
|
|
9
5
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return Object.keys(accounts).filter(Boolean);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function listZalouserAccountIds(cfg: OpenClawConfig): string[] {
|
|
19
|
-
const ids = listConfiguredAccountIds(cfg);
|
|
20
|
-
if (ids.length === 0) {
|
|
21
|
-
return [DEFAULT_ACCOUNT_ID];
|
|
22
|
-
}
|
|
23
|
-
return ids.toSorted((a, b) => a.localeCompare(b));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function resolveDefaultZalouserAccountId(cfg: OpenClawConfig): string {
|
|
27
|
-
const zalouserConfig = cfg.channels?.zalouser as ZalouserConfig | undefined;
|
|
28
|
-
const preferred = normalizeOptionalAccountId(zalouserConfig?.defaultAccount);
|
|
29
|
-
if (
|
|
30
|
-
preferred &&
|
|
31
|
-
listZalouserAccountIds(cfg).some((accountId) => normalizeAccountId(accountId) === preferred)
|
|
32
|
-
) {
|
|
33
|
-
return preferred;
|
|
34
|
-
}
|
|
35
|
-
const ids = listZalouserAccountIds(cfg);
|
|
36
|
-
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
37
|
-
return DEFAULT_ACCOUNT_ID;
|
|
38
|
-
}
|
|
39
|
-
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
40
|
-
}
|
|
6
|
+
const {
|
|
7
|
+
listAccountIds: listZalouserAccountIds,
|
|
8
|
+
resolveDefaultAccountId: resolveDefaultZalouserAccountId,
|
|
9
|
+
} = createAccountListHelpers("zalouser");
|
|
10
|
+
export { listZalouserAccountIds, resolveDefaultZalouserAccountId };
|
|
41
11
|
|
|
42
12
|
function resolveAccountConfig(
|
|
43
13
|
cfg: OpenClawConfig,
|
package/src/channel.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
buildAccountScopedDmSecurityPolicy,
|
|
3
|
+
mapAllowFromEntries,
|
|
4
|
+
} from "openclaw/plugin-sdk/compat";
|
|
3
5
|
import type {
|
|
4
6
|
ChannelAccountSnapshot,
|
|
5
7
|
ChannelDirectoryEntry,
|
|
@@ -9,21 +11,23 @@ import type {
|
|
|
9
11
|
ChannelPlugin,
|
|
10
12
|
OpenClawConfig,
|
|
11
13
|
GroupToolPolicyConfig,
|
|
12
|
-
} from "openclaw/plugin-sdk";
|
|
14
|
+
} from "openclaw/plugin-sdk/zalouser";
|
|
13
15
|
import {
|
|
14
16
|
applyAccountNameToChannelSection,
|
|
17
|
+
applySetupAccountConfigPatch,
|
|
18
|
+
buildChannelSendResult,
|
|
19
|
+
buildBaseAccountStatusSnapshot,
|
|
15
20
|
buildChannelConfigSchema,
|
|
16
21
|
DEFAULT_ACCOUNT_ID,
|
|
17
22
|
chunkTextForOutbound,
|
|
18
23
|
deleteAccountFromConfigSection,
|
|
19
24
|
formatAllowFromLowercase,
|
|
20
|
-
|
|
25
|
+
isNumericTargetId,
|
|
21
26
|
migrateBaseNameToDefaultAccount,
|
|
22
27
|
normalizeAccountId,
|
|
23
|
-
|
|
24
|
-
resolveChannelAccountConfigBasePath,
|
|
28
|
+
sendPayloadWithChunkedTextAndMedia,
|
|
25
29
|
setAccountEnabledInConfigSection,
|
|
26
|
-
} from "openclaw/plugin-sdk";
|
|
30
|
+
} from "openclaw/plugin-sdk/zalouser";
|
|
27
31
|
import {
|
|
28
32
|
listZalouserAccountIds,
|
|
29
33
|
resolveDefaultZalouserAccountId,
|
|
@@ -37,6 +41,7 @@ import { buildZalouserGroupCandidates, findZalouserGroupEntry } from "./group-po
|
|
|
37
41
|
import { resolveZalouserReactionMessageIds } from "./message-sid.js";
|
|
38
42
|
import { zalouserOnboardingAdapter } from "./onboarding.js";
|
|
39
43
|
import { probeZalouser } from "./probe.js";
|
|
44
|
+
import { writeQrDataUrlToTempFile } from "./qr-temp-file.js";
|
|
40
45
|
import { sendMessageZalouser, sendReactionZalouser } from "./send.js";
|
|
41
46
|
import { collectZalouserStatusIssues } from "./status-issues.js";
|
|
42
47
|
import {
|
|
@@ -69,25 +74,6 @@ function resolveZalouserQrProfile(accountId?: string | null): string {
|
|
|
69
74
|
return normalized;
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
async function writeQrDataUrlToTempFile(
|
|
73
|
-
qrDataUrl: string,
|
|
74
|
-
profile: string,
|
|
75
|
-
): Promise<string | null> {
|
|
76
|
-
const trimmed = qrDataUrl.trim();
|
|
77
|
-
const match = trimmed.match(/^data:image\/png;base64,(.+)$/i);
|
|
78
|
-
const base64 = (match?.[1] ?? "").trim();
|
|
79
|
-
if (!base64) {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
const safeProfile = profile.replace(/[^a-zA-Z0-9_-]+/g, "-") || "default";
|
|
83
|
-
const filePath = path.join(
|
|
84
|
-
resolvePreferredOpenClawTmpDir(),
|
|
85
|
-
`openclaw-zalouser-qr-${safeProfile}.png`,
|
|
86
|
-
);
|
|
87
|
-
await fsp.writeFile(filePath, Buffer.from(base64, "base64"));
|
|
88
|
-
return filePath;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
77
|
function mapUser(params: {
|
|
92
78
|
id: string;
|
|
93
79
|
name?: string | null;
|
|
@@ -116,15 +102,13 @@ function mapGroup(params: {
|
|
|
116
102
|
};
|
|
117
103
|
}
|
|
118
104
|
|
|
119
|
-
function
|
|
120
|
-
params: ChannelGroupContext,
|
|
121
|
-
): GroupToolPolicyConfig | undefined {
|
|
105
|
+
function resolveZalouserGroupPolicyEntry(params: ChannelGroupContext) {
|
|
122
106
|
const account = resolveZalouserAccountSync({
|
|
123
107
|
cfg: params.cfg,
|
|
124
108
|
accountId: params.accountId ?? undefined,
|
|
125
109
|
});
|
|
126
110
|
const groups = account.config.groups ?? {};
|
|
127
|
-
|
|
111
|
+
return findZalouserGroupEntry(
|
|
128
112
|
groups,
|
|
129
113
|
buildZalouserGroupCandidates({
|
|
130
114
|
groupId: params.groupId,
|
|
@@ -132,23 +116,16 @@ function resolveZalouserGroupToolPolicy(
|
|
|
132
116
|
includeWildcard: true,
|
|
133
117
|
}),
|
|
134
118
|
);
|
|
135
|
-
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function resolveZalouserGroupToolPolicy(
|
|
122
|
+
params: ChannelGroupContext,
|
|
123
|
+
): GroupToolPolicyConfig | undefined {
|
|
124
|
+
return resolveZalouserGroupPolicyEntry(params)?.tools;
|
|
136
125
|
}
|
|
137
126
|
|
|
138
127
|
function resolveZalouserRequireMention(params: ChannelGroupContext): boolean {
|
|
139
|
-
const
|
|
140
|
-
cfg: params.cfg,
|
|
141
|
-
accountId: params.accountId ?? undefined,
|
|
142
|
-
});
|
|
143
|
-
const groups = account.config.groups ?? {};
|
|
144
|
-
const entry = findZalouserGroupEntry(
|
|
145
|
-
groups,
|
|
146
|
-
buildZalouserGroupCandidates({
|
|
147
|
-
groupId: params.groupId,
|
|
148
|
-
groupChannel: params.groupChannel,
|
|
149
|
-
includeWildcard: true,
|
|
150
|
-
}),
|
|
151
|
-
);
|
|
128
|
+
const entry = resolveZalouserGroupPolicyEntry(params);
|
|
152
129
|
if (typeof entry?.requireMention === "boolean") {
|
|
153
130
|
return entry.requireMention;
|
|
154
131
|
}
|
|
@@ -234,9 +211,7 @@ export const zalouserDock: ChannelDock = {
|
|
|
234
211
|
outbound: { textChunkLimit: 2000 },
|
|
235
212
|
config: {
|
|
236
213
|
resolveAllowFrom: ({ cfg, accountId }) =>
|
|
237
|
-
(resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom
|
|
238
|
-
String(entry),
|
|
239
|
-
),
|
|
214
|
+
mapAllowFromEntries(resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom),
|
|
240
215
|
formatAllowFrom: ({ allowFrom }) =>
|
|
241
216
|
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalouser|zlu):/i }),
|
|
242
217
|
},
|
|
@@ -299,28 +274,22 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
299
274
|
configured: undefined,
|
|
300
275
|
}),
|
|
301
276
|
resolveAllowFrom: ({ cfg, accountId }) =>
|
|
302
|
-
(resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom
|
|
303
|
-
String(entry),
|
|
304
|
-
),
|
|
277
|
+
mapAllowFromEntries(resolveZalouserAccountSync({ cfg: cfg, accountId }).config.allowFrom),
|
|
305
278
|
formatAllowFrom: ({ allowFrom }) =>
|
|
306
279
|
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalouser|zlu):/i }),
|
|
307
280
|
},
|
|
308
281
|
security: {
|
|
309
282
|
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
|
310
|
-
|
|
311
|
-
const basePath = resolveChannelAccountConfigBasePath({
|
|
283
|
+
return buildAccountScopedDmSecurityPolicy({
|
|
312
284
|
cfg,
|
|
313
285
|
channelKey: "zalouser",
|
|
314
|
-
accountId
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
policy: account.config.dmPolicy ?? "pairing",
|
|
286
|
+
accountId,
|
|
287
|
+
fallbackAccountId: account.accountId ?? DEFAULT_ACCOUNT_ID,
|
|
288
|
+
policy: account.config.dmPolicy,
|
|
318
289
|
allowFrom: account.config.allowFrom ?? [],
|
|
319
|
-
|
|
320
|
-
allowFromPath: basePath,
|
|
321
|
-
approveHint: formatPairingApproveHint("zalouser"),
|
|
290
|
+
policyPathSuffix: "dmPolicy",
|
|
322
291
|
normalizeEntry: (raw) => raw.replace(/^(zalouser|zlu):/i, ""),
|
|
323
|
-
};
|
|
292
|
+
});
|
|
324
293
|
},
|
|
325
294
|
},
|
|
326
295
|
groups: {
|
|
@@ -355,35 +324,12 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
355
324
|
channelKey: "zalouser",
|
|
356
325
|
})
|
|
357
326
|
: namedConfig;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
...next.channels?.zalouser,
|
|
365
|
-
enabled: true,
|
|
366
|
-
},
|
|
367
|
-
},
|
|
368
|
-
} as OpenClawConfig;
|
|
369
|
-
}
|
|
370
|
-
return {
|
|
371
|
-
...next,
|
|
372
|
-
channels: {
|
|
373
|
-
...next.channels,
|
|
374
|
-
zalouser: {
|
|
375
|
-
...next.channels?.zalouser,
|
|
376
|
-
enabled: true,
|
|
377
|
-
accounts: {
|
|
378
|
-
...next.channels?.zalouser?.accounts,
|
|
379
|
-
[accountId]: {
|
|
380
|
-
...next.channels?.zalouser?.accounts?.[accountId],
|
|
381
|
-
enabled: true,
|
|
382
|
-
},
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
},
|
|
386
|
-
} as OpenClawConfig;
|
|
327
|
+
return applySetupAccountConfigPatch({
|
|
328
|
+
cfg: next,
|
|
329
|
+
channelKey: "zalouser",
|
|
330
|
+
accountId,
|
|
331
|
+
patch: {},
|
|
332
|
+
});
|
|
387
333
|
},
|
|
388
334
|
},
|
|
389
335
|
messaging: {
|
|
@@ -395,13 +341,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
395
341
|
return trimmed.replace(/^(zalouser|zlu):/i, "");
|
|
396
342
|
},
|
|
397
343
|
targetResolver: {
|
|
398
|
-
looksLikeId:
|
|
399
|
-
const trimmed = raw.trim();
|
|
400
|
-
if (!trimmed) {
|
|
401
|
-
return false;
|
|
402
|
-
}
|
|
403
|
-
return /^\d{3,}$/.test(trimmed);
|
|
404
|
-
},
|
|
344
|
+
looksLikeId: isNumericTargetId,
|
|
405
345
|
hint: "<threadId>",
|
|
406
346
|
},
|
|
407
347
|
},
|
|
@@ -560,49 +500,19 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
560
500
|
chunker: chunkTextForOutbound,
|
|
561
501
|
chunkerMode: "text",
|
|
562
502
|
textChunkLimit: 2000,
|
|
563
|
-
sendPayload: async (ctx) =>
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
:
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
if (urls.length > 0) {
|
|
574
|
-
let lastResult = await zalouserPlugin.outbound!.sendMedia!({
|
|
575
|
-
...ctx,
|
|
576
|
-
text,
|
|
577
|
-
mediaUrl: urls[0],
|
|
578
|
-
});
|
|
579
|
-
for (let i = 1; i < urls.length; i++) {
|
|
580
|
-
lastResult = await zalouserPlugin.outbound!.sendMedia!({
|
|
581
|
-
...ctx,
|
|
582
|
-
text: "",
|
|
583
|
-
mediaUrl: urls[i],
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
return lastResult;
|
|
587
|
-
}
|
|
588
|
-
const outbound = zalouserPlugin.outbound!;
|
|
589
|
-
const limit = outbound.textChunkLimit;
|
|
590
|
-
const chunks = limit && outbound.chunker ? outbound.chunker(text, limit) : [text];
|
|
591
|
-
let lastResult: Awaited<ReturnType<NonNullable<typeof outbound.sendText>>>;
|
|
592
|
-
for (const chunk of chunks) {
|
|
593
|
-
lastResult = await outbound.sendText!({ ...ctx, text: chunk });
|
|
594
|
-
}
|
|
595
|
-
return lastResult!;
|
|
596
|
-
},
|
|
503
|
+
sendPayload: async (ctx) =>
|
|
504
|
+
await sendPayloadWithChunkedTextAndMedia({
|
|
505
|
+
ctx,
|
|
506
|
+
textChunkLimit: zalouserPlugin.outbound!.textChunkLimit,
|
|
507
|
+
chunker: zalouserPlugin.outbound!.chunker,
|
|
508
|
+
sendText: (nextCtx) => zalouserPlugin.outbound!.sendText!(nextCtx),
|
|
509
|
+
sendMedia: (nextCtx) => zalouserPlugin.outbound!.sendMedia!(nextCtx),
|
|
510
|
+
emptyResult: { channel: "zalouser", messageId: "" },
|
|
511
|
+
}),
|
|
597
512
|
sendText: async ({ to, text, accountId, cfg }) => {
|
|
598
513
|
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
|
|
599
514
|
const result = await sendMessageZalouser(to, text, { profile: account.profile });
|
|
600
|
-
return
|
|
601
|
-
channel: "zalouser",
|
|
602
|
-
ok: result.ok,
|
|
603
|
-
messageId: result.messageId ?? "",
|
|
604
|
-
error: result.error ? new Error(result.error) : undefined,
|
|
605
|
-
};
|
|
515
|
+
return buildChannelSendResult("zalouser", result);
|
|
606
516
|
},
|
|
607
517
|
sendMedia: async ({ to, text, mediaUrl, accountId, cfg, mediaLocalRoots }) => {
|
|
608
518
|
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
|
|
@@ -611,12 +521,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
611
521
|
mediaUrl,
|
|
612
522
|
mediaLocalRoots,
|
|
613
523
|
});
|
|
614
|
-
return
|
|
615
|
-
channel: "zalouser",
|
|
616
|
-
ok: result.ok,
|
|
617
|
-
messageId: result.messageId ?? "",
|
|
618
|
-
error: result.error ? new Error(result.error) : undefined,
|
|
619
|
-
};
|
|
524
|
+
return buildChannelSendResult("zalouser", result);
|
|
620
525
|
},
|
|
621
526
|
},
|
|
622
527
|
status: {
|
|
@@ -641,17 +546,19 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|
|
641
546
|
buildAccountSnapshot: async ({ account, runtime }) => {
|
|
642
547
|
const configured = await checkZcaAuthenticated(account.profile);
|
|
643
548
|
const configError = "not authenticated";
|
|
549
|
+
const base = buildBaseAccountStatusSnapshot({
|
|
550
|
+
account: {
|
|
551
|
+
accountId: account.accountId,
|
|
552
|
+
name: account.name,
|
|
553
|
+
enabled: account.enabled,
|
|
554
|
+
configured,
|
|
555
|
+
},
|
|
556
|
+
runtime: configured
|
|
557
|
+
? runtime
|
|
558
|
+
: { ...runtime, lastError: runtime?.lastError ?? configError },
|
|
559
|
+
});
|
|
644
560
|
return {
|
|
645
|
-
|
|
646
|
-
name: account.name,
|
|
647
|
-
enabled: account.enabled,
|
|
648
|
-
configured,
|
|
649
|
-
running: runtime?.running ?? false,
|
|
650
|
-
lastStartAt: runtime?.lastStartAt ?? null,
|
|
651
|
-
lastStopAt: runtime?.lastStopAt ?? null,
|
|
652
|
-
lastError: configured ? (runtime?.lastError ?? null) : (runtime?.lastError ?? configError),
|
|
653
|
-
lastInboundAt: runtime?.lastInboundAt ?? null,
|
|
654
|
-
lastOutboundAt: runtime?.lastOutboundAt ?? null,
|
|
561
|
+
...base,
|
|
655
562
|
dmPolicy: account.config.dmPolicy ?? "pairing",
|
|
656
563
|
};
|
|
657
564
|
},
|
package/src/config-schema.ts
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
import type { OpenClawConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { OpenClawConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk/zalouser";
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import "./monitor.send-mocks.js";
|
|
3
4
|
import { __testing } from "./monitor.js";
|
|
5
|
+
import { sendMessageZalouserMock } from "./monitor.send-mocks.js";
|
|
4
6
|
import { setZalouserRuntime } from "./runtime.js";
|
|
5
7
|
import type { ResolvedZalouserAccount, ZaloInboundMessage } from "./types.js";
|
|
6
8
|
|
|
7
|
-
const sendMessageZalouserMock = vi.hoisted(() => vi.fn(async () => {}));
|
|
8
|
-
const sendTypingZalouserMock = vi.hoisted(() => vi.fn(async () => {}));
|
|
9
|
-
const sendDeliveredZalouserMock = vi.hoisted(() => vi.fn(async () => {}));
|
|
10
|
-
const sendSeenZalouserMock = vi.hoisted(() => vi.fn(async () => {}));
|
|
11
|
-
|
|
12
|
-
vi.mock("./send.js", () => ({
|
|
13
|
-
sendMessageZalouser: sendMessageZalouserMock,
|
|
14
|
-
sendTypingZalouser: sendTypingZalouserMock,
|
|
15
|
-
sendDeliveredZalouser: sendDeliveredZalouserMock,
|
|
16
|
-
sendSeenZalouser: sendSeenZalouserMock,
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
9
|
describe("zalouser monitor pairing account scoping", () => {
|
|
20
10
|
it("scopes DM pairing-store reads and pairing requests to accountId", async () => {
|
|
21
11
|
const readAllowFromStore = vi.fn(
|
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
import type { OpenClawConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { OpenClawConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk/zalouser";
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import "./monitor.send-mocks.js";
|
|
3
4
|
import { __testing } from "./monitor.js";
|
|
5
|
+
import {
|
|
6
|
+
sendDeliveredZalouserMock,
|
|
7
|
+
sendMessageZalouserMock,
|
|
8
|
+
sendSeenZalouserMock,
|
|
9
|
+
sendTypingZalouserMock,
|
|
10
|
+
} from "./monitor.send-mocks.js";
|
|
4
11
|
import { setZalouserRuntime } from "./runtime.js";
|
|
5
12
|
import type { ResolvedZalouserAccount, ZaloInboundMessage } from "./types.js";
|
|
6
13
|
|
|
7
|
-
const sendMessageZalouserMock = vi.hoisted(() => vi.fn(async () => {}));
|
|
8
|
-
const sendTypingZalouserMock = vi.hoisted(() => vi.fn(async () => {}));
|
|
9
|
-
const sendDeliveredZalouserMock = vi.hoisted(() => vi.fn(async () => {}));
|
|
10
|
-
const sendSeenZalouserMock = vi.hoisted(() => vi.fn(async () => {}));
|
|
11
|
-
|
|
12
|
-
vi.mock("./send.js", () => ({
|
|
13
|
-
sendMessageZalouser: sendMessageZalouserMock,
|
|
14
|
-
sendTypingZalouser: sendTypingZalouserMock,
|
|
15
|
-
sendDeliveredZalouser: sendDeliveredZalouserMock,
|
|
16
|
-
sendSeenZalouser: sendSeenZalouserMock,
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
14
|
function createAccount(): ResolvedZalouserAccount {
|
|
20
15
|
return {
|
|
21
16
|
accountId: "default",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const sendMocks = vi.hoisted(() => ({
|
|
4
|
+
sendMessageZalouserMock: vi.fn(async () => {}),
|
|
5
|
+
sendTypingZalouserMock: vi.fn(async () => {}),
|
|
6
|
+
sendDeliveredZalouserMock: vi.fn(async () => {}),
|
|
7
|
+
sendSeenZalouserMock: vi.fn(async () => {}),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
export const sendMessageZalouserMock = sendMocks.sendMessageZalouserMock;
|
|
11
|
+
export const sendTypingZalouserMock = sendMocks.sendTypingZalouserMock;
|
|
12
|
+
export const sendDeliveredZalouserMock = sendMocks.sendDeliveredZalouserMock;
|
|
13
|
+
export const sendSeenZalouserMock = sendMocks.sendSeenZalouserMock;
|
|
14
|
+
|
|
15
|
+
vi.mock("./send.js", () => ({
|
|
16
|
+
sendMessageZalouser: sendMessageZalouserMock,
|
|
17
|
+
sendTypingZalouser: sendTypingZalouserMock,
|
|
18
|
+
sendDeliveredZalouser: sendDeliveredZalouserMock,
|
|
19
|
+
sendSeenZalouser: sendSeenZalouserMock,
|
|
20
|
+
}));
|
package/src/monitor.ts
CHANGED
|
@@ -3,11 +3,13 @@ import type {
|
|
|
3
3
|
OpenClawConfig,
|
|
4
4
|
OutboundReplyPayload,
|
|
5
5
|
RuntimeEnv,
|
|
6
|
-
} from "openclaw/plugin-sdk";
|
|
6
|
+
} from "openclaw/plugin-sdk/zalouser";
|
|
7
7
|
import {
|
|
8
8
|
createTypingCallbacks,
|
|
9
9
|
createScopedPairingAccess,
|
|
10
10
|
createReplyPrefixOptions,
|
|
11
|
+
evaluateGroupRouteAccessForPolicy,
|
|
12
|
+
issuePairingChallenge,
|
|
11
13
|
resolveOutboundMediaUrls,
|
|
12
14
|
mergeAllowlist,
|
|
13
15
|
resolveMentionGatingWithBypass,
|
|
@@ -17,7 +19,7 @@ import {
|
|
|
17
19
|
sendMediaWithLeadingCaption,
|
|
18
20
|
summarizeMapping,
|
|
19
21
|
warnMissingProviderGroupPolicyFallbackOnce,
|
|
20
|
-
} from "openclaw/plugin-sdk";
|
|
22
|
+
} from "openclaw/plugin-sdk/zalouser";
|
|
21
23
|
import {
|
|
22
24
|
buildZalouserGroupCandidates,
|
|
23
25
|
findZalouserGroupEntry,
|
|
@@ -93,28 +95,6 @@ function isSenderAllowed(senderId: string | undefined, allowFrom: string[]): boo
|
|
|
93
95
|
});
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
function isGroupAllowed(params: {
|
|
97
|
-
groupId: string;
|
|
98
|
-
groupName?: string | null;
|
|
99
|
-
groups: Record<string, { allow?: boolean; enabled?: boolean; requireMention?: boolean }>;
|
|
100
|
-
}): boolean {
|
|
101
|
-
const groups = params.groups ?? {};
|
|
102
|
-
const keys = Object.keys(groups);
|
|
103
|
-
if (keys.length === 0) {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
const entry = findZalouserGroupEntry(
|
|
107
|
-
groups,
|
|
108
|
-
buildZalouserGroupCandidates({
|
|
109
|
-
groupId: params.groupId,
|
|
110
|
-
groupName: params.groupName,
|
|
111
|
-
includeGroupIdAlias: true,
|
|
112
|
-
includeWildcard: true,
|
|
113
|
-
}),
|
|
114
|
-
);
|
|
115
|
-
return isZalouserGroupEntryAllowed(entry);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
98
|
function resolveGroupRequireMention(params: {
|
|
119
99
|
groupId: string;
|
|
120
100
|
groupName?: string | null;
|
|
@@ -222,16 +202,36 @@ async function processMessage(
|
|
|
222
202
|
|
|
223
203
|
const groups = account.config.groups ?? {};
|
|
224
204
|
if (isGroup) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
205
|
+
const groupEntry = findZalouserGroupEntry(
|
|
206
|
+
groups,
|
|
207
|
+
buildZalouserGroupCandidates({
|
|
208
|
+
groupId: chatId,
|
|
209
|
+
groupName,
|
|
210
|
+
includeGroupIdAlias: true,
|
|
211
|
+
includeWildcard: true,
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
const routeAccess = evaluateGroupRouteAccessForPolicy({
|
|
215
|
+
groupPolicy,
|
|
216
|
+
routeAllowlistConfigured: Object.keys(groups).length > 0,
|
|
217
|
+
routeMatched: Boolean(groupEntry),
|
|
218
|
+
routeEnabled: isZalouserGroupEntryAllowed(groupEntry),
|
|
219
|
+
});
|
|
220
|
+
if (!routeAccess.allowed) {
|
|
221
|
+
if (routeAccess.reason === "disabled") {
|
|
222
|
+
logVerbose(core, runtime, `zalouser: drop group ${chatId} (groupPolicy=disabled)`);
|
|
223
|
+
} else if (routeAccess.reason === "empty_allowlist") {
|
|
224
|
+
logVerbose(
|
|
225
|
+
core,
|
|
226
|
+
runtime,
|
|
227
|
+
`zalouser: drop group ${chatId} (groupPolicy=allowlist, no allowlist)`,
|
|
228
|
+
);
|
|
229
|
+
} else if (routeAccess.reason === "route_not_allowlisted") {
|
|
232
230
|
logVerbose(core, runtime, `zalouser: drop group ${chatId} (not allowlisted)`);
|
|
233
|
-
|
|
231
|
+
} else if (routeAccess.reason === "route_disabled") {
|
|
232
|
+
logVerbose(core, runtime, `zalouser: drop group ${chatId} (group disabled)`);
|
|
234
233
|
}
|
|
234
|
+
return;
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -262,32 +262,27 @@ async function processMessage(
|
|
|
262
262
|
const allowed = senderAllowedForCommands;
|
|
263
263
|
if (!allowed) {
|
|
264
264
|
if (dmPolicy === "pairing") {
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
await issuePairingChallenge({
|
|
266
|
+
channel: "zalouser",
|
|
267
|
+
senderId,
|
|
268
|
+
senderIdLine: `Your Zalo user id: ${senderId}`,
|
|
267
269
|
meta: { name: senderName || undefined },
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
await sendMessageZalouser(
|
|
274
|
-
chatId,
|
|
275
|
-
core.channel.pairing.buildPairingReply({
|
|
276
|
-
channel: "zalouser",
|
|
277
|
-
idLine: `Your Zalo user id: ${senderId}`,
|
|
278
|
-
code,
|
|
279
|
-
}),
|
|
280
|
-
{ profile: account.profile },
|
|
281
|
-
);
|
|
270
|
+
upsertPairingRequest: pairing.upsertPairingRequest,
|
|
271
|
+
onCreated: () => {
|
|
272
|
+
logVerbose(core, runtime, `zalouser pairing request sender=${senderId}`);
|
|
273
|
+
},
|
|
274
|
+
sendPairingReply: async (text) => {
|
|
275
|
+
await sendMessageZalouser(chatId, text, { profile: account.profile });
|
|
282
276
|
statusSink?.({ lastOutboundAt: Date.now() });
|
|
283
|
-
}
|
|
277
|
+
},
|
|
278
|
+
onReplyError: (err) => {
|
|
284
279
|
logVerbose(
|
|
285
280
|
core,
|
|
286
281
|
runtime,
|
|
287
282
|
`zalouser pairing reply failed for ${senderId}: ${String(err)}`,
|
|
288
283
|
);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
284
|
+
},
|
|
285
|
+
});
|
|
291
286
|
} else {
|
|
292
287
|
logVerbose(
|
|
293
288
|
core,
|
package/src/onboarding.ts
CHANGED
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
import fsp from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import type {
|
|
4
2
|
ChannelOnboardingAdapter,
|
|
5
3
|
ChannelOnboardingDmPolicy,
|
|
6
4
|
OpenClawConfig,
|
|
7
5
|
WizardPrompter,
|
|
8
|
-
} from "openclaw/plugin-sdk";
|
|
6
|
+
} from "openclaw/plugin-sdk/zalouser";
|
|
9
7
|
import {
|
|
10
|
-
addWildcardAllowFrom,
|
|
11
8
|
DEFAULT_ACCOUNT_ID,
|
|
12
9
|
formatResolvedUnresolvedNote,
|
|
13
10
|
mergeAllowFromEntries,
|
|
14
11
|
normalizeAccountId,
|
|
15
|
-
promptAccountId,
|
|
16
12
|
promptChannelAccessConfig,
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
resolveAccountIdForConfigure,
|
|
14
|
+
setTopLevelChannelDmPolicyWithAllowFrom,
|
|
15
|
+
} from "openclaw/plugin-sdk/zalouser";
|
|
19
16
|
import {
|
|
20
17
|
listZalouserAccountIds,
|
|
21
18
|
resolveDefaultZalouserAccountId,
|
|
22
19
|
resolveZalouserAccountSync,
|
|
23
20
|
checkZcaAuthenticated,
|
|
24
21
|
} from "./accounts.js";
|
|
22
|
+
import { writeQrDataUrlToTempFile } from "./qr-temp-file.js";
|
|
25
23
|
import {
|
|
26
24
|
logoutZaloProfile,
|
|
27
25
|
resolveZaloAllowFromEntries,
|
|
@@ -75,19 +73,11 @@ function setZalouserDmPolicy(
|
|
|
75
73
|
cfg: OpenClawConfig,
|
|
76
74
|
dmPolicy: "pairing" | "allowlist" | "open" | "disabled",
|
|
77
75
|
): OpenClawConfig {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
...cfg.channels,
|
|
84
|
-
zalouser: {
|
|
85
|
-
...cfg.channels?.zalouser,
|
|
86
|
-
dmPolicy,
|
|
87
|
-
...(allowFrom ? { allowFrom } : {}),
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
} as OpenClawConfig;
|
|
76
|
+
return setTopLevelChannelDmPolicyWithAllowFrom({
|
|
77
|
+
cfg,
|
|
78
|
+
channel: "zalouser",
|
|
79
|
+
dmPolicy,
|
|
80
|
+
}) as OpenClawConfig;
|
|
91
81
|
}
|
|
92
82
|
|
|
93
83
|
async function noteZalouserHelp(prompter: WizardPrompter): Promise<void> {
|
|
@@ -103,25 +93,6 @@ async function noteZalouserHelp(prompter: WizardPrompter): Promise<void> {
|
|
|
103
93
|
);
|
|
104
94
|
}
|
|
105
95
|
|
|
106
|
-
async function writeQrDataUrlToTempFile(
|
|
107
|
-
qrDataUrl: string,
|
|
108
|
-
profile: string,
|
|
109
|
-
): Promise<string | null> {
|
|
110
|
-
const trimmed = qrDataUrl.trim();
|
|
111
|
-
const match = trimmed.match(/^data:image\/png;base64,(.+)$/i);
|
|
112
|
-
const base64 = (match?.[1] ?? "").trim();
|
|
113
|
-
if (!base64) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
const safeProfile = profile.replace(/[^a-zA-Z0-9_-]+/g, "-") || "default";
|
|
117
|
-
const filePath = path.join(
|
|
118
|
-
resolvePreferredOpenClawTmpDir(),
|
|
119
|
-
`openclaw-zalouser-qr-${safeProfile}.png`,
|
|
120
|
-
);
|
|
121
|
-
await fsp.writeFile(filePath, Buffer.from(base64, "base64"));
|
|
122
|
-
return filePath;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
96
|
async function promptZalouserAllowFrom(params: {
|
|
126
97
|
cfg: OpenClawConfig;
|
|
127
98
|
prompter: WizardPrompter;
|
|
@@ -247,20 +218,16 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
247
218
|
shouldPromptAccountIds,
|
|
248
219
|
forceAllowFrom,
|
|
249
220
|
}) => {
|
|
250
|
-
const zalouserOverride = accountOverrides.zalouser?.trim();
|
|
251
221
|
const defaultAccountId = resolveDefaultZalouserAccountId(cfg);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
defaultAccountId,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
222
|
+
const accountId = await resolveAccountIdForConfigure({
|
|
223
|
+
cfg,
|
|
224
|
+
prompter,
|
|
225
|
+
label: "Zalo Personal",
|
|
226
|
+
accountOverride: accountOverrides.zalouser,
|
|
227
|
+
shouldPromptAccountIds,
|
|
228
|
+
listAccountIds: listZalouserAccountIds,
|
|
229
|
+
defaultAccountId,
|
|
230
|
+
});
|
|
264
231
|
|
|
265
232
|
let next = cfg;
|
|
266
233
|
const account = resolveZalouserAccountSync({ cfg: next, accountId });
|
package/src/probe.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fsp from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/zalouser";
|
|
4
|
+
|
|
5
|
+
export async function writeQrDataUrlToTempFile(
|
|
6
|
+
qrDataUrl: string,
|
|
7
|
+
profile: string,
|
|
8
|
+
): Promise<string | null> {
|
|
9
|
+
const trimmed = qrDataUrl.trim();
|
|
10
|
+
const match = trimmed.match(/^data:image\/png;base64,(.+)$/i);
|
|
11
|
+
const base64 = (match?.[1] ?? "").trim();
|
|
12
|
+
if (!base64) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const safeProfile = profile.replace(/[^a-zA-Z0-9_-]+/g, "-") || "default";
|
|
16
|
+
const filePath = path.join(
|
|
17
|
+
resolvePreferredOpenClawTmpDir(),
|
|
18
|
+
`openclaw-zalouser-qr-${safeProfile}.png`,
|
|
19
|
+
);
|
|
20
|
+
await fsp.writeFile(filePath, Buffer.from(base64, "base64"));
|
|
21
|
+
return filePath;
|
|
22
|
+
}
|
package/src/runtime.ts
CHANGED
package/src/status-issues.ts
CHANGED
package/src/zalo-js.ts
CHANGED
|
@@ -3,7 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import fsp from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk";
|
|
6
|
+
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/zalouser";
|
|
7
7
|
import { normalizeZaloReactionIcon } from "./reaction.js";
|
|
8
8
|
import { getZalouserRuntime } from "./runtime.js";
|
|
9
9
|
import type {
|
package/src/zca-client.ts
CHANGED
|
@@ -126,6 +126,20 @@ export type Listener = {
|
|
|
126
126
|
stop(): void;
|
|
127
127
|
};
|
|
128
128
|
|
|
129
|
+
type DeliveryEventMessage = {
|
|
130
|
+
msgId: string;
|
|
131
|
+
cliMsgId: string;
|
|
132
|
+
uidFrom: string;
|
|
133
|
+
idTo: string;
|
|
134
|
+
msgType: string;
|
|
135
|
+
st: number;
|
|
136
|
+
at: number;
|
|
137
|
+
cmd: number;
|
|
138
|
+
ts: string | number;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
type DeliveryEventMessages = DeliveryEventMessage | DeliveryEventMessage[];
|
|
142
|
+
|
|
129
143
|
export type API = {
|
|
130
144
|
listener: Listener;
|
|
131
145
|
getContext(): {
|
|
@@ -185,57 +199,10 @@ export type API = {
|
|
|
185
199
|
): Promise<unknown>;
|
|
186
200
|
sendDeliveredEvent(
|
|
187
201
|
isSeen: boolean,
|
|
188
|
-
messages:
|
|
189
|
-
| {
|
|
190
|
-
msgId: string;
|
|
191
|
-
cliMsgId: string;
|
|
192
|
-
uidFrom: string;
|
|
193
|
-
idTo: string;
|
|
194
|
-
msgType: string;
|
|
195
|
-
st: number;
|
|
196
|
-
at: number;
|
|
197
|
-
cmd: number;
|
|
198
|
-
ts: string | number;
|
|
199
|
-
}
|
|
200
|
-
| Array<{
|
|
201
|
-
msgId: string;
|
|
202
|
-
cliMsgId: string;
|
|
203
|
-
uidFrom: string;
|
|
204
|
-
idTo: string;
|
|
205
|
-
msgType: string;
|
|
206
|
-
st: number;
|
|
207
|
-
at: number;
|
|
208
|
-
cmd: number;
|
|
209
|
-
ts: string | number;
|
|
210
|
-
}>,
|
|
211
|
-
type?: number,
|
|
212
|
-
): Promise<unknown>;
|
|
213
|
-
sendSeenEvent(
|
|
214
|
-
messages:
|
|
215
|
-
| {
|
|
216
|
-
msgId: string;
|
|
217
|
-
cliMsgId: string;
|
|
218
|
-
uidFrom: string;
|
|
219
|
-
idTo: string;
|
|
220
|
-
msgType: string;
|
|
221
|
-
st: number;
|
|
222
|
-
at: number;
|
|
223
|
-
cmd: number;
|
|
224
|
-
ts: string | number;
|
|
225
|
-
}
|
|
226
|
-
| Array<{
|
|
227
|
-
msgId: string;
|
|
228
|
-
cliMsgId: string;
|
|
229
|
-
uidFrom: string;
|
|
230
|
-
idTo: string;
|
|
231
|
-
msgType: string;
|
|
232
|
-
st: number;
|
|
233
|
-
at: number;
|
|
234
|
-
cmd: number;
|
|
235
|
-
ts: string | number;
|
|
236
|
-
}>,
|
|
202
|
+
messages: DeliveryEventMessages,
|
|
237
203
|
type?: number,
|
|
238
204
|
): Promise<unknown>;
|
|
205
|
+
sendSeenEvent(messages: DeliveryEventMessages, type?: number): Promise<unknown>;
|
|
239
206
|
};
|
|
240
207
|
|
|
241
208
|
type ZaloCtor = new (options?: { logging?: boolean; selfListen?: boolean }) => {
|