Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unreaction event not caught after restart #10487

Open
eirikhanasand opened this issue Sep 5, 2024 · 5 comments
Open

Unreaction event not caught after restart #10487

eirikhanasand opened this issue Sep 5, 2024 · 5 comments

Comments

@eirikhanasand
Copy link

eirikhanasand commented Sep 5, 2024

Which package is this bug report for?

discord.js

Issue description

When I react to a message then restart the bot then unreact it doesnt catch the unreaction event.

// Process existing reactions
            for (const [_, reaction] of roleMessage.reactions.cache) {
                const users = await reaction.users.fetch();
                users.forEach(async (user: User) => {
                    if (user.bot) return;

                    try {
                        const member = await guild.members.fetch(user.id);

                        const emoji = reaction._emoji.name;
                        const reactionText = emoji.length < 4 ? emoji.slice(0, 2) : emoji;

                        for (let i = 0; i < icons.length; i++) {
                            if (icons[i].trim() === reactionText.trim()) {
                                await member.roles.add(roleIds[i]);
                                break;
                            }
                        }
                    } catch (error: any) {
                        if (error.code === 10007) {
                            // Member not found (likely left the guild)
                        } else {
                            console.error("Error fetching member or adding role:", error);
                        }
                    }
                });
            }

if I log reactionText it logs my reaction successfully after the restart. If I then wait for

console.log("Finished processing existing reactions.");

then unreact, the following function doesnt catch it:

roleCollector.on('remove', async (clickedReaction: Reaction, user: User) => {
                const member = await guild.members.fetch(user.id);
                const emoji = clickedReaction._emoji.name;
                const reaction = emoji.length < 4 ? emoji.slice(0, 2) : emoji;

                for (let i = 0; i < icons.length; i++) {
                    if (icons[i].trim() === reaction.trim()) {
                        await member.roles.remove(roleIds[i]);
                        break;
                    }
                }
            });

However if I unreact then re-react then unreact again, the above function does catch the 2nd unreaction.

Code sample

client.once(Events.ClientReady, async () => {
    console.log("roles:", roles);
    for (const role of roles) {
        try {
            const { message, channelID } = role;

            // Fetch channel and message
            const channel = await client.channels.fetch(channelID);
            if (!channel) return console.log(`Channel with ID ${channelID} not found.`);
            const roleMessage = await (channel as any).messages.fetch(message);
            if (!roleMessage) return console.log(`Message with ID ${message} not found.`);

            // Extract guild, roles, and icons
            const guild = client.guilds.cache.get(roleMessage.guildId);
            const content = roleMessage.embeds[0].data.fields[0].value;
            if (!guild) return console.log(`Guild ${roleMessage.guildId} does not exist.`);

            const roleRegex = /<@&(\d+)>/g;
            const messageRoles = content.match(roleRegex) || [];
            const roleIds = messageRoles.map((match: string) => match.slice(3, -1));

            const icons = content.split('\n').map((icon: string) =>
                icon[1] === ':' ? icon.split(':')[1] : icon.substring(0, 2)
            );

            // Process existing reactions
            for (const [_, reaction] of roleMessage.reactions.cache) {
                const users = await reaction.users.fetch();
                users.forEach(async (user: User) => {
                    if (user.bot) return;

                    try {
                        const member = await guild.members.fetch(user.id);

                        const emoji = reaction._emoji.name;
                        const reactionText = emoji.length < 4 ? emoji.slice(0, 2) : emoji;

                        for (let i = 0; i < icons.length; i++) {
                            if (icons[i].trim() === reactionText.trim()) {
                                await member.roles.add(roleIds[i]);
                                break;
                            }
                        }
                    } catch (error: any) {
                        if (error.code === 10007) {
                            // Member not found (likely left the guild)
                        } else {
                            console.error("Error fetching member or adding role:", error);
                        }
                    }
                });
            }

            console.log("Finished processing existing reactions.");

            // Create a reaction collector
            const roleCollector = roleMessage.createReactionCollector({
                filter: (reaction: Reaction, user: User) => !user.bot,
                dispose: true,
            });

            roleCollector.on('collect', async (clickedReaction: Reaction, user: User) => {
                const member = await guild.members.fetch(user.id);
                const emoji = clickedReaction._emoji.name;
                const reaction = emoji.length < 4 ? emoji.slice(0, 2) : emoji;

                for (let i = 0; i < icons.length; i++) {
                    if (icons[i].trim() === reaction.trim()) {
                        await member.roles.add(roleIds[i]);
                        break;
                    }
                }
            });

            roleCollector.on('remove', async (clickedReaction: Reaction, user: User) => {
                const member = await guild.members.fetch(user.id);
                const emoji = clickedReaction._emoji.name;
                const reaction = emoji.length < 4 ? emoji.slice(0, 2) : emoji;

                for (let i = 0; i < icons.length; i++) {
                    if (icons[i].trim() === reaction.trim()) {
                        await member.roles.remove(roleIds[i]);
                        break;
                    }
                }
            });
        } catch (error: any) {
            console.error("Error processing roles:", error);
        }
    }

    console.log("Ready!");
});

