mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-28 18:33:37 +00:00
184 lines
6.1 KiB
TypeScript
184 lines
6.1 KiB
TypeScript
import type {
|
|
ChannelOnboardingAdapter,
|
|
ChannelOnboardingDmPolicy,
|
|
} from "../../../src/channels/plugins/onboarding-types.js";
|
|
import {
|
|
parseOnboardingEntriesAllowingWildcard,
|
|
patchChannelConfigForAccount,
|
|
promptParsedAllowFromForScopedChannel,
|
|
resolveAccountIdForConfigure,
|
|
setChannelDmPolicyWithAllowFrom,
|
|
setOnboardingChannelEnabled,
|
|
} from "../../../src/channels/plugins/onboarding/helpers.js";
|
|
import { detectBinary } from "../../../src/commands/onboard-helpers.js";
|
|
import type { OpenClawConfig } from "../../../src/config/config.js";
|
|
import { formatDocsLink } from "../../../src/terminal/links.js";
|
|
import type { WizardPrompter } from "../../../src/wizard/prompts.js";
|
|
import {
|
|
listIMessageAccountIds,
|
|
resolveDefaultIMessageAccountId,
|
|
resolveIMessageAccount,
|
|
} from "./accounts.js";
|
|
import { normalizeIMessageHandle } from "./targets.js";
|
|
|
|
const channel = "imessage" as const;
|
|
|
|
export function parseIMessageAllowFromEntries(raw: string): { entries: string[]; error?: string } {
|
|
return parseOnboardingEntriesAllowingWildcard(raw, (entry) => {
|
|
const lower = entry.toLowerCase();
|
|
if (lower.startsWith("chat_id:")) {
|
|
const id = entry.slice("chat_id:".length).trim();
|
|
if (!/^\d+$/.test(id)) {
|
|
return { error: `Invalid chat_id: ${entry}` };
|
|
}
|
|
return { value: entry };
|
|
}
|
|
if (lower.startsWith("chat_guid:")) {
|
|
if (!entry.slice("chat_guid:".length).trim()) {
|
|
return { error: "Invalid chat_guid entry" };
|
|
}
|
|
return { value: entry };
|
|
}
|
|
if (lower.startsWith("chat_identifier:")) {
|
|
if (!entry.slice("chat_identifier:".length).trim()) {
|
|
return { error: "Invalid chat_identifier entry" };
|
|
}
|
|
return { value: entry };
|
|
}
|
|
if (!normalizeIMessageHandle(entry)) {
|
|
return { error: `Invalid handle: ${entry}` };
|
|
}
|
|
return { value: entry };
|
|
});
|
|
}
|
|
|
|
async function promptIMessageAllowFrom(params: {
|
|
cfg: OpenClawConfig;
|
|
prompter: WizardPrompter;
|
|
accountId?: string;
|
|
}): Promise<OpenClawConfig> {
|
|
return promptParsedAllowFromForScopedChannel({
|
|
cfg: params.cfg,
|
|
channel: "imessage",
|
|
accountId: params.accountId,
|
|
defaultAccountId: resolveDefaultIMessageAccountId(params.cfg),
|
|
prompter: params.prompter,
|
|
noteTitle: "iMessage allowlist",
|
|
noteLines: [
|
|
"Allowlist iMessage DMs by handle or chat target.",
|
|
"Examples:",
|
|
"- +15555550123",
|
|
"- user@example.com",
|
|
"- chat_id:123",
|
|
"- chat_guid:... or chat_identifier:...",
|
|
"Multiple entries: comma-separated.",
|
|
`Docs: ${formatDocsLink("/imessage", "imessage")}`,
|
|
],
|
|
message: "iMessage allowFrom (handle or chat_id)",
|
|
placeholder: "+15555550123, user@example.com, chat_id:123",
|
|
parseEntries: parseIMessageAllowFromEntries,
|
|
getExistingAllowFrom: ({ cfg, accountId }) => {
|
|
const resolved = resolveIMessageAccount({ cfg, accountId });
|
|
return resolved.config.allowFrom ?? [];
|
|
},
|
|
});
|
|
}
|
|
|
|
const dmPolicy: ChannelOnboardingDmPolicy = {
|
|
label: "iMessage",
|
|
channel,
|
|
policyKey: "channels.imessage.dmPolicy",
|
|
allowFromKey: "channels.imessage.allowFrom",
|
|
getCurrent: (cfg) => cfg.channels?.imessage?.dmPolicy ?? "pairing",
|
|
setPolicy: (cfg, policy) =>
|
|
setChannelDmPolicyWithAllowFrom({
|
|
cfg,
|
|
channel: "imessage",
|
|
dmPolicy: policy,
|
|
}),
|
|
promptAllowFrom: promptIMessageAllowFrom,
|
|
};
|
|
|
|
export const imessageOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
channel,
|
|
getStatus: async ({ cfg }) => {
|
|
const configured = listIMessageAccountIds(cfg).some((accountId) => {
|
|
const account = resolveIMessageAccount({ cfg, accountId });
|
|
return Boolean(
|
|
account.config.cliPath ||
|
|
account.config.dbPath ||
|
|
account.config.allowFrom ||
|
|
account.config.service ||
|
|
account.config.region,
|
|
);
|
|
});
|
|
const imessageCliPath = cfg.channels?.imessage?.cliPath ?? "imsg";
|
|
const imessageCliDetected = await detectBinary(imessageCliPath);
|
|
return {
|
|
channel,
|
|
configured,
|
|
statusLines: [
|
|
`iMessage: ${configured ? "configured" : "needs setup"}`,
|
|
`imsg: ${imessageCliDetected ? "found" : "missing"} (${imessageCliPath})`,
|
|
],
|
|
selectionHint: imessageCliDetected ? "imsg found" : "imsg missing",
|
|
quickstartScore: imessageCliDetected ? 1 : 0,
|
|
};
|
|
},
|
|
configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => {
|
|
const defaultIMessageAccountId = resolveDefaultIMessageAccountId(cfg);
|
|
const imessageAccountId = await resolveAccountIdForConfigure({
|
|
cfg,
|
|
prompter,
|
|
label: "iMessage",
|
|
accountOverride: accountOverrides.imessage,
|
|
shouldPromptAccountIds,
|
|
listAccountIds: listIMessageAccountIds,
|
|
defaultAccountId: defaultIMessageAccountId,
|
|
});
|
|
|
|
let next = cfg;
|
|
const resolvedAccount = resolveIMessageAccount({
|
|
cfg: next,
|
|
accountId: imessageAccountId,
|
|
});
|
|
let resolvedCliPath = resolvedAccount.config.cliPath ?? "imsg";
|
|
const cliDetected = await detectBinary(resolvedCliPath);
|
|
if (!cliDetected) {
|
|
const entered = await prompter.text({
|
|
message: "imsg CLI path",
|
|
initialValue: resolvedCliPath,
|
|
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
});
|
|
resolvedCliPath = String(entered).trim();
|
|
if (!resolvedCliPath) {
|
|
await prompter.note("imsg CLI path required to enable iMessage.", "iMessage");
|
|
}
|
|
}
|
|
|
|
if (resolvedCliPath) {
|
|
next = patchChannelConfigForAccount({
|
|
cfg: next,
|
|
channel: "imessage",
|
|
accountId: imessageAccountId,
|
|
patch: { cliPath: resolvedCliPath },
|
|
});
|
|
}
|
|
|
|
await prompter.note(
|
|
[
|
|
"This is still a work in progress.",
|
|
"Ensure OpenClaw has Full Disk Access to Messages DB.",
|
|
"Grant Automation permission for Messages when prompted.",
|
|
"List chats with: imsg chats --limit 20",
|
|
`Docs: ${formatDocsLink("/imessage", "imessage")}`,
|
|
].join("\n"),
|
|
"iMessage next steps",
|
|
);
|
|
|
|
return { cfg: next, accountId: imessageAccountId };
|
|
},
|
|
dmPolicy,
|
|
disable: (cfg) => setOnboardingChannelEnabled(cfg, channel, false),
|
|
};
|