mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-04 09:11:41 +00:00
209 lines
7.3 KiB
TypeScript
209 lines
7.3 KiB
TypeScript
// Line plugin module implements setup surface behavior.
|
|
import { createChannelDmPolicy } from "openclaw/plugin-sdk/channel-dm-policy";
|
|
import {
|
|
createAllowFromSection,
|
|
createPromptParsedAllowFromForAccount,
|
|
createStandardChannelSetupStatus,
|
|
createSetupTranslator,
|
|
defineTokenCredential,
|
|
parseSetupEntriesWithParser,
|
|
} from "openclaw/plugin-sdk/setup";
|
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
import { resolveDefaultLineAccountId } from "./accounts.js";
|
|
import {
|
|
isLineConfigured,
|
|
listLineAccountIds,
|
|
parseLineAllowFromId,
|
|
patchLineAccountConfig,
|
|
} from "./setup-core.js";
|
|
import {
|
|
DEFAULT_ACCOUNT_ID,
|
|
formatDocsLink,
|
|
resolveLineAccount,
|
|
setSetupChannelEnabled,
|
|
splitSetupEntries,
|
|
type ChannelSetupWizard,
|
|
} from "./setup-runtime-api.js";
|
|
|
|
const t = createSetupTranslator();
|
|
|
|
const channel = "line" as const;
|
|
|
|
const LINE_SETUP_HELP_LINES = [
|
|
t("wizard.line.helpOpenConsole"),
|
|
t("wizard.line.helpCopyCredentials"),
|
|
t("wizard.line.helpEnableWebhook"),
|
|
t("wizard.line.helpWebhookUrl"),
|
|
t("wizard.channels.docs", { link: formatDocsLink("/channels/line", "channels/line") }),
|
|
];
|
|
|
|
const LINE_ALLOW_FROM_HELP_LINES = [
|
|
t("wizard.line.allowlistIntro"),
|
|
t("wizard.line.idsCaseSensitive"),
|
|
t("wizard.line.examples"),
|
|
"- U1234567890abcdef1234567890abcdef",
|
|
"- line:user:U1234567890abcdef1234567890abcdef",
|
|
t("wizard.line.multipleEntries"),
|
|
t("wizard.channels.docs", { link: formatDocsLink("/channels/line", "channels/line") }),
|
|
];
|
|
|
|
const promptLineAllowFrom = createPromptParsedAllowFromForAccount({
|
|
defaultAccountId: resolveDefaultLineAccountId,
|
|
noteTitle: t("wizard.line.allowlistTitle"),
|
|
noteLines: LINE_ALLOW_FROM_HELP_LINES,
|
|
message: t("wizard.line.allowFromPrompt"),
|
|
placeholder: "U1234567890abcdef1234567890abcdef",
|
|
parseEntries: (raw) =>
|
|
parseSetupEntriesWithParser(raw, (entry) => {
|
|
const id = parseLineAllowFromId(entry);
|
|
return id ? { value: id } : { error: t("wizard.line.allowFromInvalid") };
|
|
}),
|
|
getExistingAllowFrom: ({ cfg, accountId }) =>
|
|
resolveLineAccount({ cfg, accountId }).config.allowFrom ?? [],
|
|
applyAllowFrom: ({ cfg, accountId, allowFrom }) =>
|
|
patchLineAccountConfig({
|
|
cfg,
|
|
accountId,
|
|
enabled: true,
|
|
patch: { allowFrom },
|
|
}),
|
|
});
|
|
|
|
const lineDmPolicy = createChannelDmPolicy({
|
|
label: "LINE",
|
|
channel,
|
|
resolveAccount: (cfg, accountId) =>
|
|
resolveLineAccount({ cfg, accountId: accountId ?? resolveDefaultLineAccountId(cfg) }),
|
|
applyPatch: ({ cfg, account, patch }) =>
|
|
patchLineAccountConfig({
|
|
cfg,
|
|
accountId: account.accountId,
|
|
enabled: true,
|
|
patch,
|
|
clearFields:
|
|
patch.dmPolicy === "pairing" || patch.dmPolicy === "disabled" ? ["allowFrom"] : undefined,
|
|
}),
|
|
promptAllowFrom: promptLineAllowFrom,
|
|
});
|
|
|
|
function createLineTokenCredential(params: {
|
|
inputKey: "token" | "password";
|
|
configKey: "channelAccessToken" | "channelSecret";
|
|
fileKey: "tokenFile" | "secretFile";
|
|
providerHint: string;
|
|
credentialLabel: string;
|
|
envVar: string;
|
|
envPrompt: string;
|
|
keepPrompt: string;
|
|
inputPrompt: string;
|
|
}) {
|
|
return defineTokenCredential({
|
|
inputKey: params.inputKey,
|
|
configKey: params.configKey,
|
|
configuredFields: [params.configKey, params.fileKey],
|
|
providerHint: params.providerHint,
|
|
credentialLabel: params.credentialLabel,
|
|
preferredEnvVar: params.envVar,
|
|
helpTitle: t("wizard.line.messagingApiTitle"),
|
|
helpLines: LINE_SETUP_HELP_LINES,
|
|
envPrompt: params.envPrompt,
|
|
keepPrompt: params.keepPrompt,
|
|
inputPrompt: params.inputPrompt,
|
|
allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID,
|
|
resolveAccount: ({ cfg, accountId }) => resolveLineAccount({ cfg, accountId }),
|
|
accountConfigured: (account) =>
|
|
Boolean(
|
|
normalizeOptionalString(account.channelAccessToken) &&
|
|
normalizeOptionalString(account.channelSecret),
|
|
),
|
|
hasConfiguredValue: (account) =>
|
|
Boolean(
|
|
normalizeOptionalString(account.config[params.configKey]) ??
|
|
normalizeOptionalString(account.config[params.fileKey]),
|
|
),
|
|
resolvedValue: (account) => normalizeOptionalString(account[params.configKey]),
|
|
envValue: ({ accountId }) =>
|
|
accountId === DEFAULT_ACCOUNT_ID
|
|
? normalizeOptionalString(process.env[params.envVar])
|
|
: undefined,
|
|
patchAccount: ({ cfg, accountId, patch, clearFields }) =>
|
|
patchLineAccountConfig({ cfg, accountId, enabled: true, clearFields, patch }),
|
|
useEnv: { clearFields: [params.configKey, params.fileKey] },
|
|
set: { clearFields: [params.fileKey], value: "resolved" },
|
|
});
|
|
}
|
|
|
|
export const lineSetupWizard: ChannelSetupWizard = {
|
|
channel,
|
|
status: createStandardChannelSetupStatus({
|
|
channelLabel: "LINE",
|
|
configuredLabel: t("wizard.channels.statusConfigured"),
|
|
unconfiguredLabel: t("wizard.channels.statusNeedsTokenSecret"),
|
|
configuredHint: t("wizard.channels.statusConfigured"),
|
|
unconfiguredHint: t("wizard.channels.statusNeedsTokenSecret"),
|
|
configuredScore: 1,
|
|
unconfiguredScore: 0,
|
|
includeStatusLine: true,
|
|
resolveConfigured: ({ cfg, accountId }) =>
|
|
isLineConfigured(cfg, accountId ?? resolveDefaultLineAccountId(cfg)),
|
|
resolveExtraStatusLines: ({ cfg }) => [`Accounts: ${listLineAccountIds(cfg).length || 0}`],
|
|
}),
|
|
introNote: {
|
|
title: t("wizard.line.messagingApiTitle"),
|
|
lines: LINE_SETUP_HELP_LINES,
|
|
shouldShow: ({ cfg, accountId }) =>
|
|
!isLineConfigured(cfg, accountId ?? resolveDefaultLineAccountId(cfg)),
|
|
},
|
|
credentials: [
|
|
createLineTokenCredential({
|
|
inputKey: "token",
|
|
configKey: "channelAccessToken",
|
|
fileKey: "tokenFile",
|
|
providerHint: channel,
|
|
credentialLabel: t("wizard.line.channelAccessToken"),
|
|
envVar: "LINE_CHANNEL_ACCESS_TOKEN",
|
|
envPrompt: t("wizard.line.tokenEnvPrompt"),
|
|
keepPrompt: t("wizard.line.tokenKeepPrompt"),
|
|
inputPrompt: t("wizard.line.tokenInputPrompt"),
|
|
}),
|
|
createLineTokenCredential({
|
|
inputKey: "password",
|
|
configKey: "channelSecret",
|
|
fileKey: "secretFile",
|
|
providerHint: "line-secret",
|
|
credentialLabel: t("wizard.line.channelSecret"),
|
|
envVar: "LINE_CHANNEL_SECRET",
|
|
envPrompt: t("wizard.line.secretEnvPrompt"),
|
|
keepPrompt: t("wizard.line.secretKeepPrompt"),
|
|
inputPrompt: t("wizard.line.secretInputPrompt"),
|
|
}),
|
|
],
|
|
allowFrom: createAllowFromSection({
|
|
helpTitle: t("wizard.line.allowlistTitle"),
|
|
helpLines: LINE_ALLOW_FROM_HELP_LINES,
|
|
message: t("wizard.line.allowFromPrompt"),
|
|
placeholder: "U1234567890abcdef1234567890abcdef",
|
|
invalidWithoutCredentialNote: t("wizard.line.allowFromInvalid"),
|
|
parseInputs: splitSetupEntries,
|
|
parseId: parseLineAllowFromId,
|
|
apply: ({ cfg, accountId, allowFrom }) =>
|
|
patchLineAccountConfig({
|
|
cfg,
|
|
accountId,
|
|
enabled: true,
|
|
patch: { dmPolicy: "allowlist", allowFrom },
|
|
}),
|
|
}),
|
|
dmPolicy: lineDmPolicy,
|
|
completionNote: {
|
|
title: t("wizard.line.webhookTitle"),
|
|
lines: [
|
|
t("wizard.line.completionEnableWebhook"),
|
|
t("wizard.line.completionDefaultWebhook"),
|
|
t("wizard.line.completionWebhookPath"),
|
|
t("wizard.channels.docs", { link: formatDocsLink("/channels/line", "channels/line") }),
|
|
],
|
|
},
|
|
disable: (cfg) => setSetupChannelEnabled(cfg, channel, false),
|
|
};
|