316 lines
9.1 KiB
JavaScript
316 lines
9.1 KiB
JavaScript
const { Events, MessageFlags, Message } = require('discord.js')
|
|
const { mClient } = require('../index')
|
|
require('dotenv').config()
|
|
|
|
module.exports = {
|
|
name: Events.InteractionCreate,
|
|
once: false,
|
|
async execute(interaction) {
|
|
if (interaction.isChatInputCommand()) { commandHandler(interaction) }
|
|
if (interaction.isButton()) { buttonHandler(interaction) }
|
|
if (interaction.isMentionableSelectMenu()) { selectMenuHandler(interaction) }
|
|
if (interaction.isUserSelectMenu()) { selectMenuHandler(interaction) }
|
|
if (interaction.isModalSubmit()) { modalSubmitHandler(interaction) }
|
|
}
|
|
}
|
|
async function commandHandler(interaction) {
|
|
const command = interaction.client.commands.get(interaction.commandName);
|
|
|
|
if (!command) {
|
|
console.error(`No command matching ${interaction.commandName} was found.`);
|
|
return;
|
|
}
|
|
try {
|
|
await command.execute(interaction);
|
|
} catch (error) {
|
|
console.error(error);
|
|
if (interaction.replied || interaction.deferred) {
|
|
await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
|
|
} else {
|
|
await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
|
|
}
|
|
}
|
|
}
|
|
async function buttonHandler(interaction) {
|
|
const button = interaction.client.buttons.get(interaction.customId)
|
|
if (!button) { return }// console.error(`No button registered matching ${interaction.customId}.`) }
|
|
try {
|
|
await button.execute(interaction);
|
|
} catch (error) {
|
|
console.error(error);
|
|
if (interaction.replied || interaction.deferred) {
|
|
await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
|
|
} else {
|
|
await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
|
|
}
|
|
}
|
|
}
|
|
async function selectMenuHandler(interaction) {
|
|
const selectMenu = interaction.client.selectMenus.get(interaction.customId)
|
|
if (!selectMenu) { return console.error(`No Select Menu registered matching ${interaction.customId}.`) }
|
|
try {
|
|
await selectMenu.execute(interaction);
|
|
} catch (error) {
|
|
console.error(error);
|
|
if (interaction.replied || interaction.deferred) {
|
|
await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
|
|
} else {
|
|
await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
|
|
}
|
|
}
|
|
}
|
|
|
|
function getParticipantWeight(participant, now = Date.now()) {
|
|
// Never selected before -> strongly favor them
|
|
if (!participant.lastExpertAt) {
|
|
return 6;
|
|
}
|
|
|
|
const lastExpertAt = new Date(participant.lastExpertAt).getTime();
|
|
|
|
// Invalid date safety
|
|
if (Number.isNaN(lastExpertAt)) {
|
|
return 6;
|
|
}
|
|
|
|
const daysSinceExpert = Math.max(
|
|
0,
|
|
(now - lastExpertAt) / (24 * 60 * 60 * 1000)
|
|
);
|
|
|
|
/*
|
|
* Square-root weighting prevents very old participants
|
|
* from completely dominating the random selection.
|
|
*
|
|
* Examples:
|
|
* 1 day -> ~1.41
|
|
* 7 days -> ~2.83
|
|
* 30 days -> ~5.57
|
|
*/
|
|
return Math.sqrt(daysSinceExpert + 1);
|
|
}
|
|
|
|
function weightedRandom(participants) {
|
|
if (!participants.length) {
|
|
return null;
|
|
}
|
|
|
|
const weightedParticipants = participants.map(participant => ({
|
|
participant,
|
|
weight: getParticipantWeight(participant)
|
|
}));
|
|
|
|
const totalWeight = weightedParticipants.reduce(
|
|
(total, item) => total + item.weight,
|
|
0
|
|
);
|
|
|
|
let random = Math.random() * totalWeight;
|
|
|
|
for (const item of weightedParticipants) {
|
|
random -= item.weight;
|
|
|
|
if (random <= 0) {
|
|
return item.participant;
|
|
}
|
|
}
|
|
|
|
// Floating-point safety fallback
|
|
return weightedParticipants[weightedParticipants.length - 1].participant;
|
|
}
|
|
|
|
async function selectWgeParticipant(
|
|
participateColl,
|
|
{ excludeUserId = null } = {}
|
|
) {
|
|
const filter = {
|
|
status: true
|
|
};
|
|
|
|
if (excludeUserId) {
|
|
filter.userID = {
|
|
$ne: excludeUserId
|
|
};
|
|
}
|
|
|
|
const participants = await participateColl
|
|
.find(filter)
|
|
.toArray();
|
|
|
|
return weightedRandom(participants);
|
|
}
|
|
|
|
async function modalSubmitHandler(interaction) {
|
|
if (interaction.customId !== 'wgeBaseModal') {
|
|
return;
|
|
}
|
|
|
|
const db = mClient.db('neo-db');
|
|
|
|
const wgeColl = db.collection('timer_wge');
|
|
const participateColl = db.collection('items_wge');
|
|
const historyWgeColl = db.collection('history_wge');
|
|
|
|
const assigningUser = interaction.user;
|
|
|
|
const selectedUserId =
|
|
interaction.components[0].component.values[0];
|
|
|
|
const topic = interaction.fields.getTextInputValue(
|
|
'wgeTopicInput'
|
|
);
|
|
|
|
const defaultAssignedTime =
|
|
7 * 24 * 60 * 60 * 1000;
|
|
|
|
// Get current running WGE so the previous expert
|
|
// cannot immediately be selected again.
|
|
const prevWge = await wgeColl.findOne({
|
|
debug: false,
|
|
state: 'running'
|
|
});
|
|
|
|
let assignedUser;
|
|
let selectedParticipant;
|
|
|
|
// ---------------------------------
|
|
// RANDOM / WEIGHTED ASSIGNMENT
|
|
// ---------------------------------
|
|
if (selectedUserId === process.env.D_ID) {
|
|
selectedParticipant = await selectWgeParticipant(
|
|
participateColl,
|
|
{
|
|
excludeUserId: prevWge?.assignedUser
|
|
}
|
|
);
|
|
|
|
if (!selectedParticipant) {
|
|
return interaction.reply({
|
|
content:
|
|
'There are no eligible participants available.',
|
|
flags: MessageFlags.Ephemeral
|
|
});
|
|
}
|
|
|
|
const member = await interaction.guild.members
|
|
.fetch(selectedParticipant.userID)
|
|
.catch(() => null);
|
|
|
|
if (!member) {
|
|
return interaction.reply({
|
|
content:
|
|
'The selected participant could not be found in the server.',
|
|
flags: MessageFlags.Ephemeral
|
|
});
|
|
}
|
|
|
|
assignedUser = member.user;
|
|
}
|
|
|
|
// ---------------------------------
|
|
// MANUAL ASSIGNMENT
|
|
// ---------------------------------
|
|
else {
|
|
selectedParticipant = await participateColl.findOne({
|
|
userID: selectedUserId,
|
|
status: true
|
|
});
|
|
|
|
if (!selectedParticipant) {
|
|
return interaction.reply({
|
|
content:
|
|
`<@${selectedUserId}> is not participating!`,
|
|
flags: MessageFlags.Ephemeral
|
|
});
|
|
}
|
|
|
|
if (
|
|
prevWge &&
|
|
selectedUserId === prevWge.assignedUser
|
|
) {
|
|
return interaction.reply({
|
|
content:
|
|
`<@${selectedUserId}> was the previous expert and cannot be selected again this round.`,
|
|
flags: MessageFlags.Ephemeral
|
|
});
|
|
}
|
|
|
|
assignedUser = {
|
|
id: selectedUserId
|
|
};
|
|
}
|
|
|
|
// ---------------------------------
|
|
// ARCHIVE PREVIOUS WGE
|
|
// ---------------------------------
|
|
if (prevWge) {
|
|
await historyWgeColl.insertOne({
|
|
assignedUser: prevWge.assignedUser,
|
|
assigningUser: prevWge.assigningUser,
|
|
|
|
timeRemaining: prevWge.timeRemaining,
|
|
|
|
topic: prevWge.topic,
|
|
debug: prevWge.debug,
|
|
|
|
createdAt: prevWge.createdAt
|
|
});
|
|
|
|
console.log({
|
|
message: 'Saved previous WGE to history_wge',
|
|
data: prevWge
|
|
});
|
|
|
|
await wgeColl.deleteOne({
|
|
_id: prevWge._id
|
|
});
|
|
}
|
|
|
|
const assignedAt = new Date();
|
|
|
|
// ---------------------------------
|
|
// UPDATE PARTICIPANT STATISTICS
|
|
// ---------------------------------
|
|
await participateColl.updateOne(
|
|
{
|
|
_id: selectedParticipant._id
|
|
},
|
|
{
|
|
$set: {
|
|
lastExpertAt: assignedAt
|
|
},
|
|
$inc: {
|
|
expertCount: 1
|
|
}
|
|
}
|
|
);
|
|
|
|
// ---------------------------------
|
|
// CREATE NEW WGE
|
|
// ---------------------------------
|
|
await wgeColl.insertOne({
|
|
assignedUser: assignedUser.id,
|
|
assigningUser: assigningUser.id,
|
|
|
|
startedAt: Date.now(),
|
|
timeRemaining: defaultAssignedTime,
|
|
|
|
state: 'running',
|
|
|
|
notifications: {
|
|
half: false,
|
|
day24: false,
|
|
complete: false
|
|
},
|
|
|
|
topic,
|
|
debug: false,
|
|
|
|
createdAt: assignedAt
|
|
});
|
|
|
|
return interaction.reply({
|
|
content:
|
|
`<@${assignedUser.id}> is the next expert! Their topic is **${topic}**`
|
|
});
|
|
} |