mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-31 02:52:35 +00:00
207 lines
7.2 KiB
TypeScript
207 lines
7.2 KiB
TypeScript
import {
|
|
hasConfiguredSecretInput,
|
|
normalizeSecretInputString,
|
|
} from "openclaw/plugin-sdk/secret-input";
|
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing";
|
|
import type { ChannelSetupDmPolicy, ChannelSetupWizard, DmPolicy } from "openclaw/plugin-sdk/setup";
|
|
import {
|
|
createSetupTranslator,
|
|
createStandardChannelSetupStatus,
|
|
createTopLevelChannelDmPolicy,
|
|
createTopLevelChannelParsedAllowFromPrompt,
|
|
formatDocsLink,
|
|
mergeAllowFromEntries,
|
|
parseSetupEntriesWithParser,
|
|
patchTopLevelChannelConfigSection,
|
|
} from "openclaw/plugin-sdk/setup";
|
|
import { DEFAULT_RELAYS } from "./default-relays.js";
|
|
import { getPublicKeyFromPrivate, normalizePubkey } from "./nostr-key-utils.js";
|
|
import {
|
|
buildNostrSetupPatch,
|
|
createNostrSetupAdapter,
|
|
parseRelayUrls,
|
|
} from "./setup-adapter.js";
|
|
import { resolveDefaultNostrAccountId, resolveNostrAccount } from "./types.js";
|
|
|
|
const t = createSetupTranslator();
|
|
|
|
const channel = "nostr" as const;
|
|
const NOSTR_SETUP_HELP_LINES = [
|
|
t("wizard.nostr.helpPrivateKeyFormat"),
|
|
t("wizard.nostr.helpRelaysOptional"),
|
|
t("wizard.nostr.helpEnvVars"),
|
|
`Docs: ${formatDocsLink("/channels/nostr", "channels/nostr")}`,
|
|
];
|
|
|
|
const NOSTR_ALLOW_FROM_HELP_LINES = [
|
|
t("wizard.nostr.allowlistIntro"),
|
|
t("wizard.nostr.examples"),
|
|
"- npub1...",
|
|
"- nostr:npub1...",
|
|
"- 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
t("wizard.nostr.multipleEntries"),
|
|
`Docs: ${formatDocsLink("/channels/nostr", "channels/nostr")}`,
|
|
];
|
|
|
|
function parseNostrAllowFrom(raw: string): { entries: string[]; error?: string } {
|
|
return parseSetupEntriesWithParser(raw, (entry) => {
|
|
const cleaned = entry.replace(/^nostr:/i, "").trim();
|
|
try {
|
|
return { value: normalizePubkey(cleaned) };
|
|
} catch {
|
|
return { error: `Invalid Nostr pubkey: ${entry}` };
|
|
}
|
|
});
|
|
}
|
|
|
|
const promptNostrAllowFrom = createTopLevelChannelParsedAllowFromPrompt({
|
|
channel,
|
|
defaultAccountId: resolveDefaultNostrAccountId,
|
|
noteTitle: t("wizard.nostr.allowlistTitle"),
|
|
noteLines: NOSTR_ALLOW_FROM_HELP_LINES,
|
|
message: t("wizard.nostr.allowFromPrompt"),
|
|
placeholder: "npub1..., 0123abcd...",
|
|
parseEntries: parseNostrAllowFrom,
|
|
mergeEntries: ({ existing, parsed }) => mergeAllowFromEntries(existing, parsed),
|
|
});
|
|
|
|
const nostrDmPolicy: ChannelSetupDmPolicy = createTopLevelChannelDmPolicy({
|
|
label: "Nostr",
|
|
channel,
|
|
policyKey: "channels.nostr.dmPolicy",
|
|
allowFromKey: "channels.nostr.allowFrom",
|
|
getCurrent: (cfg) => (cfg.channels?.nostr?.dmPolicy as DmPolicy | undefined) ?? "pairing",
|
|
promptAllowFrom: promptNostrAllowFrom,
|
|
});
|
|
|
|
export const nostrSetupAdapter = createNostrSetupAdapter({
|
|
resolveAccountId: (cfg, accountId) => accountId?.trim() || resolveDefaultNostrAccountId(cfg),
|
|
validatePrivateKey: (privateKey) => {
|
|
try {
|
|
getPublicKeyFromPrivate(privateKey);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
},
|
|
});
|
|
|
|
export const nostrSetupWizard: ChannelSetupWizard = {
|
|
channel,
|
|
resolveAccountIdForConfigure: ({ accountOverride, defaultAccountId }) =>
|
|
accountOverride?.trim() || defaultAccountId,
|
|
resolveShouldPromptAccountIds: () => false,
|
|
status: createStandardChannelSetupStatus({
|
|
channelLabel: "Nostr",
|
|
configuredLabel: t("wizard.channels.statusConfigured"),
|
|
unconfiguredLabel: t("wizard.channels.statusNeedsPrivateKey"),
|
|
configuredHint: t("wizard.channels.statusConfigured"),
|
|
unconfiguredHint: t("wizard.channels.statusNeedsPrivateKey"),
|
|
configuredScore: 1,
|
|
unconfiguredScore: 0,
|
|
includeStatusLine: true,
|
|
resolveConfigured: ({ cfg }) => resolveNostrAccount({ cfg }).configured,
|
|
resolveExtraStatusLines: ({ cfg }) => {
|
|
const account = resolveNostrAccount({ cfg });
|
|
return [`Relays: ${account.relays.length || DEFAULT_RELAYS.length}`];
|
|
},
|
|
}),
|
|
introNote: {
|
|
title: t("wizard.nostr.setupTitle"),
|
|
lines: NOSTR_SETUP_HELP_LINES,
|
|
},
|
|
envShortcut: {
|
|
prompt: t("wizard.nostr.privateKeyEnvPrompt"),
|
|
preferredEnvVar: "NOSTR_PRIVATE_KEY",
|
|
isAvailable: ({ cfg, accountId }) =>
|
|
accountId === DEFAULT_ACCOUNT_ID &&
|
|
Boolean(process.env.NOSTR_PRIVATE_KEY?.trim()) &&
|
|
!hasConfiguredSecretInput(resolveNostrAccount({ cfg, accountId }).config.privateKey),
|
|
apply: async ({ cfg, accountId }) =>
|
|
patchTopLevelChannelConfigSection({
|
|
cfg,
|
|
channel,
|
|
enabled: true,
|
|
clearFields: ["privateKey"],
|
|
patch: buildNostrSetupPatch(accountId, {}),
|
|
}),
|
|
},
|
|
credentials: [
|
|
{
|
|
inputKey: "privateKey",
|
|
providerHint: channel,
|
|
credentialLabel: "private key",
|
|
preferredEnvVar: "NOSTR_PRIVATE_KEY",
|
|
helpTitle: t("wizard.nostr.privateKeyTitle"),
|
|
helpLines: NOSTR_SETUP_HELP_LINES,
|
|
envPrompt: t("wizard.nostr.privateKeyEnvPrompt"),
|
|
keepPrompt: t("wizard.nostr.privateKeyKeep"),
|
|
inputPrompt: t("wizard.nostr.privateKeyInput"),
|
|
allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID,
|
|
inspect: ({ cfg, accountId }) => {
|
|
const account = resolveNostrAccount({ cfg, accountId });
|
|
return {
|
|
accountConfigured: account.configured,
|
|
hasConfiguredValue: hasConfiguredSecretInput(account.config.privateKey),
|
|
resolvedValue: normalizeSecretInputString(account.config.privateKey),
|
|
envValue: process.env.NOSTR_PRIVATE_KEY?.trim(),
|
|
};
|
|
},
|
|
applyUseEnv: async ({ cfg, accountId }) =>
|
|
patchTopLevelChannelConfigSection({
|
|
cfg,
|
|
channel,
|
|
enabled: true,
|
|
clearFields: ["privateKey"],
|
|
patch: buildNostrSetupPatch(accountId, {}),
|
|
}),
|
|
applySet: async ({ cfg, accountId, resolvedValue }) =>
|
|
patchTopLevelChannelConfigSection({
|
|
cfg,
|
|
channel,
|
|
enabled: true,
|
|
patch: buildNostrSetupPatch(accountId, { privateKey: resolvedValue }),
|
|
}),
|
|
},
|
|
],
|
|
textInputs: [
|
|
{
|
|
inputKey: "relayUrls",
|
|
message: t("wizard.nostr.relayUrlsPrompt"),
|
|
placeholder: DEFAULT_RELAYS.join(", "),
|
|
required: false,
|
|
applyEmptyValue: true,
|
|
helpTitle: t("wizard.nostr.relaysTitle"),
|
|
helpLines: [t("wizard.nostr.relaysWsOnly"), t("wizard.nostr.helpRelaysOptional")],
|
|
currentValue: ({ cfg, accountId }) => {
|
|
const account = resolveNostrAccount({ cfg, accountId });
|
|
const configuredRelays = cfg.channels?.nostr?.relays as string[] | undefined;
|
|
const relays = configuredRelays && configuredRelays.length > 0 ? account.relays : [];
|
|
return relays.join(", ");
|
|
},
|
|
keepPrompt: (value) => t("wizard.nostr.relayUrlsKeep", { value }),
|
|
validate: ({ value }) => parseRelayUrls(value).error,
|
|
applySet: async ({ cfg, accountId, value }) => {
|
|
const relayResult = parseRelayUrls(value);
|
|
return patchTopLevelChannelConfigSection({
|
|
cfg,
|
|
channel,
|
|
enabled: true,
|
|
clearFields: relayResult.relays.length > 0 ? undefined : ["relays"],
|
|
patch: buildNostrSetupPatch(
|
|
accountId,
|
|
relayResult.relays.length > 0 ? { relays: relayResult.relays } : {},
|
|
),
|
|
});
|
|
},
|
|
},
|
|
],
|
|
dmPolicy: nostrDmPolicy,
|
|
disable: (cfg) =>
|
|
patchTopLevelChannelConfigSection({
|
|
cfg,
|
|
channel,
|
|
patch: { enabled: false },
|
|
}),
|
|
};
|