mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 01:30:21 +00:00
refactor: remove plugin sdk extension facade smells
This commit is contained in:
@@ -1,6 +1,241 @@
|
||||
import {
|
||||
parseChatTargetPrefixesOrThrow,
|
||||
resolveServicePrefixedTarget,
|
||||
type ParsedChatTarget,
|
||||
} from "./imessage-targets.js";
|
||||
|
||||
// Narrow plugin-sdk surface for the bundled bluebubbles plugin.
|
||||
// Keep this list additive and scoped to symbols used under extensions/bluebubbles.
|
||||
|
||||
type BlueBubblesService = "imessage" | "sms" | "auto";
|
||||
|
||||
type BlueBubblesTarget =
|
||||
| ParsedChatTarget
|
||||
| { kind: "handle"; to: string; service: BlueBubblesService };
|
||||
|
||||
const CHAT_ID_PREFIXES = ["chat_id:", "chatid:", "chat:"];
|
||||
const CHAT_GUID_PREFIXES = ["chat_guid:", "chatguid:", "guid:"];
|
||||
const CHAT_IDENTIFIER_PREFIXES = ["chat_identifier:", "chatidentifier:", "chatident:"];
|
||||
const SERVICE_PREFIXES: Array<{ prefix: string; service: BlueBubblesService }> = [
|
||||
{ prefix: "imessage:", service: "imessage" },
|
||||
{ prefix: "sms:", service: "sms" },
|
||||
{ prefix: "auto:", service: "auto" },
|
||||
];
|
||||
const CHAT_IDENTIFIER_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
const CHAT_IDENTIFIER_HEX_RE = /^[0-9a-f]{24,64}$/i;
|
||||
|
||||
function parseRawChatGuid(value: string): string | null {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const parts = trimmed.split(";");
|
||||
if (parts.length !== 3) {
|
||||
return null;
|
||||
}
|
||||
const service = parts[0]?.trim();
|
||||
const separator = parts[1]?.trim();
|
||||
const identifier = parts[2]?.trim();
|
||||
if (!service || !identifier) {
|
||||
return null;
|
||||
}
|
||||
if (separator !== "+" && separator !== "-") {
|
||||
return null;
|
||||
}
|
||||
return `${service};${separator};${identifier}`;
|
||||
}
|
||||
|
||||
function stripPrefix(value: string, prefix: string): string {
|
||||
return value.slice(prefix.length).trim();
|
||||
}
|
||||
|
||||
function stripBlueBubblesPrefix(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
if (!trimmed.toLowerCase().startsWith("bluebubbles:")) {
|
||||
return trimmed;
|
||||
}
|
||||
return trimmed.slice("bluebubbles:".length).trim();
|
||||
}
|
||||
|
||||
function looksLikeRawChatIdentifier(value: string): boolean {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
if (/^chat\d+$/i.test(trimmed)) {
|
||||
return true;
|
||||
}
|
||||
return CHAT_IDENTIFIER_UUID_RE.test(trimmed) || CHAT_IDENTIFIER_HEX_RE.test(trimmed);
|
||||
}
|
||||
|
||||
function parseGroupTarget(params: {
|
||||
trimmed: string;
|
||||
lower: string;
|
||||
}): { kind: "chat_id"; chatId: number } | { kind: "chat_guid"; chatGuid: string } | null {
|
||||
if (!params.lower.startsWith("group:")) {
|
||||
return null;
|
||||
}
|
||||
const value = stripPrefix(params.trimmed, "group:");
|
||||
const chatId = Number.parseInt(value, 10);
|
||||
if (Number.isFinite(chatId)) {
|
||||
return { kind: "chat_id", chatId };
|
||||
}
|
||||
if (value) {
|
||||
return { kind: "chat_guid", chatGuid: value };
|
||||
}
|
||||
throw new Error("group target is required");
|
||||
}
|
||||
|
||||
function parseRawChatIdentifierTarget(
|
||||
trimmed: string,
|
||||
): { kind: "chat_identifier"; chatIdentifier: string } | null {
|
||||
if (/^chat\d+$/i.test(trimmed)) {
|
||||
return { kind: "chat_identifier", chatIdentifier: trimmed };
|
||||
}
|
||||
if (looksLikeRawChatIdentifier(trimmed)) {
|
||||
return { kind: "chat_identifier", chatIdentifier: trimmed };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeBlueBubblesHandle(raw: string): string {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
const lowered = trimmed.toLowerCase();
|
||||
if (lowered.startsWith("imessage:")) {
|
||||
return normalizeBlueBubblesHandle(trimmed.slice(9));
|
||||
}
|
||||
if (lowered.startsWith("sms:")) {
|
||||
return normalizeBlueBubblesHandle(trimmed.slice(4));
|
||||
}
|
||||
if (lowered.startsWith("auto:")) {
|
||||
return normalizeBlueBubblesHandle(trimmed.slice(5));
|
||||
}
|
||||
if (trimmed.includes("@")) {
|
||||
return trimmed.toLowerCase();
|
||||
}
|
||||
return trimmed.replace(/\s+/g, "");
|
||||
}
|
||||
|
||||
function extractHandleFromChatGuid(chatGuid: string): string | null {
|
||||
const parts = chatGuid.split(";");
|
||||
if (parts.length === 3 && parts[1] === "-") {
|
||||
const handle = parts[2]?.trim();
|
||||
if (handle) {
|
||||
return normalizeBlueBubblesHandle(handle);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseBlueBubblesTarget(raw: string): BlueBubblesTarget {
|
||||
const trimmed = stripBlueBubblesPrefix(raw);
|
||||
if (!trimmed) {
|
||||
throw new Error("BlueBubbles target is required");
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
|
||||
const servicePrefixed = resolveServicePrefixedTarget({
|
||||
trimmed,
|
||||
lower,
|
||||
servicePrefixes: SERVICE_PREFIXES,
|
||||
isChatTarget: (remainderLower) =>
|
||||
CHAT_ID_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
|
||||
CHAT_GUID_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
|
||||
CHAT_IDENTIFIER_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
|
||||
remainderLower.startsWith("group:"),
|
||||
parseTarget: parseBlueBubblesTarget,
|
||||
});
|
||||
if (servicePrefixed) {
|
||||
return servicePrefixed;
|
||||
}
|
||||
|
||||
const chatTarget = parseChatTargetPrefixesOrThrow({
|
||||
trimmed,
|
||||
lower,
|
||||
chatIdPrefixes: CHAT_ID_PREFIXES,
|
||||
chatGuidPrefixes: CHAT_GUID_PREFIXES,
|
||||
chatIdentifierPrefixes: CHAT_IDENTIFIER_PREFIXES,
|
||||
});
|
||||
if (chatTarget) {
|
||||
return chatTarget;
|
||||
}
|
||||
|
||||
const groupTarget = parseGroupTarget({ trimmed, lower });
|
||||
if (groupTarget) {
|
||||
return groupTarget;
|
||||
}
|
||||
|
||||
const rawChatGuid = parseRawChatGuid(trimmed);
|
||||
if (rawChatGuid) {
|
||||
return { kind: "chat_guid", chatGuid: rawChatGuid };
|
||||
}
|
||||
|
||||
const rawChatIdentifierTarget = parseRawChatIdentifierTarget(trimmed);
|
||||
if (rawChatIdentifierTarget) {
|
||||
return rawChatIdentifierTarget;
|
||||
}
|
||||
|
||||
return { kind: "handle", to: trimmed, service: "auto" };
|
||||
}
|
||||
|
||||
export function normalizeBlueBubblesAcpConversationId(
|
||||
conversationId: string,
|
||||
): { conversationId: string } | null {
|
||||
const trimmed = conversationId.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = parseBlueBubblesTarget(trimmed);
|
||||
if (parsed.kind === "handle") {
|
||||
const handle = normalizeBlueBubblesHandle(parsed.to);
|
||||
return handle ? { conversationId: handle } : null;
|
||||
}
|
||||
if (parsed.kind === "chat_id") {
|
||||
return { conversationId: String(parsed.chatId) };
|
||||
}
|
||||
if (parsed.kind === "chat_guid") {
|
||||
const handle = extractHandleFromChatGuid(parsed.chatGuid);
|
||||
return {
|
||||
conversationId: handle || parsed.chatGuid,
|
||||
};
|
||||
}
|
||||
return { conversationId: parsed.chatIdentifier };
|
||||
} catch {
|
||||
const handle = normalizeBlueBubblesHandle(trimmed);
|
||||
return handle ? { conversationId: handle } : null;
|
||||
}
|
||||
}
|
||||
|
||||
export function matchBlueBubblesAcpConversation(params: {
|
||||
bindingConversationId: string;
|
||||
conversationId: string;
|
||||
}): { conversationId: string; matchPriority: number } | null {
|
||||
const binding = normalizeBlueBubblesAcpConversationId(params.bindingConversationId);
|
||||
const conversation = normalizeBlueBubblesAcpConversationId(params.conversationId);
|
||||
if (!binding || !conversation) {
|
||||
return null;
|
||||
}
|
||||
if (binding.conversationId !== conversation.conversationId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
conversationId: conversation.conversationId,
|
||||
matchPriority: 2,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveBlueBubblesConversationIdFromTarget(target: string): string | undefined {
|
||||
return normalizeBlueBubblesAcpConversationId(target)?.conversationId;
|
||||
}
|
||||
|
||||
export { resolveAckReaction } from "../agents/identity.js";
|
||||
export {
|
||||
createActionGate,
|
||||
@@ -74,11 +309,6 @@ export {
|
||||
readStoreAllowFromForDmPolicy,
|
||||
resolveDmGroupAccessWithLists,
|
||||
} from "../security/dm-policy-shared.js";
|
||||
export {
|
||||
matchBlueBubblesAcpConversation,
|
||||
normalizeBlueBubblesAcpConversationId,
|
||||
resolveBlueBubblesConversationIdFromTarget,
|
||||
} from "../../extensions/bluebubbles/api.js";
|
||||
export { formatDocsLink } from "../terminal/links.js";
|
||||
export type { WizardPrompter } from "../wizard/prompts.js";
|
||||
export { isAllowedParsedChatSender } from "./allow-from.js";
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
import {
|
||||
normalizeIMessageHandle,
|
||||
parseChatAllowTargetPrefixes,
|
||||
parseChatTargetPrefixesOrThrow,
|
||||
resolveServicePrefixedAllowTarget,
|
||||
resolveServicePrefixedTarget,
|
||||
type ParsedChatTarget,
|
||||
} from "./imessage-targets.js";
|
||||
|
||||
export type { ChannelPlugin } from "./channel-plugin-common.js";
|
||||
export {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
@@ -13,14 +22,113 @@ export {
|
||||
} from "./channel-config-helpers.js";
|
||||
export { IMessageConfigSchema } from "../config/zod-schema.providers-core.js";
|
||||
export {
|
||||
matchIMessageAcpConversation,
|
||||
normalizeIMessageAcpConversationId,
|
||||
resolveIMessageConversationIdFromTarget,
|
||||
} from "../../extensions/imessage/api.js";
|
||||
export {
|
||||
normalizeIMessageHandle,
|
||||
parseChatAllowTargetPrefixes,
|
||||
parseChatTargetPrefixesOrThrow,
|
||||
resolveServicePrefixedAllowTarget,
|
||||
resolveServicePrefixedTarget,
|
||||
type ParsedChatTarget,
|
||||
} from "./imessage-targets.js";
|
||||
|
||||
type IMessageService = "imessage" | "sms" | "auto";
|
||||
|
||||
type IMessageTarget = ParsedChatTarget | { kind: "handle"; to: string; service: IMessageService };
|
||||
|
||||
const CHAT_ID_PREFIXES = ["chat_id:", "chatid:", "chat:"];
|
||||
const CHAT_GUID_PREFIXES = ["chat_guid:", "chatguid:", "guid:"];
|
||||
const CHAT_IDENTIFIER_PREFIXES = ["chat_identifier:", "chatidentifier:", "chatident:"];
|
||||
const SERVICE_PREFIXES: Array<{ prefix: string; service: IMessageService }> = [
|
||||
{ prefix: "imessage:", service: "imessage" },
|
||||
{ prefix: "sms:", service: "sms" },
|
||||
{ prefix: "auto:", service: "auto" },
|
||||
];
|
||||
|
||||
function startsWithAnyPrefix(value: string, prefixes: readonly string[]): boolean {
|
||||
return prefixes.some((prefix) => value.startsWith(prefix));
|
||||
}
|
||||
|
||||
function parseIMessageTarget(raw: string): IMessageTarget {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
throw new Error("iMessage target is required");
|
||||
}
|
||||
const lower = trimmed.toLowerCase();
|
||||
|
||||
const servicePrefixed = resolveServicePrefixedTarget({
|
||||
trimmed,
|
||||
lower,
|
||||
servicePrefixes: SERVICE_PREFIXES,
|
||||
isChatTarget: (remainderLower) =>
|
||||
startsWithAnyPrefix(remainderLower, [
|
||||
...CHAT_ID_PREFIXES,
|
||||
...CHAT_GUID_PREFIXES,
|
||||
...CHAT_IDENTIFIER_PREFIXES,
|
||||
]),
|
||||
parseTarget: parseIMessageTarget,
|
||||
});
|
||||
if (servicePrefixed) {
|
||||
return servicePrefixed;
|
||||
}
|
||||
|
||||
const chatTarget = parseChatTargetPrefixesOrThrow({
|
||||
trimmed,
|
||||
lower,
|
||||
chatIdPrefixes: CHAT_ID_PREFIXES,
|
||||
chatGuidPrefixes: CHAT_GUID_PREFIXES,
|
||||
chatIdentifierPrefixes: CHAT_IDENTIFIER_PREFIXES,
|
||||
});
|
||||
if (chatTarget) {
|
||||
return chatTarget;
|
||||
}
|
||||
|
||||
return { kind: "handle", to: trimmed, service: "auto" };
|
||||
}
|
||||
|
||||
export function normalizeIMessageAcpConversationId(
|
||||
conversationId: string,
|
||||
): { conversationId: string } | null {
|
||||
const trimmed = conversationId.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = parseIMessageTarget(trimmed);
|
||||
if (parsed.kind === "handle") {
|
||||
const handle = normalizeIMessageHandle(parsed.to);
|
||||
return handle ? { conversationId: handle } : null;
|
||||
}
|
||||
if (parsed.kind === "chat_id") {
|
||||
return { conversationId: String(parsed.chatId) };
|
||||
}
|
||||
if (parsed.kind === "chat_guid") {
|
||||
return { conversationId: parsed.chatGuid };
|
||||
}
|
||||
return { conversationId: parsed.chatIdentifier };
|
||||
} catch {
|
||||
const handle = normalizeIMessageHandle(trimmed);
|
||||
return handle ? { conversationId: handle } : null;
|
||||
}
|
||||
}
|
||||
|
||||
export function matchIMessageAcpConversation(params: {
|
||||
bindingConversationId: string;
|
||||
conversationId: string;
|
||||
}): { conversationId: string; matchPriority: number } | null {
|
||||
const binding = normalizeIMessageAcpConversationId(params.bindingConversationId);
|
||||
const conversation = normalizeIMessageAcpConversationId(params.conversationId);
|
||||
if (!binding || !conversation) {
|
||||
return null;
|
||||
}
|
||||
if (binding.conversationId !== conversation.conversationId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
conversationId: conversation.conversationId,
|
||||
matchPriority: 2,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveIMessageConversationIdFromTarget(target: string): string | undefined {
|
||||
return normalizeIMessageAcpConversationId(target)?.conversationId;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user