Problem is the Discord bot is skipping the first reaction emoji.
If I un-react and re-react with the same emoji, then the bot registers it.
I've been trying to figure this out for a while, and am stumped as to what I am missing in my logic or if I need to find another solution.
Intention:
Discord bot gathers and prints out all reactions to a message by a user
Relevant Information:
Discordjs 14.7.1
Discord Developer Website:
Presence Intent: Enabled
Server Members Intent: Enabled
Message Content Intent: Enabled
Code:
const { Client, Events, GatewayIntentBits, Partials } = require('discord.js');
const { token } = require('./includes/config.json');
// create a new discord client
const client = new Client({
intents: [
GatewayIntentBits.DirectMessageReactions,
GatewayIntentBits.DirectMessageTyping,
GatewayIntentBits.DirectMessages,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.Guilds,
GatewayIntentBits.MessageContent,
],
partials: [
Partials.Channel,
Partials.GuildMember,
Partials.Message,
Partials.Reaction,
Partials.User,
]
});
// ready
client.once(Events.ClientReady, () => {
console.log('Ready!');
});
// if partial, fetch full
client.on(Events.MessageReactionAdd, async (reaction, user) => {
if (reaction.partial) {
try {
await reaction.fetch();
} catch (error) {
console.error(error);
return;
}
}
// define the message component
const message = reaction.message;
// all userReactions by user.id
const userReactions = await message.reactions.cache.filter(reaction => reaction.users.cache.has(user.id));
// print emojis
try {
for (const reaction of userReactions.values()) {
console.log(`Emoji: ${reaction.emoji}`);
}
} catch(error) {
console.error(error);
}
})
// login
client.login(token);
Problem:
The bot is not registering the first reaction placed
Example:
- I react to a message in this order: 😄❤️🔥
- the logfile shows:
Emoji: ❤️
Emoji: ❤️
Emoji: 🔥
Expected result:
Emoji: 😄
Emoji: 😄
Emoji: ❤️
Emoji: 😄
Emoji: ❤️
Emoji: 🔥
Observations:
I noticed that the bot works fine for all messages sent after it is running
The problematic behaviour is experienced only for messages sent before the bot was running
Conclusion:
MessageReactionAdd() does get called, as tested by placing 'console.log(test output); statements. I learned that it works on cached messages only, which I thought was mitigated by fetching the full reaction. Such was not the case. I tried testing by forcing a message fetch manually by channel.id and message.id, it still did not work.
My Solution:
Used an sqlite database to keep track of reactions.
From within MessageReactionAdd() I was able to check for the reactions I was interested in logging, and added them to an sqlite database.
Within an equivalent MessageReactionRemove() section, I was able to check for removal of the reactions and delete the pertinent row from the database.
Thank you for reading and assisting. :)
Related
I'm trying to make a blacklist command, and how do I make it so it sends an embed instead of normal message.
(Also, I'm new to discord.js)
Here's my script:
module.exports = {
data: new SlashCommandBuilder()
.setName('blacklist')
.setDescription('Blacklist someone from the server')
.addUserOption((option) => option.setName('member')
.setDescription('Who are you blacklisting?')),
async execute(interaction) {
await interaction.deferReply();
const member = interaction.options.getMember('member');
if (!interaction.member.roles.cache.some(r => r.name === "Blacklist perms")) {
return interaction.editReply({ content: 'You do not have permission to use this command!' });
}
member.send("You have been blacklisted!")
member.kick().catch(err => {
interaction.editReply({ content: `I do not have enough permissions to do that.`})
})
await interaction.editReply({ content: `${member} has been succesfully blacklisted!` });
},
};```
Since your function is already async, simply await sending the message before you call kick.
await member.send("You have been blacklisted!")
member.kick().catch(err => {
interaction.editReply({ content: 'I do not have enough permissions to do that.'})
})
You might also wanna catch() on the send method because users can have DMs from server members disabled or blocked your bot.
You also might consider banning the user instead, because this system would fail if your bot ever happens to go offline when a blacklisted user tries to join. Additionally, depending on how fast your bot reacts, the user may still have time to spam in channels or do something harmful before your bot is able to kick them.
To send an embed either use an EmbedBuilder or a plain JavaScript object with the correct embed structure.
Example:
member.send({ embeds:[{
title: "Title",
description: "Hello World!"
}] })
Newbie here. I am trying to get a slash command to send an embed with the amount of time it took between the initial message and the message response time. I am getting TypeError: Cannot read properties of undefined (reading 'createdTimestamp') and Error [INTERACTION_ALREADY_REPLIED]: The reply to this interaction has already been sent or deferred. I've jumped around looking at others code trying to find a way to make this work but slash command handling still doesn't make a lot of sense to me. My code block is below. I followed along with https://discordjs.guide so if you have any other suggestions with structure feel free to comment them below. Thank you!
const { SlashCommandBuilder } = require('#discordjs/builders');
const { MessageEmbed } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with Pong!'),
async execute(interaction) {
await interaction.reply("Pinging bot...").then (async (msg) =>{
const exampleEmbed = new MessageEmbed()
.setColor('0x0000ff')
.setTitle('Pong! :ping_pong:')
.addField("Time taken: ", `${msg.createdTimestamp - message.createdTimestamp}`)
.setThumbnail("https://78.media.tumblr.com/be43242341a7be9d50bb2ff8965abf61/tumblr_o1ximcnp1I1qf84u9o1_500.gif")
interaction.editReply({ embeds: [exampleEmbed] });
})
},
};
first you need to fetch the reply you send, u can use fetchReply to get the interaction reply. instead of replying with "Pinging bot..." you can defer the reply and then use the createdTimestamp. A basic example of a ping command would be
const { SlashCommandBuilder } = require('#discordjs/builders');
module.exports = {
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with Pong!'),
async execute(interaction) {
const sent = await interaction.reply({ content: 'Pinging...', fetchReply: true });
await interaction.editReply(`:ping_pong: Pong!\n:stopwatch: Uptime: ${Math.round(interaction.client.uptime / 60000)} minutes\n:sparkling_heart: Websocket heartbeat: ${interaction.client.ws.ping}ms.\n:round_pushpin: Rountrip Latency: ${sent.createdTimestamp - interaction.createdTimestamp}ms`);
},
};
You can customize the response into an embed or however you like. The djs guide has a section on ping command here
So i need a bot that tracks another bot's status. like if its online it will say in a channel (with an embed) "The Bot Is Online" And The Same if it goes offline and whenever someone does !status {botname} it shows the uptime/downtime of the bot and 'last online' date
if someone can make it happen i will really appricate it!
also i found this github rebo but it dosen't work, it just says the bot is online and whenever i type !setup {channel} it turns off
The Link to the Repo: https://github.com/sujalgoel/discord-bot-status-checker
Also uh it can be any language, i don't really want to add anything else 😅.
Again, Thanks!
First of all, you would need the privileged Presence intent, which you can enable in the Developer Portal.
Tracking the bot's status
In order to have this work, we have to listen to the presenceUpdate event in discord.js. This event emits whenever someone's presence (a.k.a. status) updates.
Add this in your index.js file, or an event handler file:
// put this with your other imports (and esm equivalent if necessary)
const { MessageEmbed } = require("discord.js");
client.on("presenceUpdate", async (oldPresence, newPresence) => {
// check if the member is the bot we're looking for, if not, return
if (newPresence.member !== "your other bot id here") return;
// check if the status (online, idle, dnd, offline) updated, if not, return
if (oldPresence?.status === newPresence.status) return;
// fetch the channel that we're sending a message in
const channel = await client.channels.fetch("your updating channel id here");
// create the embed
const embed = new MessageEmbed()
.setTitle(`${newPresence.member.displayName}'s status updated!`)
.addField("Old status", oldPresence?.status ?? "offline")
.addField("New status", newPresence.status ?? "offline");
channel.send({ embeds: [embed] });
});
Now, whenever we update the targeted bot's status (online, idle, dnd, offline), it should send the embed we created!
!status command
This one will be a bit harder. If you don't have or want to use a database, we will need to store it in a Collection. The important thing about a Collection is that it resets whenever your bot updates, meaning that even if your bot restarts, everything in that Collection is gone. Collections, rather than just a variable, allows you to store more than one bot's value if you need it in the future.
However, because I don't know what you want or what database you're using, we're going to use Collections.
In your index.js file from before:
// put this with your other imports (and esm equivalent if necessary)
const { Collection, MessageEmbed } = require("discord.js");
// create a collection to store our status data
const client.statusCollection = new Collection();
client.statusCollection.set("your other bot id here", Date.now());
client.on("presenceUpdate", async (oldPresence, newPresence) => {
// check if the member is the bot we're looking for, if not, return
if (newPresence.member !== "your other bot id here") return;
// check if the status (online, idle, dnd, offline) updated, if not, return
if (oldPresence?.status === newPresence.status) return;
// fetch the channel that we're sending a message in
const channel = await client.channels.fetch("your updating channel id here");
// create the embed
const embed = new MessageEmbed()
.setTitle(`${newPresence.member.displayName}'s status updated!`)
.addField("Old status", oldPresence?.status ?? "offline")
.addField("New status", newPresence.status ?? "offline");
channel.send({ embeds: [embed] });
// add the changes in our Collection if changed from/to offline
if ((oldPresence?.status === "offline" || !oldPresence) || (newPresence.status === "offline")) {
client.statusCollection.set("your other bot id here", Date.now());
}
});
Assuming that you already have a prefix command handler (not slash commands) and that the message, args (array of arguments separated by spaces), and client exists, put this in a command file, and make sure it's in an async/await context:
// put this at the top of the file
const { MessageEmbed } = require("discord.js");
const bot = await message.guild.members.fetch("your other bot id here");
const embed = new MessageEmbed()
.setTitle(`${bot.displayName}'s status`);
// if bot is currently offline
if ((bot.presence?.status === "offline") || (!bot.presence)) {
const lastOnline = client.statusCollection.get("your other bot id here");
embed.setDescription(`The bot is currently offline, it was last online at <t:${lastOnline / 1000}:F>`);
} else { // if bot is not currently offline
const uptime = client.statusCollection.get("your other bot id here");
embed.setDescription(`The bot is currently online, its uptime is ${uptime / 1000}`);
};
message.reply({ embeds: [embed] });
In no way is this the most perfect code, but, it does the trick and is a great starting point for you to add on. Some suggestions would be to add the tracker in an event handler rather than your index.js file, use a database rather than a local Collection, and of course, prettify the embed messages!
I'm trying to script a bot that gives people a role when they click on an emoji.
My problem is that people don't get the role when they click on the emoji. When I enter the first Command t!role the Message with the emoji is sent correctly. However, the people reacting will not receive the role. I tried everything that was suggested in the Comments. Unfortunatley nothing seems to work.
const bot = new Discord.Client( {
intents: [
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_MEMBERS,
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
Intents.FLAGS.DIRECT_MESSAGE_REACTIONS,
],
partials: ["MESSAGE", "CHANNEL", "REACTION"]
});
else if(parts[0].toLowerCase() == "t!role") {
let sendMessage = await message.channel.send('React with 👮to get the role')
sendMessage.react('👮')
bot.on('messageReactionAdd', async (reaction, user, channel) => {
if(reaction.message.partial) await reaction.message.fetch();
if(reaction.partial) await reaction.fetch();
if(user.bot) return;
if(!reaction.message.guild) return;
if(reaction.message.channel.id === '8786607442820137464') {
if(reaction.emoji.id === '8789972642138767364') {
reaction.message.guild.members.cache.get(user.id).roles.add('8779841244749004804')
}
}
})
Discord had added a few new features in their developer portal a few months ago and most of the documentation and tutorials skip over that but it is necessary for them to be active in order to have a functional reaction roles feature.
The process to turn these on is to go to the application page of your bot, go to the bot page under the application and activate Presence Intent and Server Members Intent. Hopefully after this your reaction roles should start working.
Enable partials in your client initialization. Discord.js requires you now to add partials and intent flags you actually gonna use. The messageReactionAdd event requires some partials as well: REACTION, MESSAGE, CHANNEL.
For example, this is how you should use partials.
const bot = new Client({
intents: [
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_MEMBERS,
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
Intents.FLAGS.DIRECT_MESSAGE_REACTIONS,
],
partials: ["MESSAGE", "CHANNEL", "REACTION"]
});
I have used the same exact code in your post, only changed a few id's on my side, and it's working as intended. As my comment before:
Bots can only give roles below their own, even if they have Administrator permissions. Make sure the role is below the bot's role. Use .catch((error) => console.log(error.message)); to log any errors if giving out the roles has failed for some reason.
bot.on('messageReactionAdd', async (reaction, user, channel) => {
if(reaction.message.partial) await reaction.message.fetch();
if(reaction.partial) await reaction.fetch();
if(user.bot) return;
if(!reaction.message.guild) return;
if(reaction.message.channel.id === '8786607442820137464') {
if(reaction.emoji.id === '8789972642138767364') {
reaction.message.guild.members.cache.get(user.id).roles.add('8779841244749004804')
}
}
});
I have a raw event:
this.on('raw', packet => {
if (!['MESSAGE_REACTION_ADD', 'MESSAGE_REACTION_REMOVE'].includes(packet.t)) return;
const channel = this.channels.cache.get(packet.d.channel_id);
if (channel.messages.cache.has(packet.d.message_id)) return;
channel.messages.fetch(packet.d.message_id).then(message => {
const emoji = packet.d.emoji.id ? `${packet.d.emoji.name}:${packet.d.emoji.id}` : packet.d.emoji.name;
const reaction = message.reactions.cache.get(emoji);
if (reaction) reaction.users.cache.set(packet.d.user_id, this.users.cache.get(packet.d.user_id));
if (packet.t === 'MESSAGE_REACTION_ADD') {
this.emit('messageReactionAdd', reaction, this.users.cache.get(packet.d.user_id));
}
if (packet.t === 'MESSAGE_REACTION_REMOVE') {
this.emit('messageReactionRemove', reaction, this.users.cache.get(packet.d.user_id));
}
});
});
This event spams continuously when one reaction is added, I want to make it so if you react it will run once. How can I do this?
You should not use the raw event past discord.js version 12. As there are some issues when your bot grows.
Instead use Partials as explained in the offical Discord.js Guide
const Discord = require('discord.js');
const client = new Discord.Client({ partials: ['MESSAGE', 'CHANNEL', 'REACTION'] });
client.on('messageReactionAdd', async (reaction, user) => {
// When we receive a reaction we check if the reaction is partial or not
if (reaction.partial) {
// If the message this reaction belongs to was removed the fetching might result in an API error, which we need to handle
try {
await reaction.fetch();
} catch (error) {
console.error('Something went wrong when fetching the message: ', error);
// Return as `reaction.message.author` may be undefined/null
return;
}
}
// Now the message has been cached and is fully available
console.log(`${reaction.message.author}'s message "${reaction.message.content}" gained a reaction!`);
// The reaction is now also fully available and the properties will be reflected accurately:
console.log(`${reaction.count} user(s) have given the same reaction to this message!`);
});
Source and more information: https://discordjs.guide/popular-topics/reactions.html#listening-for-reactions-on-old-messages