'DiscordJS reacting to message with emoji adds role, but requires unreacting, reacting, and unreacting again to remove the role
Edit: I found the issue. I was checking if a user already has the role and was jumping out of the function call if we were attempting to remove a role they did not have. The function checked the cache, which was likely what was not being updated, and removing the check altogether has resolved the issue. I'll leave this here in case anyone else creates this problem for themselves in the future :)
I am trying to code a Discord bot with DiscordJS that allows users to react to a specific message with a specific emoji to have roles assigned or removed. Currently, reacting to the tracked message with the appropriate emoji assigns the role as expected every time. The error is that when a user with the assigned role removes their emoji reaction from the message, it does not remove the role until they react/unreact a second time.
Here is the code in my index.js that handles running events. This calls messageReactionAdd.js and messageReactionRemove.js to listen for their corresponding events.
const fs = require('node:fs');
const { Client, Intents, Collection } = require('discord.js');
const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MESSAGE_REACTIONS], partials: ['MESSAGE', 'CHANNEL', 'REACTION']});
// Handle Events
for (const file of eventFiles){
const event = require(`../src/events/${file}`);
if (event.once){
client.once(event.name, (...args) => event.execute(...args));
} else {
client.on(event.name, (...args) => event.execute(...args));
}
}
An example of my messageReactionAdd.js. There is also a messageReactionRemove.js file that mirrors this except its name is messageReactionRemove and calls the applyRemoveRole helper function with false. These listeners check to see if the message being reacted to is listed in our trackedRoleMessages object, which stores the message id and other data as key/value pairs.
const trackedRoleMessages = require('../util/trackedRoleMessages');
const applyRemoveRole = require('../util/applyRemoveRole');
module.exports = {
name: 'messageReactionAdd',
execute(reaction, user){
const messageID = reaction.message.id;
if (trackedRoleMessages[messageID]){
applyRemoveRole(reaction, user, true);
}
},
};
The applyRemoveRole.js helper function that handles the applying and removing of roles from users when they react to a message we are listening for. The direction param is a boolean that distinguishes between which event we're responding to, add or remove (true or false). The roles object stores emoji unicode values and role ID's as key/value pairs to form an association between them.
const roles = require('./roles');
/*
param1 reaction: reaction object
param2 user: user object
param3 direction: boolean, True = add role, False = remove role
*/
const applyRemoveRole = async (reaction, user, direction) => {
const emojiUnicode = reaction.emoji.name.codePointAt(0).toString(16);
const role = reaction.message.guild.roles.cache.find;
const member = await reaction.message.guild.members.cache.find(member => member.id == user.id);
const targetRole = roles[emojiUnicode];
const hasRole = doesMemberHaveRole(member, targetRole)
console.log(user)
console.log(hasRole)
console.log(member._roles)
try {
if (role && member) {
if (!hasRole && direction) {
console.log("adding role")
addRemoveRole(member, roles[emojiUnicode], direction)
} else if (hasRole && !direction) {
console.log("removing role")
addRemoveRole(member, roles[emojiUnicode], direction)
}
}
} catch (e) {
console.error(e);
return
}
}
// Checks if the passed member has the passed roleID
// Returns True or False
const doesMemberHaveRole = (member, roleID) => {
return member.roles.cache.some(role => role.id === roleID)
}
// Handles the actual applying/removing of roles.
const addRemoveRole = async (member, role, addRemove) => {
(addRemove) ? await member.roles.add(role) : await member.roles.remove(role);
}
module.exports = applyRemoveRole;
This has me thinking it could be an async/await error where the role is attempting to be removed before it is ever saved in the member's roles. I have a console.log spitting out the members roles that is reacting to the message and it isn't until the second reaction sequence that the expected role is populating in their roles array (and then removed as the code executes). I have tried combing through many StackOverflow questions and YouTube videos, but a lot of the questions/answers aren't specific to this issue or dated and use deprecated code.
Here is the current flow based on console.logging member._roles at the start of each call, if it helps make it more clear what the error is.
Add Reaction, roles: [no role], member does not have role, add it (this works).
Remove Reaction, roles: [no role], role is not removed because it isn't there
Add reaction, roles: [role], role is not added because you already have it.
Remove Reaction, roles: [role], member has role, removes it from them.
Solution 1:[1]
The const hasRole = doesMemberHaveRole(member, targetRole) check was redundant and likely due to checking the user cache wasn't picking up the new user data with the new role, keeping the block of code to remove the role until the cache was updated. Removing this check altogether and simply checking to add/remove the role and executing, whether they have the role or not, cleared it right up.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Archa9 |
