import type { Actor, Bug } from "@stealth-developers/db"; import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, ContainerBuilder, FileUploadBuilder, LabelBuilder, MediaGalleryBuilder, MediaGalleryItemBuilder, Message, ModalBuilder, SectionBuilder, StringSelectMenuBuilder, TextDisplayBuilder, TextInputBuilder, TextInputStyle, ThumbnailBuilder, } from "discord.js"; import { bugStatusButton, editButton } from "./buttons"; import { bugModal, editBugModal } from "./modals"; export const Projects = { gw: { universe: "6583326485", name: "ground war", displayName: "Ground War", iconURL: "https://tr.rbxcdn.com/180DAY-08f5294f7b48367cccbe389e705a4060/256/256/Image/Webp/noFilter", }, wft: { universe: "21449357", name: "warfare tycoon", displayName: "Warfare Tycoon", iconURL: "https://tr.rbxcdn.com/180DAY-3fd1cc01fe04259c77a635662f096c9e/256/256/Image/Webp/noFilter", codes: [ { code: "FIRSTCODE", expired: true, addedAt: "2022-09-03T12:12:00Z", }, { code: "40KLIKES", expired: false, addedAt: "2022-09-29T18:31:00Z", }, { code: "HAPPYHOLIDAYS", expired: true, addedAt: "2022-12-24T21:27:00Z", }, { code: "50KLIKES", expired: false, addedAt: "2022-12-24T21:34:00Z", }, { code: "BLACKHAWKISHERE", expired: false, addedAt: "2022-12-24T21:32:00Z", }, { code: "60KLIKES", expired: false, addedAt: "2023-03-09T21:19:00Z", }, { code: "70KLIKES", expired: false, addedAt: "2023-07-07T20:43:00Z", }, ], }, ab: { universe: "5611522097", name: "airsoft battles", displayName: "Airsoft Battles", iconURL: "https://tr.rbxcdn.com/180DAY-704ac49e2129f50ca709751588cdd45b/256/256/Image/Webp/noFilter", }, } as const; type ProjectChoice = T extends "name" ? { name: string; value: string } : { label: string; value: string; default: boolean }; export function getProjectChoices( label: T = "name" as T, defaultProjectKey?: string, ): ProjectChoice[] { return Object.entries(Projects).map(([key, project]) => label === "name" ? { name: project.displayName, value: key } : { label: project.displayName, value: key, default: key === defaultProjectKey, }, ) as ProjectChoice[]; } export function buildReportModal(bug?: Bug) { const projectOptions = getProjectChoices("label"); const gameLabel = new LabelBuilder() .setLabel("Affected Game") .setDescription(`The game affected by this bug`) .setStringSelectMenuComponent( new StringSelectMenuBuilder() .setMaxValues(1) .setMinValues(1) .setCustomId("affected") .setPlaceholder(`Select a game`) .addOptions(projectOptions), ); const titleLabel = new LabelBuilder() .setLabel("Bug Title") .setDescription("A short summary of the bug") .setTextInputComponent( (() => { const input = new TextInputBuilder() .setCustomId("title") .setStyle(TextInputStyle.Short) .setValue(bug?.title ?? "") .setRequired(true) .setMaxLength(128); return input; })(), ); const descriptionLabel = new LabelBuilder() .setLabel("Bug Description") .setDescription("Describe the bug in detail") .setTextInputComponent( (() => { const input = new TextInputBuilder() .setCustomId("description") .setStyle(TextInputStyle.Paragraph) .setValue(bug?.description ?? "") .setRequired(true) .setMaxLength(1024); return input; })(), ); const modalId = bug ? editBugModal.id({ id: bug.id }) : bugModal.id(); const modal = new ModalBuilder().setCustomId(modalId).setTitle("Report a Bug"); modal.setLabelComponents(gameLabel, titleLabel, descriptionLabel); if (!bug) { const mediaLabel = new LabelBuilder() .setLabel("Media") .setDescription("Provide any screenshots or videos that demonstrate the bug") .setFileUploadComponent(new FileUploadBuilder().setCustomId("media").setRequired(false)); modal.addLabelComponents(mediaLabel); } return modal; } export async function constructButtons(bug: Bug) { const row = new ActionRowBuilder(); const isOpen = bug.status === "open"; const statusLabel = isOpen ? "Close" : "Reopen"; const _statusToggle = new ButtonBuilder() .setCustomId(bugStatusButton.id({ id: bug.id, status: bug.status || "open" })) .setLabel(statusLabel) .setStyle(isOpen ? ButtonStyle.Danger : ButtonStyle.Success); const _editButton = new ButtonBuilder() .setCustomId(editButton.id({ id: bug.id })) .setLabel("Edit") .setStyle(ButtonStyle.Secondary); row.addComponents(_statusToggle, _editButton); return row; } export function text(content: string) { return new TextDisplayBuilder().setContent(content); } export function thumbnail(url?: string) { return url ? new ThumbnailBuilder().setURL(url) : undefined; } export async function constructBugContainer( bug: Bug, actor: Actor, file?: AttachmentBuilder, existingMessage?: Message, ): Promise { const affected = bug.affectedProjects[0]; if (!affected) return undefined; // @ts-expect-error: shudup const project = Projects[affected]; if (!project) return undefined; const { title, description, bugNumber } = bug; const bodyText = text(`### ${title}\n${description}`); const footerText = text( [`-# #${bugNumber}`, project.displayName, `Reported by <@${actor.discordId!}>`].join(" • "), ); const icon = thumbnail(project.iconURL); const section = new SectionBuilder().addTextDisplayComponents(bodyText); if (icon) section.setThumbnailAccessory(icon); const buttons = await constructButtons(bug); let gallery: MediaGalleryBuilder | undefined = undefined; if (file) { gallery = new MediaGalleryBuilder().addItems( new MediaGalleryItemBuilder().setURL(`attachment://${file.name}`), ); } else if (existingMessage) { const oldContainer = existingMessage.components[0] as any; if (oldContainer) { const subComponents = oldContainer.components || oldContainer.data?.components || []; const oldGallery = subComponents.find( (c: any) => c.items !== undefined || c.type === 11 || (c.constructor && c.constructor.name.includes("MediaGallery")), ); if (oldGallery) { const itemsToLoop = oldGallery.items || oldGallery.components || oldGallery.data?.items || []; const extractedUrls: string[] = []; for (const item of itemsToLoop) { const url = item.url ?? item.data?.url ?? item.media?.url ?? item.data?.media?.url; if (url) extractedUrls.push(url); } if (extractedUrls.length > 0) { gallery = new MediaGalleryBuilder(); for (const url of extractedUrls) { gallery.addItems(new MediaGalleryItemBuilder().setURL(url)); } } } } if (!gallery) { const attachment = existingMessage.attachments.first(); if (attachment && attachment.url) { gallery = new MediaGalleryBuilder().addItems( new MediaGalleryItemBuilder().setURL(attachment.url), ); } } } const container = new ContainerBuilder(); container.addSectionComponents(section); if (gallery) container.addMediaGalleryComponents(gallery); container.addTextDisplayComponents(footerText); container.addActionRowComponents(buttons); return container; }