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: */ 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 };