const { SlashCommandBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder } = require("discord.js"); const { ObjectId } = require("mongodb"); const { mClient } = require("../.."); const DATABASE = "neo-db"; const DUTY_COLLECTION = "items_duties"; const RAID_TYPES = [ "Trial", "Raid", "AllianceRaid" ]; const RAID_DIFFICULTIES = [ "Extreme", "Unreal", "Savage", "Ultimate", "Chaotic" ]; module.exports = { data: new SlashCommandBuilder() .setName("raid") .setDescription("Raid planning commands.") .addSubcommand(subcommand => subcommand .setName("plan") .setDescription("Create a new raid event.") .addStringOption(option => option .setName("duty") .setDescription("Select the duty you want to run.") .setRequired(true) .setAutocomplete(true) ) ), async execute(interaction) { switch (interaction.options.getSubcommand()) { case "plan": return planRaid(interaction); } }, async autocomplete(interaction) { return autocompleteDuty(interaction); } }; async function autocompleteDuty(interaction) { const focused = interaction.options.getFocused(); const collection = mClient .db(DATABASE) .collection(DUTY_COLLECTION); const query = { Type: { $in: RAID_TYPES }, Difficulty: { $in: RAID_DIFFICULTIES } }; if (focused) { query.Name = { $regex: escapeRegex(focused), $options: "i" }; } const duties = await collection .find(query) .sort({ Name: 1 }) .limit(25) .toArray(); return interaction.respond( duties.map(duty => ({ name: buildAutocompleteName(duty), value: duty._id.toString() })) ); } async function planRaid(interaction) { const dutyId = interaction.options.getString( "duty", true ); if (!ObjectId.isValid(dutyId)) { return interaction.reply({ content: "Please select a duty from the autocomplete results.", ephemeral: true }); } const collection = mClient .db(DATABASE) .collection(DUTY_COLLECTION); const duty = await collection.findOne({ _id: new ObjectId(dutyId), Type: { $in: RAID_TYPES }, Difficulty: { $in: RAID_DIFFICULTIES } }); if (!duty) { return interaction.reply({ content: "That duty could not be found or is not eligible for raid planning.", ephemeral: true }); } /* * The duty ObjectId is embedded in the modal customId. * * The modal handler can then retrieve the selected duty * without trusting editable modal input. */ const modal = buildRaidModal(duty); return interaction.showModal(modal); } function buildRaidModal(duty) { const modal = new ModalBuilder() .setCustomId( `raidPlan:${duty._id}` ) .setTitle( "Shaiwase Raid Planner" ); const nameInput = new TextInputBuilder() .setCustomId("raid-name") .setLabel("Name") .setPlaceholder( "Friday Savage Prog" ) .setStyle( TextInputStyle.Short ) .setMinLength(1) .setMaxLength(100) .setRequired(true); const descriptionInput = new TextInputBuilder() .setCustomId( "raid-description" ) .setLabel("Description") .setPlaceholder( "What are we doing? Any requirements?" ) .setStyle( TextInputStyle.Paragraph ) .setMaxLength(1000) .setRequired(true); /* * Display only. * * We don't use this field to identify the duty. * The ObjectId in the modal customId is authoritative. */ const dutyInput = new TextInputBuilder() .setCustomId("raid-duty") .setLabel("Duty") .setValue(duty.Name) .setStyle( TextInputStyle.Short ) .setRequired(true); const dateInput = new TextInputBuilder() .setCustomId("raid-date") .setLabel("Date") .setPlaceholder("DD-MM-YYYY") .setStyle(TextInputStyle.Short) .setMinLength(10) .setMaxLength(10) .setRequired(true); const timeInput = new TextInputBuilder() .setCustomId("raid-time") .setLabel("Start - End (24h clock)") .setPlaceholder("20:00 - 23:00") .setStyle(TextInputStyle.Short) .setMinLength(13) .setMaxLength(13) .setRequired(true); modal.addComponents( new ActionRowBuilder() .addComponents(nameInput), new ActionRowBuilder() .addComponents(descriptionInput), new ActionRowBuilder() .addComponents(dutyInput), new ActionRowBuilder() .addComponents(dateInput), new ActionRowBuilder() .addComponents(timeInput) ); return modal; } function buildAutocompleteName(duty) { const label = `${duty.Name} — ${formatDutyType(duty.Type)} / ${duty.Difficulty}`; return label.slice( 0, 100 ); } function formatDutyType(type) { switch (type) { case "AllianceRaid": return "Alliance Raid"; default: return type; } } function escapeRegex(value) { return value.replace( /[.*+?^${}()|[\]\\]/g, "\\$&" ); }