@openclaw/zalo 2026.5.2 → 2026.5.3-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/dist/accounts-9NLDDlZ8.js +118 -0
- package/dist/actions.runtime-kJ65ZxW7.js +5 -0
- package/dist/api.js +5 -0
- package/dist/channel-VPbtV3Oq.js +343 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-BnTAWQx5.js +106 -0
- package/dist/contract-api.js +3 -0
- package/dist/group-access-DZR43lOR.js +30 -0
- package/dist/index.js +22 -0
- package/dist/monitor-DMysJBWa.js +823 -0
- package/dist/monitor.webhook-DqnuvgjV.js +175 -0
- package/dist/proxy-CY8VuC6H.js +135 -0
- package/dist/runtime-BRFxnYQx.js +8 -0
- package/dist/runtime-api-MOTmRW4F.js +19 -0
- package/dist/runtime-api.js +3 -0
- package/dist/secret-contract-Dw93tGo2.js +87 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/send-Gv3l5EGI.js +101 -0
- package/dist/setup-api.js +30 -0
- package/dist/setup-core-DigRD3j1.js +166 -0
- package/dist/setup-entry.js +15 -0
- package/dist/setup-surface-2Up3yWov.js +216 -0
- package/dist/test-api.js +2 -0
- package/package.json +15 -6
- package/api.ts +0 -9
- package/channel-plugin-api.ts +0 -1
- package/contract-api.ts +0 -5
- package/index.test.ts +0 -15
- package/index.ts +0 -20
- package/runtime-api.test.ts +0 -17
- package/runtime-api.ts +0 -75
- package/secret-contract-api.ts +0 -5
- package/setup-api.ts +0 -34
- package/setup-entry.ts +0 -13
- package/src/accounts.test.ts +0 -70
- package/src/accounts.ts +0 -60
- package/src/actions.runtime.ts +0 -5
- package/src/actions.test.ts +0 -32
- package/src/actions.ts +0 -62
- package/src/api.test.ts +0 -149
- package/src/api.ts +0 -265
- package/src/approval-auth.test.ts +0 -17
- package/src/approval-auth.ts +0 -25
- package/src/channel.directory.test.ts +0 -59
- package/src/channel.runtime.ts +0 -93
- package/src/channel.startup.test.ts +0 -101
- package/src/channel.ts +0 -275
- package/src/config-schema.test.ts +0 -30
- package/src/config-schema.ts +0 -29
- package/src/group-access.ts +0 -49
- package/src/monitor.group-policy.test.ts +0 -94
- package/src/monitor.image.polling.test.ts +0 -110
- package/src/monitor.lifecycle.test.ts +0 -198
- package/src/monitor.pairing.lifecycle.test.ts +0 -141
- package/src/monitor.polling.media-reply.test.ts +0 -425
- package/src/monitor.reply-once.lifecycle.test.ts +0 -171
- package/src/monitor.ts +0 -1028
- package/src/monitor.types.ts +0 -4
- package/src/monitor.webhook.test.ts +0 -806
- package/src/monitor.webhook.ts +0 -278
- package/src/outbound-media.test.ts +0 -182
- package/src/outbound-media.ts +0 -241
- package/src/outbound-payload.contract.test.ts +0 -45
- package/src/probe.ts +0 -45
- package/src/proxy.ts +0 -24
- package/src/runtime-api.ts +0 -75
- package/src/runtime-support.ts +0 -91
- package/src/runtime.ts +0 -9
- package/src/secret-contract.ts +0 -109
- package/src/secret-input.ts +0 -5
- package/src/send.test.ts +0 -120
- package/src/send.ts +0 -153
- package/src/session-route.ts +0 -32
- package/src/setup-allow-from.ts +0 -94
- package/src/setup-core.ts +0 -149
- package/src/setup-status.test.ts +0 -33
- package/src/setup-surface.test.ts +0 -175
- package/src/setup-surface.ts +0 -291
- package/src/status-issues.test.ts +0 -17
- package/src/status-issues.ts +0 -37
- package/src/test-support/lifecycle-test-support.ts +0 -413
- package/src/test-support/monitor-mocks-test-support.ts +0 -209
- package/src/token.test.ts +0 -92
- package/src/token.ts +0 -79
- package/src/types.ts +0 -50
- package/test-api.ts +0 -1
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createAccountListHelpers, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-helpers";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
|
3
|
+
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
|
4
|
+
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/core";
|
|
5
|
+
import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
|
|
6
|
+
import { buildSecretInputSchema, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) __defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
16
|
+
return target;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region extensions/zalo/src/token.ts
|
|
20
|
+
function readTokenFromFile(tokenFile) {
|
|
21
|
+
return tryReadSecretFileSync(tokenFile, "Zalo token file", { rejectSymlink: true }) ?? "";
|
|
22
|
+
}
|
|
23
|
+
function resolveZaloToken(config, accountId, options) {
|
|
24
|
+
const resolvedAccountId = normalizeAccountId(accountId ?? config?.defaultAccount);
|
|
25
|
+
const isDefaultAccount = resolvedAccountId === DEFAULT_ACCOUNT_ID;
|
|
26
|
+
const baseConfig = config;
|
|
27
|
+
const accountConfig = resolveAccountEntry(baseConfig?.accounts, normalizeAccountId(resolvedAccountId));
|
|
28
|
+
const accountHasBotToken = Boolean(accountConfig && Object.prototype.hasOwnProperty.call(accountConfig, "botToken"));
|
|
29
|
+
if (accountConfig && accountHasBotToken) {
|
|
30
|
+
const token = options?.allowUnresolvedSecretRef ? normalizeSecretInputString(accountConfig.botToken) : normalizeResolvedSecretInputString({
|
|
31
|
+
value: accountConfig.botToken,
|
|
32
|
+
path: `channels.zalo.accounts.${resolvedAccountId}.botToken`
|
|
33
|
+
});
|
|
34
|
+
if (token) return {
|
|
35
|
+
token,
|
|
36
|
+
source: "config"
|
|
37
|
+
};
|
|
38
|
+
const fileToken = readTokenFromFile(accountConfig.tokenFile);
|
|
39
|
+
if (fileToken) return {
|
|
40
|
+
token: fileToken,
|
|
41
|
+
source: "configFile"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (!accountHasBotToken) {
|
|
45
|
+
const fileToken = readTokenFromFile(accountConfig?.tokenFile);
|
|
46
|
+
if (fileToken) return {
|
|
47
|
+
token: fileToken,
|
|
48
|
+
source: "configFile"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (!accountHasBotToken) {
|
|
52
|
+
const token = options?.allowUnresolvedSecretRef ? normalizeSecretInputString(baseConfig?.botToken) : normalizeResolvedSecretInputString({
|
|
53
|
+
value: baseConfig?.botToken,
|
|
54
|
+
path: "channels.zalo.botToken"
|
|
55
|
+
});
|
|
56
|
+
if (token) return {
|
|
57
|
+
token,
|
|
58
|
+
source: "config"
|
|
59
|
+
};
|
|
60
|
+
const fileToken = readTokenFromFile(baseConfig?.tokenFile);
|
|
61
|
+
if (fileToken) return {
|
|
62
|
+
token: fileToken,
|
|
63
|
+
source: "configFile"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (isDefaultAccount) {
|
|
67
|
+
const envToken = process.env.ZALO_BOT_TOKEN?.trim();
|
|
68
|
+
if (envToken) return {
|
|
69
|
+
token: envToken,
|
|
70
|
+
source: "env"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
token: "",
|
|
75
|
+
source: "none"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region extensions/zalo/src/accounts.ts
|
|
80
|
+
var accounts_exports = /* @__PURE__ */ __exportAll({
|
|
81
|
+
listEnabledZaloAccounts: () => listEnabledZaloAccounts,
|
|
82
|
+
listZaloAccountIds: () => listZaloAccountIds,
|
|
83
|
+
resolveDefaultZaloAccountId: () => resolveDefaultZaloAccountId,
|
|
84
|
+
resolveZaloAccount: () => resolveZaloAccount
|
|
85
|
+
});
|
|
86
|
+
const { listAccountIds: listZaloAccountIds, resolveDefaultAccountId: resolveDefaultZaloAccountId } = createAccountListHelpers("zalo");
|
|
87
|
+
function mergeZaloAccountConfig(cfg, accountId) {
|
|
88
|
+
return resolveMergedAccountConfig({
|
|
89
|
+
channelConfig: cfg.channels?.zalo,
|
|
90
|
+
accounts: (cfg.channels?.zalo)?.accounts,
|
|
91
|
+
accountId,
|
|
92
|
+
omitKeys: ["defaultAccount"]
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function resolveZaloAccount(params) {
|
|
96
|
+
const accountId = normalizeAccountId(params.accountId ?? (params.cfg.channels?.zalo)?.defaultAccount);
|
|
97
|
+
const baseEnabled = (params.cfg.channels?.zalo)?.enabled !== false;
|
|
98
|
+
const merged = mergeZaloAccountConfig(params.cfg, accountId);
|
|
99
|
+
const accountEnabled = merged.enabled !== false;
|
|
100
|
+
const enabled = baseEnabled && accountEnabled;
|
|
101
|
+
const tokenResolution = resolveZaloToken(params.cfg.channels?.zalo, accountId, { allowUnresolvedSecretRef: params.allowUnresolvedSecretRef });
|
|
102
|
+
return {
|
|
103
|
+
accountId,
|
|
104
|
+
name: normalizeOptionalString(merged.name),
|
|
105
|
+
enabled,
|
|
106
|
+
token: tokenResolution.token,
|
|
107
|
+
tokenSource: tokenResolution.source,
|
|
108
|
+
config: merged
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function listEnabledZaloAccounts(cfg) {
|
|
112
|
+
return listZaloAccountIds(cfg).map((accountId) => resolveZaloAccount({
|
|
113
|
+
cfg,
|
|
114
|
+
accountId
|
|
115
|
+
})).filter((account) => account.enabled);
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
export { resolveZaloAccount as a, normalizeSecretInputString as c, resolveDefaultZaloAccountId as i, listEnabledZaloAccounts as n, resolveZaloToken as o, listZaloAccountIds as r, buildSecretInputSchema as s, accounts_exports as t };
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { t as zaloPlugin } from "./channel-VPbtV3Oq.js";
|
|
2
|
+
import { n as zaloDmPolicy, r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-DigRD3j1.js";
|
|
3
|
+
import { r as resolveZaloRuntimeGroupPolicy, t as evaluateZaloGroupAccess } from "./group-access-DZR43lOR.js";
|
|
4
|
+
import { zaloSetupWizard } from "./setup-api.js";
|
|
5
|
+
export { createZaloSetupWizardProxy, evaluateZaloGroupAccess, resolveZaloRuntimeGroupPolicy, zaloDmPolicy, zaloPlugin, zaloSetupAdapter, zaloSetupWizard };
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { a as resolveZaloAccount, i as resolveDefaultZaloAccountId, n as listEnabledZaloAccounts, r as listZaloAccountIds, s as buildSecretInputSchema } from "./accounts-9NLDDlZ8.js";
|
|
2
|
+
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-Dw93tGo2.js";
|
|
3
|
+
import { r as zaloSetupAdapter, t as createZaloSetupWizardProxy } from "./setup-core-DigRD3j1.js";
|
|
4
|
+
import { describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
5
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
6
|
+
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
|
7
|
+
import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver, mapAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
8
|
+
import { buildChannelConfigSchema, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
9
|
+
import { buildOpenGroupPolicyRestrictSendersWarning, buildOpenGroupPolicyWarning, createOpenProviderGroupPolicyWarningCollector } from "openclaw/plugin-sdk/channel-policy";
|
|
10
|
+
import { createEmptyChannelResult, createRawChannelSendResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
|
|
11
|
+
import { buildTokenChannelStatusSummary } from "openclaw/plugin-sdk/channel-status";
|
|
12
|
+
import { createStaticReplyToModeResolver } from "openclaw/plugin-sdk/conversation-runtime";
|
|
13
|
+
import { createChannelDirectoryAdapter, listResolvedDirectoryUserEntriesFromAllowFrom } from "openclaw/plugin-sdk/directory-runtime";
|
|
14
|
+
import { createLazyRuntimeModule, createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
|
15
|
+
import { isNumericTargetId, sendPayloadWithChunkedTextAndMedia } from "openclaw/plugin-sdk/reply-payload";
|
|
16
|
+
import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
|
|
17
|
+
import { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
|
|
18
|
+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
19
|
+
import { buildChannelOutboundSessionRoute, stripChannelTargetPrefix, stripTargetKindPrefix } from "openclaw/plugin-sdk/core";
|
|
20
|
+
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/channel-actions";
|
|
21
|
+
import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
|
|
22
|
+
import { createResolvedApproverActionAuthAdapter, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
23
|
+
import { AllowFromListSchema, DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, buildCatchallMultiAccountChannelSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
24
|
+
import { z } from "openclaw/plugin-sdk/zod";
|
|
25
|
+
import { coerceStatusIssueAccountId, readStatusIssueFields } from "openclaw/plugin-sdk/extension-shared";
|
|
26
|
+
//#region extensions/zalo/src/actions.ts
|
|
27
|
+
const loadZaloActionsRuntime = createLazyRuntimeNamedExport(() => import("./actions.runtime-kJ65ZxW7.js"), "zaloActionsRuntime");
|
|
28
|
+
const providerId = "zalo";
|
|
29
|
+
function listEnabledAccounts(cfg, accountId) {
|
|
30
|
+
return (accountId ? [resolveZaloAccount({
|
|
31
|
+
cfg,
|
|
32
|
+
accountId
|
|
33
|
+
})] : listEnabledZaloAccounts(cfg)).filter((account) => account.enabled && account.tokenSource !== "none");
|
|
34
|
+
}
|
|
35
|
+
const zaloMessageActions = {
|
|
36
|
+
describeMessageTool: ({ cfg, accountId }) => {
|
|
37
|
+
if (listEnabledAccounts(cfg, accountId).length === 0) return null;
|
|
38
|
+
const actions = new Set(["send"]);
|
|
39
|
+
return {
|
|
40
|
+
actions: Array.from(actions),
|
|
41
|
+
capabilities: []
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"),
|
|
45
|
+
handleAction: async ({ action, params, cfg, accountId }) => {
|
|
46
|
+
if (action === "send") {
|
|
47
|
+
const to = readStringParam(params, "to", { required: true });
|
|
48
|
+
const content = readStringParam(params, "message", {
|
|
49
|
+
required: true,
|
|
50
|
+
allowEmpty: true
|
|
51
|
+
});
|
|
52
|
+
const mediaUrl = readStringParam(params, "media", { trim: false });
|
|
53
|
+
const { sendMessageZalo } = await loadZaloActionsRuntime();
|
|
54
|
+
const result = await sendMessageZalo(to ?? "", content ?? "", {
|
|
55
|
+
accountId: accountId ?? void 0,
|
|
56
|
+
mediaUrl: mediaUrl ?? void 0,
|
|
57
|
+
cfg
|
|
58
|
+
});
|
|
59
|
+
if (!result.ok) return jsonResult({
|
|
60
|
+
ok: false,
|
|
61
|
+
error: result.error ?? "Failed to send Zalo message"
|
|
62
|
+
});
|
|
63
|
+
return jsonResult({
|
|
64
|
+
ok: true,
|
|
65
|
+
to,
|
|
66
|
+
messageId: result.messageId
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region extensions/zalo/src/approval-auth.ts
|
|
74
|
+
function normalizeZaloApproverId(value) {
|
|
75
|
+
const normalized = String(value).trim().replace(/^(zalo|zl):/i, "").trim();
|
|
76
|
+
return /^\d+$/.test(normalized) ? normalized : void 0;
|
|
77
|
+
}
|
|
78
|
+
const zaloApprovalAuth = createResolvedApproverActionAuthAdapter({
|
|
79
|
+
channelLabel: "Zalo",
|
|
80
|
+
resolveApprovers: ({ cfg, accountId }) => {
|
|
81
|
+
const account = resolveZaloAccount({
|
|
82
|
+
cfg,
|
|
83
|
+
accountId
|
|
84
|
+
}).config;
|
|
85
|
+
return resolveApprovalApprovers({
|
|
86
|
+
allowFrom: account.allowFrom,
|
|
87
|
+
normalizeApprover: normalizeZaloApproverId
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
normalizeSenderId: (value) => normalizeZaloApproverId(value)
|
|
91
|
+
});
|
|
92
|
+
const ZaloConfigSchema = buildCatchallMultiAccountChannelSchema(z.object({
|
|
93
|
+
name: z.string().optional(),
|
|
94
|
+
enabled: z.boolean().optional(),
|
|
95
|
+
markdown: MarkdownConfigSchema,
|
|
96
|
+
botToken: buildSecretInputSchema().optional(),
|
|
97
|
+
tokenFile: z.string().optional(),
|
|
98
|
+
webhookUrl: z.string().optional(),
|
|
99
|
+
webhookSecret: buildSecretInputSchema().optional(),
|
|
100
|
+
webhookPath: z.string().optional(),
|
|
101
|
+
dmPolicy: DmPolicySchema.optional(),
|
|
102
|
+
allowFrom: AllowFromListSchema,
|
|
103
|
+
groupPolicy: GroupPolicySchema.optional(),
|
|
104
|
+
groupAllowFrom: AllowFromListSchema,
|
|
105
|
+
mediaMaxMb: z.number().optional(),
|
|
106
|
+
proxy: z.string().optional(),
|
|
107
|
+
responsePrefix: z.string().optional()
|
|
108
|
+
}));
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region extensions/zalo/src/session-route.ts
|
|
111
|
+
function resolveZaloOutboundSessionRoute(params) {
|
|
112
|
+
const trimmed = stripChannelTargetPrefix(params.target, "zalo", "zl");
|
|
113
|
+
if (!trimmed) return null;
|
|
114
|
+
const isGroup = normalizeLowercaseStringOrEmpty(trimmed).startsWith("group:");
|
|
115
|
+
const peerId = stripTargetKindPrefix(trimmed);
|
|
116
|
+
if (!peerId) return null;
|
|
117
|
+
return buildChannelOutboundSessionRoute({
|
|
118
|
+
cfg: params.cfg,
|
|
119
|
+
agentId: params.agentId,
|
|
120
|
+
channel: "zalo",
|
|
121
|
+
accountId: params.accountId,
|
|
122
|
+
peer: {
|
|
123
|
+
kind: isGroup ? "group" : "direct",
|
|
124
|
+
id: peerId
|
|
125
|
+
},
|
|
126
|
+
chatType: isGroup ? "group" : "direct",
|
|
127
|
+
from: isGroup ? `zalo:group:${peerId}` : `zalo:${peerId}`,
|
|
128
|
+
to: `zalo:${peerId}`
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region extensions/zalo/src/status-issues.ts
|
|
133
|
+
const ZALO_STATUS_FIELDS = [
|
|
134
|
+
"accountId",
|
|
135
|
+
"enabled",
|
|
136
|
+
"configured",
|
|
137
|
+
"dmPolicy"
|
|
138
|
+
];
|
|
139
|
+
function collectZaloStatusIssues(accounts) {
|
|
140
|
+
const issues = [];
|
|
141
|
+
for (const entry of accounts) {
|
|
142
|
+
const account = readStatusIssueFields(entry, ZALO_STATUS_FIELDS);
|
|
143
|
+
if (!account) continue;
|
|
144
|
+
const accountId = coerceStatusIssueAccountId(account.accountId) ?? "default";
|
|
145
|
+
const enabled = account.enabled !== false;
|
|
146
|
+
const configured = account.configured === true;
|
|
147
|
+
if (!enabled || !configured) continue;
|
|
148
|
+
if (account.dmPolicy === "open") issues.push({
|
|
149
|
+
channel: "zalo",
|
|
150
|
+
accountId,
|
|
151
|
+
kind: "config",
|
|
152
|
+
message: "Zalo dmPolicy is \"open\", allowing any user to message the bot without pairing.",
|
|
153
|
+
fix: "Set channels.zalo.dmPolicy to \"pairing\" or \"allowlist\" to restrict access."
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return issues;
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region extensions/zalo/src/channel.ts
|
|
160
|
+
const meta = {
|
|
161
|
+
id: "zalo",
|
|
162
|
+
label: "Zalo",
|
|
163
|
+
selectionLabel: "Zalo (Bot API)",
|
|
164
|
+
docsPath: "/channels/zalo",
|
|
165
|
+
docsLabel: "zalo",
|
|
166
|
+
blurb: "Vietnam-focused messaging platform with Bot API.",
|
|
167
|
+
aliases: ["zl"],
|
|
168
|
+
order: 80,
|
|
169
|
+
quickstartAllowFrom: true
|
|
170
|
+
};
|
|
171
|
+
function normalizeZaloMessagingTarget(raw) {
|
|
172
|
+
const trimmed = raw?.trim();
|
|
173
|
+
if (!trimmed) return;
|
|
174
|
+
return trimmed.replace(/^(zalo|zl):/i, "").trim();
|
|
175
|
+
}
|
|
176
|
+
const loadZaloChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-BnTAWQx5.js"));
|
|
177
|
+
const zaloSetupWizard = createZaloSetupWizardProxy(async () => (await import("./setup-surface-2Up3yWov.js")).zaloSetupWizard);
|
|
178
|
+
const zaloTextChunkLimit = 2e3;
|
|
179
|
+
const zaloRawSendResultAdapter = createRawChannelSendResultAdapter({
|
|
180
|
+
channel: "zalo",
|
|
181
|
+
sendText: async ({ to, text, accountId, cfg }) => await (await loadZaloChannelRuntime()).sendZaloText({
|
|
182
|
+
to,
|
|
183
|
+
text,
|
|
184
|
+
accountId: accountId ?? void 0,
|
|
185
|
+
cfg
|
|
186
|
+
}),
|
|
187
|
+
sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => await (await loadZaloChannelRuntime()).sendZaloText({
|
|
188
|
+
to,
|
|
189
|
+
text,
|
|
190
|
+
accountId: accountId ?? void 0,
|
|
191
|
+
mediaUrl,
|
|
192
|
+
cfg
|
|
193
|
+
})
|
|
194
|
+
});
|
|
195
|
+
const zaloConfigAdapter = createScopedChannelConfigAdapter({
|
|
196
|
+
sectionKey: "zalo",
|
|
197
|
+
listAccountIds: listZaloAccountIds,
|
|
198
|
+
resolveAccount: adaptScopedAccountAccessor(resolveZaloAccount),
|
|
199
|
+
defaultAccountId: resolveDefaultZaloAccountId,
|
|
200
|
+
clearBaseFields: [
|
|
201
|
+
"botToken",
|
|
202
|
+
"tokenFile",
|
|
203
|
+
"name"
|
|
204
|
+
],
|
|
205
|
+
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
206
|
+
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({
|
|
207
|
+
allowFrom,
|
|
208
|
+
stripPrefixRe: /^(zalo|zl):/i
|
|
209
|
+
})
|
|
210
|
+
});
|
|
211
|
+
const resolveZaloDmPolicy = createScopedDmSecurityResolver({
|
|
212
|
+
channelKey: "zalo",
|
|
213
|
+
resolvePolicy: (account) => account.config.dmPolicy,
|
|
214
|
+
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
215
|
+
policyPathSuffix: "dmPolicy",
|
|
216
|
+
normalizeEntry: (raw) => raw.trim().replace(/^(zalo|zl):/i, "")
|
|
217
|
+
});
|
|
218
|
+
const collectZaloSecurityWarnings = createOpenProviderGroupPolicyWarningCollector({
|
|
219
|
+
providerConfigPresent: (cfg) => cfg.channels?.zalo !== void 0,
|
|
220
|
+
resolveGroupPolicy: ({ account }) => account.config.groupPolicy,
|
|
221
|
+
collect: ({ account, groupPolicy }) => {
|
|
222
|
+
if (groupPolicy !== "open") return [];
|
|
223
|
+
const explicitGroupAllowFrom = mapAllowFromEntries(account.config.groupAllowFrom);
|
|
224
|
+
const dmAllowFrom = mapAllowFromEntries(account.config.allowFrom);
|
|
225
|
+
if ((explicitGroupAllowFrom.length > 0 ? explicitGroupAllowFrom : dmAllowFrom).length > 0) return [buildOpenGroupPolicyRestrictSendersWarning({
|
|
226
|
+
surface: "Zalo groups",
|
|
227
|
+
openScope: "any member",
|
|
228
|
+
groupPolicyPath: "channels.zalo.groupPolicy",
|
|
229
|
+
groupAllowFromPath: "channels.zalo.groupAllowFrom"
|
|
230
|
+
})];
|
|
231
|
+
return [buildOpenGroupPolicyWarning({
|
|
232
|
+
surface: "Zalo groups",
|
|
233
|
+
openBehavior: "with no groupAllowFrom/allowFrom allowlist; any member can trigger (mention-gated)",
|
|
234
|
+
remediation: "Set channels.zalo.groupPolicy=\"allowlist\" + channels.zalo.groupAllowFrom"
|
|
235
|
+
})];
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
const zaloPlugin = createChatChannelPlugin({
|
|
239
|
+
base: {
|
|
240
|
+
id: "zalo",
|
|
241
|
+
meta,
|
|
242
|
+
setup: zaloSetupAdapter,
|
|
243
|
+
setupWizard: zaloSetupWizard,
|
|
244
|
+
capabilities: {
|
|
245
|
+
chatTypes: ["direct", "group"],
|
|
246
|
+
media: true,
|
|
247
|
+
reactions: false,
|
|
248
|
+
threads: false,
|
|
249
|
+
polls: false,
|
|
250
|
+
nativeCommands: false,
|
|
251
|
+
blockStreaming: true
|
|
252
|
+
},
|
|
253
|
+
reload: { configPrefixes: ["channels.zalo"] },
|
|
254
|
+
configSchema: buildChannelConfigSchema(ZaloConfigSchema),
|
|
255
|
+
config: {
|
|
256
|
+
...zaloConfigAdapter,
|
|
257
|
+
isConfigured: (account) => Boolean(account.token?.trim()),
|
|
258
|
+
describeAccount: (account) => describeWebhookAccountSnapshot({
|
|
259
|
+
account,
|
|
260
|
+
configured: Boolean(account.token?.trim()),
|
|
261
|
+
mode: account.config.webhookUrl ? "webhook" : "polling",
|
|
262
|
+
extra: { tokenSource: account.tokenSource }
|
|
263
|
+
})
|
|
264
|
+
},
|
|
265
|
+
approvalCapability: zaloApprovalAuth,
|
|
266
|
+
secrets: {
|
|
267
|
+
secretTargetRegistryEntries,
|
|
268
|
+
collectRuntimeConfigAssignments
|
|
269
|
+
},
|
|
270
|
+
groups: { resolveRequireMention: () => true },
|
|
271
|
+
actions: zaloMessageActions,
|
|
272
|
+
messaging: {
|
|
273
|
+
targetPrefixes: ["zalo", "zl"],
|
|
274
|
+
normalizeTarget: normalizeZaloMessagingTarget,
|
|
275
|
+
resolveOutboundSessionRoute: (params) => resolveZaloOutboundSessionRoute(params),
|
|
276
|
+
targetResolver: {
|
|
277
|
+
looksLikeId: isNumericTargetId,
|
|
278
|
+
hint: "<chatId>"
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
directory: createChannelDirectoryAdapter({
|
|
282
|
+
listPeers: async (params) => listResolvedDirectoryUserEntriesFromAllowFrom({
|
|
283
|
+
...params,
|
|
284
|
+
resolveAccount: adaptScopedAccountAccessor(resolveZaloAccount),
|
|
285
|
+
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
286
|
+
normalizeId: (entry) => entry.trim().replace(/^(zalo|zl):/i, "")
|
|
287
|
+
}),
|
|
288
|
+
listGroups: async () => []
|
|
289
|
+
}),
|
|
290
|
+
status: createComputedAccountStatusAdapter({
|
|
291
|
+
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
|
|
292
|
+
collectStatusIssues: collectZaloStatusIssues,
|
|
293
|
+
buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot),
|
|
294
|
+
probeAccount: async ({ account, timeoutMs }) => await (await loadZaloChannelRuntime()).probeZaloAccount({
|
|
295
|
+
account,
|
|
296
|
+
timeoutMs
|
|
297
|
+
}),
|
|
298
|
+
resolveAccountSnapshot: ({ account }) => {
|
|
299
|
+
const configured = Boolean(account.token?.trim());
|
|
300
|
+
return {
|
|
301
|
+
accountId: account.accountId,
|
|
302
|
+
name: account.name,
|
|
303
|
+
enabled: account.enabled,
|
|
304
|
+
configured,
|
|
305
|
+
extra: {
|
|
306
|
+
tokenSource: account.tokenSource,
|
|
307
|
+
mode: account.config.webhookUrl ? "webhook" : "polling",
|
|
308
|
+
dmPolicy: account.config.dmPolicy ?? "pairing"
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}),
|
|
313
|
+
gateway: { startAccount: async (ctx) => await (await loadZaloChannelRuntime()).startZaloGatewayAccount(ctx) }
|
|
314
|
+
},
|
|
315
|
+
security: {
|
|
316
|
+
resolveDmPolicy: resolveZaloDmPolicy,
|
|
317
|
+
collectWarnings: collectZaloSecurityWarnings
|
|
318
|
+
},
|
|
319
|
+
pairing: { text: {
|
|
320
|
+
idLabel: "zaloUserId",
|
|
321
|
+
message: "Your pairing request has been approved.",
|
|
322
|
+
normalizeAllowEntry: (entry) => entry.trim().replace(/^(zalo|zl):/i, ""),
|
|
323
|
+
notify: async (params) => await (await loadZaloChannelRuntime()).notifyZaloPairingApproval(params)
|
|
324
|
+
} },
|
|
325
|
+
threading: { resolveReplyToMode: createStaticReplyToModeResolver("off") },
|
|
326
|
+
outbound: {
|
|
327
|
+
deliveryMode: "direct",
|
|
328
|
+
chunker: chunkTextForOutbound,
|
|
329
|
+
chunkerMode: "text",
|
|
330
|
+
textChunkLimit: zaloTextChunkLimit,
|
|
331
|
+
sendPayload: async (ctx) => await sendPayloadWithChunkedTextAndMedia({
|
|
332
|
+
ctx,
|
|
333
|
+
textChunkLimit: zaloTextChunkLimit,
|
|
334
|
+
chunker: chunkTextForOutbound,
|
|
335
|
+
sendText: (nextCtx) => zaloRawSendResultAdapter.sendText(nextCtx),
|
|
336
|
+
sendMedia: (nextCtx) => zaloRawSendResultAdapter.sendMedia(nextCtx),
|
|
337
|
+
emptyResult: createEmptyChannelResult("zalo")
|
|
338
|
+
}),
|
|
339
|
+
...zaloRawSendResultAdapter
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
//#endregion
|
|
343
|
+
export { zaloPlugin as t };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { c as normalizeSecretInputString } from "./accounts-9NLDDlZ8.js";
|
|
2
|
+
import { n as PAIRING_APPROVED_MESSAGE } from "./runtime-api-MOTmRW4F.js";
|
|
3
|
+
import { i as getMe, n as ZaloApiError, t as resolveZaloProxyFetch } from "./proxy-CY8VuC6H.js";
|
|
4
|
+
import { t as sendMessageZalo } from "./send-Gv3l5EGI.js";
|
|
5
|
+
import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-lifecycle";
|
|
6
|
+
//#region extensions/zalo/src/probe.ts
|
|
7
|
+
async function probeZalo(token, timeoutMs = 5e3, fetcher) {
|
|
8
|
+
if (!token?.trim()) return {
|
|
9
|
+
ok: false,
|
|
10
|
+
error: "No token provided",
|
|
11
|
+
elapsedMs: 0
|
|
12
|
+
};
|
|
13
|
+
const startTime = Date.now();
|
|
14
|
+
try {
|
|
15
|
+
const response = await getMe(token.trim(), timeoutMs, fetcher);
|
|
16
|
+
const elapsedMs = Date.now() - startTime;
|
|
17
|
+
if (response.ok && response.result) return {
|
|
18
|
+
ok: true,
|
|
19
|
+
bot: response.result,
|
|
20
|
+
elapsedMs
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
error: "Invalid response from Zalo API",
|
|
25
|
+
elapsedMs
|
|
26
|
+
};
|
|
27
|
+
} catch (err) {
|
|
28
|
+
const elapsedMs = Date.now() - startTime;
|
|
29
|
+
if (err instanceof ZaloApiError) return {
|
|
30
|
+
ok: false,
|
|
31
|
+
error: err.description ?? err.message,
|
|
32
|
+
elapsedMs
|
|
33
|
+
};
|
|
34
|
+
if (err instanceof Error) {
|
|
35
|
+
if (err.name === "AbortError") return {
|
|
36
|
+
ok: false,
|
|
37
|
+
error: `Request timed out after ${timeoutMs}ms`,
|
|
38
|
+
elapsedMs
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
error: err.message,
|
|
43
|
+
elapsedMs
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
error: String(err),
|
|
49
|
+
elapsedMs
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region extensions/zalo/src/channel.runtime.ts
|
|
55
|
+
async function notifyZaloPairingApproval(params) {
|
|
56
|
+
const { resolveZaloAccount } = await import("./accounts-9NLDDlZ8.js").then((n) => n.t);
|
|
57
|
+
const account = resolveZaloAccount({ cfg: params.cfg });
|
|
58
|
+
if (!account.token) throw new Error("Zalo token not configured");
|
|
59
|
+
await sendMessageZalo(params.id, PAIRING_APPROVED_MESSAGE, { token: account.token });
|
|
60
|
+
}
|
|
61
|
+
async function sendZaloText(params) {
|
|
62
|
+
return await sendMessageZalo(params.to, params.text, params);
|
|
63
|
+
}
|
|
64
|
+
async function probeZaloAccount(params) {
|
|
65
|
+
return await probeZalo(params.account.token, params.timeoutMs, resolveZaloProxyFetch(params.account.config.proxy));
|
|
66
|
+
}
|
|
67
|
+
async function startZaloGatewayAccount(ctx) {
|
|
68
|
+
const account = ctx.account;
|
|
69
|
+
const token = account.token.trim();
|
|
70
|
+
const mode = account.config.webhookUrl ? "webhook" : "polling";
|
|
71
|
+
let zaloBotLabel = "";
|
|
72
|
+
const fetcher = resolveZaloProxyFetch(account.config.proxy);
|
|
73
|
+
try {
|
|
74
|
+
const probe = await probeZalo(token, 2500, fetcher);
|
|
75
|
+
const name = probe.ok ? probe.bot?.name?.trim() : null;
|
|
76
|
+
if (name) zaloBotLabel = ` (${name})`;
|
|
77
|
+
if (!probe.ok) ctx.log?.warn?.(`[${account.accountId}] Zalo probe failed before provider start (${String(probe.elapsedMs)}ms): ${probe.error}`);
|
|
78
|
+
ctx.setStatus({
|
|
79
|
+
accountId: account.accountId,
|
|
80
|
+
bot: probe.bot
|
|
81
|
+
});
|
|
82
|
+
} catch (err) {
|
|
83
|
+
ctx.log?.warn?.(`[${account.accountId}] Zalo probe threw before provider start: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
|
|
84
|
+
}
|
|
85
|
+
const statusSink = createAccountStatusSink({
|
|
86
|
+
accountId: ctx.accountId,
|
|
87
|
+
setStatus: ctx.setStatus
|
|
88
|
+
});
|
|
89
|
+
ctx.log?.info(`[${account.accountId}] starting provider${zaloBotLabel} mode=${mode}`);
|
|
90
|
+
const { monitorZaloProvider } = await import("./monitor-DMysJBWa.js");
|
|
91
|
+
return monitorZaloProvider({
|
|
92
|
+
token,
|
|
93
|
+
account,
|
|
94
|
+
config: ctx.cfg,
|
|
95
|
+
runtime: ctx.runtime,
|
|
96
|
+
abortSignal: ctx.abortSignal,
|
|
97
|
+
useWebhook: Boolean(account.config.webhookUrl),
|
|
98
|
+
webhookUrl: account.config.webhookUrl,
|
|
99
|
+
webhookSecret: normalizeSecretInputString(account.config.webhookSecret),
|
|
100
|
+
webhookPath: account.config.webhookPath,
|
|
101
|
+
fetcher,
|
|
102
|
+
statusSink
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { notifyZaloPairingApproval, probeZaloAccount, sendZaloText, startZaloGatewayAccount };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-Dw93tGo2.js";
|
|
2
|
+
import { r as resolveZaloRuntimeGroupPolicy, t as evaluateZaloGroupAccess } from "./group-access-DZR43lOR.js";
|
|
3
|
+
export { collectRuntimeConfigAssignments, evaluateZaloGroupAccess, resolveZaloRuntimeGroupPolicy, secretTargetRegistryEntries };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isNormalizedSenderAllowed } from "openclaw/plugin-sdk/allow-from";
|
|
2
|
+
import { evaluateSenderGroupAccess, resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/group-access";
|
|
3
|
+
//#region extensions/zalo/src/group-access.ts
|
|
4
|
+
const ZALO_ALLOW_FROM_PREFIX_RE = /^(zalo|zl):/i;
|
|
5
|
+
function isZaloSenderAllowed(senderId, allowFrom) {
|
|
6
|
+
return isNormalizedSenderAllowed({
|
|
7
|
+
senderId,
|
|
8
|
+
allowFrom,
|
|
9
|
+
stripPrefixRe: ZALO_ALLOW_FROM_PREFIX_RE
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function resolveZaloRuntimeGroupPolicy(params) {
|
|
13
|
+
return resolveOpenProviderRuntimeGroupPolicy({
|
|
14
|
+
providerConfigPresent: params.providerConfigPresent,
|
|
15
|
+
groupPolicy: params.groupPolicy,
|
|
16
|
+
defaultGroupPolicy: params.defaultGroupPolicy
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function evaluateZaloGroupAccess(params) {
|
|
20
|
+
return evaluateSenderGroupAccess({
|
|
21
|
+
providerConfigPresent: params.providerConfigPresent,
|
|
22
|
+
configuredGroupPolicy: params.configuredGroupPolicy,
|
|
23
|
+
defaultGroupPolicy: params.defaultGroupPolicy,
|
|
24
|
+
groupAllowFrom: params.groupAllowFrom,
|
|
25
|
+
senderId: params.senderId,
|
|
26
|
+
isSenderAllowed: isZaloSenderAllowed
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { isZaloSenderAllowed as n, resolveZaloRuntimeGroupPolicy as r, evaluateZaloGroupAccess as t };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
|
|
2
|
+
//#region extensions/zalo/index.ts
|
|
3
|
+
var zalo_default = defineBundledChannelEntry({
|
|
4
|
+
id: "zalo",
|
|
5
|
+
name: "Zalo",
|
|
6
|
+
description: "Zalo channel plugin",
|
|
7
|
+
importMetaUrl: import.meta.url,
|
|
8
|
+
plugin: {
|
|
9
|
+
specifier: "./channel-plugin-api.js",
|
|
10
|
+
exportName: "zaloPlugin"
|
|
11
|
+
},
|
|
12
|
+
secrets: {
|
|
13
|
+
specifier: "./secret-contract-api.js",
|
|
14
|
+
exportName: "channelSecrets"
|
|
15
|
+
},
|
|
16
|
+
runtime: {
|
|
17
|
+
specifier: "./runtime-api.js",
|
|
18
|
+
exportName: "setZaloRuntime"
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
//#endregion
|
|
22
|
+
export { zalo_default as default };
|