@openclaw/zalo 2026.5.2 → 2026.5.3-beta.2
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
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
installChannelOutboundPayloadContractSuite,
|
|
3
|
-
primeChannelOutboundSendMock,
|
|
4
|
-
type OutboundPayloadHarnessParams,
|
|
5
|
-
} from "openclaw/plugin-sdk/channel-contract-testing";
|
|
6
|
-
import { describe, vi } from "vitest";
|
|
7
|
-
import { zaloPlugin } from "./channel.js";
|
|
8
|
-
|
|
9
|
-
const { sendZaloTextMock } = vi.hoisted(() => ({
|
|
10
|
-
sendZaloTextMock: vi.fn(),
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
vi.mock("./channel.runtime.js", () => ({
|
|
14
|
-
sendZaloText: sendZaloTextMock,
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
function createZaloHarness(params: OutboundPayloadHarnessParams) {
|
|
18
|
-
const sendZalo = vi.fn();
|
|
19
|
-
primeChannelOutboundSendMock(sendZalo, { ok: true, messageId: "zl-1" }, params.sendResults);
|
|
20
|
-
sendZaloTextMock.mockReset().mockImplementation(
|
|
21
|
-
async (nextCtx: { to: string; text: string; mediaUrl?: string }) =>
|
|
22
|
-
await sendZalo(nextCtx.to, nextCtx.text, {
|
|
23
|
-
mediaUrl: nextCtx.mediaUrl,
|
|
24
|
-
}),
|
|
25
|
-
);
|
|
26
|
-
const ctx = {
|
|
27
|
-
cfg: {},
|
|
28
|
-
to: "123456789",
|
|
29
|
-
text: "",
|
|
30
|
-
payload: params.payload,
|
|
31
|
-
};
|
|
32
|
-
return {
|
|
33
|
-
run: async () => await zaloPlugin.outbound!.sendPayload!(ctx),
|
|
34
|
-
sendMock: sendZalo,
|
|
35
|
-
to: ctx.to,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe("Zalo outbound payload contract", () => {
|
|
40
|
-
installChannelOutboundPayloadContractSuite({
|
|
41
|
-
channel: "zalo",
|
|
42
|
-
chunking: { mode: "split", longTextLength: 3000, maxChunkLength: 2000 },
|
|
43
|
-
createHarness: createZaloHarness,
|
|
44
|
-
});
|
|
45
|
-
});
|
package/src/probe.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { BaseProbeResult } from "openclaw/plugin-sdk/channel-contract";
|
|
2
|
-
import { getMe, ZaloApiError, type ZaloBotInfo, type ZaloFetch } from "./api.js";
|
|
3
|
-
|
|
4
|
-
export type ZaloProbeResult = BaseProbeResult<string> & {
|
|
5
|
-
bot?: ZaloBotInfo;
|
|
6
|
-
elapsedMs: number;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export async function probeZalo(
|
|
10
|
-
token: string,
|
|
11
|
-
timeoutMs = 5000,
|
|
12
|
-
fetcher?: ZaloFetch,
|
|
13
|
-
): Promise<ZaloProbeResult> {
|
|
14
|
-
if (!token?.trim()) {
|
|
15
|
-
return { ok: false, error: "No token provided", elapsedMs: 0 };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const startTime = Date.now();
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const response = await getMe(token.trim(), timeoutMs, fetcher);
|
|
22
|
-
const elapsedMs = Date.now() - startTime;
|
|
23
|
-
|
|
24
|
-
if (response.ok && response.result) {
|
|
25
|
-
return { ok: true, bot: response.result, elapsedMs };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return { ok: false, error: "Invalid response from Zalo API", elapsedMs };
|
|
29
|
-
} catch (err) {
|
|
30
|
-
const elapsedMs = Date.now() - startTime;
|
|
31
|
-
|
|
32
|
-
if (err instanceof ZaloApiError) {
|
|
33
|
-
return { ok: false, error: err.description ?? err.message, elapsedMs };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (err instanceof Error) {
|
|
37
|
-
if (err.name === "AbortError") {
|
|
38
|
-
return { ok: false, error: `Request timed out after ${timeoutMs}ms`, elapsedMs };
|
|
39
|
-
}
|
|
40
|
-
return { ok: false, error: err.message, elapsedMs };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return { ok: false, error: String(err), elapsedMs };
|
|
44
|
-
}
|
|
45
|
-
}
|
package/src/proxy.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { RequestInit as UndiciRequestInit } from "undici";
|
|
2
|
-
import { ProxyAgent, fetch as undiciFetch } from "undici";
|
|
3
|
-
import type { ZaloFetch } from "./api.js";
|
|
4
|
-
|
|
5
|
-
const proxyCache = new Map<string, ZaloFetch>();
|
|
6
|
-
|
|
7
|
-
export function resolveZaloProxyFetch(proxyUrl?: string | null): ZaloFetch | undefined {
|
|
8
|
-
const trimmed = proxyUrl?.trim();
|
|
9
|
-
if (!trimmed) {
|
|
10
|
-
return undefined;
|
|
11
|
-
}
|
|
12
|
-
const cached = proxyCache.get(trimmed);
|
|
13
|
-
if (cached) {
|
|
14
|
-
return cached;
|
|
15
|
-
}
|
|
16
|
-
const agent = new ProxyAgent(trimmed);
|
|
17
|
-
const fetcher: ZaloFetch = (input, init) =>
|
|
18
|
-
undiciFetch(input, {
|
|
19
|
-
...init,
|
|
20
|
-
dispatcher: agent,
|
|
21
|
-
} as UndiciRequestInit) as unknown as Promise<Response>;
|
|
22
|
-
proxyCache.set(trimmed, fetcher);
|
|
23
|
-
return fetcher;
|
|
24
|
-
}
|
package/src/runtime-api.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
addWildcardAllowFrom,
|
|
3
|
-
applyAccountNameToChannelSection,
|
|
4
|
-
applyBasicWebhookRequestGuards,
|
|
5
|
-
applySetupAccountConfigPatch,
|
|
6
|
-
type BaseProbeResult,
|
|
7
|
-
type BaseTokenResolution,
|
|
8
|
-
buildBaseAccountStatusSnapshot,
|
|
9
|
-
buildChannelConfigSchema,
|
|
10
|
-
buildSecretInputSchema,
|
|
11
|
-
buildSingleChannelSecretPromptState,
|
|
12
|
-
buildTokenChannelStatusSummary,
|
|
13
|
-
type ChannelAccountSnapshot,
|
|
14
|
-
type ChannelMessageActionAdapter,
|
|
15
|
-
type ChannelMessageActionName,
|
|
16
|
-
type ChannelPlugin,
|
|
17
|
-
type ChannelStatusIssue,
|
|
18
|
-
chunkTextForOutbound,
|
|
19
|
-
createChannelPairingController,
|
|
20
|
-
createChannelReplyPipeline,
|
|
21
|
-
createDedupeCache,
|
|
22
|
-
createFixedWindowRateLimiter,
|
|
23
|
-
createWebhookAnomalyTracker,
|
|
24
|
-
DEFAULT_ACCOUNT_ID,
|
|
25
|
-
deliverTextOrMediaReply,
|
|
26
|
-
evaluateSenderGroupAccess,
|
|
27
|
-
formatAllowFromLowercase,
|
|
28
|
-
formatPairingApproveHint,
|
|
29
|
-
type GroupPolicy,
|
|
30
|
-
hasConfiguredSecretInput,
|
|
31
|
-
isNormalizedSenderAllowed,
|
|
32
|
-
isNumericTargetId,
|
|
33
|
-
jsonResult,
|
|
34
|
-
logTypingFailure,
|
|
35
|
-
type MarkdownTableMode,
|
|
36
|
-
mergeAllowFromEntries,
|
|
37
|
-
migrateBaseNameToDefaultAccount,
|
|
38
|
-
normalizeAccountId,
|
|
39
|
-
normalizeResolvedSecretInputString,
|
|
40
|
-
normalizeSecretInputString,
|
|
41
|
-
type OpenClawConfig,
|
|
42
|
-
type OutboundReplyPayload,
|
|
43
|
-
PAIRING_APPROVED_MESSAGE,
|
|
44
|
-
type PluginRuntime,
|
|
45
|
-
promptSingleChannelSecretInput,
|
|
46
|
-
readJsonWebhookBodyOrReject,
|
|
47
|
-
readStringParam,
|
|
48
|
-
registerPluginHttpRoute,
|
|
49
|
-
type RegisterWebhookPluginRouteOptions,
|
|
50
|
-
registerWebhookTarget,
|
|
51
|
-
type RegisterWebhookTargetOptions,
|
|
52
|
-
registerWebhookTargetWithPluginRoute,
|
|
53
|
-
type ReplyPayload,
|
|
54
|
-
resolveClientIp,
|
|
55
|
-
resolveDefaultGroupPolicy,
|
|
56
|
-
resolveDirectDmAuthorizationOutcome,
|
|
57
|
-
resolveInboundRouteEnvelopeBuilderWithRuntime,
|
|
58
|
-
resolveOpenProviderRuntimeGroupPolicy,
|
|
59
|
-
resolveSenderCommandAuthorizationWithRuntime,
|
|
60
|
-
resolveWebhookPath,
|
|
61
|
-
resolveWebhookTargetWithAuthOrRejectSync,
|
|
62
|
-
runSingleChannelSecretStep,
|
|
63
|
-
type RuntimeEnv,
|
|
64
|
-
type SecretInput,
|
|
65
|
-
type SenderGroupAccessDecision,
|
|
66
|
-
sendPayloadWithChunkedTextAndMedia,
|
|
67
|
-
setTopLevelChannelDmPolicyWithAllowFrom,
|
|
68
|
-
waitForAbortSignal,
|
|
69
|
-
warnMissingProviderGroupPolicyFallbackOnce,
|
|
70
|
-
WEBHOOK_ANOMALY_COUNTER_DEFAULTS,
|
|
71
|
-
WEBHOOK_RATE_LIMIT_DEFAULTS,
|
|
72
|
-
withResolvedWebhookRequestPipeline,
|
|
73
|
-
type WizardPrompter,
|
|
74
|
-
} from "./runtime-support.js";
|
|
75
|
-
export { setZaloRuntime } from "./runtime.js";
|
package/src/runtime-support.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
export type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
|
2
|
-
export type { OpenClawConfig, GroupPolicy } from "openclaw/plugin-sdk/config-types";
|
|
3
|
-
export type { MarkdownTableMode } from "openclaw/plugin-sdk/config-types";
|
|
4
|
-
export type { BaseTokenResolution } from "openclaw/plugin-sdk/channel-contract";
|
|
5
|
-
export type {
|
|
6
|
-
BaseProbeResult,
|
|
7
|
-
ChannelAccountSnapshot,
|
|
8
|
-
ChannelMessageActionAdapter,
|
|
9
|
-
ChannelMessageActionName,
|
|
10
|
-
ChannelStatusIssue,
|
|
11
|
-
} from "openclaw/plugin-sdk/channel-contract";
|
|
12
|
-
export type { SecretInput } from "openclaw/plugin-sdk/secret-input";
|
|
13
|
-
export type { SenderGroupAccessDecision } from "openclaw/plugin-sdk/group-access";
|
|
14
|
-
export type { ChannelPlugin, PluginRuntime, WizardPrompter } from "openclaw/plugin-sdk/core";
|
|
15
|
-
export type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
|
|
16
|
-
export type { OutboundReplyPayload } from "openclaw/plugin-sdk/reply-payload";
|
|
17
|
-
export {
|
|
18
|
-
DEFAULT_ACCOUNT_ID,
|
|
19
|
-
buildChannelConfigSchema,
|
|
20
|
-
createDedupeCache,
|
|
21
|
-
formatPairingApproveHint,
|
|
22
|
-
jsonResult,
|
|
23
|
-
normalizeAccountId,
|
|
24
|
-
readStringParam,
|
|
25
|
-
resolveClientIp,
|
|
26
|
-
} from "openclaw/plugin-sdk/core";
|
|
27
|
-
export {
|
|
28
|
-
applyAccountNameToChannelSection,
|
|
29
|
-
applySetupAccountConfigPatch,
|
|
30
|
-
buildSingleChannelSecretPromptState,
|
|
31
|
-
mergeAllowFromEntries,
|
|
32
|
-
migrateBaseNameToDefaultAccount,
|
|
33
|
-
promptSingleChannelSecretInput,
|
|
34
|
-
runSingleChannelSecretStep,
|
|
35
|
-
setTopLevelChannelDmPolicyWithAllowFrom,
|
|
36
|
-
} from "openclaw/plugin-sdk/setup";
|
|
37
|
-
export {
|
|
38
|
-
buildSecretInputSchema,
|
|
39
|
-
hasConfiguredSecretInput,
|
|
40
|
-
normalizeResolvedSecretInputString,
|
|
41
|
-
normalizeSecretInputString,
|
|
42
|
-
} from "openclaw/plugin-sdk/secret-input";
|
|
43
|
-
export {
|
|
44
|
-
buildTokenChannelStatusSummary,
|
|
45
|
-
PAIRING_APPROVED_MESSAGE,
|
|
46
|
-
} from "openclaw/plugin-sdk/channel-status";
|
|
47
|
-
export { buildBaseAccountStatusSnapshot } from "openclaw/plugin-sdk/status-helpers";
|
|
48
|
-
export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
|
|
49
|
-
export {
|
|
50
|
-
formatAllowFromLowercase,
|
|
51
|
-
isNormalizedSenderAllowed,
|
|
52
|
-
} from "openclaw/plugin-sdk/allow-from";
|
|
53
|
-
export { addWildcardAllowFrom } from "openclaw/plugin-sdk/setup";
|
|
54
|
-
export { evaluateSenderGroupAccess } from "openclaw/plugin-sdk/group-access";
|
|
55
|
-
export { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
56
|
-
export {
|
|
57
|
-
warnMissingProviderGroupPolicyFallbackOnce,
|
|
58
|
-
resolveDefaultGroupPolicy,
|
|
59
|
-
} from "openclaw/plugin-sdk/runtime-group-policy";
|
|
60
|
-
export { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
|
|
61
|
-
export { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
|
|
62
|
-
export { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
|
|
63
|
-
export {
|
|
64
|
-
deliverTextOrMediaReply,
|
|
65
|
-
isNumericTargetId,
|
|
66
|
-
sendPayloadWithChunkedTextAndMedia,
|
|
67
|
-
} from "openclaw/plugin-sdk/reply-payload";
|
|
68
|
-
export {
|
|
69
|
-
resolveDirectDmAuthorizationOutcome,
|
|
70
|
-
resolveSenderCommandAuthorizationWithRuntime,
|
|
71
|
-
} from "openclaw/plugin-sdk/command-auth";
|
|
72
|
-
export { resolveInboundRouteEnvelopeBuilderWithRuntime } from "openclaw/plugin-sdk/inbound-envelope";
|
|
73
|
-
export { waitForAbortSignal } from "openclaw/plugin-sdk/runtime";
|
|
74
|
-
export {
|
|
75
|
-
applyBasicWebhookRequestGuards,
|
|
76
|
-
createFixedWindowRateLimiter,
|
|
77
|
-
createWebhookAnomalyTracker,
|
|
78
|
-
readJsonWebhookBodyOrReject,
|
|
79
|
-
registerPluginHttpRoute,
|
|
80
|
-
registerWebhookTarget,
|
|
81
|
-
registerWebhookTargetWithPluginRoute,
|
|
82
|
-
resolveWebhookPath,
|
|
83
|
-
resolveWebhookTargetWithAuthOrRejectSync,
|
|
84
|
-
WEBHOOK_ANOMALY_COUNTER_DEFAULTS,
|
|
85
|
-
WEBHOOK_RATE_LIMIT_DEFAULTS,
|
|
86
|
-
withResolvedWebhookRequestPipeline,
|
|
87
|
-
} from "openclaw/plugin-sdk/webhook-ingress";
|
|
88
|
-
export type {
|
|
89
|
-
RegisterWebhookPluginRouteOptions,
|
|
90
|
-
RegisterWebhookTargetOptions,
|
|
91
|
-
} from "openclaw/plugin-sdk/webhook-ingress";
|
package/src/runtime.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
2
|
-
import type { PluginRuntime } from "./runtime-support.js";
|
|
3
|
-
|
|
4
|
-
const { setRuntime: setZaloRuntime, getRuntime: getZaloRuntime } =
|
|
5
|
-
createPluginRuntimeStore<PluginRuntime>({
|
|
6
|
-
pluginId: "zalo",
|
|
7
|
-
errorMessage: "Zalo runtime not initialized",
|
|
8
|
-
});
|
|
9
|
-
export { getZaloRuntime, setZaloRuntime };
|
package/src/secret-contract.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collectConditionalChannelFieldAssignments,
|
|
3
|
-
getChannelSurface,
|
|
4
|
-
hasOwnProperty,
|
|
5
|
-
type ResolverContext,
|
|
6
|
-
type SecretDefaults,
|
|
7
|
-
type SecretTargetRegistryEntry,
|
|
8
|
-
} from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|
9
|
-
|
|
10
|
-
export const secretTargetRegistryEntries: SecretTargetRegistryEntry[] = [
|
|
11
|
-
{
|
|
12
|
-
id: "channels.zalo.accounts.*.botToken",
|
|
13
|
-
targetType: "channels.zalo.accounts.*.botToken",
|
|
14
|
-
configFile: "openclaw.json",
|
|
15
|
-
pathPattern: "channels.zalo.accounts.*.botToken",
|
|
16
|
-
secretShape: "secret_input",
|
|
17
|
-
expectedResolvedValue: "string",
|
|
18
|
-
includeInPlan: true,
|
|
19
|
-
includeInConfigure: true,
|
|
20
|
-
includeInAudit: true,
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
id: "channels.zalo.accounts.*.webhookSecret",
|
|
24
|
-
targetType: "channels.zalo.accounts.*.webhookSecret",
|
|
25
|
-
configFile: "openclaw.json",
|
|
26
|
-
pathPattern: "channels.zalo.accounts.*.webhookSecret",
|
|
27
|
-
secretShape: "secret_input",
|
|
28
|
-
expectedResolvedValue: "string",
|
|
29
|
-
includeInPlan: true,
|
|
30
|
-
includeInConfigure: true,
|
|
31
|
-
includeInAudit: true,
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
id: "channels.zalo.botToken",
|
|
35
|
-
targetType: "channels.zalo.botToken",
|
|
36
|
-
configFile: "openclaw.json",
|
|
37
|
-
pathPattern: "channels.zalo.botToken",
|
|
38
|
-
secretShape: "secret_input",
|
|
39
|
-
expectedResolvedValue: "string",
|
|
40
|
-
includeInPlan: true,
|
|
41
|
-
includeInConfigure: true,
|
|
42
|
-
includeInAudit: true,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: "channels.zalo.webhookSecret",
|
|
46
|
-
targetType: "channels.zalo.webhookSecret",
|
|
47
|
-
configFile: "openclaw.json",
|
|
48
|
-
pathPattern: "channels.zalo.webhookSecret",
|
|
49
|
-
secretShape: "secret_input",
|
|
50
|
-
expectedResolvedValue: "string",
|
|
51
|
-
includeInPlan: true,
|
|
52
|
-
includeInConfigure: true,
|
|
53
|
-
includeInAudit: true,
|
|
54
|
-
},
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
export function collectRuntimeConfigAssignments(params: {
|
|
58
|
-
config: { channels?: Record<string, unknown> };
|
|
59
|
-
defaults?: SecretDefaults;
|
|
60
|
-
context: ResolverContext;
|
|
61
|
-
}): void {
|
|
62
|
-
const resolved = getChannelSurface(params.config, "zalo");
|
|
63
|
-
if (!resolved) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const { channel: zalo, surface } = resolved;
|
|
67
|
-
collectConditionalChannelFieldAssignments({
|
|
68
|
-
channelKey: "zalo",
|
|
69
|
-
field: "botToken",
|
|
70
|
-
channel: zalo,
|
|
71
|
-
surface,
|
|
72
|
-
defaults: params.defaults,
|
|
73
|
-
context: params.context,
|
|
74
|
-
topLevelActiveWithoutAccounts: true,
|
|
75
|
-
topLevelInheritedAccountActive: ({ account, enabled }) =>
|
|
76
|
-
enabled && !hasOwnProperty(account, "botToken"),
|
|
77
|
-
accountActive: ({ enabled }) => enabled,
|
|
78
|
-
topInactiveReason: "no enabled Zalo surface inherits this top-level botToken.",
|
|
79
|
-
accountInactiveReason: "Zalo account is disabled.",
|
|
80
|
-
});
|
|
81
|
-
const baseWebhookUrl = typeof zalo.webhookUrl === "string" ? zalo.webhookUrl.trim() : "";
|
|
82
|
-
const accountWebhookUrl = (account: Record<string, unknown>) =>
|
|
83
|
-
hasOwnProperty(account, "webhookUrl")
|
|
84
|
-
? typeof account.webhookUrl === "string"
|
|
85
|
-
? account.webhookUrl.trim()
|
|
86
|
-
: ""
|
|
87
|
-
: baseWebhookUrl;
|
|
88
|
-
collectConditionalChannelFieldAssignments({
|
|
89
|
-
channelKey: "zalo",
|
|
90
|
-
field: "webhookSecret",
|
|
91
|
-
channel: zalo,
|
|
92
|
-
surface,
|
|
93
|
-
defaults: params.defaults,
|
|
94
|
-
context: params.context,
|
|
95
|
-
topLevelActiveWithoutAccounts: baseWebhookUrl.length > 0,
|
|
96
|
-
topLevelInheritedAccountActive: ({ account, enabled }) =>
|
|
97
|
-
enabled && !hasOwnProperty(account, "webhookSecret") && accountWebhookUrl(account).length > 0,
|
|
98
|
-
accountActive: ({ account, enabled }) => enabled && accountWebhookUrl(account).length > 0,
|
|
99
|
-
topInactiveReason:
|
|
100
|
-
"no enabled Zalo webhook surface inherits this top-level webhookSecret (webhook mode is not active).",
|
|
101
|
-
accountInactiveReason:
|
|
102
|
-
"Zalo account is disabled or webhook mode is not active for this account.",
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export const channelSecrets = {
|
|
107
|
-
secretTargetRegistryEntries,
|
|
108
|
-
collectRuntimeConfigAssignments,
|
|
109
|
-
};
|
package/src/secret-input.ts
DELETED
package/src/send.test.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
const sendMessageMock = vi.fn();
|
|
4
|
-
const sendPhotoMock = vi.fn();
|
|
5
|
-
const resolveZaloProxyFetchMock = vi.fn();
|
|
6
|
-
|
|
7
|
-
vi.mock("./api.js", () => ({
|
|
8
|
-
sendMessage: (...args: unknown[]) => sendMessageMock(...args),
|
|
9
|
-
sendPhoto: (...args: unknown[]) => sendPhotoMock(...args),
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
vi.mock("./proxy.js", () => ({
|
|
13
|
-
resolveZaloProxyFetch: (...args: unknown[]) => resolveZaloProxyFetchMock(...args),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
import { sendMessageZalo, sendPhotoZalo } from "./send.js";
|
|
17
|
-
|
|
18
|
-
describe("zalo send", () => {
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
sendMessageMock.mockReset();
|
|
21
|
-
sendPhotoMock.mockReset();
|
|
22
|
-
resolveZaloProxyFetchMock.mockReset();
|
|
23
|
-
resolveZaloProxyFetchMock.mockReturnValue(undefined);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("sends text messages through the message API", async () => {
|
|
27
|
-
sendMessageMock.mockResolvedValueOnce({
|
|
28
|
-
ok: true,
|
|
29
|
-
result: { message_id: "z-msg-1" },
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const result = await sendMessageZalo("dm-chat-1", "hello there", {
|
|
33
|
-
token: "zalo-token",
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
expect(sendMessageMock).toHaveBeenCalledWith(
|
|
37
|
-
"zalo-token",
|
|
38
|
-
{
|
|
39
|
-
chat_id: "dm-chat-1",
|
|
40
|
-
text: "hello there",
|
|
41
|
-
},
|
|
42
|
-
undefined,
|
|
43
|
-
);
|
|
44
|
-
expect(sendPhotoMock).not.toHaveBeenCalled();
|
|
45
|
-
expect(result).toEqual({ ok: true, messageId: "z-msg-1" });
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("routes media-bearing sends through the photo API and uses text as caption", async () => {
|
|
49
|
-
sendPhotoMock.mockResolvedValueOnce({
|
|
50
|
-
ok: true,
|
|
51
|
-
result: { message_id: "z-photo-1" },
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const result = await sendMessageZalo("dm-chat-2", "caption text", {
|
|
55
|
-
token: "zalo-token",
|
|
56
|
-
mediaUrl: "https://example.com/photo.jpg",
|
|
57
|
-
caption: "ignored fallback caption",
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
expect(sendPhotoMock).toHaveBeenCalledWith(
|
|
61
|
-
"zalo-token",
|
|
62
|
-
{
|
|
63
|
-
chat_id: "dm-chat-2",
|
|
64
|
-
photo: "https://example.com/photo.jpg",
|
|
65
|
-
caption: "caption text",
|
|
66
|
-
},
|
|
67
|
-
undefined,
|
|
68
|
-
);
|
|
69
|
-
expect(sendMessageMock).not.toHaveBeenCalled();
|
|
70
|
-
expect(result).toEqual({ ok: true, messageId: "z-photo-1" });
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("fails fast for missing token or blank photo URLs", async () => {
|
|
74
|
-
await expect(sendMessageZalo("dm-chat-3", "hello", {})).resolves.toEqual({
|
|
75
|
-
ok: false,
|
|
76
|
-
error: "No Zalo bot token configured",
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
await expect(
|
|
80
|
-
sendPhotoZalo("dm-chat-4", " ", {
|
|
81
|
-
token: "zalo-token",
|
|
82
|
-
}),
|
|
83
|
-
).resolves.toEqual({
|
|
84
|
-
ok: false,
|
|
85
|
-
error: "No photo URL provided",
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
expect(sendMessageMock).not.toHaveBeenCalled();
|
|
89
|
-
expect(sendPhotoMock).not.toHaveBeenCalled();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("sends cfg-backed media directly without hosted-media rewrites", async () => {
|
|
93
|
-
sendPhotoMock.mockResolvedValueOnce({
|
|
94
|
-
ok: true,
|
|
95
|
-
result: { message_id: "z-photo-2" },
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const result = await sendPhotoZalo("dm-chat-5", "https://example.com/photo.jpg", {
|
|
99
|
-
cfg: {
|
|
100
|
-
channels: {
|
|
101
|
-
zalo: {
|
|
102
|
-
botToken: "zalo-token",
|
|
103
|
-
webhookUrl: "https://gateway.example.com/zalo-webhook",
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
} as never,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
expect(sendPhotoMock).toHaveBeenCalledWith(
|
|
110
|
-
"zalo-token",
|
|
111
|
-
{
|
|
112
|
-
chat_id: "dm-chat-5",
|
|
113
|
-
photo: "https://example.com/photo.jpg",
|
|
114
|
-
caption: undefined,
|
|
115
|
-
},
|
|
116
|
-
undefined,
|
|
117
|
-
);
|
|
118
|
-
expect(result).toEqual({ ok: true, messageId: "z-photo-2" });
|
|
119
|
-
});
|
|
120
|
-
});
|
package/src/send.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
2
|
-
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
3
|
-
import { resolveZaloAccount } from "./accounts.js";
|
|
4
|
-
import type { ZaloFetch } from "./api.js";
|
|
5
|
-
import { sendMessage, sendPhoto } from "./api.js";
|
|
6
|
-
import { resolveZaloProxyFetch } from "./proxy.js";
|
|
7
|
-
import { resolveZaloToken } from "./token.js";
|
|
8
|
-
|
|
9
|
-
type ZaloSendOptions = {
|
|
10
|
-
token?: string;
|
|
11
|
-
accountId?: string;
|
|
12
|
-
cfg?: OpenClawConfig;
|
|
13
|
-
mediaUrl?: string;
|
|
14
|
-
caption?: string;
|
|
15
|
-
verbose?: boolean;
|
|
16
|
-
proxy?: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type ZaloSendResult = {
|
|
20
|
-
ok: boolean;
|
|
21
|
-
messageId?: string;
|
|
22
|
-
error?: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
function toZaloSendResult(response: {
|
|
26
|
-
ok?: boolean;
|
|
27
|
-
result?: { message_id?: string };
|
|
28
|
-
}): ZaloSendResult {
|
|
29
|
-
if (response.ok && response.result) {
|
|
30
|
-
return { ok: true, messageId: response.result.message_id };
|
|
31
|
-
}
|
|
32
|
-
return { ok: false, error: "Failed to send message" };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function runZaloSend(
|
|
36
|
-
failureMessage: string,
|
|
37
|
-
send: () => Promise<{ ok?: boolean; result?: { message_id?: string } }>,
|
|
38
|
-
): Promise<ZaloSendResult> {
|
|
39
|
-
try {
|
|
40
|
-
const result = toZaloSendResult(await send());
|
|
41
|
-
return result.ok ? result : { ok: false, error: failureMessage };
|
|
42
|
-
} catch (err) {
|
|
43
|
-
return { ok: false, error: formatErrorMessage(err) };
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function resolveSendContext(options: ZaloSendOptions): {
|
|
48
|
-
token: string;
|
|
49
|
-
fetcher?: ZaloFetch;
|
|
50
|
-
} {
|
|
51
|
-
if (options.cfg) {
|
|
52
|
-
const account = resolveZaloAccount({
|
|
53
|
-
cfg: options.cfg,
|
|
54
|
-
accountId: options.accountId,
|
|
55
|
-
});
|
|
56
|
-
const token = options.token || account.token;
|
|
57
|
-
const proxy = options.proxy ?? account.config.proxy;
|
|
58
|
-
return { token, fetcher: resolveZaloProxyFetch(proxy) };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const token = options.token ?? resolveZaloToken(undefined, options.accountId).token;
|
|
62
|
-
const proxy = options.proxy;
|
|
63
|
-
return { token, fetcher: resolveZaloProxyFetch(proxy) };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function resolveValidatedSendContext(
|
|
67
|
-
chatId: string,
|
|
68
|
-
options: ZaloSendOptions,
|
|
69
|
-
): { ok: true; chatId: string; token: string; fetcher?: ZaloFetch } | { ok: false; error: string } {
|
|
70
|
-
const { token, fetcher } = resolveSendContext(options);
|
|
71
|
-
if (!token) {
|
|
72
|
-
return { ok: false, error: "No Zalo bot token configured" };
|
|
73
|
-
}
|
|
74
|
-
const trimmedChatId = chatId?.trim();
|
|
75
|
-
if (!trimmedChatId) {
|
|
76
|
-
return { ok: false, error: "No chat_id provided" };
|
|
77
|
-
}
|
|
78
|
-
return { ok: true, chatId: trimmedChatId, token, fetcher };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function resolveSendContextOrFailure(
|
|
82
|
-
chatId: string,
|
|
83
|
-
options: ZaloSendOptions,
|
|
84
|
-
):
|
|
85
|
-
| { context: { chatId: string; token: string; fetcher?: ZaloFetch } }
|
|
86
|
-
| { failure: ZaloSendResult } {
|
|
87
|
-
const context = resolveValidatedSendContext(chatId, options);
|
|
88
|
-
return context.ok
|
|
89
|
-
? { context }
|
|
90
|
-
: {
|
|
91
|
-
failure: { ok: false, error: context.error },
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function sendMessageZalo(
|
|
96
|
-
chatId: string,
|
|
97
|
-
text: string,
|
|
98
|
-
options: ZaloSendOptions = {},
|
|
99
|
-
): Promise<ZaloSendResult> {
|
|
100
|
-
const resolved = resolveSendContextOrFailure(chatId, options);
|
|
101
|
-
if ("failure" in resolved) {
|
|
102
|
-
return resolved.failure;
|
|
103
|
-
}
|
|
104
|
-
const { context } = resolved;
|
|
105
|
-
|
|
106
|
-
if (options.mediaUrl) {
|
|
107
|
-
return sendPhotoZalo(context.chatId, options.mediaUrl, {
|
|
108
|
-
...options,
|
|
109
|
-
token: context.token,
|
|
110
|
-
caption: text || options.caption,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return await runZaloSend("Failed to send message", () =>
|
|
115
|
-
sendMessage(
|
|
116
|
-
context.token,
|
|
117
|
-
{
|
|
118
|
-
chat_id: context.chatId,
|
|
119
|
-
text: text.slice(0, 2000),
|
|
120
|
-
},
|
|
121
|
-
context.fetcher,
|
|
122
|
-
),
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export async function sendPhotoZalo(
|
|
127
|
-
chatId: string,
|
|
128
|
-
photoUrl: string,
|
|
129
|
-
options: ZaloSendOptions = {},
|
|
130
|
-
): Promise<ZaloSendResult> {
|
|
131
|
-
const resolved = resolveSendContextOrFailure(chatId, options);
|
|
132
|
-
if ("failure" in resolved) {
|
|
133
|
-
return resolved.failure;
|
|
134
|
-
}
|
|
135
|
-
const { context } = resolved;
|
|
136
|
-
|
|
137
|
-
if (!photoUrl?.trim()) {
|
|
138
|
-
return { ok: false, error: "No photo URL provided" };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return await runZaloSend("Failed to send photo", () =>
|
|
142
|
-
(async () =>
|
|
143
|
-
sendPhoto(
|
|
144
|
-
context.token,
|
|
145
|
-
{
|
|
146
|
-
chat_id: context.chatId,
|
|
147
|
-
photo: photoUrl.trim(),
|
|
148
|
-
caption: options.caption?.slice(0, 2000),
|
|
149
|
-
},
|
|
150
|
-
context.fetcher,
|
|
151
|
-
))(),
|
|
152
|
-
);
|
|
153
|
-
}
|