mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-18 05:20:48 +00:00
230 lines
7.4 KiB
TypeScript
230 lines
7.4 KiB
TypeScript
import {
|
|
addWildcardAllowFrom,
|
|
applySetupAccountConfigPatch,
|
|
DEFAULT_ACCOUNT_ID,
|
|
formatDocsLink,
|
|
mergeAllowFromEntries,
|
|
migrateBaseNameToDefaultAccount,
|
|
setTopLevelChannelDmPolicyWithAllowFrom,
|
|
splitSetupEntries,
|
|
type ChannelSetupDmPolicy,
|
|
type ChannelSetupWizard,
|
|
type DmPolicy,
|
|
type OpenClawConfig,
|
|
} from "openclaw/plugin-sdk/setup";
|
|
import {
|
|
listGoogleChatAccountIds,
|
|
resolveDefaultGoogleChatAccountId,
|
|
resolveGoogleChatAccount,
|
|
} from "./accounts.js";
|
|
import { googlechatSetupAdapter } from "./setup-core.js";
|
|
|
|
const channel = "googlechat" as const;
|
|
const ENV_SERVICE_ACCOUNT = "GOOGLE_CHAT_SERVICE_ACCOUNT";
|
|
const ENV_SERVICE_ACCOUNT_FILE = "GOOGLE_CHAT_SERVICE_ACCOUNT_FILE";
|
|
const USE_ENV_FLAG = "__googlechatUseEnv";
|
|
const AUTH_METHOD_FLAG = "__googlechatAuthMethod";
|
|
|
|
function setGoogleChatDmPolicy(cfg: OpenClawConfig, policy: DmPolicy) {
|
|
const allowFrom =
|
|
policy === "open" ? addWildcardAllowFrom(cfg.channels?.googlechat?.dm?.allowFrom) : undefined;
|
|
return {
|
|
...cfg,
|
|
channels: {
|
|
...cfg.channels,
|
|
googlechat: {
|
|
...cfg.channels?.googlechat,
|
|
dm: {
|
|
...cfg.channels?.googlechat?.dm,
|
|
policy,
|
|
...(allowFrom ? { allowFrom } : {}),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
async function promptAllowFrom(params: {
|
|
cfg: OpenClawConfig;
|
|
prompter: Parameters<NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>>[0]["prompter"];
|
|
}): Promise<OpenClawConfig> {
|
|
const current = params.cfg.channels?.googlechat?.dm?.allowFrom ?? [];
|
|
const entry = await params.prompter.text({
|
|
message: "Google Chat allowFrom (users/<id> or raw email; avoid users/<email>)",
|
|
placeholder: "users/123456789, name@example.com",
|
|
initialValue: current[0] ? String(current[0]) : undefined,
|
|
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
});
|
|
const parts = splitSetupEntries(String(entry));
|
|
const unique = mergeAllowFromEntries(undefined, parts);
|
|
return {
|
|
...params.cfg,
|
|
channels: {
|
|
...params.cfg.channels,
|
|
googlechat: {
|
|
...params.cfg.channels?.googlechat,
|
|
enabled: true,
|
|
dm: {
|
|
...params.cfg.channels?.googlechat?.dm,
|
|
policy: "allowlist",
|
|
allowFrom: unique,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
const googlechatDmPolicy: ChannelSetupDmPolicy = {
|
|
label: "Google Chat",
|
|
channel,
|
|
policyKey: "channels.googlechat.dm.policy",
|
|
allowFromKey: "channels.googlechat.dm.allowFrom",
|
|
getCurrent: (cfg) => cfg.channels?.googlechat?.dm?.policy ?? "pairing",
|
|
setPolicy: (cfg, policy) => setGoogleChatDmPolicy(cfg, policy),
|
|
promptAllowFrom,
|
|
};
|
|
|
|
export { googlechatSetupAdapter } from "./setup-core.js";
|
|
|
|
export const googlechatSetupWizard: ChannelSetupWizard = {
|
|
channel,
|
|
status: {
|
|
configuredLabel: "configured",
|
|
unconfiguredLabel: "needs service account",
|
|
configuredHint: "configured",
|
|
unconfiguredHint: "needs auth",
|
|
resolveConfigured: ({ cfg }) =>
|
|
listGoogleChatAccountIds(cfg).some(
|
|
(accountId) => resolveGoogleChatAccount({ cfg, accountId }).credentialSource !== "none",
|
|
),
|
|
resolveStatusLines: ({ cfg }) => {
|
|
const configured = listGoogleChatAccountIds(cfg).some(
|
|
(accountId) => resolveGoogleChatAccount({ cfg, accountId }).credentialSource !== "none",
|
|
);
|
|
return [`Google Chat: ${configured ? "configured" : "needs service account"}`];
|
|
},
|
|
},
|
|
introNote: {
|
|
title: "Google Chat setup",
|
|
lines: [
|
|
"Google Chat apps use service-account auth and an HTTPS webhook.",
|
|
"Set the Chat API scopes in your service account and configure the Chat app URL.",
|
|
"Webhook verification requires audience type + audience value.",
|
|
`Docs: ${formatDocsLink("/channels/googlechat", "googlechat")}`,
|
|
],
|
|
},
|
|
prepare: async ({ cfg, accountId, credentialValues, prompter }) => {
|
|
const envReady =
|
|
accountId === DEFAULT_ACCOUNT_ID &&
|
|
(Boolean(process.env[ENV_SERVICE_ACCOUNT]) || Boolean(process.env[ENV_SERVICE_ACCOUNT_FILE]));
|
|
if (envReady) {
|
|
const useEnv = await prompter.confirm({
|
|
message: "Use GOOGLE_CHAT_SERVICE_ACCOUNT env vars?",
|
|
initialValue: true,
|
|
});
|
|
if (useEnv) {
|
|
return {
|
|
cfg: applySetupAccountConfigPatch({
|
|
cfg,
|
|
channelKey: channel,
|
|
accountId,
|
|
patch: {},
|
|
}),
|
|
credentialValues: {
|
|
...credentialValues,
|
|
[USE_ENV_FLAG]: "1",
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
const method = await prompter.select({
|
|
message: "Google Chat auth method",
|
|
options: [
|
|
{ value: "file", label: "Service account JSON file" },
|
|
{ value: "inline", label: "Paste service account JSON" },
|
|
],
|
|
initialValue: "file",
|
|
});
|
|
|
|
return {
|
|
credentialValues: {
|
|
...credentialValues,
|
|
[USE_ENV_FLAG]: "0",
|
|
[AUTH_METHOD_FLAG]: String(method),
|
|
},
|
|
};
|
|
},
|
|
credentials: [],
|
|
textInputs: [
|
|
{
|
|
inputKey: "tokenFile",
|
|
message: "Service account JSON path",
|
|
placeholder: "/path/to/service-account.json",
|
|
shouldPrompt: ({ credentialValues }) =>
|
|
credentialValues[USE_ENV_FLAG] !== "1" && credentialValues[AUTH_METHOD_FLAG] === "file",
|
|
validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
normalizeValue: ({ value }) => String(value).trim(),
|
|
applySet: async ({ cfg, accountId, value }) =>
|
|
applySetupAccountConfigPatch({
|
|
cfg,
|
|
channelKey: channel,
|
|
accountId,
|
|
patch: { serviceAccountFile: value },
|
|
}),
|
|
},
|
|
{
|
|
inputKey: "token",
|
|
message: "Service account JSON (single line)",
|
|
placeholder: '{"type":"service_account", ... }',
|
|
shouldPrompt: ({ credentialValues }) =>
|
|
credentialValues[USE_ENV_FLAG] !== "1" && credentialValues[AUTH_METHOD_FLAG] === "inline",
|
|
validate: ({ value }) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
normalizeValue: ({ value }) => String(value).trim(),
|
|
applySet: async ({ cfg, accountId, value }) =>
|
|
applySetupAccountConfigPatch({
|
|
cfg,
|
|
channelKey: channel,
|
|
accountId,
|
|
patch: { serviceAccount: value },
|
|
}),
|
|
},
|
|
],
|
|
finalize: async ({ cfg, accountId, prompter }) => {
|
|
const account = resolveGoogleChatAccount({
|
|
cfg,
|
|
accountId,
|
|
});
|
|
const audienceType = await prompter.select({
|
|
message: "Webhook audience type",
|
|
options: [
|
|
{ value: "app-url", label: "App URL (recommended)" },
|
|
{ value: "project-number", label: "Project number" },
|
|
],
|
|
initialValue: account.config.audienceType === "project-number" ? "project-number" : "app-url",
|
|
});
|
|
const audience = await prompter.text({
|
|
message: audienceType === "project-number" ? "Project number" : "App URL",
|
|
placeholder:
|
|
audienceType === "project-number" ? "1234567890" : "https://your.host/googlechat",
|
|
initialValue: account.config.audience || undefined,
|
|
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
});
|
|
return {
|
|
cfg: migrateBaseNameToDefaultAccount({
|
|
cfg: applySetupAccountConfigPatch({
|
|
cfg,
|
|
channelKey: channel,
|
|
accountId,
|
|
patch: {
|
|
audienceType,
|
|
audience: String(audience).trim(),
|
|
},
|
|
}),
|
|
channelKey: channel,
|
|
}),
|
|
};
|
|
},
|
|
dmPolicy: googlechatDmPolicy,
|
|
};
|