@kodelyth/zalouser 2026.5.39 → 2026.5.42

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.
Files changed (106) hide show
  1. package/README.md +120 -0
  2. package/api.ts +9 -0
  3. package/channel-plugin-api.ts +3 -0
  4. package/contract-api.ts +2 -0
  5. package/dist/accounts-DOefD_if.js +66 -0
  6. package/dist/accounts.runtime-KT101uuu.js +2 -0
  7. package/dist/api-DSWT4Dh_.js +133 -0
  8. package/dist/api.js +7 -0
  9. package/dist/channel-pby_3Sur.js +602 -0
  10. package/dist/channel-plugin-api.js +2 -0
  11. package/dist/channel.runtime-0aJ2O7Y8.js +25 -0
  12. package/dist/channel.setup-CqyWwqcQ.js +9 -0
  13. package/dist/contract-api.js +3 -0
  14. package/dist/doctor-contract-B9EvrW0j.js +128 -0
  15. package/dist/doctor-contract-api.js +2 -0
  16. package/dist/index.js +27 -0
  17. package/dist/monitor-CVtrUqyW.js +708 -0
  18. package/dist/runtime-api.js +19 -0
  19. package/dist/secret-contract-api.js +5 -0
  20. package/dist/security-audit-D_rftvs-.js +34 -0
  21. package/dist/send-uRjUB8mG.js +542 -0
  22. package/dist/session-route-CalHiv1d.js +92 -0
  23. package/dist/setup-entry.js +11 -0
  24. package/dist/setup-plugin-api.js +2 -0
  25. package/dist/setup-surface-Cfj4GQlB.js +360 -0
  26. package/dist/shared-DjK0e2FC.js +160 -0
  27. package/dist/test-api.js +5 -0
  28. package/dist/zalo-js-B80cRyDF.js +1285 -0
  29. package/doctor-contract-api.ts +1 -0
  30. package/index.ts +34 -0
  31. package/klaw.plugin.json +3 -286
  32. package/package.json +4 -4
  33. package/runtime-api.ts +62 -0
  34. package/secret-contract-api.ts +4 -0
  35. package/setup-entry.ts +9 -0
  36. package/setup-plugin-api.ts +2 -0
  37. package/src/accounts.runtime.ts +1 -0
  38. package/src/accounts.test-mocks.ts +14 -0
  39. package/src/accounts.test.ts +298 -0
  40. package/src/accounts.ts +136 -0
  41. package/src/channel-api.ts +16 -0
  42. package/src/channel.adapters.ts +432 -0
  43. package/src/channel.directory.test.ts +59 -0
  44. package/src/channel.runtime.ts +12 -0
  45. package/src/channel.sendpayload.test.ts +311 -0
  46. package/src/channel.setup.test.ts +30 -0
  47. package/src/channel.setup.ts +12 -0
  48. package/src/channel.test.ts +424 -0
  49. package/src/channel.ts +221 -0
  50. package/src/config-schema.ts +33 -0
  51. package/src/directory.ts +54 -0
  52. package/src/doctor-contract.ts +156 -0
  53. package/src/doctor.test.ts +87 -0
  54. package/src/doctor.ts +37 -0
  55. package/src/group-policy.test.ts +61 -0
  56. package/src/group-policy.ts +83 -0
  57. package/src/message-sid.test.ts +66 -0
  58. package/src/message-sid.ts +80 -0
  59. package/src/monitor.account-scope.test.ts +122 -0
  60. package/src/monitor.group-gating.test.ts +967 -0
  61. package/src/monitor.send-mocks.ts +20 -0
  62. package/src/monitor.ts +1057 -0
  63. package/src/probe.test.ts +60 -0
  64. package/src/probe.ts +35 -0
  65. package/src/qr-temp-file.ts +19 -0
  66. package/src/reaction.test.ts +19 -0
  67. package/src/reaction.ts +32 -0
  68. package/src/runtime.ts +9 -0
  69. package/src/security-audit.test.ts +83 -0
  70. package/src/security-audit.ts +71 -0
  71. package/src/send-receipt.ts +31 -0
  72. package/src/send.test.ts +424 -0
  73. package/src/send.ts +280 -0
  74. package/src/session-route.ts +121 -0
  75. package/src/setup-core.ts +36 -0
  76. package/src/setup-surface.test.ts +367 -0
  77. package/src/setup-surface.ts +481 -0
  78. package/src/setup-test-helpers.ts +42 -0
  79. package/src/shared.ts +92 -0
  80. package/src/status-issues.test.ts +31 -0
  81. package/src/status-issues.ts +55 -0
  82. package/src/test-helpers.ts +26 -0
  83. package/src/text-styles.test.ts +203 -0
  84. package/src/text-styles.ts +540 -0
  85. package/src/tool.test.ts +212 -0
  86. package/src/tool.ts +200 -0
  87. package/src/types.ts +127 -0
  88. package/src/zalo-js.credentials.test.ts +465 -0
  89. package/src/zalo-js.test-mocks.ts +89 -0
  90. package/src/zalo-js.ts +1889 -0
  91. package/src/zca-client.test.ts +27 -0
  92. package/src/zca-client.ts +259 -0
  93. package/src/zca-constants.ts +55 -0
  94. package/src/zca-js-exports.d.ts +22 -0
  95. package/test-api.ts +21 -0
  96. package/tsconfig.json +16 -0
  97. package/api.js +0 -7
  98. package/channel-plugin-api.js +0 -7
  99. package/contract-api.js +0 -7
  100. package/doctor-contract-api.js +0 -7
  101. package/index.js +0 -7
  102. package/runtime-api.js +0 -7
  103. package/secret-contract-api.js +0 -7
  104. package/setup-entry.js +0 -7
  105. package/setup-plugin-api.js +0 -7
  106. package/test-api.js +0 -7
@@ -0,0 +1,602 @@
1
+ import { a as resolveZalouserAccountSync, i as resolveDefaultZalouserAccountId, r as listZalouserAccountIds, t as checkZcaAuthenticated } from "./accounts-DOefD_if.js";
2
+ import { a as DEFAULT_ACCOUNT_ID, c as isNumericTargetId, i as writeQrDataUrlToTempFile, l as normalizeAccountId, n as createZalouserSetupWizardProxy, o as chunkTextForOutbound, r as zalouserSetupAdapter, s as isDangerousNameMatchingEnabled, t as createZalouserPluginBase, u as sendPayloadWithChunkedTextAndMedia } from "./shared-DjK0e2FC.js";
3
+ import { i as resolveZalouserOutboundSessionRoute, n as parseZalouserDirectoryGroupId, r as parseZalouserOutboundTarget, t as normalizeZalouserTarget } from "./session-route-CalHiv1d.js";
4
+ import { createChatChannelPlugin } from "klaw/plugin-sdk/channel-core";
5
+ import { createAccountStatusSink } from "klaw/plugin-sdk/channel-lifecycle";
6
+ import { buildPassiveProbedChannelStatusSummary, coerceStatusIssueAccountId, readStatusIssueFields } from "klaw/plugin-sdk/extension-shared";
7
+ import { createLazyRuntimeModule } from "klaw/plugin-sdk/lazy-runtime";
8
+ import { createAsyncComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "klaw/plugin-sdk/status-helpers";
9
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString } from "klaw/plugin-sdk/string-coerce-runtime";
10
+ import { createScopedDmSecurityResolver } from "klaw/plugin-sdk/channel-config-helpers";
11
+ import { defineChannelMessageAdapter } from "klaw/plugin-sdk/channel-message";
12
+ import { createPairingPrefixStripper } from "klaw/plugin-sdk/channel-pairing";
13
+ import { createEmptyChannelResult, createRawChannelSendResultAdapter } from "klaw/plugin-sdk/channel-send-result";
14
+ import { createStaticReplyToModeResolver } from "klaw/plugin-sdk/conversation-runtime";
15
+ import { createPluginRuntimeStore } from "klaw/plugin-sdk/runtime-store";
16
+ //#region extensions/zalouser/src/group-policy.ts
17
+ function toGroupCandidate(value) {
18
+ return value?.trim() ?? "";
19
+ }
20
+ function normalizeZalouserGroupSlug(raw) {
21
+ const trimmed = normalizeOptionalLowercaseString(raw) ?? "";
22
+ if (!trimmed) return "";
23
+ return trimmed.replace(/^#/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
24
+ }
25
+ function buildZalouserGroupCandidates(params) {
26
+ const seen = /* @__PURE__ */ new Set();
27
+ const out = [];
28
+ const push = (value) => {
29
+ const normalized = toGroupCandidate(value);
30
+ if (!normalized || seen.has(normalized)) return;
31
+ seen.add(normalized);
32
+ out.push(normalized);
33
+ };
34
+ const groupId = toGroupCandidate(params.groupId);
35
+ const groupChannel = toGroupCandidate(params.groupChannel);
36
+ const groupName = toGroupCandidate(params.groupName);
37
+ push(groupId);
38
+ if (params.includeGroupIdAlias === true && groupId) push(`group:${groupId}`);
39
+ if (params.allowNameMatching !== false) {
40
+ push(groupChannel);
41
+ push(groupName);
42
+ if (groupName) push(normalizeZalouserGroupSlug(groupName));
43
+ }
44
+ if (params.includeWildcard !== false) push("*");
45
+ return out;
46
+ }
47
+ function findZalouserGroupEntry(groups, candidates) {
48
+ if (!groups) return;
49
+ for (const candidate of candidates) {
50
+ const entry = groups[candidate];
51
+ if (entry) return entry;
52
+ }
53
+ }
54
+ function isZalouserGroupEntryAllowed(entry) {
55
+ if (!entry) return false;
56
+ return entry.allow !== false && entry.enabled !== false;
57
+ }
58
+ //#endregion
59
+ //#region extensions/zalouser/src/message-sid.ts
60
+ function toMessageSidPart(value) {
61
+ if (typeof value === "string") return value.trim();
62
+ if (typeof value === "number" && Number.isFinite(value)) return String(Math.trunc(value));
63
+ return "";
64
+ }
65
+ function parseZalouserMessageSidFull(value) {
66
+ const raw = toMessageSidPart(value);
67
+ if (!raw) return null;
68
+ const [msgIdPart, cliMsgIdPart] = raw.split(":").map((entry) => entry.trim());
69
+ if (!msgIdPart || !cliMsgIdPart) return null;
70
+ return {
71
+ msgId: msgIdPart,
72
+ cliMsgId: cliMsgIdPart
73
+ };
74
+ }
75
+ function resolveZalouserReactionMessageIds(params) {
76
+ const explicitMessageId = toMessageSidPart(params.messageId);
77
+ const explicitCliMsgId = toMessageSidPart(params.cliMsgId);
78
+ if (explicitMessageId && explicitCliMsgId) return {
79
+ msgId: explicitMessageId,
80
+ cliMsgId: explicitCliMsgId
81
+ };
82
+ const parsedFromCurrent = parseZalouserMessageSidFull(params.currentMessageId);
83
+ if (parsedFromCurrent) return parsedFromCurrent;
84
+ const currentRaw = toMessageSidPart(params.currentMessageId);
85
+ if (!currentRaw) return null;
86
+ if (explicitMessageId && !explicitCliMsgId) return {
87
+ msgId: explicitMessageId,
88
+ cliMsgId: currentRaw
89
+ };
90
+ if (!explicitMessageId && explicitCliMsgId) return {
91
+ msgId: currentRaw,
92
+ cliMsgId: explicitCliMsgId
93
+ };
94
+ return {
95
+ msgId: currentRaw,
96
+ cliMsgId: currentRaw
97
+ };
98
+ }
99
+ function formatZalouserMessageSidFull(params) {
100
+ const msgId = toMessageSidPart(params.msgId);
101
+ const cliMsgId = toMessageSidPart(params.cliMsgId);
102
+ if (!msgId && !cliMsgId) return;
103
+ if (msgId && cliMsgId) return `${msgId}:${cliMsgId}`;
104
+ return msgId || cliMsgId || void 0;
105
+ }
106
+ function resolveZalouserMessageSid(params) {
107
+ const msgId = toMessageSidPart(params.msgId);
108
+ const cliMsgId = toMessageSidPart(params.cliMsgId);
109
+ if (msgId || cliMsgId) return msgId || cliMsgId;
110
+ return toMessageSidPart(params.fallback) || void 0;
111
+ }
112
+ //#endregion
113
+ //#region extensions/zalouser/src/runtime.ts
114
+ const { setRuntime: setZalouserRuntime, getRuntime: getZalouserRuntime } = createPluginRuntimeStore({
115
+ pluginId: "zalouser",
116
+ errorMessage: "Zalouser runtime not initialized"
117
+ });
118
+ //#endregion
119
+ //#region extensions/zalouser/src/channel.adapters.ts
120
+ const loadZalouserChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel.runtime-0aJ2O7Y8.js"));
121
+ const ZALOUSER_TEXT_CHUNK_LIMIT = 2e3;
122
+ function resolveZalouserQrProfile(accountId) {
123
+ const normalized = normalizeAccountId(accountId);
124
+ if (!normalized || normalized === DEFAULT_ACCOUNT_ID) return process.env.ZALOUSER_PROFILE?.trim() || process.env.ZCA_PROFILE?.trim() || "default";
125
+ return normalized;
126
+ }
127
+ function resolveZalouserOutboundChunkMode(cfg, accountId) {
128
+ return getZalouserRuntime().channel.text.resolveChunkMode(cfg, "zalouser", accountId);
129
+ }
130
+ function resolveZalouserOutboundTextChunkLimit(cfg, accountId) {
131
+ return getZalouserRuntime().channel.text.resolveTextChunkLimit(cfg, "zalouser", accountId, { fallbackLimit: ZALOUSER_TEXT_CHUNK_LIMIT });
132
+ }
133
+ function resolveZalouserGroupPolicyEntry(params) {
134
+ const account = resolveZalouserAccountSync({
135
+ cfg: params.cfg,
136
+ accountId: params.accountId ?? void 0
137
+ });
138
+ return findZalouserGroupEntry(account.config.groups ?? {}, buildZalouserGroupCandidates({
139
+ groupId: params.groupId,
140
+ groupChannel: params.groupChannel,
141
+ includeWildcard: true,
142
+ allowNameMatching: isDangerousNameMatchingEnabled(account.config)
143
+ }));
144
+ }
145
+ function resolveZalouserGroupToolPolicy(params) {
146
+ return resolveZalouserGroupPolicyEntry(params)?.tools;
147
+ }
148
+ function resolveZalouserRequireMention(params) {
149
+ const entry = resolveZalouserGroupPolicyEntry(params);
150
+ if (typeof entry?.requireMention === "boolean") return entry.requireMention;
151
+ return true;
152
+ }
153
+ async function sendZalouserTextFromContext({ to, text, accountId, cfg }) {
154
+ const { sendMessageZalouser } = await loadZalouserChannelRuntime$1();
155
+ const account = resolveZalouserAccountSync({
156
+ cfg,
157
+ accountId
158
+ });
159
+ const target = parseZalouserOutboundTarget(to);
160
+ return await sendMessageZalouser(target.threadId, text, {
161
+ profile: account.profile,
162
+ isGroup: target.isGroup,
163
+ textMode: "markdown",
164
+ textChunkMode: resolveZalouserOutboundChunkMode(cfg, account.accountId),
165
+ textChunkLimit: resolveZalouserOutboundTextChunkLimit(cfg, account.accountId)
166
+ });
167
+ }
168
+ async function sendZalouserMediaFromContext({ to, text, mediaUrl, accountId, cfg, mediaLocalRoots, mediaReadFile }) {
169
+ const { sendMessageZalouser } = await loadZalouserChannelRuntime$1();
170
+ const account = resolveZalouserAccountSync({
171
+ cfg,
172
+ accountId
173
+ });
174
+ const target = parseZalouserOutboundTarget(to);
175
+ return await sendMessageZalouser(target.threadId, text, {
176
+ profile: account.profile,
177
+ isGroup: target.isGroup,
178
+ mediaUrl,
179
+ mediaLocalRoots,
180
+ mediaReadFile,
181
+ textMode: "markdown",
182
+ textChunkMode: resolveZalouserOutboundChunkMode(cfg, account.accountId),
183
+ textChunkLimit: resolveZalouserOutboundTextChunkLimit(cfg, account.accountId)
184
+ });
185
+ }
186
+ const zalouserRawSendResultAdapter = createRawChannelSendResultAdapter({
187
+ channel: "zalouser",
188
+ sendText: sendZalouserTextFromContext,
189
+ sendMedia: sendZalouserMediaFromContext
190
+ });
191
+ const zalouserMessageAdapter = defineChannelMessageAdapter({
192
+ id: "zalouser",
193
+ durableFinal: { capabilities: {
194
+ text: true,
195
+ media: true,
196
+ messageSendingHooks: true
197
+ } },
198
+ send: {
199
+ text: sendZalouserTextFromContext,
200
+ media: sendZalouserMediaFromContext
201
+ }
202
+ });
203
+ const resolveZalouserDmPolicy = createScopedDmSecurityResolver({
204
+ channelKey: "zalouser",
205
+ resolvePolicy: (account) => account.config.dmPolicy,
206
+ resolveAllowFrom: (account) => account.config.allowFrom,
207
+ policyPathSuffix: "dmPolicy",
208
+ normalizeEntry: (raw) => raw.trim().replace(/^(zalouser|zlu):/i, "")
209
+ });
210
+ const zalouserGroupsAdapter = {
211
+ resolveRequireMention: resolveZalouserRequireMention,
212
+ resolveToolPolicy: resolveZalouserGroupToolPolicy
213
+ };
214
+ const zalouserMessageActions = {
215
+ describeMessageTool: ({ cfg, accountId }) => {
216
+ if ((accountId ? [resolveZalouserAccountSync({
217
+ cfg,
218
+ accountId
219
+ })].filter((account) => account.enabled) : listZalouserAccountIds(cfg).map((resolvedAccountId) => resolveZalouserAccountSync({
220
+ cfg,
221
+ accountId: resolvedAccountId
222
+ })).filter((account) => account.enabled)).length === 0) return null;
223
+ return { actions: ["react"] };
224
+ },
225
+ supportsAction: ({ action }) => action === "react",
226
+ handleAction: async ({ action, params, cfg, accountId, toolContext }) => {
227
+ if (action !== "react") throw new Error(`Zalouser action ${action} not supported`);
228
+ const { sendReactionZalouser } = await loadZalouserChannelRuntime$1();
229
+ const account = resolveZalouserAccountSync({
230
+ cfg,
231
+ accountId
232
+ });
233
+ const threadId = (typeof params.threadId === "string" ? params.threadId.trim() : "") || (typeof params.to === "string" ? params.to.trim() : "") || (typeof params.chatId === "string" ? params.chatId.trim() : "") || (toolContext?.currentChannelId?.trim() ?? "");
234
+ if (!threadId) throw new Error("Zalouser react requires threadId (or to/chatId).");
235
+ const emoji = typeof params.emoji === "string" ? params.emoji.trim() : "";
236
+ if (!emoji) throw new Error("Zalouser react requires emoji.");
237
+ const ids = resolveZalouserReactionMessageIds({
238
+ messageId: typeof params.messageId === "string" ? params.messageId : void 0,
239
+ cliMsgId: typeof params.cliMsgId === "string" ? params.cliMsgId : void 0,
240
+ currentMessageId: toolContext?.currentMessageId
241
+ });
242
+ if (!ids) throw new Error("Zalouser react requires messageId + cliMsgId (or a current message context id).");
243
+ const result = await sendReactionZalouser({
244
+ profile: account.profile,
245
+ threadId,
246
+ isGroup: params.isGroup === true,
247
+ msgId: ids.msgId,
248
+ cliMsgId: ids.cliMsgId,
249
+ emoji,
250
+ remove: params.remove === true
251
+ });
252
+ if (!result.ok) throw new Error(result.error || "Failed to react on Zalo message");
253
+ return {
254
+ content: [{
255
+ type: "text",
256
+ text: params.remove === true ? `Removed reaction ${emoji} from ${ids.msgId}` : `Reacted ${emoji} on ${ids.msgId}`
257
+ }],
258
+ details: {
259
+ messageId: ids.msgId,
260
+ cliMsgId: ids.cliMsgId,
261
+ threadId
262
+ }
263
+ };
264
+ }
265
+ };
266
+ const zalouserResolverAdapter = { resolveTargets: async ({ cfg, accountId, inputs, kind, runtime }) => {
267
+ const results = [];
268
+ for (const input of inputs) {
269
+ const trimmed = input.trim();
270
+ if (!trimmed) {
271
+ results.push({
272
+ input,
273
+ resolved: false,
274
+ note: "empty input"
275
+ });
276
+ continue;
277
+ }
278
+ if (/^\d+$/.test(trimmed)) {
279
+ results.push({
280
+ input,
281
+ resolved: true,
282
+ id: trimmed
283
+ });
284
+ continue;
285
+ }
286
+ try {
287
+ const runtimeModule = await loadZalouserChannelRuntime$1();
288
+ const account = resolveZalouserAccountSync({
289
+ cfg,
290
+ accountId: accountId ?? resolveDefaultZalouserAccountId(cfg)
291
+ });
292
+ if (kind === "user") {
293
+ const friends = await runtimeModule.listZaloFriendsMatching(account.profile, trimmed);
294
+ const best = friends[0];
295
+ results.push({
296
+ input,
297
+ resolved: Boolean(best?.userId),
298
+ id: best?.userId,
299
+ name: best?.displayName,
300
+ note: friends.length > 1 ? "multiple matches; chose first" : void 0
301
+ });
302
+ } else {
303
+ const groups = await runtimeModule.listZaloGroupsMatching(account.profile, trimmed);
304
+ const best = groups.find((group) => normalizeLowercaseStringOrEmpty(group.name) === normalizeLowercaseStringOrEmpty(trimmed)) ?? groups[0];
305
+ results.push({
306
+ input,
307
+ resolved: Boolean(best?.groupId),
308
+ id: best?.groupId,
309
+ name: best?.name,
310
+ note: groups.length > 1 ? "multiple matches; chose first" : void 0
311
+ });
312
+ }
313
+ } catch (err) {
314
+ runtime.error?.(`zalouser resolve failed: ${String(err)}`);
315
+ results.push({
316
+ input,
317
+ resolved: false,
318
+ note: "lookup failed"
319
+ });
320
+ }
321
+ }
322
+ return results;
323
+ } };
324
+ const zalouserAuthAdapter = { login: async ({ cfg, accountId, runtime }) => {
325
+ const { startZaloQrLogin, waitForZaloQrLogin } = await loadZalouserChannelRuntime$1();
326
+ const account = resolveZalouserAccountSync({
327
+ cfg,
328
+ accountId: accountId ?? resolveDefaultZalouserAccountId(cfg)
329
+ });
330
+ runtime.log(`Generating QR login for Zalo Personal (account: ${account.accountId}, profile: ${account.profile})...`);
331
+ const started = await startZaloQrLogin({
332
+ profile: account.profile,
333
+ timeoutMs: 35e3
334
+ });
335
+ if (!started.qrDataUrl) throw new Error(started.message || "Failed to start QR login");
336
+ const qrPath = await writeQrDataUrlToTempFile(started.qrDataUrl, account.profile);
337
+ if (qrPath) runtime.log(`Scan QR image: ${qrPath}`);
338
+ else runtime.log("QR generated but could not be written to a temp file.");
339
+ const waited = await waitForZaloQrLogin({
340
+ profile: account.profile,
341
+ timeoutMs: 18e4
342
+ });
343
+ if (!waited.connected) throw new Error(waited.message || "Zalouser login failed");
344
+ runtime.log(waited.message);
345
+ } };
346
+ const zalouserSecurityAdapter = {
347
+ resolveDmPolicy: resolveZalouserDmPolicy,
348
+ collectAuditFindings: async (params) => (await loadZalouserChannelRuntime$1()).collectZalouserSecurityAuditFindings(params)
349
+ };
350
+ const zalouserThreadingAdapter = { resolveReplyToMode: createStaticReplyToModeResolver("off") };
351
+ const zalouserPairingTextAdapter = {
352
+ idLabel: "zalouserUserId",
353
+ message: "Your pairing request has been approved.",
354
+ normalizeAllowEntry: createPairingPrefixStripper(/^(zalouser|zlu):/i),
355
+ notify: async ({ cfg, id, message }) => {
356
+ const { sendMessageZalouser } = await loadZalouserChannelRuntime$1();
357
+ const account = resolveZalouserAccountSync({ cfg });
358
+ if (!await checkZcaAuthenticated(account.profile)) throw new Error("Zalouser not authenticated");
359
+ await sendMessageZalouser(id, message, { profile: account.profile });
360
+ }
361
+ };
362
+ const zalouserOutboundAdapter = {
363
+ deliveryMode: "direct",
364
+ chunker: chunkTextForOutbound,
365
+ chunkerMode: "markdown",
366
+ sendPayload: async (ctx) => await sendPayloadWithChunkedTextAndMedia({
367
+ ctx,
368
+ sendText: (nextCtx) => zalouserRawSendResultAdapter.sendText(nextCtx),
369
+ sendMedia: (nextCtx) => zalouserRawSendResultAdapter.sendMedia(nextCtx),
370
+ emptyResult: createEmptyChannelResult("zalouser")
371
+ }),
372
+ ...zalouserRawSendResultAdapter
373
+ };
374
+ const zalouserMessagingAdapter = {
375
+ targetPrefixes: ["zalouser", "zlu"],
376
+ normalizeTarget: (raw) => normalizeZalouserTarget(raw),
377
+ resolveOutboundSessionRoute: (params) => resolveZalouserOutboundSessionRoute(params),
378
+ targetResolver: {
379
+ looksLikeId: (raw) => {
380
+ const normalized = normalizeZalouserTarget(raw);
381
+ if (!normalized) return false;
382
+ if (/^group:[^\s]+$/i.test(normalized) || /^user:[^\s]+$/i.test(normalized)) return true;
383
+ return isNumericTargetId(normalized);
384
+ },
385
+ hint: "<user:id|group:id>"
386
+ }
387
+ };
388
+ //#endregion
389
+ //#region extensions/zalouser/src/directory.ts
390
+ function mapUser$1(params) {
391
+ return {
392
+ kind: "user",
393
+ id: params.id,
394
+ name: params.name ?? void 0,
395
+ avatarUrl: params.avatarUrl ?? void 0,
396
+ raw: params.raw
397
+ };
398
+ }
399
+ async function listZalouserDirectoryGroupMembers(params, deps) {
400
+ const account = resolveZalouserAccountSync({
401
+ cfg: params.cfg,
402
+ accountId: params.accountId
403
+ });
404
+ const normalizedGroupId = parseZalouserDirectoryGroupId(params.groupId);
405
+ const rows = (await deps.listZaloGroupMembers(account.profile, normalizedGroupId)).map((member) => mapUser$1({
406
+ id: member.userId,
407
+ name: member.displayName,
408
+ avatarUrl: member.avatar ?? null,
409
+ raw: member
410
+ }));
411
+ return typeof params.limit === "number" && params.limit > 0 ? rows.slice(0, params.limit) : rows;
412
+ }
413
+ //#endregion
414
+ //#region extensions/zalouser/src/status-issues.ts
415
+ const ZALOUSER_STATUS_FIELDS = [
416
+ "accountId",
417
+ "enabled",
418
+ "configured",
419
+ "dmPolicy",
420
+ "lastError"
421
+ ];
422
+ function collectZalouserStatusIssues(accounts) {
423
+ const issues = [];
424
+ for (const entry of accounts) {
425
+ const account = readStatusIssueFields(entry, ZALOUSER_STATUS_FIELDS);
426
+ if (!account) continue;
427
+ const accountId = coerceStatusIssueAccountId(account.accountId) ?? "default";
428
+ if (!(account.enabled !== false)) continue;
429
+ if (!(account.configured === true)) {
430
+ issues.push({
431
+ channel: "zalouser",
432
+ accountId,
433
+ kind: "auth",
434
+ message: "Not authenticated (no saved Zalo session).",
435
+ fix: "Run: klaw channels login --channel zalouser"
436
+ });
437
+ continue;
438
+ }
439
+ if (account.dmPolicy === "open") issues.push({
440
+ channel: "zalouser",
441
+ accountId,
442
+ kind: "config",
443
+ message: "Zalo Personal dmPolicy is \"open\", allowing any user to message the bot without pairing.",
444
+ fix: "Set channels.zalouser.dmPolicy to \"pairing\" or \"allowlist\" to restrict access."
445
+ });
446
+ }
447
+ return issues;
448
+ }
449
+ //#endregion
450
+ //#region extensions/zalouser/src/channel.ts
451
+ const loadZalouserChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-0aJ2O7Y8.js"));
452
+ const zalouserSetupWizardProxy = createZalouserSetupWizardProxy(async () => (await import("./setup-surface-Cfj4GQlB.js").then((n) => n.t)).zalouserSetupWizard);
453
+ function mapUser(params) {
454
+ return {
455
+ kind: "user",
456
+ id: params.id,
457
+ name: params.name ?? void 0,
458
+ avatarUrl: params.avatarUrl ?? void 0,
459
+ raw: params.raw
460
+ };
461
+ }
462
+ function mapGroup(params) {
463
+ return {
464
+ kind: "group",
465
+ id: params.id,
466
+ name: params.name ?? void 0,
467
+ raw: params.raw
468
+ };
469
+ }
470
+ const zalouserPlugin = createChatChannelPlugin({
471
+ base: {
472
+ ...createZalouserPluginBase({
473
+ setupWizard: zalouserSetupWizardProxy,
474
+ setup: zalouserSetupAdapter
475
+ }),
476
+ groups: zalouserGroupsAdapter,
477
+ actions: zalouserMessageActions,
478
+ messaging: zalouserMessagingAdapter,
479
+ directory: {
480
+ self: async ({ cfg, accountId }) => {
481
+ const { getZaloUserInfo } = await loadZalouserChannelRuntime();
482
+ const parsed = await getZaloUserInfo(resolveZalouserAccountSync({
483
+ cfg,
484
+ accountId
485
+ }).profile);
486
+ if (!parsed?.userId) return null;
487
+ return mapUser({
488
+ id: parsed.userId,
489
+ name: parsed.displayName ?? null,
490
+ avatarUrl: parsed.avatar ?? null,
491
+ raw: parsed
492
+ });
493
+ },
494
+ listPeers: async ({ cfg, accountId, query, limit }) => {
495
+ const { listZaloFriendsMatching } = await loadZalouserChannelRuntime();
496
+ const rows = (await listZaloFriendsMatching(resolveZalouserAccountSync({
497
+ cfg,
498
+ accountId
499
+ }).profile, query)).map((friend) => mapUser({
500
+ id: friend.userId,
501
+ name: friend.displayName ?? null,
502
+ avatarUrl: friend.avatar ?? null,
503
+ raw: friend
504
+ }));
505
+ return typeof limit === "number" && limit > 0 ? rows.slice(0, limit) : rows;
506
+ },
507
+ listGroups: async ({ cfg, accountId, query, limit }) => {
508
+ const { listZaloGroupsMatching } = await loadZalouserChannelRuntime();
509
+ const rows = (await listZaloGroupsMatching(resolveZalouserAccountSync({
510
+ cfg,
511
+ accountId
512
+ }).profile, query)).map((group) => mapGroup({
513
+ id: `group:${group.groupId}`,
514
+ name: group.name ?? null,
515
+ raw: group
516
+ }));
517
+ return typeof limit === "number" && limit > 0 ? rows.slice(0, limit) : rows;
518
+ },
519
+ listGroupMembers: async ({ cfg, accountId, groupId, limit }) => {
520
+ const { listZaloGroupMembers } = await loadZalouserChannelRuntime();
521
+ return await listZalouserDirectoryGroupMembers({
522
+ cfg,
523
+ accountId: accountId ?? void 0,
524
+ groupId,
525
+ limit: limit ?? void 0
526
+ }, { listZaloGroupMembers });
527
+ }
528
+ },
529
+ resolver: zalouserResolverAdapter,
530
+ auth: zalouserAuthAdapter,
531
+ message: zalouserMessageAdapter,
532
+ status: createAsyncComputedAccountStatusAdapter({
533
+ defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
534
+ collectStatusIssues: collectZalouserStatusIssues,
535
+ buildChannelSummary: ({ snapshot }) => buildPassiveProbedChannelStatusSummary(snapshot),
536
+ probeAccount: async ({ account, timeoutMs }) => (await loadZalouserChannelRuntime()).probeZalouser(account.profile, timeoutMs),
537
+ resolveAccountSnapshot: async ({ account, runtime }) => {
538
+ const configured = await checkZcaAuthenticated(account.profile);
539
+ return {
540
+ accountId: account.accountId,
541
+ name: account.name,
542
+ enabled: account.enabled,
543
+ configured,
544
+ extra: {
545
+ dmPolicy: account.config.dmPolicy ?? "pairing",
546
+ lastError: configured ? runtime?.lastError ?? null : runtime?.lastError ?? "not authenticated"
547
+ }
548
+ };
549
+ }
550
+ }),
551
+ gateway: {
552
+ startAccount: async (ctx) => {
553
+ const { getZaloUserInfo } = await loadZalouserChannelRuntime();
554
+ const account = ctx.account;
555
+ let userLabel = "";
556
+ try {
557
+ const userInfo = await getZaloUserInfo(account.profile);
558
+ if (userInfo?.displayName) userLabel = ` (${userInfo.displayName})`;
559
+ ctx.setStatus({
560
+ accountId: account.accountId,
561
+ profile: userInfo
562
+ });
563
+ } catch {}
564
+ const statusSink = createAccountStatusSink({
565
+ accountId: ctx.accountId,
566
+ setStatus: ctx.setStatus
567
+ });
568
+ ctx.log?.info(`[${account.accountId}] starting zalouser provider${userLabel}`);
569
+ const { monitorZalouserProvider } = await import("./monitor-CVtrUqyW.js");
570
+ return monitorZalouserProvider({
571
+ account,
572
+ config: ctx.cfg,
573
+ runtime: ctx.runtime,
574
+ abortSignal: ctx.abortSignal,
575
+ statusSink
576
+ });
577
+ },
578
+ loginWithQrStart: async (params) => {
579
+ const { startZaloQrLogin } = await loadZalouserChannelRuntime();
580
+ return await startZaloQrLogin({
581
+ profile: resolveZalouserQrProfile(params.accountId),
582
+ force: params.force,
583
+ timeoutMs: params.timeoutMs
584
+ });
585
+ },
586
+ loginWithQrWait: async (params) => {
587
+ const { waitForZaloQrLogin } = await loadZalouserChannelRuntime();
588
+ return await waitForZaloQrLogin({
589
+ profile: resolveZalouserQrProfile(params.accountId),
590
+ timeoutMs: params.timeoutMs
591
+ });
592
+ },
593
+ logoutAccount: async (ctx) => await (await loadZalouserChannelRuntime()).logoutZaloProfile(ctx.account.profile || resolveZalouserQrProfile(ctx.accountId))
594
+ }
595
+ },
596
+ security: zalouserSecurityAdapter,
597
+ threading: zalouserThreadingAdapter,
598
+ pairing: { text: zalouserPairingTextAdapter },
599
+ outbound: zalouserOutboundAdapter
600
+ });
601
+ //#endregion
602
+ export { resolveZalouserMessageSid as a, isZalouserGroupEntryAllowed as c, formatZalouserMessageSidFull as i, getZalouserRuntime as n, buildZalouserGroupCandidates as o, setZalouserRuntime as r, findZalouserGroupEntry as s, zalouserPlugin as t };
@@ -0,0 +1,2 @@
1
+ import { t as zalouserPlugin } from "./channel-pby_3Sur.js";
2
+ export { zalouserPlugin };
@@ -0,0 +1,25 @@
1
+ import { t as collectZalouserSecurityAuditFindings } from "./security-audit-D_rftvs-.js";
2
+ import { a as listZaloGroupMembers, b as waitForZaloQrLogin, c as logoutZaloProfile, i as listZaloFriendsMatching, n as getZaloUserInfo, s as listZaloGroupsMatching, y as startZaloQrLogin } from "./zalo-js-B80cRyDF.js";
3
+ import { a as sendReactionZalouser, i as sendMessageZalouser } from "./send-uRjUB8mG.js";
4
+ import { formatErrorMessage } from "klaw/plugin-sdk/error-runtime";
5
+ //#region extensions/zalouser/src/probe.ts
6
+ async function probeZalouser(profile, timeoutMs) {
7
+ try {
8
+ const user = timeoutMs ? await Promise.race([getZaloUserInfo(profile), new Promise((resolve) => setTimeout(() => resolve(null), Math.max(timeoutMs, 1e3)))]) : await getZaloUserInfo(profile);
9
+ if (!user) return {
10
+ ok: false,
11
+ error: "Not authenticated"
12
+ };
13
+ return {
14
+ ok: true,
15
+ user
16
+ };
17
+ } catch (error) {
18
+ return {
19
+ ok: false,
20
+ error: formatErrorMessage(error)
21
+ };
22
+ }
23
+ }
24
+ //#endregion
25
+ export { collectZalouserSecurityAuditFindings, getZaloUserInfo, listZaloFriendsMatching, listZaloGroupMembers, listZaloGroupsMatching, logoutZaloProfile, probeZalouser, sendMessageZalouser, sendReactionZalouser, startZaloQrLogin, waitForZaloQrLogin };
@@ -0,0 +1,9 @@
1
+ import { n as zalouserSetupWizard } from "./setup-surface-Cfj4GQlB.js";
2
+ import { r as zalouserSetupAdapter, t as createZalouserPluginBase } from "./shared-DjK0e2FC.js";
3
+ //#region extensions/zalouser/src/channel.setup.ts
4
+ const zalouserSetupPlugin = { ...createZalouserPluginBase({
5
+ setupWizard: zalouserSetupWizard,
6
+ setup: zalouserSetupAdapter
7
+ }) };
8
+ //#endregion
9
+ export { zalouserSetupPlugin as t };
@@ -0,0 +1,3 @@
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-B9EvrW0j.js";
2
+ import { t as collectZalouserSecurityAuditFindings } from "./security-audit-D_rftvs-.js";
3
+ export { collectZalouserSecurityAuditFindings, legacyConfigRules, normalizeCompatibilityConfig };