import type { ClawdbotConfig } from "../../config/types.js"; import type { ChannelDirectoryEntry } from "./types.js"; import { resolveSlackAccount } from "../../slack/accounts.js"; import { resolveDiscordAccount } from "../../discord/accounts.js"; import { resolveTelegramAccount } from "../../telegram/accounts.js"; import { resolveWhatsAppAccount } from "../../web/accounts.js"; import { normalizeSlackMessagingTarget } from "./normalize/slack.js"; import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../whatsapp/normalize.js"; export type DirectoryConfigParams = { cfg: ClawdbotConfig; accountId?: string | null; query?: string | null; limit?: number | null; }; export async function listSlackDirectoryPeersFromConfig( params: DirectoryConfigParams, ): Promise { const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId }); const q = params.query?.trim().toLowerCase() || ""; const ids = new Set(); for (const entry of account.dm?.allowFrom ?? []) { const raw = String(entry).trim(); if (!raw || raw === "*") continue; ids.add(raw); } for (const id of Object.keys(account.config.dms ?? {})) { const trimmed = id.trim(); if (trimmed) ids.add(trimmed); } for (const channel of Object.values(account.config.channels ?? {})) { for (const user of channel.users ?? []) { const raw = String(user).trim(); if (raw) ids.add(raw); } } return Array.from(ids) .map((raw) => raw.trim()) .filter(Boolean) .map((raw) => { const mention = raw.match(/^<@([A-Z0-9]+)>$/i); const normalizedUserId = (mention?.[1] ?? raw).replace(/^(slack|user):/i, "").trim(); if (!normalizedUserId) return null; const target = `user:${normalizedUserId}`; return normalizeSlackMessagingTarget(target) ?? target.toLowerCase(); }) .filter((id): id is string => Boolean(id)) .filter((id) => id.startsWith("user:")) .filter((id) => (q ? id.toLowerCase().includes(q) : true)) .slice(0, params.limit && params.limit > 0 ? params.limit : undefined) .map((id) => ({ kind: "user", id }) as const); } export async function listSlackDirectoryGroupsFromConfig( params: DirectoryConfigParams, ): Promise { const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId }); const q = params.query?.trim().toLowerCase() || ""; return Object.keys(account.config.channels ?? {}) .map((raw) => raw.trim()) .filter(Boolean) .map((raw) => normalizeSlackMessagingTarget(raw) ?? raw.toLowerCase()) .filter((id) => id.startsWith("channel:")) .filter((id) => (q ? id.toLowerCase().includes(q) : true)) .slice(0, params.limit && params.limit > 0 ? params.limit : undefined) .map((id) => ({ kind: "group", id }) as const); } export async function listDiscordDirectoryPeersFromConfig( params: DirectoryConfigParams, ): Promise { const account = resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId }); const q = params.query?.trim().toLowerCase() || ""; const ids = new Set(); for (const entry of account.config.dm?.allowFrom ?? []) { const raw = String(entry).trim(); if (!raw || raw === "*") continue; ids.add(raw); } for (const id of Object.keys(account.config.dms ?? {})) { const trimmed = id.trim(); if (trimmed) ids.add(trimmed); } for (const guild of Object.values(account.config.guilds ?? {})) { for (const entry of guild.users ?? []) { const raw = String(entry).trim(); if (raw) ids.add(raw); } for (const channel of Object.values(guild.channels ?? {})) { for (const user of channel.users ?? []) { const raw = String(user).trim(); if (raw) ids.add(raw); } } } return Array.from(ids) .map((raw) => raw.trim()) .filter(Boolean) .map((raw) => { const mention = raw.match(/^<@!?(\d+)>$/); const cleaned = (mention?.[1] ?? raw).replace(/^(discord|user):/i, "").trim(); if (!/^\d+$/.test(cleaned)) return null; return `user:${cleaned}`; }) .filter((id): id is string => Boolean(id)) .filter((id) => (q ? id.toLowerCase().includes(q) : true)) .slice(0, params.limit && params.limit > 0 ? params.limit : undefined) .map((id) => ({ kind: "user", id }) as const); } export async function listDiscordDirectoryGroupsFromConfig( params: DirectoryConfigParams, ): Promise { const account = resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId }); const q = params.query?.trim().toLowerCase() || ""; const ids = new Set(); for (const guild of Object.values(account.config.guilds ?? {})) { for (const channelId of Object.keys(guild.channels ?? {})) { const trimmed = channelId.trim(); if (trimmed) ids.add(trimmed); } } return Array.from(ids) .map((raw) => raw.trim()) .filter(Boolean) .map((raw) => { const mention = raw.match(/^<#(\d+)>$/); const cleaned = (mention?.[1] ?? raw).replace(/^(discord|channel|group):/i, "").trim(); if (!/^\d+$/.test(cleaned)) return null; return `channel:${cleaned}`; }) .filter((id): id is string => Boolean(id)) .filter((id) => (q ? id.toLowerCase().includes(q) : true)) .slice(0, params.limit && params.limit > 0 ? params.limit : undefined) .map((id) => ({ kind: "group", id }) as const); } export async function listTelegramDirectoryPeersFromConfig( params: DirectoryConfigParams, ): Promise { const account = resolveTelegramAccount({ cfg: params.cfg, accountId: params.accountId }); const q = params.query?.trim().toLowerCase() || ""; const raw = [ ...(account.config.allowFrom ?? []).map((entry) => String(entry)), ...Object.keys(account.config.dms ?? {}), ]; return Array.from( new Set( raw .map((entry) => entry.trim()) .filter(Boolean) .map((entry) => entry.replace(/^(telegram|tg):/i, "")), ), ) .map((entry) => { const trimmed = entry.trim(); if (!trimmed) return null; if (/^-?\d+$/.test(trimmed)) return trimmed; const withAt = trimmed.startsWith("@") ? trimmed : `@${trimmed}`; return withAt; }) .filter((id): id is string => Boolean(id)) .filter((id) => (q ? id.toLowerCase().includes(q) : true)) .slice(0, params.limit && params.limit > 0 ? params.limit : undefined) .map((id) => ({ kind: "user", id }) as const); } export async function listTelegramDirectoryGroupsFromConfig( params: DirectoryConfigParams, ): Promise { const account = resolveTelegramAccount({ cfg: params.cfg, accountId: params.accountId }); const q = params.query?.trim().toLowerCase() || ""; return Object.keys(account.config.groups ?? {}) .map((id) => id.trim()) .filter((id) => Boolean(id) && id !== "*") .filter((id) => (q ? id.toLowerCase().includes(q) : true)) .slice(0, params.limit && params.limit > 0 ? params.limit : undefined) .map((id) => ({ kind: "group", id }) as const); } export async function listWhatsAppDirectoryPeersFromConfig( params: DirectoryConfigParams, ): Promise { const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.accountId }); const q = params.query?.trim().toLowerCase() || ""; return (account.allowFrom ?? []) .map((entry) => String(entry).trim()) .filter((entry) => Boolean(entry) && entry !== "*") .map((entry) => normalizeWhatsAppTarget(entry) ?? "") .filter(Boolean) .filter((id) => !isWhatsAppGroupJid(id)) .filter((id) => (q ? id.toLowerCase().includes(q) : true)) .slice(0, params.limit && params.limit > 0 ? params.limit : undefined) .map((id) => ({ kind: "user", id }) as const); } export async function listWhatsAppDirectoryGroupsFromConfig( params: DirectoryConfigParams, ): Promise { const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.accountId }); const q = params.query?.trim().toLowerCase() || ""; return Object.keys(account.groups ?? {}) .map((id) => id.trim()) .filter((id) => Boolean(id) && id !== "*") .filter((id) => (q ? id.toLowerCase().includes(q) : true)) .slice(0, params.limit && params.limit > 0 ? params.limit : undefined) .map((id) => ({ kind: "group", id }) as const); }