diff --git a/extensions/matrix/src/channel.ts b/extensions/matrix/src/channel.ts index e0752cf70..9b2a0efc2 100644 --- a/extensions/matrix/src/channel.ts +++ b/extensions/matrix/src/channel.ts @@ -164,13 +164,15 @@ export const matrixPlugin: ChannelPlugin = { }, messaging: { normalizeTarget: normalizeMatrixMessagingTarget, - looksLikeTargetId: (raw) => { - const trimmed = raw.trim(); - if (!trimmed) return false; - if (/^(matrix:)?[!#@]/i.test(trimmed)) return true; - return trimmed.includes(":"); + targetResolver: { + looksLikeId: (raw) => { + const trimmed = raw.trim(); + if (!trimmed) return false; + if (/^(matrix:)?[!#@]/i.test(trimmed)) return true; + return trimmed.includes(":"); + }, + hint: "", }, - targetHint: "", }, directory: { self: async () => null, diff --git a/extensions/msteams/src/channel.ts b/extensions/msteams/src/channel.ts index a41dcff7a..17fad386e 100644 --- a/extensions/msteams/src/channel.ts +++ b/extensions/msteams/src/channel.ts @@ -133,13 +133,15 @@ export const msteamsPlugin: ChannelPlugin = { }, messaging: { normalizeTarget: normalizeMSTeamsMessagingTarget, - looksLikeTargetId: (raw) => { - const trimmed = raw.trim(); - if (!trimmed) return false; - if (/^(conversation:|user:)/i.test(trimmed)) return true; - return trimmed.includes("@thread"); + targetResolver: { + looksLikeId: (raw) => { + const trimmed = raw.trim(); + if (!trimmed) return false; + if (/^(conversation:|user:)/i.test(trimmed)) return true; + return trimmed.includes("@thread"); + }, + hint: "", }, - targetHint: "", }, directory: { self: async () => null, diff --git a/extensions/zalo/src/channel.ts b/extensions/zalo/src/channel.ts index 0104437b7..61048a16a 100644 --- a/extensions/zalo/src/channel.ts +++ b/extensions/zalo/src/channel.ts @@ -150,12 +150,14 @@ export const zaloPlugin: ChannelPlugin = { actions: zaloMessageActions, messaging: { normalizeTarget: normalizeZaloMessagingTarget, - looksLikeTargetId: (raw) => { - const trimmed = raw.trim(); - if (!trimmed) return false; - return /^\d{3,}$/.test(trimmed); + targetResolver: { + looksLikeId: (raw) => { + const trimmed = raw.trim(); + if (!trimmed) return false; + return /^\d{3,}$/.test(trimmed); + }, + hint: "", }, - targetHint: "", }, directory: { self: async () => null, diff --git a/extensions/zalouser/src/channel.ts b/extensions/zalouser/src/channel.ts index ac581d1f2..8cab366ec 100644 --- a/extensions/zalouser/src/channel.ts +++ b/extensions/zalouser/src/channel.ts @@ -218,12 +218,14 @@ export const zalouserPlugin: ChannelPlugin = { if (!trimmed) return undefined; return trimmed.replace(/^(zalouser|zlu):/i, ""); }, - looksLikeTargetId: (raw) => { - const trimmed = raw.trim(); - if (!trimmed) return false; - return /^\d{3,}$/.test(trimmed); + targetResolver: { + looksLikeId: (raw) => { + const trimmed = raw.trim(); + if (!trimmed) return false; + return /^\d{3,}$/.test(trimmed); + }, + hint: "", }, - targetHint: "", }, directory: { self: async ({ cfg, accountId, runtime }) => { diff --git a/src/channels/plugins/index.ts b/src/channels/plugins/index.ts index 49e1767c8..a83d14186 100644 --- a/src/channels/plugins/index.ts +++ b/src/channels/plugins/index.ts @@ -74,4 +74,15 @@ export function normalizeChannelId(raw?: string | null): ChannelId | null { } export { discordPlugin, imessagePlugin, signalPlugin, slackPlugin, telegramPlugin, whatsappPlugin }; +export { + listDiscordDirectoryGroupsFromConfig, + listDiscordDirectoryPeersFromConfig, + listSlackDirectoryGroupsFromConfig, + listSlackDirectoryPeersFromConfig, + listTelegramDirectoryGroupsFromConfig, + listTelegramDirectoryPeersFromConfig, + listWhatsAppDirectoryGroupsFromConfig, + listWhatsAppDirectoryPeersFromConfig, + type DirectoryConfigParams, +} from "./directory-config.js"; export type { ChannelId, ChannelPlugin } from "./types.js"; diff --git a/src/infra/outbound/target-errors.ts b/src/infra/outbound/target-errors.ts index 14caf79c3..f6d303cb9 100644 --- a/src/infra/outbound/target-errors.ts +++ b/src/infra/outbound/target-errors.ts @@ -1,5 +1,7 @@ +import { formatTargetHint } from "./target-format.js"; + export function missingTargetMessage(provider: string, hint?: string): string { - return `Delivering to ${provider} requires target${formatHint(hint)}`; + return `Delivering to ${provider} requires target${formatTargetHint(hint)}`; } export function missingTargetError(provider: string, hint?: string): Error { @@ -7,7 +9,7 @@ export function missingTargetError(provider: string, hint?: string): Error { } export function ambiguousTargetMessage(provider: string, raw: string, hint?: string): string { - return `Ambiguous target "${raw}" for ${provider}. Provide a unique name or an explicit id.${formatHint(hint, true)}`; + return `Ambiguous target "${raw}" for ${provider}. Provide a unique name or an explicit id.${formatTargetHint(hint, true)}`; } export function ambiguousTargetError(provider: string, raw: string, hint?: string): Error { @@ -15,14 +17,9 @@ export function ambiguousTargetError(provider: string, raw: string, hint?: strin } export function unknownTargetMessage(provider: string, raw: string, hint?: string): string { - return `Unknown target "${raw}" for ${provider}.${formatHint(hint, true)}`; + return `Unknown target "${raw}" for ${provider}.${formatTargetHint(hint, true)}`; } export function unknownTargetError(provider: string, raw: string, hint?: string): Error { return new Error(unknownTargetMessage(provider, raw, hint)); } - -function formatHint(hint?: string, withLabel = false): string { - if (!hint) return ""; - return withLabel ? ` Hint: ${hint}` : ` ${hint}`; -} diff --git a/src/infra/outbound/target-format.ts b/src/infra/outbound/target-format.ts new file mode 100644 index 000000000..827d78a7d --- /dev/null +++ b/src/infra/outbound/target-format.ts @@ -0,0 +1,4 @@ +export function formatTargetHint(hint?: string, withLabel = false): string { + if (!hint) return ""; + return withLabel ? ` Hint: ${hint}` : ` ${hint}`; +} diff --git a/src/infra/outbound/target-resolver.ts b/src/infra/outbound/target-resolver.ts index e27db0eaa..ae587d667 100644 --- a/src/infra/outbound/target-resolver.ts +++ b/src/infra/outbound/target-resolver.ts @@ -164,24 +164,6 @@ function resolveMatch(params: { return { kind: "ambiguous" as const, entries: matches }; } -function looksLikeTargetId(params: { - channel: ChannelId; - raw: string; - normalized: string; -}): boolean { - const raw = params.raw.trim(); - if (!raw) return false; - const plugin = getChannelPlugin(params.channel); - const lookup = plugin?.messaging?.targetResolver?.looksLikeId; - if (lookup) return lookup(raw, params.normalized); - if (/^(channel|group|user):/i.test(raw)) return true; - if (/^[@#]/.test(raw)) return true; - if (/^\+?\d{6,}$/.test(raw)) return true; - if (raw.includes("@thread")) return true; - if (/^(conversation|user):/i.test(raw)) return true; - return false; -} - async function listDirectoryEntries(params: { cfg: ClawdbotConfig; channel: ChannelId; @@ -288,7 +270,19 @@ export async function resolveMessagingTarget(params: { const hint = plugin?.messaging?.targetResolver?.hint; const kind = detectTargetKind(raw, params.preferredKind); const normalized = normalizeTargetForProvider(params.channel, raw) ?? raw; - if (looksLikeTargetId({ channel: params.channel, raw, normalized })) { + const looksLikeTargetId = (): boolean => { + const trimmed = raw.trim(); + if (!trimmed) return false; + const lookup = plugin?.messaging?.targetResolver?.looksLikeId; + if (lookup) return lookup(trimmed, normalized); + if (/^(channel|group|user):/i.test(trimmed)) return true; + if (/^[@#]/.test(trimmed)) return true; + if (/^\+?\d{6,}$/.test(trimmed)) return true; + if (trimmed.includes("@thread")) return true; + if (/^(conversation|user):/i.test(trimmed)) return true; + return false; + }; + if (looksLikeTargetId()) { const directTarget = preserveTargetCase(params.channel, raw, normalized); return { ok: true,