const { SlashCommandBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, MessageFlags } = 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); } }; /* * AUTOCOMPLETE */ 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() })) ); } /* * /raid plan */ async function planRaid(interaction) { if (!interaction.guild) { return interaction.reply({ content: "Raid planning can only be used inside a server.", flags: MessageFlags.Ephemeral }); } const dutyId = interaction.options.getString( "duty", true ); if (!ObjectId.isValid(dutyId)) { return interaction.reply({ content: "Please select a duty from the autocomplete results.", flags: MessageFlags.Ephemeral }); } 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.", flags: MessageFlags.Ephemeral }); } const modal = buildRaidModal(duty); return interaction.showModal( modal ); } /* * 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); 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 ) .setRequired(true); modal.addComponents( new ActionRowBuilder() .addComponents( nameInput ), new ActionRowBuilder() .addComponents( descriptionInput ), new ActionRowBuilder() .addComponents( dateInput ), new ActionRowBuilder() .addComponents( timeInput ) ); return modal; } /* * HELPERS */ 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, "\\$&" ); }