import { listChannelMappings, readCategoryId, type ChannelMapping, } from './discord-map.ts' import { plusAddressFor } from './plus-alias.ts' import { listBotRecords, listWakeSecrets, normalizeName, secretNameFor, secretUseStatusFrom, urlHostOf, type BotSummary, } from './registry.ts' import { plainClone } from './plain.ts' export type ListResult = { categoryId: string channels: ChannelMapping[] bots: BotSummary[] } /** * List registered Grok Bots without full webhook URLs. * * @returns Roster summaries with urlHost, pendingSecret, needsHostApproval, channelId, and plus-alias emailAddress * @example * import list from 'kody:@kentcdodds/grok-bot/list' * const roster = await list() * // => { categoryId, channels, bots: [{ id, name, aliases, secretName, pendingSecret, needsHostApproval, urlHost, channelId, emailAddress }] } */ export default async function list(): Promise { const [records, secrets, categoryId, channels] = await Promise.all([ listBotRecords(), listWakeSecrets(), readCategoryId(), listChannelMappings(), ]) const bots: BotSummary[] = records.map((record) => { const secret = secretUseStatusFrom(secrets, record.secretName) return { id: record.id, name: record.name, aliases: record.aliases, secretName: record.secretName, pendingSecret: !secret.exists, needsHostApproval: secret.needsHostApproval, urlHost: urlHostOf(record.url), emailAddress: plusAddressFor(record.name), ...(secret.approvalUrl && secret.needsHostApproval ? { approvalUrl: secret.approvalUrl } : {}), ...(record.channelId ? { channelId: record.channelId } : {}), } }) for (const channel of channels) { if (bots.some((bot) => bot.id === channel.botId)) continue bots.push({ id: channel.botId, name: channel.name, aliases: [normalizeName(channel.name)], secretName: secretNameFor(channel.botId), pendingSecret: true, needsHostApproval: false, urlHost: '', emailAddress: plusAddressFor(channel.name), channelId: channel.channelId, }) } return plainClone({ categoryId, channels, bots }) }