Versions

Discord: [email protected]
Node: v22.7.0
Typescript: [email protected]
MacOS: 14.3.1 (23D60)

Issue priority

Medium (should be fixed soon)

Which partials do you have configured?

Message, Channel, User, Reaction

Which gateway intents are you subscribing to?

Guilds, GuildModeration, GuildMessages, GuildMessageReactions, MessageContent

I have tested this issue on a development release

No response

@almostSouji
Copy link
Member

@almostSouji almostSouji closed this as not planned Won't fix, can't repro, duplicate, stale Sep 5, 2024
@eirikhanasand
Copy link
Author

eirikhanasand commented Sep 5, 2024

Hello, thanks for the quick attention @almostSouji. I have reviewed the above documentation. However the message is not partial as I have already fetched it. Furthermore Im able to catch the first collect event if I do that first, but not the first unreact if I do that first. I have verified that nothing in the structure is partial by implementing partials and logging the state of the partial property according to the above documentation.

Catches reactions as intended (even the first)

export default function addRole({collector, guild, roles, icons}: CollectProps) {
    collector.on('collect', async (reaction: Reaction, user: User) => {
        console.log('add')
        const member = await guild.members.fetch(user.id)
        const emoji = reaction._emoji.name
        const reactionEmoji = emoji.length < 4 ? emoji.slice(0, 2).trim() : emoji.trim()
        
        for (let i = 0; i < icons.length; i++) {
            if (icons[i].trim() === reactionEmoji) {
                await member.roles.add(roles[i])
                break
            }
        }
    })
}

Does not catch the first unreaction event, only subsequent ones:

export function removeRole({collector, guild, roles, icons}: CollectProps) {
    collector.on('remove', async (reaction: Reaction, user: User) => {
        console.log('remove')
        const member = await guild.members.fetch(user.id)
        const emoji = reaction._emoji.name
        const reactionEmoji = emoji.length < 4 ? emoji.slice(0, 2).trim() : emoji.trim()

        for (let i = 0; i < icons.length; i++) {
            if (icons[i].trim() === reactionEmoji) {
                await member.roles.remove(roles[i])
                break
            }
        }
    })
}

It doesnt seem to have with the partials to do, as users are reporting this behavior after the same message has been interacted with for many hours. I was able to catch the first one using Events.MessageReactionRemove, but this does seem like a bug?

@almostSouji
Copy link
Member

Please provide a minimal reproducible code sample so we can attempt to reproduce and verify your issue. This means isolating the erroneous behavior to a fresh bot, or stripping away functionality until you can be sure the code is minimal. Also, include a manual with the steps you take interacting with the code in question which results in the behavior you outlined.

Please try to reproduce the issue yourself on a fresh bot. If you can't reproduce it, it's very likely that we can't either and the code sample in question is not enough.

Minimal Reproducible sample: https://stackoverflow.com/help/minimal-reproducible-example

@Qjuh
Copy link
Contributor

Qjuh commented Sep 6, 2024

Your misconception here is that you think ReactionCollector#remove will emit if any reaction was removed from the message. But it will only emit if a reaction that was previously collected with that collector got removed.

Why are you using Collectors (which are made for short-term handling) for handling that in the first place instead of using the global messageReactionRemove event and check for the correct message id there?

@eirikhanasand
Copy link
Author

eirikhanasand commented Sep 6, 2024

I used Collectors because I didnt know I had other options till you sent the above documentation. Didnt know to look there, nothing in the Collector docs mention that they are short term or that they will only catch unreactions previously collected by the same process

it will only emit if a reaction that was previously collected with that collector got removed

If this is the intended behavior then the issue can be closed. If that is something you are considering changing then I can provide a minrep within a few days. I think for me if the docs just mentioned that I should use "messageReactionRemove" I wouldnt have encountered this issue. My desired outcome here would be a documentation update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants