Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
484
extensions/zalouser/src/setup-surface.ts
Normal file
484
extensions/zalouser/src/setup-surface.ts
Normal file
@@ -0,0 +1,484 @@
|
||||
// Zalouser plugin module implements setup surface behavior.
|
||||
import {
|
||||
addWildcardAllowFrom,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
formatCliCommand,
|
||||
formatDocsLink,
|
||||
formatResolvedUnresolvedNote,
|
||||
mergeAllowFromEntries,
|
||||
normalizeAccountId,
|
||||
patchScopedAccountConfig,
|
||||
createSetupTranslator,
|
||||
type ChannelSetupDmPolicy,
|
||||
type ChannelSetupWizard,
|
||||
type DmPolicy,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
checkZcaAuthenticated,
|
||||
listZalouserAccountIds,
|
||||
resolveDefaultZalouserAccountId,
|
||||
resolveZalouserAccountSync,
|
||||
} from "./accounts.js";
|
||||
import { writeQrDataUrlToTempFile } from "./qr-temp-file.js";
|
||||
import {
|
||||
logoutZaloProfile,
|
||||
resolveZaloAllowFromEntries,
|
||||
resolveZaloGroupsByEntries,
|
||||
startZaloQrLogin,
|
||||
waitForZaloQrLogin,
|
||||
} from "./zalo-js.js";
|
||||
|
||||
const t = createSetupTranslator();
|
||||
|
||||
const channel = "zalouser" as const;
|
||||
const ZALOUSER_ALLOW_FROM_PLACEHOLDER = t("wizard.zalouser.allowFromPlaceholder");
|
||||
const ZALOUSER_GROUPS_PLACEHOLDER = t("wizard.zalouser.groupsPlaceholder");
|
||||
const ZALOUSER_DM_ACCESS_TITLE = t("wizard.zalouser.dmAccessTitle");
|
||||
const ZALOUSER_ALLOWLIST_TITLE = t("wizard.zalouser.allowlistTitle");
|
||||
const ZALOUSER_GROUPS_TITLE = t("wizard.zalouser.groupsTitle");
|
||||
|
||||
function parseZalouserEntries(raw: string): string[] {
|
||||
return normalizeStringEntries(raw.split(/[\n,;]+/g));
|
||||
}
|
||||
|
||||
function setZalouserAccountScopedConfig(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
defaultPatch: Record<string, unknown>,
|
||||
accountPatch: Record<string, unknown> = defaultPatch,
|
||||
): OpenClawConfig {
|
||||
return patchScopedAccountConfig({
|
||||
cfg,
|
||||
channelKey: channel,
|
||||
accountId,
|
||||
patch: defaultPatch,
|
||||
accountPatch,
|
||||
});
|
||||
}
|
||||
|
||||
function setZalouserDmPolicy(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
policy: DmPolicy,
|
||||
): OpenClawConfig {
|
||||
const resolvedAccountId = normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID;
|
||||
const resolved = resolveZalouserAccountSync({ cfg, accountId: resolvedAccountId });
|
||||
return setZalouserAccountScopedConfig(
|
||||
cfg,
|
||||
resolvedAccountId,
|
||||
{
|
||||
dmPolicy: policy,
|
||||
...(policy === "open" ? { allowFrom: addWildcardAllowFrom(resolved.config.allowFrom) } : {}),
|
||||
},
|
||||
{
|
||||
dmPolicy: policy,
|
||||
...(policy === "open" ? { allowFrom: addWildcardAllowFrom(resolved.config.allowFrom) } : {}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function setZalouserGroupPolicy(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
groupPolicy: "open" | "allowlist" | "disabled",
|
||||
): OpenClawConfig {
|
||||
return setZalouserAccountScopedConfig(cfg, accountId, {
|
||||
groupPolicy,
|
||||
});
|
||||
}
|
||||
|
||||
function setZalouserGroupAllowlist(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
groupKeys: string[],
|
||||
): OpenClawConfig {
|
||||
const groups = Object.fromEntries(
|
||||
groupKeys.map((key) => [key, { enabled: true, requireMention: true }]),
|
||||
);
|
||||
return setZalouserAccountScopedConfig(cfg, accountId, {
|
||||
groups,
|
||||
});
|
||||
}
|
||||
|
||||
function ensureZalouserPluginEnabled(cfg: OpenClawConfig): OpenClawConfig {
|
||||
const next: OpenClawConfig = {
|
||||
...cfg,
|
||||
plugins: {
|
||||
...cfg.plugins,
|
||||
entries: {
|
||||
...cfg.plugins?.entries,
|
||||
zalouser: {
|
||||
...cfg.plugins?.entries?.zalouser,
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const allow = next.plugins?.allow;
|
||||
if (!Array.isArray(allow) || allow.includes(channel)) {
|
||||
return next;
|
||||
}
|
||||
return {
|
||||
...next,
|
||||
plugins: {
|
||||
...next.plugins,
|
||||
allow: [...allow, channel],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function noteZalouserHelp(
|
||||
prompter: Parameters<NonNullable<ChannelSetupWizard["prepare"]>>[0]["prompter"],
|
||||
): Promise<void> {
|
||||
await prompter.note(
|
||||
[
|
||||
t("wizard.zalouser.helpQrLogin"),
|
||||
"",
|
||||
t("wizard.zalouser.helpZcaJs"),
|
||||
"",
|
||||
`Docs: ${formatDocsLink("/channels/zalouser", "zalouser")}`,
|
||||
].join("\n"),
|
||||
t("wizard.zalouser.setupTitle"),
|
||||
);
|
||||
}
|
||||
|
||||
async function promptZalouserAllowFrom(params: {
|
||||
cfg: OpenClawConfig;
|
||||
prompter: Parameters<NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>>[0]["prompter"];
|
||||
accountId: string;
|
||||
}): Promise<OpenClawConfig> {
|
||||
const { cfg, prompter, accountId } = params;
|
||||
const resolved = resolveZalouserAccountSync({ cfg, accountId });
|
||||
const existingAllowFrom = resolved.config.allowFrom ?? [];
|
||||
|
||||
while (true) {
|
||||
const entry = await prompter.text({
|
||||
message: t("wizard.zalouser.allowFromPrompt"),
|
||||
placeholder: ZALOUSER_ALLOW_FROM_PLACEHOLDER,
|
||||
initialValue: existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : undefined,
|
||||
});
|
||||
const parts = parseZalouserEntries(entry);
|
||||
if (parts.length === 0) {
|
||||
await prompter.note(
|
||||
[
|
||||
t("wizard.zalouser.noDmAllowlist"),
|
||||
t("wizard.zalouser.directChatsBlocked"),
|
||||
t("wizard.zalouser.peersLookupTip", {
|
||||
command: formatCliCommand("openclaw directory peers list --channel zalouser"),
|
||||
}),
|
||||
].join("\n"),
|
||||
ZALOUSER_ALLOWLIST_TITLE,
|
||||
);
|
||||
return setZalouserAccountScopedConfig(cfg, accountId, {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: [],
|
||||
});
|
||||
}
|
||||
const resolvedEntries = await resolveZaloAllowFromEntries({
|
||||
profile: resolved.profile,
|
||||
entries: parts,
|
||||
});
|
||||
|
||||
const unresolved = resolvedEntries.filter((item) => !item.resolved).map((item) => item.input);
|
||||
if (unresolved.length > 0) {
|
||||
await prompter.note(
|
||||
t("wizard.zalouser.couldNotResolve", { entries: unresolved.join(", ") }),
|
||||
ZALOUSER_ALLOWLIST_TITLE,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolvedIds = resolvedEntries
|
||||
.filter((item) => item.resolved && item.id)
|
||||
.map((item) => item.id as string);
|
||||
const unique = mergeAllowFromEntries(existingAllowFrom, resolvedIds);
|
||||
|
||||
const notes = resolvedEntries
|
||||
.filter((item) => item.note)
|
||||
.map((item) => `${item.input} -> ${item.id} (${item.note})`);
|
||||
if (notes.length > 0) {
|
||||
await prompter.note(notes.join("\n"), ZALOUSER_ALLOWLIST_TITLE);
|
||||
}
|
||||
|
||||
return setZalouserAccountScopedConfig(cfg, accountId, {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: unique,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const zalouserDmPolicy: ChannelSetupDmPolicy = {
|
||||
label: "Zalo Personal",
|
||||
channel,
|
||||
policyKey: "channels.zalouser.dmPolicy",
|
||||
allowFromKey: "channels.zalouser.allowFrom",
|
||||
resolveConfigKeys: (cfg, accountId) =>
|
||||
(accountId ?? resolveDefaultZalouserAccountId(cfg)) !== DEFAULT_ACCOUNT_ID
|
||||
? {
|
||||
policyKey: `channels.zalouser.accounts.${accountId ?? resolveDefaultZalouserAccountId(cfg)}.dmPolicy`,
|
||||
allowFromKey: `channels.zalouser.accounts.${accountId ?? resolveDefaultZalouserAccountId(cfg)}.allowFrom`,
|
||||
}
|
||||
: {
|
||||
policyKey: "channels.zalouser.dmPolicy",
|
||||
allowFromKey: "channels.zalouser.allowFrom",
|
||||
},
|
||||
getCurrent: (cfg, accountId) =>
|
||||
resolveZalouserAccountSync({
|
||||
cfg,
|
||||
accountId: accountId ?? resolveDefaultZalouserAccountId(cfg),
|
||||
}).config.dmPolicy ?? "pairing",
|
||||
setPolicy: (cfg, policy, accountId) =>
|
||||
setZalouserDmPolicy(cfg, accountId ?? resolveDefaultZalouserAccountId(cfg), policy),
|
||||
promptAllowFrom: async ({ cfg, prompter, accountId }) => {
|
||||
const id =
|
||||
accountId && normalizeAccountId(accountId)
|
||||
? (normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID)
|
||||
: resolveDefaultZalouserAccountId(cfg);
|
||||
return await promptZalouserAllowFrom({
|
||||
cfg,
|
||||
prompter,
|
||||
accountId: id,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
async function promptZalouserQuickstartDmPolicy(params: {
|
||||
cfg: OpenClawConfig;
|
||||
prompter: Parameters<NonNullable<ChannelSetupWizard["prepare"]>>[0]["prompter"];
|
||||
accountId: string;
|
||||
}): Promise<OpenClawConfig> {
|
||||
const { cfg, prompter, accountId } = params;
|
||||
const resolved = resolveZalouserAccountSync({ cfg, accountId });
|
||||
const existingPolicy = resolved.config.dmPolicy ?? "pairing";
|
||||
const existingAllowFrom = resolved.config.allowFrom ?? [];
|
||||
const existingLabel = existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset";
|
||||
|
||||
await prompter.note(
|
||||
[
|
||||
t("wizard.zalouser.dmHelpSeparate"),
|
||||
t("wizard.zalouser.dmHelpPairing"),
|
||||
t("wizard.zalouser.dmHelpAllowlist"),
|
||||
t("wizard.zalouser.dmHelpOpen"),
|
||||
t("wizard.zalouser.dmHelpDisabled"),
|
||||
"",
|
||||
`Current: dmPolicy=${existingPolicy}, allowFrom=${existingLabel}`,
|
||||
t("wizard.zalouser.dmHelpAllowlistEmpty"),
|
||||
].join("\n"),
|
||||
ZALOUSER_DM_ACCESS_TITLE,
|
||||
);
|
||||
|
||||
const policy = (await prompter.select({
|
||||
message: t("wizard.zalouser.dmPolicyPrompt"),
|
||||
options: [
|
||||
{ value: "pairing", label: t("wizard.channels.dmPolicyPairing") },
|
||||
{ value: "allowlist", label: t("wizard.channels.dmPolicyAllowlistOption") },
|
||||
{ value: "open", label: t("wizard.channels.dmPolicyOpenOption") },
|
||||
{ value: "disabled", label: t("wizard.channels.dmPolicyDisabledOption") },
|
||||
],
|
||||
initialValue: existingPolicy,
|
||||
})) as DmPolicy;
|
||||
|
||||
if (policy === "allowlist") {
|
||||
return await promptZalouserAllowFrom({
|
||||
cfg,
|
||||
prompter,
|
||||
accountId,
|
||||
});
|
||||
}
|
||||
return setZalouserDmPolicy(cfg, accountId, policy);
|
||||
}
|
||||
|
||||
export { zalouserSetupAdapter } from "./setup-core.js";
|
||||
|
||||
export const zalouserSetupWizard: ChannelSetupWizard = {
|
||||
channel,
|
||||
status: {
|
||||
configuredLabel: t("wizard.channels.statusLoggedIn"),
|
||||
unconfiguredLabel: t("wizard.channels.statusNeedsQrLogin"),
|
||||
configuredHint: t("wizard.channels.statusRecommendedLoggedIn"),
|
||||
unconfiguredHint: t("wizard.channels.statusRecommendedQrLogin"),
|
||||
configuredScore: 1,
|
||||
unconfiguredScore: 15,
|
||||
resolveConfigured: async ({ cfg, accountId }) => {
|
||||
const ids = accountId ? [accountId] : listZalouserAccountIds(cfg);
|
||||
for (const resolvedAccountId of ids) {
|
||||
const account = resolveZalouserAccountSync({ cfg, accountId: resolvedAccountId });
|
||||
if (await checkZcaAuthenticated(account.profile)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
resolveStatusLines: async ({ cfg, accountId, configured }) => {
|
||||
void cfg;
|
||||
const label =
|
||||
accountId && accountId !== DEFAULT_ACCOUNT_ID
|
||||
? `Zalo Personal (${accountId})`
|
||||
: "Zalo Personal";
|
||||
return [`${label}: ${configured ? "logged in" : "needs QR login"}`];
|
||||
},
|
||||
},
|
||||
prepare: async ({ cfg, accountId, prompter, options }) => {
|
||||
let next = cfg;
|
||||
const account = resolveZalouserAccountSync({ cfg: next, accountId });
|
||||
const alreadyAuthenticated = await checkZcaAuthenticated(account.profile);
|
||||
|
||||
if (!alreadyAuthenticated) {
|
||||
await noteZalouserHelp(prompter);
|
||||
const wantsLogin = await prompter.confirm({
|
||||
message: t("wizard.zalouser.loginQrPrompt"),
|
||||
initialValue: true,
|
||||
});
|
||||
|
||||
if (wantsLogin) {
|
||||
const start = await startZaloQrLogin({ profile: account.profile, timeoutMs: 35_000 });
|
||||
if (start.qrDataUrl) {
|
||||
const qrPath = await writeQrDataUrlToTempFile(start.qrDataUrl, account.profile);
|
||||
await prompter.note(
|
||||
[
|
||||
start.message,
|
||||
qrPath
|
||||
? t("wizard.zalouser.qrImageSaved", { path: qrPath })
|
||||
: t("wizard.zalouser.qrImageWriteFailed"),
|
||||
t("wizard.zalouser.scanApproveContinue"),
|
||||
].join("\n"),
|
||||
t("wizard.zalouser.qrLoginTitle"),
|
||||
);
|
||||
const scanned = await prompter.confirm({
|
||||
message: t("wizard.zalouser.qrScannedPrompt"),
|
||||
initialValue: true,
|
||||
});
|
||||
if (scanned) {
|
||||
const waited = await waitForZaloQrLogin({
|
||||
profile: account.profile,
|
||||
timeoutMs: 120_000,
|
||||
});
|
||||
await prompter.note(
|
||||
waited.message,
|
||||
waited.connected ? t("common.done") : t("wizard.zalouser.loginPendingTitle"),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await prompter.note(start.message, t("wizard.zalouser.loginPendingTitle"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const keepSession = await prompter.confirm({
|
||||
message: t("wizard.zalouser.keepSessionPrompt"),
|
||||
initialValue: true,
|
||||
});
|
||||
if (!keepSession) {
|
||||
await logoutZaloProfile(account.profile);
|
||||
const start = await startZaloQrLogin({
|
||||
profile: account.profile,
|
||||
force: true,
|
||||
timeoutMs: 35_000,
|
||||
});
|
||||
if (start.qrDataUrl) {
|
||||
const qrPath = await writeQrDataUrlToTempFile(start.qrDataUrl, account.profile);
|
||||
await prompter.note(
|
||||
[
|
||||
start.message,
|
||||
qrPath ? t("wizard.zalouser.qrImageSaved", { path: qrPath }) : undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
t("wizard.zalouser.qrLoginTitle"),
|
||||
);
|
||||
const waited = await waitForZaloQrLogin({ profile: account.profile, timeoutMs: 120_000 });
|
||||
await prompter.note(
|
||||
waited.message,
|
||||
waited.connected ? t("common.done") : t("wizard.zalouser.loginPendingTitle"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next = setZalouserAccountScopedConfig(
|
||||
next,
|
||||
accountId,
|
||||
{ profile: account.profile !== "default" ? account.profile : undefined },
|
||||
{ profile: account.profile, enabled: true },
|
||||
);
|
||||
|
||||
if (options?.quickstartDefaults) {
|
||||
next = await promptZalouserQuickstartDmPolicy({
|
||||
cfg: next,
|
||||
prompter,
|
||||
accountId,
|
||||
});
|
||||
}
|
||||
|
||||
return { cfg: next };
|
||||
},
|
||||
credentials: [],
|
||||
groupAccess: {
|
||||
label: "Zalo groups",
|
||||
placeholder: ZALOUSER_GROUPS_PLACEHOLDER,
|
||||
currentPolicy: ({ cfg, accountId }) =>
|
||||
resolveZalouserAccountSync({ cfg, accountId }).config.groupPolicy ?? "allowlist",
|
||||
currentEntries: ({ cfg, accountId }) =>
|
||||
Object.keys(resolveZalouserAccountSync({ cfg, accountId }).config.groups ?? {}),
|
||||
updatePrompt: ({ cfg, accountId }) =>
|
||||
Boolean(resolveZalouserAccountSync({ cfg, accountId }).config.groups),
|
||||
setPolicy: ({ cfg, accountId, policy }) => setZalouserGroupPolicy(cfg, accountId, policy),
|
||||
resolveAllowlist: async ({ cfg, accountId, entries, prompter }) => {
|
||||
if (entries.length === 0) {
|
||||
await prompter.note(
|
||||
[
|
||||
t("wizard.zalouser.noGroupAllowlist"),
|
||||
t("wizard.zalouser.groupChatsBlocked"),
|
||||
t("wizard.zalouser.groupsLookupTip", {
|
||||
command: formatCliCommand("openclaw directory groups list --channel zalouser"),
|
||||
}),
|
||||
t("wizard.zalouser.groupMentionRequirement"),
|
||||
].join("\n"),
|
||||
ZALOUSER_GROUPS_TITLE,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
const updatedAccount = resolveZalouserAccountSync({ cfg, accountId });
|
||||
try {
|
||||
const resolved = await resolveZaloGroupsByEntries({
|
||||
profile: updatedAccount.profile,
|
||||
entries,
|
||||
});
|
||||
const resolvedIds = resolved
|
||||
.filter((entry) => entry.resolved && entry.id)
|
||||
.map((entry) => entry.id as string);
|
||||
const unresolved = resolved.filter((entry) => !entry.resolved).map((entry) => entry.input);
|
||||
const keys = [...resolvedIds, ...normalizeStringEntries(unresolved)];
|
||||
const resolution = formatResolvedUnresolvedNote({
|
||||
resolved: resolvedIds,
|
||||
unresolved,
|
||||
});
|
||||
if (resolution) {
|
||||
await prompter.note(resolution, ZALOUSER_GROUPS_TITLE);
|
||||
}
|
||||
return keys;
|
||||
} catch (err) {
|
||||
await prompter.note(
|
||||
t("wizard.zalouser.groupLookupFailed", { error: String(err) }),
|
||||
ZALOUSER_GROUPS_TITLE,
|
||||
);
|
||||
return normalizeStringEntries(entries);
|
||||
}
|
||||
},
|
||||
applyAllowlist: ({ cfg, accountId, resolved }) =>
|
||||
setZalouserGroupAllowlist(cfg, accountId, resolved as string[]),
|
||||
},
|
||||
finalize: async ({ cfg, accountId, forceAllowFrom, options, prompter }) => {
|
||||
let next = cfg;
|
||||
if (forceAllowFrom && !options?.quickstartDefaults) {
|
||||
next = await promptZalouserAllowFrom({
|
||||
cfg: next,
|
||||
prompter,
|
||||
accountId,
|
||||
});
|
||||
}
|
||||
return { cfg: ensureZalouserPluginEnabled(next) };
|
||||
},
|
||||
dmPolicy: zalouserDmPolicy,
|
||||
};
|
||||
Reference in New Issue
Block a user