all repos — stealth-developers @ 25f8fcb5ae1044a683b1a6935228890b9d88502b

feat: handle media in bug reports
vi v@vt3e.cat
Sun, 01 Mar 2026 03:47:06 +0000
commit

25f8fcb5ae1044a683b1a6935228890b9d88502b

parent

6c06f4e3a6d9cddf81f94f2cd254d222fef3feab

M src/events/messageCreate.tssrc/events/messageCreate.ts

@@ -24,8 +24,12 @@ event: Events.MessageCreate,

async execute(client: Client, message: Message) { if (!client.user) return; - if (message.content.includes("grok is this true")) { - if (message.channel.isSendable()) await message.channel.send("yeh"); + const triggers = ["grok is this true", "grok is this false "]; + if ( + message.channel.isSendable() && + message.content.includes("grok is this true") + ) { + message.channel.send("yeh"); } const ticket = db

@@ -43,7 +47,6 @@ if (!ticket) return;

try { const isSystem = message.author.id === client.user.id; - const isReporter = message.author.id === ticket.authorId; const isModerator = await hasManagerPermissions( message?.member as GuildMember, );
M src/interactions/commands/bug/_shared.tssrc/interactions/commands/bug/_shared.ts

@@ -3,12 +3,15 @@ import type { Bug } from "@/database";

import { text, thumbnail } from "@/utils/components"; import { ActionRowBuilder, + type AttachmentBuilder, ButtonBuilder, ButtonStyle, type Client, ContainerBuilder, FileUploadBuilder, LabelBuilder, + MediaGalleryBuilder, + MediaGalleryItemBuilder, ModalBuilder, SectionBuilder, type Snowflake,

@@ -65,6 +68,7 @@ // -- main -----------------------------------------------------------------------------------------

export async function constructContainer( bug: Bug, userId: Snowflake, + file?: AttachmentBuilder, ): Promise<ContainerBuilder | undefined> { const project = PROJECT_MAP[bug.projects[0]]; if (!project) return undefined;

@@ -79,22 +83,21 @@

const section = new SectionBuilder().addTextDisplayComponents(bodyText); if (icon) section.setThumbnailAccessory(icon); - /* - TODO)) render media in containers - const media = await db - .select() - .from(attachments) - .where( - and(eq(attachments.ownerId, bug.id), eq(attachments.ownerType, "bug")), - ); - const urls = media.map((attachment) => attachment.url); - */ - const buttons = await constructButtons(bug); - return new ContainerBuilder() - .addSectionComponents(section) - .addTextDisplayComponents(footerText) - .addActionRowComponents(buttons); + + const gallery = file + ? new MediaGalleryBuilder().addItems( + new MediaGalleryItemBuilder().setURL(`attachment://${file.name}`), + ) + : undefined; + + const container = new ContainerBuilder(); + container.addSectionComponents(section); + if (file) container.addMediaGalleryComponents(gallery); + container.addTextDisplayComponents(footerText); + container.addActionRowComponents(buttons); + + return container; } export function buildReportModal(bugId?: string, bug?: Bug) {
M src/interactions/commands/bug/modals.tssrc/interactions/commands/bug/modals.ts

@@ -1,5 +1,9 @@

+import { unlink, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; import { bugs, db } from "@/database"; import { getGuild } from "@/utils/queries"; +import { AttachmentBuilder } from "discord.js"; import type { Client, ModalSubmitInteraction } from "discord.js"; import { eq, sql } from "drizzle-orm"; import {

@@ -55,8 +59,43 @@ closedAt: null,

}) .returning(); - // TODO)) media uploads to s3 - const container = await constructContainer(bug[0], interaction.user.id); + const tempFiles: string[] = []; + let attachment: AttachmentBuilder | undefined; + + if (media) { + for (const [_, _attachment] of media) { + try { + const fileUrl = _attachment.url; + const fileName = _attachment.name; + if (!fileUrl) continue; + + const res = await fetch(fileUrl); + if (!res.ok) continue; + + const arrayBuffer = await res.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + + const safeName = fileName.replace(/[^a-zA-Z0-9._-]/g, "_"); + const tmpPath = path.join( + os.tmpdir(), + `${Date.now()}-${Math.random().toString(36).slice(2)}-${safeName}`, + ); + + await writeFile(tmpPath, buffer); + tempFiles.push(tmpPath); + + attachment = new AttachmentBuilder(buffer, { name: fileName }); + } catch (err) { + console.error("error processing uploaded file:", err); + } + } + } + + const container = await constructContainer( + bug[0], + interaction.user.id, + attachment, + ); if (!container) return; const { data, exists } = await getGuild(interaction.guildId);

@@ -68,11 +107,19 @@

const channel = await interaction.guild?.channels.fetch(channelId); if (!channel || !channel.isSendable()) return; - // send message const message = await channel.send({ components: [container], flags: ["IsComponentsV2"], + ...(attachment ? { files: [attachment] } : {}), }); + + for (const filePath of tempFiles) { + try { + await unlink(filePath); + } catch (err) { + console.error("error deleting temp file:", err); + } + } // thread function truncateTitle(title: string, maxLength: number): string {