@openclaw/zalo 2026.1.29
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 +60 -0
- package/README.md +50 -0
- package/index.ts +20 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +33 -0
- package/src/accounts.ts +71 -0
- package/src/actions.ts +62 -0
- package/src/api.ts +206 -0
- package/src/channel.directory.test.ts +35 -0
- package/src/channel.ts +394 -0
- package/src/config-schema.ts +24 -0
- package/src/monitor.ts +760 -0
- package/src/monitor.webhook.test.ts +70 -0
- package/src/onboarding.ts +405 -0
- package/src/probe.ts +46 -0
- package/src/proxy.ts +18 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +117 -0
- package/src/status-issues.ts +50 -0
- package/src/token.ts +55 -0
- package/src/types.ts +42 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ChannelAccountSnapshot, ChannelStatusIssue } from "openclaw/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
type ZaloAccountStatus = {
|
|
4
|
+
accountId?: unknown;
|
|
5
|
+
enabled?: unknown;
|
|
6
|
+
configured?: unknown;
|
|
7
|
+
dmPolicy?: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
11
|
+
Boolean(value && typeof value === "object");
|
|
12
|
+
|
|
13
|
+
const asString = (value: unknown): string | undefined =>
|
|
14
|
+
typeof value === "string" ? value : typeof value === "number" ? String(value) : undefined;
|
|
15
|
+
|
|
16
|
+
function readZaloAccountStatus(value: ChannelAccountSnapshot): ZaloAccountStatus | null {
|
|
17
|
+
if (!isRecord(value)) return null;
|
|
18
|
+
return {
|
|
19
|
+
accountId: value.accountId,
|
|
20
|
+
enabled: value.enabled,
|
|
21
|
+
configured: value.configured,
|
|
22
|
+
dmPolicy: value.dmPolicy,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function collectZaloStatusIssues(
|
|
27
|
+
accounts: ChannelAccountSnapshot[],
|
|
28
|
+
): ChannelStatusIssue[] {
|
|
29
|
+
const issues: ChannelStatusIssue[] = [];
|
|
30
|
+
for (const entry of accounts) {
|
|
31
|
+
const account = readZaloAccountStatus(entry);
|
|
32
|
+
if (!account) continue;
|
|
33
|
+
const accountId = asString(account.accountId) ?? "default";
|
|
34
|
+
const enabled = account.enabled !== false;
|
|
35
|
+
const configured = account.configured === true;
|
|
36
|
+
if (!enabled || !configured) continue;
|
|
37
|
+
|
|
38
|
+
if (account.dmPolicy === "open") {
|
|
39
|
+
issues.push({
|
|
40
|
+
channel: "zalo",
|
|
41
|
+
accountId,
|
|
42
|
+
kind: "config",
|
|
43
|
+
message:
|
|
44
|
+
'Zalo dmPolicy is "open", allowing any user to message the bot without pairing.',
|
|
45
|
+
fix: 'Set channels.zalo.dmPolicy to "pairing" or "allowlist" to restrict access.',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return issues;
|
|
50
|
+
}
|
package/src/token.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
|
|
4
|
+
|
|
5
|
+
import type { ZaloConfig } from "./types.js";
|
|
6
|
+
|
|
7
|
+
export type ZaloTokenResolution = {
|
|
8
|
+
token: string;
|
|
9
|
+
source: "env" | "config" | "configFile" | "none";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function resolveZaloToken(
|
|
13
|
+
config: ZaloConfig | undefined,
|
|
14
|
+
accountId?: string | null,
|
|
15
|
+
): ZaloTokenResolution {
|
|
16
|
+
const resolvedAccountId = accountId ?? DEFAULT_ACCOUNT_ID;
|
|
17
|
+
const isDefaultAccount = resolvedAccountId === DEFAULT_ACCOUNT_ID;
|
|
18
|
+
const baseConfig = config;
|
|
19
|
+
const accountConfig =
|
|
20
|
+
resolvedAccountId !== DEFAULT_ACCOUNT_ID
|
|
21
|
+
? (baseConfig?.accounts?.[resolvedAccountId] as ZaloConfig | undefined)
|
|
22
|
+
: undefined;
|
|
23
|
+
|
|
24
|
+
if (accountConfig) {
|
|
25
|
+
const token = accountConfig.botToken?.trim();
|
|
26
|
+
if (token) return { token, source: "config" };
|
|
27
|
+
const tokenFile = accountConfig.tokenFile?.trim();
|
|
28
|
+
if (tokenFile) {
|
|
29
|
+
try {
|
|
30
|
+
const fileToken = readFileSync(tokenFile, "utf8").trim();
|
|
31
|
+
if (fileToken) return { token: fileToken, source: "configFile" };
|
|
32
|
+
} catch {
|
|
33
|
+
// ignore read failures
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isDefaultAccount) {
|
|
39
|
+
const token = baseConfig?.botToken?.trim();
|
|
40
|
+
if (token) return { token, source: "config" };
|
|
41
|
+
const tokenFile = baseConfig?.tokenFile?.trim();
|
|
42
|
+
if (tokenFile) {
|
|
43
|
+
try {
|
|
44
|
+
const fileToken = readFileSync(tokenFile, "utf8").trim();
|
|
45
|
+
if (fileToken) return { token: fileToken, source: "configFile" };
|
|
46
|
+
} catch {
|
|
47
|
+
// ignore read failures
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const envToken = process.env.ZALO_BOT_TOKEN?.trim();
|
|
51
|
+
if (envToken) return { token: envToken, source: "env" };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { token: "", source: "none" };
|
|
55
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type ZaloAccountConfig = {
|
|
2
|
+
/** Optional display name for this account (used in CLI/UI lists). */
|
|
3
|
+
name?: string;
|
|
4
|
+
/** If false, do not start this Zalo account. Default: true. */
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
/** Bot token from Zalo Bot Creator. */
|
|
7
|
+
botToken?: string;
|
|
8
|
+
/** Path to file containing the bot token. */
|
|
9
|
+
tokenFile?: string;
|
|
10
|
+
/** Webhook URL for receiving updates (HTTPS required). */
|
|
11
|
+
webhookUrl?: string;
|
|
12
|
+
/** Webhook secret token (8-256 chars) for request verification. */
|
|
13
|
+
webhookSecret?: string;
|
|
14
|
+
/** Webhook path for the gateway HTTP server (defaults to webhook URL path). */
|
|
15
|
+
webhookPath?: string;
|
|
16
|
+
/** Direct message access policy (default: pairing). */
|
|
17
|
+
dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
|
|
18
|
+
/** Allowlist for DM senders (Zalo user IDs). */
|
|
19
|
+
allowFrom?: Array<string | number>;
|
|
20
|
+
/** Max inbound media size in MB. */
|
|
21
|
+
mediaMaxMb?: number;
|
|
22
|
+
/** Proxy URL for API requests. */
|
|
23
|
+
proxy?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ZaloConfig = {
|
|
27
|
+
/** Optional per-account Zalo configuration (multi-account). */
|
|
28
|
+
accounts?: Record<string, ZaloAccountConfig>;
|
|
29
|
+
/** Default account ID when multiple accounts are configured. */
|
|
30
|
+
defaultAccount?: string;
|
|
31
|
+
} & ZaloAccountConfig;
|
|
32
|
+
|
|
33
|
+
export type ZaloTokenSource = "env" | "config" | "configFile" | "none";
|
|
34
|
+
|
|
35
|
+
export type ResolvedZaloAccount = {
|
|
36
|
+
accountId: string;
|
|
37
|
+
name?: string;
|
|
38
|
+
enabled: boolean;
|
|
39
|
+
token: string;
|
|
40
|
+
tokenSource: ZaloTokenSource;
|
|
41
|
+
config: ZaloAccountConfig;
|
|
42
|
+
};
|