feat: migrate zalouser plugin to sdk

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
Peter Steinberger
2026-01-18 04:23:25 +00:00
parent b105745299
commit 89c5185f1c
9 changed files with 152 additions and 397 deletions

View File

@@ -1,6 +1,16 @@
import type { ChannelAccountSnapshot, ChannelDirectoryEntry, ChannelPlugin } from "clawdbot/plugin-sdk";
import { formatPairingApproveHint } from "clawdbot/plugin-sdk";
import type {
ChannelAccountSnapshot,
ChannelDirectoryEntry,
ChannelPlugin,
ClawdbotConfig,
} from "clawdbot/plugin-sdk";
import {
DEFAULT_ACCOUNT_ID,
deleteAccountFromConfigSection,
formatPairingApproveHint,
normalizeAccountId,
setAccountEnabledInConfigSection,
} from "clawdbot/plugin-sdk";
import {
listZalouserAccountIds,
resolveDefaultZalouserAccountId,
@@ -12,14 +22,7 @@ import {
import { zalouserOnboardingAdapter } from "./onboarding.js";
import { sendMessageZalouser } from "./send.js";
import { checkZcaInstalled, parseJsonOutput, runZca, runZcaInteractive } from "./zca.js";
import {
DEFAULT_ACCOUNT_ID,
type CoreConfig,
type ZalouserConfig,
type ZcaFriend,
type ZcaGroup,
type ZcaUserInfo,
} from "./types.js";
import type { ZcaFriend, ZcaGroup, ZcaUserInfo } from "./types.js";
const meta = {
id: "zalouser",
@@ -34,7 +37,7 @@ const meta = {
};
function resolveZalouserQrProfile(accountId?: string | null): string {
const normalized = String(accountId ?? "").trim();
const normalized = normalizeAccountId(accountId);
if (!normalized || normalized === DEFAULT_ACCOUNT_ID) {
return process.env.ZCA_PROFILE?.trim() || "default";
}
@@ -69,65 +72,6 @@ function mapGroup(params: {
};
}
function deleteAccountFromConfigSection(params: {
cfg: CoreConfig;
accountId: string;
}): CoreConfig {
const { cfg, accountId } = params;
if (accountId === DEFAULT_ACCOUNT_ID) {
const { zalouser: _removed, ...restChannels } = cfg.channels ?? {};
return { ...cfg, channels: restChannels };
}
const accounts = { ...(cfg.channels?.zalouser?.accounts ?? {}) };
delete accounts[accountId];
return {
...cfg,
channels: {
...cfg.channels,
zalouser: {
...cfg.channels?.zalouser,
accounts,
},
},
};
}
function setAccountEnabledInConfigSection(params: {
cfg: CoreConfig;
accountId: string;
enabled: boolean;
}): CoreConfig {
const { cfg, accountId, enabled } = params;
if (accountId === DEFAULT_ACCOUNT_ID) {
return {
...cfg,
channels: {
...cfg.channels,
zalouser: {
...cfg.channels?.zalouser,
enabled,
},
},
};
}
return {
...cfg,
channels: {
...cfg.channels,
zalouser: {
...cfg.channels?.zalouser,
accounts: {
...(cfg.channels?.zalouser?.accounts ?? {}),
[accountId]: {
...(cfg.channels?.zalouser?.accounts?.[accountId] ?? {}),
enabled,
},
},
},
},
};
}
export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
id: "zalouser",
meta,
@@ -143,20 +87,24 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
},
reload: { configPrefixes: ["channels.zalouser"] },
config: {
listAccountIds: (cfg) => listZalouserAccountIds(cfg as CoreConfig),
listAccountIds: (cfg) => listZalouserAccountIds(cfg as ClawdbotConfig),
resolveAccount: (cfg, accountId) =>
resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId }),
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg as CoreConfig),
resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId }),
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg as ClawdbotConfig),
setAccountEnabled: ({ cfg, accountId, enabled }) =>
setAccountEnabledInConfigSection({
cfg: cfg as CoreConfig,
cfg: cfg as ClawdbotConfig,
sectionKey: "zalouser",
accountId,
enabled,
allowTopLevel: true,
}),
deleteAccount: ({ cfg, accountId }) =>
deleteAccountFromConfigSection({
cfg: cfg as CoreConfig,
cfg: cfg as ClawdbotConfig,
sectionKey: "zalouser",
accountId,
clearBaseFields: ["profile", "name", "dmPolicy", "allowFrom", "groupPolicy", "groups", "messagePrefix"],
}),
isConfigured: async (account) => {
// Check if zca auth status is OK for this profile
@@ -173,7 +121,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
configured: undefined,
}),
resolveAllowFrom: ({ cfg, accountId }) =>
(resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId }).config.allowFrom ?? []).map(
(resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId }).config.allowFrom ?? []).map(
(entry) => String(entry),
),
formatAllowFrom: ({ allowFrom }) =>
@@ -187,7 +135,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
resolveDmPolicy: ({ cfg, accountId, account }) => {
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
const useAccountPath = Boolean(
(cfg as CoreConfig).channels?.zalouser?.accounts?.[resolvedAccountId],
(cfg as ClawdbotConfig).channels?.zalouser?.accounts?.[resolvedAccountId],
);
const basePath = useAccountPath
? `channels.zalouser.accounts.${resolvedAccountId}.`
@@ -227,7 +175,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
self: async ({ cfg, accountId, runtime }) => {
const ok = await checkZcaInstalled();
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
const result = await runZca(["me", "info", "-j"], { profile: account.profile, timeout: 10000 });
if (!result.ok) {
runtime.error(result.stderr || "Failed to fetch profile");
@@ -245,7 +193,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
listPeers: async ({ cfg, accountId, query, limit }) => {
const ok = await checkZcaInstalled();
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
const args = query?.trim()
? ["friend", "find", query.trim()]
: ["friend", "list", "-j"];
@@ -269,7 +217,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
listGroups: async ({ cfg, accountId, query, limit }) => {
const ok = await checkZcaInstalled();
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
const result = await runZca(["group", "list", "-j"], { profile: account.profile, timeout: 15000 });
if (!result.ok) {
throw new Error(result.stderr || "Failed to list groups");
@@ -293,7 +241,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
listGroupMembers: async ({ cfg, accountId, groupId, limit }) => {
const ok = await checkZcaInstalled();
if (!ok) throw new Error("Missing dependency: `zca` not found in PATH");
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
const result = await runZca(["group", "members", groupId, "-j"], {
profile: account.profile,
timeout: 20000,
@@ -335,7 +283,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
}
try {
const account = resolveZalouserAccountSync({
cfg: cfg as CoreConfig,
cfg: cfg as ClawdbotConfig,
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
});
const args =
@@ -391,7 +339,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
idLabel: "zalouserUserId",
normalizeAllowEntry: (entry) => entry.replace(/^(zalouser|zlu):/i, ""),
notifyApproval: async ({ cfg, id }) => {
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig });
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig });
const authenticated = await checkZcaAuthenticated(account.profile);
if (!authenticated) throw new Error("Zalouser not authenticated");
await sendMessageZalouser(id, "Your pairing request has been approved.", {
@@ -402,7 +350,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
auth: {
login: async ({ cfg, accountId, runtime }) => {
const account = resolveZalouserAccountSync({
cfg: cfg as CoreConfig,
cfg: cfg as ClawdbotConfig,
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
});
const ok = await checkZcaInstalled();
@@ -445,7 +393,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
},
textChunkLimit: 2000,
sendText: async ({ to, text, accountId, cfg }) => {
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
const result = await sendMessageZalouser(to, text, { profile: account.profile });
return {
channel: "zalouser",
@@ -455,7 +403,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
};
},
sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => {
const account = resolveZalouserAccountSync({ cfg: cfg as CoreConfig, accountId });
const account = resolveZalouserAccountSync({ cfg: cfg as ClawdbotConfig, accountId });
const result = await sendMessageZalouser(to, text, {
profile: account.profile,
mediaUrl,
@@ -534,7 +482,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
const { monitorZalouserProvider } = await import("./monitor.js");
return monitorZalouserProvider({
account,
config: ctx.cfg as CoreConfig,
config: ctx.cfg as ClawdbotConfig,
runtime: ctx.runtime,
abortSignal: ctx.abortSignal,
statusSink: (patch) => ctx.setStatus({ accountId: ctx.accountId, ...patch }),