@kodelyth/zalouser 2026.5.39 → 2026.6.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 ADDED
@@ -0,0 +1,120 @@
1
+ # @klaw/zalouser
2
+
3
+ Klaw extension for Zalo Personal Account messaging via native `zca-js` integration.
4
+
5
+ > **Warning:** Using Zalo automation may result in account suspension or ban. Use at your own risk. This is an unofficial integration.
6
+
7
+ ## Features
8
+
9
+ - Channel plugin integration with setup wizard + QR login
10
+ - In-process listener/sender via `zca-js` (no external CLI)
11
+ - Multi-account support
12
+ - Agent tool integration (`zalouser`)
13
+ - DM/group policy support
14
+
15
+ ## Prerequisites
16
+
17
+ - Klaw Gateway
18
+ - Zalo mobile app (for QR login)
19
+
20
+ No external `zca`, `openzca`, or `zca-cli` binary is required.
21
+
22
+ ## Install
23
+
24
+ ### Option A: npm
25
+
26
+ ```bash
27
+ klaw plugins install @klaw/zalouser
28
+ ```
29
+
30
+ ### Option B: local source checkout
31
+
32
+ ```bash
33
+ PLUGIN_SRC=./path/to/local/zalouser-plugin
34
+ klaw plugins install "$PLUGIN_SRC"
35
+ cd "$PLUGIN_SRC" && pnpm install
36
+ ```
37
+
38
+ Restart the Gateway after install.
39
+
40
+ ## Quick start
41
+
42
+ ### Login (QR)
43
+
44
+ ```bash
45
+ klaw channels login --channel zalouser
46
+ ```
47
+
48
+ Scan the QR code with the Zalo app on your phone.
49
+
50
+ ### Enable channel
51
+
52
+ ```yaml
53
+ channels:
54
+ zalouser:
55
+ enabled: true
56
+ dmPolicy: pairing # pairing | allowlist | open | disabled
57
+ ```
58
+
59
+ ### Send a message
60
+
61
+ ```bash
62
+ klaw message send --channel zalouser --target <threadId> --message "Hello from Klaw"
63
+ ```
64
+
65
+ ## Configuration
66
+
67
+ Basic:
68
+
69
+ ```yaml
70
+ channels:
71
+ zalouser:
72
+ enabled: true
73
+ dmPolicy: pairing
74
+ ```
75
+
76
+ Multi-account:
77
+
78
+ ```yaml
79
+ channels:
80
+ zalouser:
81
+ enabled: true
82
+ defaultAccount: default
83
+ accounts:
84
+ default:
85
+ enabled: true
86
+ profile: default
87
+ work:
88
+ enabled: true
89
+ profile: work
90
+ ```
91
+
92
+ ## Useful commands
93
+
94
+ ```bash
95
+ klaw channels login --channel zalouser
96
+ klaw channels login --channel zalouser --account work
97
+ klaw channels status --probe
98
+ klaw channels logout --channel zalouser
99
+
100
+ klaw directory self --channel zalouser
101
+ klaw directory peers list --channel zalouser --query "name"
102
+ klaw directory groups list --channel zalouser --query "work"
103
+ klaw directory groups members --channel zalouser --group-id <id>
104
+ ```
105
+
106
+ ## Agent tool
107
+
108
+ The extension registers a `zalouser` tool for AI agents.
109
+
110
+ Available actions: `send`, `image`, `link`, `friends`, `groups`, `me`, `status`
111
+
112
+ ## Troubleshooting
113
+
114
+ - Login not persisted: `klaw channels logout --channel zalouser && klaw channels login --channel zalouser`
115
+ - Probe status: `klaw channels status --probe`
116
+ - Name resolution issues (allowlist/groups): use numeric IDs or exact Zalo names
117
+
118
+ ## Credits
119
+
120
+ Built on [zca-js](https://github.com/RFS-ADRENO/zca-js).
@@ -0,0 +1,66 @@
1
+ import { DEFAULT_ACCOUNT_ID, createAccountListHelpers, normalizeAccountId, resolveMergedAccountConfig } from "klaw/plugin-sdk/account-resolution";
2
+ import { normalizeOptionalString } from "klaw/plugin-sdk/string-coerce-runtime";
3
+ //#region extensions/zalouser/src/accounts.ts
4
+ let zalouserAccountsRuntimePromise;
5
+ async function loadZalouserAccountsRuntime() {
6
+ zalouserAccountsRuntimePromise ??= import("./accounts.runtime-KT101uuu.js");
7
+ return await zalouserAccountsRuntimePromise;
8
+ }
9
+ const { listAccountIds: listZalouserAccountIds, resolveDefaultAccountId: resolveDefaultZalouserAccountId } = createAccountListHelpers("zalouser", { implicitDefaultAccount: {
10
+ channelKeys: ["profile"],
11
+ envVars: ["ZALOUSER_PROFILE", "ZCA_PROFILE"]
12
+ } });
13
+ function mergeZalouserAccountConfig(cfg, accountId) {
14
+ const merged = resolveMergedAccountConfig({
15
+ channelConfig: cfg.channels?.zalouser,
16
+ accounts: (cfg.channels?.zalouser)?.accounts,
17
+ accountId,
18
+ omitKeys: ["defaultAccount"]
19
+ });
20
+ return {
21
+ ...merged,
22
+ groupPolicy: merged.groupPolicy ?? "allowlist"
23
+ };
24
+ }
25
+ function resolveProfile(config, accountId) {
26
+ if (config.profile?.trim()) return config.profile.trim();
27
+ if (process.env.ZALOUSER_PROFILE?.trim()) return process.env.ZALOUSER_PROFILE.trim();
28
+ if (process.env.ZCA_PROFILE?.trim()) return process.env.ZCA_PROFILE.trim();
29
+ if (accountId !== DEFAULT_ACCOUNT_ID) return accountId;
30
+ return "default";
31
+ }
32
+ function resolveZalouserAccountBase(params) {
33
+ const accountId = normalizeAccountId(params.accountId ?? resolveDefaultZalouserAccountId(params.cfg));
34
+ const baseEnabled = (params.cfg.channels?.zalouser)?.enabled !== false;
35
+ const merged = mergeZalouserAccountConfig(params.cfg, accountId);
36
+ return {
37
+ accountId,
38
+ enabled: baseEnabled && merged.enabled !== false,
39
+ merged,
40
+ profile: resolveProfile(merged, accountId)
41
+ };
42
+ }
43
+ function resolveZalouserAccountSync(params) {
44
+ const { accountId, enabled, merged, profile } = resolveZalouserAccountBase(params);
45
+ return {
46
+ accountId,
47
+ name: normalizeOptionalString(merged.name),
48
+ enabled,
49
+ profile,
50
+ authenticated: false,
51
+ config: merged
52
+ };
53
+ }
54
+ async function getZcaUserInfo(profile) {
55
+ const info = await (await loadZalouserAccountsRuntime()).getZaloUserInfo(profile);
56
+ if (!info) return null;
57
+ return {
58
+ userId: info.userId,
59
+ displayName: info.displayName
60
+ };
61
+ }
62
+ async function checkZcaAuthenticated(profile) {
63
+ return await (await loadZalouserAccountsRuntime()).checkZaloAuthenticated(profile);
64
+ }
65
+ //#endregion
66
+ export { resolveZalouserAccountSync as a, resolveDefaultZalouserAccountId as i, getZcaUserInfo as n, listZalouserAccountIds as r, checkZcaAuthenticated as t };
@@ -0,0 +1,2 @@
1
+ import { n as getZaloUserInfo, t as checkZaloAuthenticated } from "./zalo-js-B80cRyDF.js";
2
+ export { checkZaloAuthenticated, getZaloUserInfo };
@@ -0,0 +1,133 @@
1
+ import "./setup-surface-Cfj4GQlB.js";
2
+ import "./shared-DjK0e2FC.js";
3
+ import "./channel-pby_3Sur.js";
4
+ import { r as parseZalouserOutboundTarget } from "./session-route-CalHiv1d.js";
5
+ import "./security-audit-D_rftvs-.js";
6
+ import { i as listZaloFriendsMatching, n as getZaloUserInfo, s as listZaloGroupsMatching, t as checkZaloAuthenticated } from "./zalo-js-B80cRyDF.js";
7
+ import "./channel.setup-CqyWwqcQ.js";
8
+ import { i as sendMessageZalouser, n as sendImageZalouser, r as sendLinkZalouser } from "./send-uRjUB8mG.js";
9
+ import { stringEnum } from "klaw/plugin-sdk/channel-actions";
10
+ import { formatErrorMessage } from "klaw/plugin-sdk/error-runtime";
11
+ import { Type } from "typebox";
12
+ //#region extensions/zalouser/src/tool.ts
13
+ const ACTIONS = [
14
+ "send",
15
+ "image",
16
+ "link",
17
+ "friends",
18
+ "groups",
19
+ "me",
20
+ "status"
21
+ ];
22
+ const ZalouserToolSchema = Type.Object({
23
+ action: stringEnum(ACTIONS, { description: `Action to perform: ${ACTIONS.join(", ")}` }),
24
+ threadId: Type.Optional(Type.String({ description: "Thread ID for messaging" })),
25
+ message: Type.Optional(Type.String({ description: "Message text" })),
26
+ isGroup: Type.Optional(Type.Boolean({ description: "Is group chat" })),
27
+ profile: Type.Optional(Type.String({ description: "Profile name" })),
28
+ query: Type.Optional(Type.String({ description: "Search query" })),
29
+ url: Type.Optional(Type.String({ description: "URL for media/link" }))
30
+ }, { additionalProperties: false });
31
+ function json(payload) {
32
+ return {
33
+ content: [{
34
+ type: "text",
35
+ text: JSON.stringify(payload, null, 2)
36
+ }],
37
+ details: payload
38
+ };
39
+ }
40
+ function resolveAmbientZalouserTarget(context) {
41
+ const deliveryContext = context?.deliveryContext;
42
+ const rawTarget = deliveryContext?.to;
43
+ if ((deliveryContext?.channel === void 0 || deliveryContext.channel === "zalouser") && typeof rawTarget === "string" && rawTarget.trim()) try {
44
+ return parseZalouserOutboundTarget(rawTarget);
45
+ } catch {}
46
+ if (deliveryContext?.channel && deliveryContext.channel !== "zalouser") return {};
47
+ const ambientThreadId = deliveryContext?.threadId;
48
+ if (typeof ambientThreadId === "string" && ambientThreadId.trim()) return { threadId: ambientThreadId.trim() };
49
+ if (typeof ambientThreadId === "number" && Number.isFinite(ambientThreadId)) return { threadId: String(ambientThreadId) };
50
+ return {};
51
+ }
52
+ function resolveZalouserSendTarget(params, context) {
53
+ const explicitThreadId = typeof params.threadId === "string" ? params.threadId.trim() : "";
54
+ const ambientTarget = resolveAmbientZalouserTarget(context);
55
+ return {
56
+ threadId: explicitThreadId || ambientTarget.threadId,
57
+ isGroup: typeof params.isGroup === "boolean" ? params.isGroup : ambientTarget.isGroup
58
+ };
59
+ }
60
+ async function executeZalouserTool(_toolCallId, params, _signal, _onUpdate, context) {
61
+ try {
62
+ switch (params.action) {
63
+ case "send": {
64
+ const target = resolveZalouserSendTarget(params, context);
65
+ if (!target.threadId || !params.message) throw new Error("threadId and message required for send action");
66
+ const result = await sendMessageZalouser(target.threadId, params.message, {
67
+ profile: params.profile,
68
+ isGroup: target.isGroup
69
+ });
70
+ if (!result.ok) throw new Error(result.error || "Failed to send message");
71
+ return json({
72
+ success: true,
73
+ messageId: result.messageId
74
+ });
75
+ }
76
+ case "image": {
77
+ const target = resolveZalouserSendTarget(params, context);
78
+ if (!target.threadId) throw new Error("threadId required for image action");
79
+ if (!params.url) throw new Error("url required for image action");
80
+ const result = await sendImageZalouser(target.threadId, params.url, {
81
+ profile: params.profile,
82
+ caption: params.message,
83
+ isGroup: target.isGroup
84
+ });
85
+ if (!result.ok) throw new Error(result.error || "Failed to send image");
86
+ return json({
87
+ success: true,
88
+ messageId: result.messageId
89
+ });
90
+ }
91
+ case "link": {
92
+ const target = resolveZalouserSendTarget(params, context);
93
+ if (!target.threadId || !params.url) throw new Error("threadId and url required for link action");
94
+ const result = await sendLinkZalouser(target.threadId, params.url, {
95
+ profile: params.profile,
96
+ caption: params.message,
97
+ isGroup: target.isGroup
98
+ });
99
+ if (!result.ok) throw new Error(result.error || "Failed to send link");
100
+ return json({
101
+ success: true,
102
+ messageId: result.messageId
103
+ });
104
+ }
105
+ case "friends": return json(await listZaloFriendsMatching(params.profile, params.query));
106
+ case "groups": return json(await listZaloGroupsMatching(params.profile, params.query));
107
+ case "me": return json(await getZaloUserInfo(params.profile) ?? { error: "Not authenticated" });
108
+ case "status": {
109
+ const authenticated = await checkZaloAuthenticated(params.profile);
110
+ return json({
111
+ authenticated,
112
+ output: authenticated ? "authenticated" : "not authenticated"
113
+ });
114
+ }
115
+ default:
116
+ params.action;
117
+ throw new Error(`Unknown action: ${String(params.action)}. Valid actions: send, image, link, friends, groups, me, status`);
118
+ }
119
+ } catch (err) {
120
+ return json({ error: formatErrorMessage(err) });
121
+ }
122
+ }
123
+ function createZalouserTool(context) {
124
+ return {
125
+ name: "zalouser",
126
+ label: "Zalo Personal",
127
+ description: "Send messages and access data via Zalo personal account. Actions: send (text message), image (send image URL), link (send link), friends (list/search friends), groups (list groups), me (profile info), status (auth check).",
128
+ parameters: ZalouserToolSchema,
129
+ execute: async (toolCallId, params, signal, onUpdate) => await executeZalouserTool(toolCallId, params, signal, onUpdate, context)
130
+ };
131
+ }
132
+ //#endregion
133
+ export { createZalouserTool as t };
package/dist/api.js ADDED
@@ -0,0 +1,7 @@
1
+ import { n as zalouserSetupWizard } from "./setup-surface-Cfj4GQlB.js";
2
+ import { n as createZalouserSetupWizardProxy, r as zalouserSetupAdapter } from "./shared-DjK0e2FC.js";
3
+ import { t as zalouserPlugin } from "./channel-pby_3Sur.js";
4
+ import { n as isZalouserMutableGroupEntry, t as collectZalouserSecurityAuditFindings } from "./security-audit-D_rftvs-.js";
5
+ import { t as zalouserSetupPlugin } from "./channel.setup-CqyWwqcQ.js";
6
+ import { t as createZalouserTool } from "./api-DSWT4Dh_.js";
7
+ export { collectZalouserSecurityAuditFindings, createZalouserSetupWizardProxy, createZalouserTool, isZalouserMutableGroupEntry, zalouserPlugin, zalouserSetupAdapter, zalouserSetupPlugin, zalouserSetupWizard };