import { option } from "@purrkit/router"; import db, { bugs, eq, getActor, sql } from "@stealth-developers/db"; import { AttachmentBuilder } from "discord.js"; import { errorMessage, getTextChannel, logger } from "@/lib"; import { useGetData } from "@/middleware"; import { constructBugContainer } from "./shared"; export const bugModal = useGetData.modal("bug-report", { run: async (interaction, _, { actor, guild }) => { if (!actor || !guild) return errorMessage(interaction, "You must be in a server to report a bug."); const { fields: _fields } = interaction; const fields = { affected: _fields.getStringSelectValues("affected"), title: _fields.getTextInputValue("title"), description: _fields.getTextInputValue("description"), media: _fields.getUploadedFiles("media", false), }; await interaction.deferReply({ flags: ["Ephemeral"] }); const bugRes = await db.transaction(async (tx) => { const [res] = await tx .select({ count: sql`COUNT(*)`.mapWith(Number) }) .from(bugs) .where(eq(bugs.guildId, guild.id)); if (!res) return null; const { count } = res; const bugNumber = count + 1; return await tx .insert(bugs) .values({ guildId: guild.id, actorId: actor.id, bugNumber: bugNumber, status: "open", title: fields.title, description: fields.description, affectedProjects: [...fields.affected], sent: false, messageId: null, threadId: null, createdAt: new Date(), updatedAt: new Date(), closedAt: null, }) .returning(); }); if (!bugRes) return errorMessage(interaction, "Failed to create bug report."); const bug = bugRes[0]; if (!bug) return errorMessage(interaction, "Failed to create bug report."); let attachment: AttachmentBuilder | undefined; if (fields.media && fields.media.size > 0) { const [_, _attachment] = Array.from(fields.media)[0]!; try { const fileUrl = _attachment.url; const fileName = _attachment.name; if (fileUrl) { const res = await fetch(fileUrl); if (res.ok) { const arrayBuffer = await res.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); attachment = new AttachmentBuilder(buffer, { name: fileName }); } } } catch (err) { console.error("error processing uploaded file:", err); } } const container = await constructBugContainer(bug, actor, attachment); if (!container) return; const channel = await getTextChannel("1520861689048993843"); if (!channel) return errorMessage(interaction, "Failed to send bug report."); const message = await channel.send({ components: [container], flags: ["IsComponentsV2"], ...(attachment ? { files: [attachment] } : {}), }); function truncateTitle(title: string, maxLength: number): string { return title.length > maxLength ? `${title.substring(0, maxLength)}...` : title; } const paddedNumber = bug.bugNumber.toString().padStart(4, "0"); const threadName = `#${paddedNumber} ${truncateTitle(fields.title, 150)}`; const thread = await message.startThread({ name: threadName, autoArchiveDuration: 1440, }); await db .update(bugs) .set({ threadId: thread.id, messageId: message.id }) .where(eq(bugs.id, bug.id)); thread.send({ content: "Use this space to discuss the bug, provide additional details, or ask questions.", }); interaction.editReply( `Bug #${paddedNumber} created successfully! Find it here: ${message.url}`, ); }, }); export const editBugModal = useGetData.modal("m-edit-bug", { options: { id: option.integer({ required: true }), }, run: async (interaction, { id }, { actor, guild }) => { if (!actor || !guild) return errorMessage(interaction, "You must be in a server to edit a bug."); const { fields: _fields } = interaction; const fields = { affected: _fields.getStringSelectValues("affected"), title: _fields.getTextInputValue("title"), description: _fields.getTextInputValue("description"), }; await interaction.deferReply({ flags: ["Ephemeral"] }); const [bug] = await db.select().from(bugs).where(eq(bugs.id, id)); if (!bug) return errorMessage(interaction, "Bug report not found."); const [updatedBug] = await db .update(bugs) .set({ title: fields.title, description: fields.description, affectedProjects: [...fields.affected], updatedAt: new Date(), }) .where(eq(bugs.id, bug.id)) .returning(); if (!updatedBug) return errorMessage(interaction, "Failed to update bug report."); const originalActor = await getActor(updatedBug.actorId); if (!originalActor) return errorMessage(interaction, "Failed to fetch original bug author."); const channel = await getTextChannel("1520861689048993843"); if (!channel) return errorMessage(interaction, "Failed to find bug report channel."); let message; if (updatedBug.messageId) { try { message = await channel.messages.fetch(updatedBug.messageId); } catch (err) { logger.error(err, "error fetching discord message"); } } const container = await constructBugContainer(updatedBug, originalActor, undefined, message); if (!container) return; if (message) { try { await message.edit({ components: [container], }); } catch (err) { logger.error(err, "error updating discord message"); } } const paddedNumber = updatedBug.bugNumber.toString().padStart(4, "0"); await interaction.editReply({ content: `Bug #${paddedNumber} updated,`, }); }, });