import { packageStorage } from 'kody:runtime'
import {
botKey,
normalizeName,
parseBotRecord,
secretNameFor,
writeBotRecord,
type BotRecord,
} from './registry.ts'
export const GROK_BOT_CATEGORY_ID = '1542616354274676827'
export const DISCORD_CATEGORY_KEY = 'discord.categoryId'
export const CHANNEL_PREFIX = 'discord.channel.'
export const BOT_CHANNEL_PREFIX = 'discord.botChannel.'
export const KNOWN_GROK_BOT_CHANNELS = [
{ name: 'kody', channelId: '1542616408020619334', botId: '71e7550e-746d-417f-b253-05165975ff69' },
{ name: 'cole', channelId: '1542616409694015558', botId: '6519d644-b6ac-4e61-989c-3d96d0b63cca' },
{ name: 'connie', channelId: '1542616411204096020', botId: '57072205-edee-46ed-b4fa-a24b657ef369' },
{ name: 'howie', channelId: '1542616412428828822', botId: '021d6409-01e7-4af0-a59c-4cbf2785cb50' },
{ name: 'pam', channelId: '1542616413708222547', botId: '58b84955-b713-4814-b0ca-b7982b4b5829' },
{ name: 'felix', channelId: '1542616415280963725', botId: '60f91f03-6b17-4b1a-a590-529613c79977' },
{ name: 'arthur', channelId: '1542616416547766332', botId: '775984a5-71ac-4e8c-bba3-b93de8ff6a06' },
{ name: 'marley', channelId: '1542616417948536913', botId: 'a08ff635-d146-4205-bb62-d538e9723d77' },
{ name: 'scott', channelId: '1542616422168002612', botId: 'b5cf16d8-dde6-4d3d-b5b8-4452bfade775' },
{ name: 'imogen', channelId: '1542616423845728337', botId: 'f835c77a-c93f-424e-a0f1-f191c2235e19' },
] as const
export type ChannelMapping = {
channelId: string
botId: string
name: string
}
function listEntries(listed: unknown): Array<{ key?: string; value?: unknown }> {
if (Array.isArray(listed)) {
return listed.filter((item) => item && typeof item === 'object') as Array<{
key?: string
value?: unknown
}>
}
if (listed && typeof listed === 'object') {
const entries = (listed as { entries?: unknown }).entries
if (Array.isArray(entries)) {
return entries.filter((item) => item && typeof item === 'object') as Array<{
key?: string
value?: unknown
}>
}
}
return []
}
export function channelSlug(name: string): string {
return name
.trim()
.toLowerCase()
.replace(/[^a-z0-9-]+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
.slice(0, 100)
}
export function channelKey(channelId: string): string {
return `${CHANNEL_PREFIX}${channelId}`
}
export function botChannelKey(botId: string): string {
return `${BOT_CHANNEL_PREFIX}${botId}`
}
export function parseChannelMapping(value: unknown): ChannelMapping | null {
let parsed = value
if (typeof parsed === 'string') {
try {
parsed = JSON.parse(parsed)
} catch {
return null
}
}
if (!parsed || typeof parsed !== 'object') return null
const row = parsed as Record<string, unknown>
const channelId = typeof row.channelId === 'string' ? row.channelId.trim() : ''
const botId = typeof row.botId === 'string' ? row.botId.trim() : ''
const name = typeof row.name === 'string' ? row.name.trim() : ''
if (!channelId || !botId || !name) return null
return { channelId, botId, name }
}
export async function readCategoryId(): Promise<string> {
const raw = await packageStorage().get(DISCORD_CATEGORY_KEY)
const stored = typeof raw === 'string' ? raw.trim() : ''
return stored || GROK_BOT_CATEGORY_ID
}
export async function writeCategoryId(categoryId: string): Promise<string> {
const id = categoryId.trim() || GROK_BOT_CATEGORY_ID
await packageStorage().set(DISCORD_CATEGORY_KEY, id)
return id
}
export async function writeChannelMapping(mapping: ChannelMapping): Promise<void> {
const bucket = packageStorage()
const previousRaw = await bucket.get(botChannelKey(mapping.botId))
const previous = typeof previousRaw === 'string' ? previousRaw.trim() : ''
if (previous && previous !== mapping.channelId) {
await bucket.delete(channelKey(previous))
}
await bucket.set(channelKey(mapping.channelId), {
channelId: mapping.channelId,
botId: mapping.botId,
name: mapping.name,
})
await bucket.set(botChannelKey(mapping.botId), mapping.channelId)
}
export async function getMappingByChannelId(
channelId: string,
): Promise<ChannelMapping | null> {
if (!channelId.trim()) return null
return parseChannelMapping(await packageStorage().get(channelKey(channelId.trim())))
}
export async function getMappingByBotId(botId: string): Promise<ChannelMapping | null> {
if (!botId.trim()) return null
const channelId = await packageStorage().get(botChannelKey(botId.trim()))
const id = typeof channelId === 'string' ? channelId.trim() : ''
if (!id) return null
return getMappingByChannelId(id)
}
export async function listChannelMappings(): Promise<ChannelMapping[]> {
const listed = await packageStorage().list({ prefix: CHANNEL_PREFIX })
const mappings: ChannelMapping[] = []
const seen = new Set<string>()
for (const entry of listEntries(listed)) {
const value = 'value' in entry ? entry.value : entry
const mapping = parseChannelMapping(value)
if (!mapping || seen.has(mapping.channelId)) continue
seen.add(mapping.channelId)
mappings.push(mapping)
}
return mappings
}
export async function getMappingByName(name: string): Promise<ChannelMapping | null> {
const slug = channelSlug(name)
if (!slug) return null
const mappings = await listChannelMappings()
return mappings.find((mapping) => channelSlug(mapping.name) === slug) ?? null
}
export async function resolveChannelMapping(event: {
channelId?: string | null
parentChannelId?: string | null
threadId?: string | null
}): Promise<ChannelMapping | null> {
const ids = [event.channelId, event.parentChannelId, event.threadId]
for (const id of ids) {
if (typeof id !== 'string' || !id.trim()) continue
const mapping = await getMappingByChannelId(id)
if (mapping) return mapping
}
return null
}
export async function upsertStubBotRecord(input: {
id: string
name: string
channelId: string
}): Promise<BotRecord> {
const now = new Date().toISOString()
const existing = parseBotRecord(await packageStorage().get(botKey(input.id)))
const normalized = normalizeName(input.name)
const aliases = existing
? Array.from(new Set([...existing.aliases, normalized]))
: [normalized]
const record: BotRecord = {
id: input.id,
name: existing?.name || input.name,
aliases,
url: existing?.url ?? '',
secretName: existing?.secretName || secretNameFor(input.id),
channelId: input.channelId,
createdAt: existing?.createdAt ?? now,
updatedAt: now,
}
await writeBotRecord(record)
return record
}