added proper modal handler and raidplan

This commit is contained in:
DeSqBlocki
2026-08-14 23:49:31 +02:00
parent 9e94e99874
commit dafa81e2a6
5 changed files with 1696 additions and 44 deletions
+300
View File
@@ -0,0 +1,300 @@
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,
"\\$&"
);
}