@openclaw/zalouser 2026.3.13 → 2026.5.2-beta.1
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/README.md +4 -3
- package/api.ts +9 -0
- package/channel-plugin-api.ts +3 -0
- package/contract-api.ts +2 -0
- package/doctor-contract-api.ts +1 -0
- package/index.ts +29 -24
- package/openclaw.plugin.json +293 -1
- package/package.json +38 -11
- package/runtime-api.ts +67 -0
- package/secret-contract-api.ts +4 -0
- package/setup-entry.ts +9 -0
- package/setup-plugin-api.ts +2 -0
- package/src/accounts.runtime.ts +1 -0
- package/src/accounts.test-mocks.ts +7 -3
- package/src/accounts.test.ts +53 -1
- package/src/accounts.ts +38 -24
- package/src/channel-api.ts +20 -0
- package/src/channel.adapters.ts +391 -0
- package/src/channel.directory.test.ts +47 -40
- package/src/channel.runtime.ts +12 -0
- package/src/channel.sendpayload.test.ts +41 -23
- package/src/channel.setup.test.ts +33 -0
- package/src/channel.setup.ts +12 -0
- package/src/channel.test.ts +231 -20
- package/src/channel.ts +176 -685
- package/src/config-schema.ts +5 -5
- package/src/directory.ts +54 -0
- package/src/doctor-contract.ts +156 -0
- package/src/doctor.test.ts +77 -0
- package/src/doctor.ts +37 -0
- package/src/group-policy.test.ts +4 -4
- package/src/group-policy.ts +4 -2
- package/src/monitor.account-scope.test.ts +2 -1
- package/src/monitor.group-gating.test.ts +162 -8
- package/src/monitor.ts +233 -173
- package/src/probe.ts +3 -2
- package/src/qr-temp-file.ts +1 -1
- package/src/reaction.ts +5 -2
- package/src/runtime.ts +6 -3
- package/src/security-audit.test.ts +80 -0
- package/src/security-audit.ts +71 -0
- package/src/send.test.ts +2 -2
- package/src/send.ts +3 -3
- package/src/session-route.ts +121 -0
- package/src/setup-core.ts +33 -0
- package/src/setup-surface.test.ts +363 -0
- package/src/setup-surface.ts +470 -0
- package/src/setup-test-helpers.ts +42 -0
- package/src/shared.ts +92 -0
- package/src/status-issues.test.ts +1 -13
- package/src/status-issues.ts +8 -2
- package/src/test-helpers.ts +1 -1
- package/src/text-styles.test.ts +1 -1
- package/src/text-styles.ts +5 -2
- package/src/tool.test.ts +66 -3
- package/src/tool.ts +76 -14
- package/src/types.ts +3 -3
- package/src/zalo-js.credentials.test.ts +465 -0
- package/src/zalo-js.test-mocks.ts +89 -0
- package/src/zalo-js.ts +491 -274
- package/src/zca-client.test.ts +24 -0
- package/src/zca-client.ts +24 -58
- package/src/zca-constants.ts +55 -0
- package/test-api.ts +21 -0
- package/tsconfig.json +16 -0
- package/CHANGELOG.md +0 -107
- package/src/onboarding.ts +0 -340
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
describe("zca-client runtime loading", () => {
|
|
4
|
+
it("does not import zca-js until a session is created", async () => {
|
|
5
|
+
vi.clearAllMocks();
|
|
6
|
+
const runtimeFactory = vi.fn(() => ({
|
|
7
|
+
Zalo: class MockZalo {
|
|
8
|
+
constructor(public readonly options?: { logging?: boolean; selfListen?: boolean }) {}
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.doMock("zca-js", runtimeFactory);
|
|
13
|
+
|
|
14
|
+
const zcaClient = await import("./zca-client.js");
|
|
15
|
+
expect(runtimeFactory).not.toHaveBeenCalled();
|
|
16
|
+
|
|
17
|
+
const client = await zcaClient.createZalo({ logging: false, selfListen: true });
|
|
18
|
+
|
|
19
|
+
expect(runtimeFactory).toHaveBeenCalledTimes(1);
|
|
20
|
+
expect(client).toMatchObject({
|
|
21
|
+
options: { logging: false, selfListen: true },
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
package/src/zca-client.ts
CHANGED
|
@@ -1,65 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
|
-
LoginQRCallbackEventType
|
|
3
|
-
Reactions
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
LoginQRCallbackEventType,
|
|
3
|
+
Reactions,
|
|
4
|
+
TextStyle,
|
|
5
|
+
ThreadType,
|
|
6
|
+
type Style,
|
|
7
|
+
} from "./zca-constants.js";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Group: 1;
|
|
9
|
+
type ZcaJsRuntime = {
|
|
10
|
+
Zalo: unknown;
|
|
11
11
|
};
|
|
12
|
+
let zcaJsRuntimePromise: Promise<ZcaJsRuntime> | null = null;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const Reactions = ReactionsRuntime as Record<string, string> & {
|
|
22
|
-
HEART: string;
|
|
23
|
-
LIKE: string;
|
|
24
|
-
HAHA: string;
|
|
25
|
-
WOW: string;
|
|
26
|
-
CRY: string;
|
|
27
|
-
ANGRY: string;
|
|
28
|
-
NONE: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// Mirror zca-js sendMessage style constants locally because the package root
|
|
32
|
-
// typing surface does not consistently expose TextStyle/Style to tsgo.
|
|
33
|
-
export const TextStyle = {
|
|
34
|
-
Bold: "b",
|
|
35
|
-
Italic: "i",
|
|
36
|
-
Underline: "u",
|
|
37
|
-
StrikeThrough: "s",
|
|
38
|
-
Red: "c_db342e",
|
|
39
|
-
Orange: "c_f27806",
|
|
40
|
-
Yellow: "c_f7b503",
|
|
41
|
-
Green: "c_15a85f",
|
|
42
|
-
Small: "f_13",
|
|
43
|
-
Big: "f_18",
|
|
44
|
-
UnorderedList: "lst_1",
|
|
45
|
-
OrderedList: "lst_2",
|
|
46
|
-
Indent: "ind_$",
|
|
47
|
-
} as const;
|
|
48
|
-
|
|
49
|
-
type TextStyleValue = (typeof TextStyle)[keyof typeof TextStyle];
|
|
14
|
+
async function loadZcaJsRuntime(): Promise<ZcaJsRuntime> {
|
|
15
|
+
// Keep zca-js behind a runtime boundary so bundled metadata/contracts can load
|
|
16
|
+
// without resolving its optional WebSocket dependency tree.
|
|
17
|
+
zcaJsRuntimePromise ??= import("zca-js").then((mod) => mod as unknown as ZcaJsRuntime);
|
|
18
|
+
return await zcaJsRuntimePromise;
|
|
19
|
+
}
|
|
50
20
|
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
start: number;
|
|
54
|
-
len: number;
|
|
55
|
-
st: Exclude<TextStyleValue, typeof TextStyle.Indent>;
|
|
56
|
-
}
|
|
57
|
-
| {
|
|
58
|
-
start: number;
|
|
59
|
-
len: number;
|
|
60
|
-
st: typeof TextStyle.Indent;
|
|
61
|
-
indentSize?: number;
|
|
62
|
-
};
|
|
21
|
+
export { LoginQRCallbackEventType, Reactions, TextStyle, ThreadType };
|
|
22
|
+
export type { Style };
|
|
63
23
|
|
|
64
24
|
export type Credentials = {
|
|
65
25
|
imei: string;
|
|
@@ -290,4 +250,10 @@ type ZaloCtor = new (options?: { logging?: boolean; selfListen?: boolean }) => {
|
|
|
290
250
|
): Promise<API>;
|
|
291
251
|
};
|
|
292
252
|
|
|
293
|
-
export
|
|
253
|
+
export async function createZalo(
|
|
254
|
+
options?: ConstructorParameters<ZaloCtor>[0],
|
|
255
|
+
): Promise<InstanceType<ZaloCtor>> {
|
|
256
|
+
const zcaJs = await loadZcaJsRuntime();
|
|
257
|
+
const Zalo = zcaJs.Zalo as ZaloCtor;
|
|
258
|
+
return new Zalo(options);
|
|
259
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const ThreadType = {
|
|
2
|
+
User: 0,
|
|
3
|
+
Group: 1,
|
|
4
|
+
} as const;
|
|
5
|
+
|
|
6
|
+
export const LoginQRCallbackEventType = {
|
|
7
|
+
QRCodeGenerated: 0,
|
|
8
|
+
QRCodeExpired: 1,
|
|
9
|
+
QRCodeScanned: 2,
|
|
10
|
+
QRCodeDeclined: 3,
|
|
11
|
+
GotLoginInfo: 4,
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const Reactions = {
|
|
15
|
+
HEART: "/-heart",
|
|
16
|
+
LIKE: "/-strong",
|
|
17
|
+
HAHA: ":>",
|
|
18
|
+
WOW: ":o",
|
|
19
|
+
CRY: ":-((",
|
|
20
|
+
ANGRY: ":-h",
|
|
21
|
+
NONE: "",
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
// Mirror zca-js sendMessage style constants locally because the package root
|
|
25
|
+
// typing surface does not consistently expose TextStyle/Style to tsgo.
|
|
26
|
+
export const TextStyle = {
|
|
27
|
+
Bold: "b",
|
|
28
|
+
Italic: "i",
|
|
29
|
+
Underline: "u",
|
|
30
|
+
StrikeThrough: "s",
|
|
31
|
+
Red: "c_db342e",
|
|
32
|
+
Orange: "c_f27806",
|
|
33
|
+
Yellow: "c_f7b503",
|
|
34
|
+
Green: "c_15a85f",
|
|
35
|
+
Small: "f_13",
|
|
36
|
+
Big: "f_18",
|
|
37
|
+
UnorderedList: "lst_1",
|
|
38
|
+
OrderedList: "lst_2",
|
|
39
|
+
Indent: "ind_$",
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
type TextStyleValue = (typeof TextStyle)[keyof typeof TextStyle];
|
|
43
|
+
|
|
44
|
+
export type Style =
|
|
45
|
+
| {
|
|
46
|
+
start: number;
|
|
47
|
+
len: number;
|
|
48
|
+
st: Exclude<TextStyleValue, typeof TextStyle.Indent>;
|
|
49
|
+
}
|
|
50
|
+
| {
|
|
51
|
+
start: number;
|
|
52
|
+
len: number;
|
|
53
|
+
st: typeof TextStyle.Indent;
|
|
54
|
+
indentSize?: number;
|
|
55
|
+
};
|
package/test-api.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { sendMessageZalouser } from "./src/send.js";
|
|
2
|
+
export { parseZalouserOutboundTarget } from "./src/session-route.js";
|
|
3
|
+
export {
|
|
4
|
+
checkZcaAuthenticated,
|
|
5
|
+
getZcaUserInfo,
|
|
6
|
+
listZalouserAccountIds,
|
|
7
|
+
resolveDefaultZalouserAccountId,
|
|
8
|
+
resolveZalouserAccountSync,
|
|
9
|
+
} from "./src/accounts.js";
|
|
10
|
+
export {
|
|
11
|
+
checkZaloAuthenticated,
|
|
12
|
+
getZaloUserInfo,
|
|
13
|
+
listZaloFriendsMatching,
|
|
14
|
+
listZaloGroupMembers,
|
|
15
|
+
listZaloGroupsMatching,
|
|
16
|
+
logoutZaloProfile,
|
|
17
|
+
resolveZaloAllowFromEntries,
|
|
18
|
+
resolveZaloGroupsByEntries,
|
|
19
|
+
startZaloQrLogin,
|
|
20
|
+
waitForZaloQrLogin,
|
|
21
|
+
} from "./src/zalo-js.js";
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.package-boundary.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "."
|
|
5
|
+
},
|
|
6
|
+
"include": ["./*.ts", "./src/**/*.ts"],
|
|
7
|
+
"exclude": [
|
|
8
|
+
"./**/*.test.ts",
|
|
9
|
+
"./dist/**",
|
|
10
|
+
"./node_modules/**",
|
|
11
|
+
"./src/test-support/**",
|
|
12
|
+
"./src/**/*test-helpers.ts",
|
|
13
|
+
"./src/**/*test-harness.ts",
|
|
14
|
+
"./src/**/*test-support.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 2026.3.13
|
|
4
|
-
|
|
5
|
-
### Changes
|
|
6
|
-
|
|
7
|
-
- Version alignment with core OpenClaw release numbers.
|
|
8
|
-
|
|
9
|
-
## 2026.3.12
|
|
10
|
-
|
|
11
|
-
### Changes
|
|
12
|
-
|
|
13
|
-
- Version alignment with core OpenClaw release numbers.
|
|
14
|
-
|
|
15
|
-
## 2026.3.11
|
|
16
|
-
|
|
17
|
-
### Changes
|
|
18
|
-
|
|
19
|
-
- Version alignment with core OpenClaw release numbers.
|
|
20
|
-
|
|
21
|
-
## 2026.3.10
|
|
22
|
-
|
|
23
|
-
### Changes
|
|
24
|
-
|
|
25
|
-
- Version alignment with core OpenClaw release numbers.
|
|
26
|
-
|
|
27
|
-
## 2026.3.9
|
|
28
|
-
|
|
29
|
-
### Changes
|
|
30
|
-
|
|
31
|
-
- Version alignment with core OpenClaw release numbers.
|
|
32
|
-
|
|
33
|
-
## 2026.3.8-beta.1
|
|
34
|
-
|
|
35
|
-
### Changes
|
|
36
|
-
|
|
37
|
-
- Version alignment with core OpenClaw release numbers.
|
|
38
|
-
|
|
39
|
-
## 2026.3.8
|
|
40
|
-
|
|
41
|
-
### Changes
|
|
42
|
-
|
|
43
|
-
- Version alignment with core OpenClaw release numbers.
|
|
44
|
-
|
|
45
|
-
## 2026.3.7
|
|
46
|
-
|
|
47
|
-
### Changes
|
|
48
|
-
|
|
49
|
-
- Version alignment with core OpenClaw release numbers.
|
|
50
|
-
|
|
51
|
-
## 2026.3.3
|
|
52
|
-
|
|
53
|
-
### Changes
|
|
54
|
-
|
|
55
|
-
- Version alignment with core OpenClaw release numbers.
|
|
56
|
-
|
|
57
|
-
## 2026.3.2
|
|
58
|
-
|
|
59
|
-
### Changes
|
|
60
|
-
|
|
61
|
-
- Rebuilt the plugin to use native `zca-js` integration inside OpenClaw (no external `zca` CLI runtime dependency).
|
|
62
|
-
|
|
63
|
-
### Breaking
|
|
64
|
-
|
|
65
|
-
- **BREAKING:** Removed the old external CLI-based backend (`zca`/`openzca`/`zca-cli`) from runtime flow. Existing setups that depended on external CLI binaries should re-login with `openclaw channels login --channel zalouser` after upgrading.
|
|
66
|
-
|
|
67
|
-
## 2026.3.1
|
|
68
|
-
|
|
69
|
-
### Changes
|
|
70
|
-
|
|
71
|
-
- Version alignment with core OpenClaw release numbers.
|
|
72
|
-
|
|
73
|
-
## 2026.2.26
|
|
74
|
-
|
|
75
|
-
### Changes
|
|
76
|
-
|
|
77
|
-
- Version alignment with core OpenClaw release numbers.
|
|
78
|
-
|
|
79
|
-
## 2026.2.25
|
|
80
|
-
|
|
81
|
-
### Changes
|
|
82
|
-
|
|
83
|
-
- Version alignment with core OpenClaw release numbers.
|
|
84
|
-
|
|
85
|
-
## 2026.2.24
|
|
86
|
-
|
|
87
|
-
### Changes
|
|
88
|
-
|
|
89
|
-
- Version alignment with core OpenClaw release numbers.
|
|
90
|
-
|
|
91
|
-
## 2026.2.22
|
|
92
|
-
|
|
93
|
-
### Changes
|
|
94
|
-
|
|
95
|
-
- Version alignment with core OpenClaw release numbers.
|
|
96
|
-
|
|
97
|
-
## 2026.1.17-1
|
|
98
|
-
|
|
99
|
-
- Initial version with full channel plugin support
|
|
100
|
-
- QR code login via zca-cli
|
|
101
|
-
- Multi-account support
|
|
102
|
-
- Agent tool for sending messages
|
|
103
|
-
- Group and DM policy support
|
|
104
|
-
- ChannelDock for lightweight shared metadata
|
|
105
|
-
- Zod-based config schema validation
|
|
106
|
-
- Setup adapter for programmatic configuration
|
|
107
|
-
- Dedicated probe and status issues modules
|
package/src/onboarding.ts
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ChannelOnboardingAdapter,
|
|
3
|
-
ChannelOnboardingDmPolicy,
|
|
4
|
-
OpenClawConfig,
|
|
5
|
-
WizardPrompter,
|
|
6
|
-
} from "openclaw/plugin-sdk/zalouser";
|
|
7
|
-
import {
|
|
8
|
-
DEFAULT_ACCOUNT_ID,
|
|
9
|
-
formatResolvedUnresolvedNote,
|
|
10
|
-
mergeAllowFromEntries,
|
|
11
|
-
normalizeAccountId,
|
|
12
|
-
patchScopedAccountConfig,
|
|
13
|
-
promptChannelAccessConfig,
|
|
14
|
-
resolveAccountIdForConfigure,
|
|
15
|
-
setTopLevelChannelDmPolicyWithAllowFrom,
|
|
16
|
-
} from "openclaw/plugin-sdk/zalouser";
|
|
17
|
-
import {
|
|
18
|
-
listZalouserAccountIds,
|
|
19
|
-
resolveDefaultZalouserAccountId,
|
|
20
|
-
resolveZalouserAccountSync,
|
|
21
|
-
checkZcaAuthenticated,
|
|
22
|
-
} from "./accounts.js";
|
|
23
|
-
import { writeQrDataUrlToTempFile } from "./qr-temp-file.js";
|
|
24
|
-
import {
|
|
25
|
-
logoutZaloProfile,
|
|
26
|
-
resolveZaloAllowFromEntries,
|
|
27
|
-
resolveZaloGroupsByEntries,
|
|
28
|
-
startZaloQrLogin,
|
|
29
|
-
waitForZaloQrLogin,
|
|
30
|
-
} from "./zalo-js.js";
|
|
31
|
-
|
|
32
|
-
const channel = "zalouser" as const;
|
|
33
|
-
|
|
34
|
-
function setZalouserAccountScopedConfig(
|
|
35
|
-
cfg: OpenClawConfig,
|
|
36
|
-
accountId: string,
|
|
37
|
-
defaultPatch: Record<string, unknown>,
|
|
38
|
-
accountPatch: Record<string, unknown> = defaultPatch,
|
|
39
|
-
): OpenClawConfig {
|
|
40
|
-
return patchScopedAccountConfig({
|
|
41
|
-
cfg,
|
|
42
|
-
channelKey: channel,
|
|
43
|
-
accountId,
|
|
44
|
-
patch: defaultPatch,
|
|
45
|
-
accountPatch,
|
|
46
|
-
}) as OpenClawConfig;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function setZalouserDmPolicy(
|
|
50
|
-
cfg: OpenClawConfig,
|
|
51
|
-
dmPolicy: "pairing" | "allowlist" | "open" | "disabled",
|
|
52
|
-
): OpenClawConfig {
|
|
53
|
-
return setTopLevelChannelDmPolicyWithAllowFrom({
|
|
54
|
-
cfg,
|
|
55
|
-
channel: "zalouser",
|
|
56
|
-
dmPolicy,
|
|
57
|
-
}) as OpenClawConfig;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function noteZalouserHelp(prompter: WizardPrompter): Promise<void> {
|
|
61
|
-
await prompter.note(
|
|
62
|
-
[
|
|
63
|
-
"Zalo Personal Account login via QR code.",
|
|
64
|
-
"",
|
|
65
|
-
"This plugin uses zca-js directly (no external CLI dependency).",
|
|
66
|
-
"",
|
|
67
|
-
"Docs: https://docs.openclaw.ai/channels/zalouser",
|
|
68
|
-
].join("\n"),
|
|
69
|
-
"Zalo Personal Setup",
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function promptZalouserAllowFrom(params: {
|
|
74
|
-
cfg: OpenClawConfig;
|
|
75
|
-
prompter: WizardPrompter;
|
|
76
|
-
accountId: string;
|
|
77
|
-
}): Promise<OpenClawConfig> {
|
|
78
|
-
const { cfg, prompter, accountId } = params;
|
|
79
|
-
const resolved = resolveZalouserAccountSync({ cfg, accountId });
|
|
80
|
-
const existingAllowFrom = resolved.config.allowFrom ?? [];
|
|
81
|
-
const parseInput = (raw: string) =>
|
|
82
|
-
raw
|
|
83
|
-
.split(/[\n,;]+/g)
|
|
84
|
-
.map((entry) => entry.trim())
|
|
85
|
-
.filter(Boolean);
|
|
86
|
-
|
|
87
|
-
while (true) {
|
|
88
|
-
const entry = await prompter.text({
|
|
89
|
-
message: "Zalouser allowFrom (name or user id)",
|
|
90
|
-
placeholder: "Alice, 123456789",
|
|
91
|
-
initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined,
|
|
92
|
-
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
93
|
-
});
|
|
94
|
-
const parts = parseInput(String(entry));
|
|
95
|
-
const resolvedEntries = await resolveZaloAllowFromEntries({
|
|
96
|
-
profile: resolved.profile,
|
|
97
|
-
entries: parts,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const unresolved = resolvedEntries.filter((item) => !item.resolved).map((item) => item.input);
|
|
101
|
-
if (unresolved.length > 0) {
|
|
102
|
-
await prompter.note(
|
|
103
|
-
`Could not resolve: ${unresolved.join(", ")}. Use numeric user ids or exact friend names.`,
|
|
104
|
-
"Zalo Personal allowlist",
|
|
105
|
-
);
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const resolvedIds = resolvedEntries
|
|
110
|
-
.filter((item) => item.resolved && item.id)
|
|
111
|
-
.map((item) => item.id as string);
|
|
112
|
-
const unique = mergeAllowFromEntries(existingAllowFrom, resolvedIds);
|
|
113
|
-
|
|
114
|
-
const notes = resolvedEntries
|
|
115
|
-
.filter((item) => item.note)
|
|
116
|
-
.map((item) => `${item.input} -> ${item.id} (${item.note})`);
|
|
117
|
-
if (notes.length > 0) {
|
|
118
|
-
await prompter.note(notes.join("\n"), "Zalo Personal allowlist");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return setZalouserAccountScopedConfig(cfg, accountId, {
|
|
122
|
-
dmPolicy: "allowlist",
|
|
123
|
-
allowFrom: unique,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function setZalouserGroupPolicy(
|
|
129
|
-
cfg: OpenClawConfig,
|
|
130
|
-
accountId: string,
|
|
131
|
-
groupPolicy: "open" | "allowlist" | "disabled",
|
|
132
|
-
): OpenClawConfig {
|
|
133
|
-
return setZalouserAccountScopedConfig(cfg, accountId, {
|
|
134
|
-
groupPolicy,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function setZalouserGroupAllowlist(
|
|
139
|
-
cfg: OpenClawConfig,
|
|
140
|
-
accountId: string,
|
|
141
|
-
groupKeys: string[],
|
|
142
|
-
): OpenClawConfig {
|
|
143
|
-
const groups = Object.fromEntries(groupKeys.map((key) => [key, { allow: true }]));
|
|
144
|
-
return setZalouserAccountScopedConfig(cfg, accountId, {
|
|
145
|
-
groups,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const dmPolicy: ChannelOnboardingDmPolicy = {
|
|
150
|
-
label: "Zalo Personal",
|
|
151
|
-
channel,
|
|
152
|
-
policyKey: "channels.zalouser.dmPolicy",
|
|
153
|
-
allowFromKey: "channels.zalouser.allowFrom",
|
|
154
|
-
getCurrent: (cfg) => (cfg.channels?.zalouser?.dmPolicy ?? "pairing") as "pairing",
|
|
155
|
-
setPolicy: (cfg, policy) => setZalouserDmPolicy(cfg, policy),
|
|
156
|
-
promptAllowFrom: async ({ cfg, prompter, accountId }) => {
|
|
157
|
-
const id =
|
|
158
|
-
accountId && normalizeAccountId(accountId)
|
|
159
|
-
? (normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID)
|
|
160
|
-
: resolveDefaultZalouserAccountId(cfg);
|
|
161
|
-
return promptZalouserAllowFrom({
|
|
162
|
-
cfg,
|
|
163
|
-
prompter,
|
|
164
|
-
accountId: id,
|
|
165
|
-
});
|
|
166
|
-
},
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
170
|
-
channel,
|
|
171
|
-
dmPolicy,
|
|
172
|
-
getStatus: async ({ cfg }) => {
|
|
173
|
-
const ids = listZalouserAccountIds(cfg);
|
|
174
|
-
let configured = false;
|
|
175
|
-
for (const accountId of ids) {
|
|
176
|
-
const account = resolveZalouserAccountSync({ cfg, accountId });
|
|
177
|
-
const isAuth = await checkZcaAuthenticated(account.profile);
|
|
178
|
-
if (isAuth) {
|
|
179
|
-
configured = true;
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return {
|
|
184
|
-
channel,
|
|
185
|
-
configured,
|
|
186
|
-
statusLines: [`Zalo Personal: ${configured ? "logged in" : "needs QR login"}`],
|
|
187
|
-
selectionHint: configured ? "recommended · logged in" : "recommended · QR login",
|
|
188
|
-
quickstartScore: configured ? 1 : 15,
|
|
189
|
-
};
|
|
190
|
-
},
|
|
191
|
-
configure: async ({
|
|
192
|
-
cfg,
|
|
193
|
-
prompter,
|
|
194
|
-
accountOverrides,
|
|
195
|
-
shouldPromptAccountIds,
|
|
196
|
-
forceAllowFrom,
|
|
197
|
-
}) => {
|
|
198
|
-
const defaultAccountId = resolveDefaultZalouserAccountId(cfg);
|
|
199
|
-
const accountId = await resolveAccountIdForConfigure({
|
|
200
|
-
cfg,
|
|
201
|
-
prompter,
|
|
202
|
-
label: "Zalo Personal",
|
|
203
|
-
accountOverride: accountOverrides.zalouser,
|
|
204
|
-
shouldPromptAccountIds,
|
|
205
|
-
listAccountIds: listZalouserAccountIds,
|
|
206
|
-
defaultAccountId,
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
let next = cfg;
|
|
210
|
-
const account = resolveZalouserAccountSync({ cfg: next, accountId });
|
|
211
|
-
const alreadyAuthenticated = await checkZcaAuthenticated(account.profile);
|
|
212
|
-
|
|
213
|
-
if (!alreadyAuthenticated) {
|
|
214
|
-
await noteZalouserHelp(prompter);
|
|
215
|
-
|
|
216
|
-
const wantsLogin = await prompter.confirm({
|
|
217
|
-
message: "Login via QR code now?",
|
|
218
|
-
initialValue: true,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
if (wantsLogin) {
|
|
222
|
-
const start = await startZaloQrLogin({ profile: account.profile, timeoutMs: 35_000 });
|
|
223
|
-
if (start.qrDataUrl) {
|
|
224
|
-
const qrPath = await writeQrDataUrlToTempFile(start.qrDataUrl, account.profile);
|
|
225
|
-
await prompter.note(
|
|
226
|
-
[
|
|
227
|
-
start.message,
|
|
228
|
-
qrPath
|
|
229
|
-
? `QR image saved to: ${qrPath}`
|
|
230
|
-
: "Could not write QR image file; use gateway web login UI instead.",
|
|
231
|
-
"Scan + approve on phone, then continue.",
|
|
232
|
-
].join("\n"),
|
|
233
|
-
"QR Login",
|
|
234
|
-
);
|
|
235
|
-
const scanned = await prompter.confirm({
|
|
236
|
-
message: "Did you scan and approve the QR on your phone?",
|
|
237
|
-
initialValue: true,
|
|
238
|
-
});
|
|
239
|
-
if (scanned) {
|
|
240
|
-
const waited = await waitForZaloQrLogin({
|
|
241
|
-
profile: account.profile,
|
|
242
|
-
timeoutMs: 120_000,
|
|
243
|
-
});
|
|
244
|
-
await prompter.note(waited.message, waited.connected ? "Success" : "Login pending");
|
|
245
|
-
}
|
|
246
|
-
} else {
|
|
247
|
-
await prompter.note(start.message, "Login pending");
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
const keepSession = await prompter.confirm({
|
|
252
|
-
message: "Zalo Personal already logged in. Keep session?",
|
|
253
|
-
initialValue: true,
|
|
254
|
-
});
|
|
255
|
-
if (!keepSession) {
|
|
256
|
-
await logoutZaloProfile(account.profile);
|
|
257
|
-
const start = await startZaloQrLogin({
|
|
258
|
-
profile: account.profile,
|
|
259
|
-
force: true,
|
|
260
|
-
timeoutMs: 35_000,
|
|
261
|
-
});
|
|
262
|
-
if (start.qrDataUrl) {
|
|
263
|
-
const qrPath = await writeQrDataUrlToTempFile(start.qrDataUrl, account.profile);
|
|
264
|
-
await prompter.note(
|
|
265
|
-
[start.message, qrPath ? `QR image saved to: ${qrPath}` : undefined]
|
|
266
|
-
.filter(Boolean)
|
|
267
|
-
.join("\n"),
|
|
268
|
-
"QR Login",
|
|
269
|
-
);
|
|
270
|
-
const waited = await waitForZaloQrLogin({ profile: account.profile, timeoutMs: 120_000 });
|
|
271
|
-
await prompter.note(waited.message, waited.connected ? "Success" : "Login pending");
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
next = setZalouserAccountScopedConfig(
|
|
277
|
-
next,
|
|
278
|
-
accountId,
|
|
279
|
-
{ profile: account.profile !== "default" ? account.profile : undefined },
|
|
280
|
-
{ profile: account.profile, enabled: true },
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
if (forceAllowFrom) {
|
|
284
|
-
next = await promptZalouserAllowFrom({
|
|
285
|
-
cfg: next,
|
|
286
|
-
prompter,
|
|
287
|
-
accountId,
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const updatedAccount = resolveZalouserAccountSync({ cfg: next, accountId });
|
|
292
|
-
const accessConfig = await promptChannelAccessConfig({
|
|
293
|
-
prompter,
|
|
294
|
-
label: "Zalo groups",
|
|
295
|
-
currentPolicy: updatedAccount.config.groupPolicy ?? "allowlist",
|
|
296
|
-
currentEntries: Object.keys(updatedAccount.config.groups ?? {}),
|
|
297
|
-
placeholder: "Family, Work, 123456789",
|
|
298
|
-
updatePrompt: Boolean(updatedAccount.config.groups),
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
if (accessConfig) {
|
|
302
|
-
if (accessConfig.policy !== "allowlist") {
|
|
303
|
-
next = setZalouserGroupPolicy(next, accountId, accessConfig.policy);
|
|
304
|
-
} else {
|
|
305
|
-
let keys = accessConfig.entries;
|
|
306
|
-
if (accessConfig.entries.length > 0) {
|
|
307
|
-
try {
|
|
308
|
-
const resolved = await resolveZaloGroupsByEntries({
|
|
309
|
-
profile: updatedAccount.profile,
|
|
310
|
-
entries: accessConfig.entries,
|
|
311
|
-
});
|
|
312
|
-
const resolvedIds = resolved
|
|
313
|
-
.filter((entry) => entry.resolved && entry.id)
|
|
314
|
-
.map((entry) => entry.id as string);
|
|
315
|
-
const unresolved = resolved
|
|
316
|
-
.filter((entry) => !entry.resolved)
|
|
317
|
-
.map((entry) => entry.input);
|
|
318
|
-
keys = [...resolvedIds, ...unresolved.map((entry) => entry.trim()).filter(Boolean)];
|
|
319
|
-
const resolution = formatResolvedUnresolvedNote({
|
|
320
|
-
resolved: resolvedIds,
|
|
321
|
-
unresolved,
|
|
322
|
-
});
|
|
323
|
-
if (resolution) {
|
|
324
|
-
await prompter.note(resolution, "Zalo groups");
|
|
325
|
-
}
|
|
326
|
-
} catch (err) {
|
|
327
|
-
await prompter.note(
|
|
328
|
-
`Group lookup failed; keeping entries as typed. ${String(err)}`,
|
|
329
|
-
"Zalo groups",
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
next = setZalouserGroupPolicy(next, accountId, "allowlist");
|
|
334
|
-
next = setZalouserGroupAllowlist(next, accountId, keys);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return { cfg: next, accountId };
|
|
339
|
-
},
|
|
340
|
-
};
|