Files
ShaiBot/utils/raidPlanner.js
T
2026-08-15 00:32:27 +02:00

522 lines
7.7 KiB
JavaScript

const {
EmbedBuilder
} = require("discord.js");
const { mClient } = require("../index");
const DATABASE =
"neo-db";
const DUTY_COLLECTION =
"items_duties";
const EVENT_COLLECTION =
"items_events";
/*
* RAID SIGNUP EMOJIS
*/
const RAID_EMOJIS = {
Tank: {
id:
"1537931883369726042",
display:
"<:TankRole:1537931883369726042>"
},
Healer: {
id:
"1537931880756805704",
display:
"<:HealerRole:1537931880756805704>"
},
DPS: {
id:
"1537931878668050442",
display:
"<:DPSRole:1537931878668050442>"
},
Flex: {
id:
"1537948665606574210",
display:
"<:AllRounderRole:1537948665606574210>"
}
};
/*
* MAIN REACTION HANDLER
*
* added = true
* MessageReactionAdd
*
* added = false
* MessageReactionRemove
*/
async function handleRaidReaction(
reaction,
user,
added
) {
/*
* Ignore bots.
*
* This prevents ShaiBot's own initial
* signup reactions from being counted.
*/
if (user.bot)
return;
/*
* Resolve partial reactions/messages.
*/
if (reaction.partial) {
try {
await reaction.fetch();
} catch (error) {
console.error(
"Failed to fetch partial reaction:",
error
);
return;
}
}
if (reaction.message.partial) {
try {
await reaction.message.fetch();
} catch (error) {
console.error(
"Failed to fetch partial raid planner message:",
error
);
return;
}
}
const message =
reaction.message;
/*
* Raid planner messages always contain
* an embed.
*/
if (
!message.embeds ||
message.embeds.length === 0
) {
return;
}
const embed =
message.embeds[0];
/*
* Footer format:
*
* raidPlanner:<raidId>
*/
const footer =
embed.footer?.text;
if (
!footer ||
!footer.startsWith(
"raidPlanner:"
)
) {
return;
}
const [
prefix,
raidId
] =
footer.split(":");
if (
prefix !== "raidPlanner" ||
!raidId
) {
return;
}
/*
* Determine which signup role
* this reaction represents.
*/
const role =
getRoleFromEmoji(
reaction.emoji
);
/*
* Ignore unrelated reactions.
*/
if (!role)
return;
const db =
mClient.db(
DATABASE
);
const events =
db.collection(
EVENT_COLLECTION
);
/*
* Make sure event exists.
*/
const raid =
await events.findOne({
raidId
});
if (!raid)
return;
/*
* REACTION ADDED
*
* Each role is independent.
* Users may select multiple roles.
*/
if (added) {
await events.updateOne(
{
raidId
},
{
$addToSet: {
[`signups.${role}`]:
user.id
}
}
);
}
/*
* REACTION REMOVED
*
* Remove only this role.
*/
else {
await events.updateOne(
{
raidId
},
{
$pull: {
[`signups.${role}`]:
user.id
}
}
);
}
/*
* Rebuild planner from current Mongo state.
*/
await updateRaidPlanner(
message,
raidId
);
}
/*
* UPDATE RAID PLANNER EMBED
*/
async function updateRaidPlanner(
message,
raidId
) {
const db =
mClient.db(
DATABASE
);
/*
* Fetch raid state.
*/
const raid =
await db
.collection(
EVENT_COLLECTION
)
.findOne({
raidId
});
if (!raid)
return;
/*
* Fetch duty.
*/
const duty =
await db
.collection(
DUTY_COLLECTION
)
.findOne({
_id:
raid.dutyId
});
if (!duty)
return;
/*
* Role counts.
*/
const counts = {
Tank:
raid.signups?.Tank
?.length
?? 0,
Healer:
raid.signups?.Healer
?.length
?? 0,
DPS:
raid.signups?.DPS
?.length
?? 0,
Flex:
raid.signups?.Flex
?.length
?? 0
};
/*
* Count UNIQUE participants.
*
* A user who reacts Tank + DPS + Flex
* still counts as one participant.
*/
const uniqueParticipants =
new Set([
...(
raid.signups?.Tank
?? []
),
...(
raid.signups?.Healer
?? []
),
...(
raid.signups?.DPS
?? []
),
...(
raid.signups?.Flex
?? []
)
]).size;
/*
* Clone existing embed.
*/
const oldEmbed =
message.embeds[0];
const updatedEmbed =
EmbedBuilder.from(
oldEmbed
);
/*
* Replace only Party field.
*/
const fields =
oldEmbed.fields.map(
field => {
if (
field.name !==
"Party"
) {
return {
name:
field.name,
value:
field.value,
inline:
field.inline
};
}
return {
name:
"Party",
value:
buildPartyField(
duty,
counts,
uniqueParticipants
),
inline:
false
};
}
);
updatedEmbed.setFields(
fields
);
/*
* Update message.
*/
await message.edit({
embeds: [
updatedEmbed
]
});
}
/*
* BUILD PARTY FIELD
*/
function buildPartyField(
duty,
counts,
uniqueParticipants
) {
return (
`${RAID_EMOJIS.Tank.display} Tank **(${counts.Tank}/${duty.Party.Tanks})**\n` +
`${RAID_EMOJIS.Healer.display} Healer **(${counts.Healer}/${duty.Party.Healers})**\n` +
`${RAID_EMOJIS.DPS.display} DPS **(${counts.DPS}/${duty.Party.DPS})**\n` +
`${RAID_EMOJIS.Flex.display} Flex **(${counts.Flex})**\n\n` +
`**Participants: ${uniqueParticipants}/${duty.Party.Size}**`
);
}
/*
* EMOJI -> ROLE
*/
function getRoleFromEmoji(
emoji
) {
if (
emoji.id ===
RAID_EMOJIS.Tank.id
) {
return "Tank";
}
if (
emoji.id ===
RAID_EMOJIS.Healer.id
) {
return "Healer";
}
if (
emoji.id ===
RAID_EMOJIS.DPS.id
) {
return "DPS";
}
if (
emoji.id ===
RAID_EMOJIS.Flex.id
) {
return "Flex";
}
return null;
}
module.exports = {
handleRaidReaction,
updateRaidPlanner
};