← Public packages
@kentcdodds/grok-bot
Wake a named Grok Bot from Kody by posting a prompt to that bot's own Cursor webhook.
src/discord-attachments.ts
108 lines · 3.3 KB · TypeScriptexport type DiscordImage = {
url: string
filename: string
contentType: string
}
const IMAGE_EXT = /\.(png|jpe?g|gif|webp|avif|bmp)$/i
function isImageName(filename: string, contentType: string, url: string): boolean {
if (contentType.toLowerCase().startsWith('image/')) return true
return IMAGE_EXT.test(filename) || IMAGE_EXT.test(url.split('?')[0] || '')
}
function pushImage(
images: DiscordImage[],
seen: Set<string>,
row: Record<string, unknown>,
): void {
const url =
(typeof row.url === 'string' && row.url.trim()) ||
(typeof row.proxy_url === 'string' && row.proxy_url.trim()) ||
''
if (!url || seen.has(url)) return
const filename =
(typeof row.filename === 'string' && row.filename.trim()) ||
url.split('/').pop()?.split('?')[0] ||
'image'
const contentType =
(typeof row.content_type === 'string' && row.content_type.trim()) ||
(typeof row.contentType === 'string' && row.contentType.trim()) ||
''
if (!isImageName(filename, contentType, url)) return
seen.add(url)
images.push({
url,
filename,
contentType: contentType || 'image/png',
})
}
/**
* Pull image attachments off a raw Discord MESSAGE_CREATE (or wrapped
* subscription payload). `normalize-message-created` drops attachments, so
* grok-bot has to read the raw event.
*/
export function collectDiscordImages(raw: unknown, depth = 0): DiscordImage[] {
const images: DiscordImage[] = []
const seen = new Set<string>()
visit(raw, 0)
return images
function visit(value: unknown, level: number): void {
if (level > 6 || value == null) return
if (Array.isArray(value)) {
for (const item of value) visit(item, level + 1)
return
}
if (typeof value !== 'object') return
const row = value as Record<string, unknown>
if (Array.isArray(row.attachments)) {
for (const att of row.attachments) {
if (att && typeof att === 'object') pushImage(images, seen, att as Record<string, unknown>)
}
}
if (row.image && typeof row.image === 'object') {
pushImage(images, seen, row.image as Record<string, unknown>)
}
if (row.thumbnail && typeof row.thumbnail === 'object') {
pushImage(images, seen, row.thumbnail as Record<string, unknown>)
}
if (Array.isArray(row.embeds)) visit(row.embeds, level + 1)
if (row.payload) visit(row.payload, level + 1)
if (row.event && row.event !== value) visit(row.event, level + 1)
if (row.d) visit(row.d, level + 1)
if (row.message && row.message !== value) visit(row.message, level + 1)
}
}
export function buildImageWakePrompt(input: {
botName: string
content: string
images: Array<DiscordImage>
}): string {
const lines: string[] = []
const isImogen = input.botName.trim().toLowerCase() === 'imogen'
if (isImogen) {
lines.push(
'Write one brief copyable alt block for the attached image. Most important part only. Nothing else. Reply in this Discord channel; stay quiet in Grok Bot.',
)
if (input.content) {
lines.push('')
lines.push(`Kent said: ${input.content}`)
}
} else if (input.content) {
lines.push(input.content)
} else {
lines.push('Kent posted an image in Discord.')
}
if (input.images.length) {
lines.push('')
lines.push('Attached image(s):')
for (const image of input.images) {
lines.push(`- ${image.filename}: ${image.url}`)
}
lines.push('Look at those image URLs. They are the message, not decoration.')
}
return lines.join('\n')
}