Discord js direct message await not working - javascript

So my discord bot asks a user in DM for an API secret. I want to await the user's response so that I can do further things with my key.
From client.on('messageCreate') after a user requests to add key I call this function
async function takeApiSecret(msg) {
const botMsg = await msg.author.send("Please provide your api secret");
const filter = collected => collected.author.id === msg.author.id;
const collected = await botMsg.channel.awaitMessages(filter, {
max: 1,
time: 50000,
}).catch(() => {
msg.author.send('Timeout');
});
However I am not able to await the user's response and collect it. Instead when I reply I get another message on my client.on('messageCreate'). Any leads what I could be doing wrong?

In discord.js v13.x, the parameters of awaitMessages() changed a little bit. There are no longer separate parameters for filter and options; filter is now contained within options. This should fix your problem:
const filter = collected => collected.author.id === msg.author.id;
const collected = await botMsg.channel.awaitMessages({
filter,
max: 1,
time: 50000,
}).catch(() => {
msg.author.send('Timeout');
});
You can find the documentation here. For some reason, the options do not appear to be fully documented, but you can view the example on that page to see the new format.
Additionally, if this code is called whenever a message is sent via DM, you may need to prevent collected messages from triggering the rest of your messageCreate event listener code. Here's one way you could do that:
Outside messageCreate handler:
const respondingUsers = new Set();
Right before awaitMessages()
respondingUsers.add(msg.author.id);
Inside your .then() and .catch() on your awaitMessages():
respondingUsers.delete(msg.author.id);
Near the top of your messageCreate handler, right after your other checks (e.g. checking if the message is a DM):
if (respondingUsers.has(msg.author.id)) return;
If we put all of this together, it may look something like this (obviously, modify this to work with your code):
const respondingUsers = new Set();
client.on('messageCreate', msg => {
if (msg.channel.type != "DM") return;
if (respondingUsers.has(msg.author.id)) return;
respondingUsers.add(msg.author.id);
const filter = collected => collected.author.id === msg.author.id;
const collected = botMsg.channel.awaitMessages({
filter,
max: 1,
time: 50000,
})
.then(messages => {
msg.author.send("Received messages");
respondingUsers.delete(msg.author.id);
})
.catch(() => {
msg.author.send('Timeout');
respondingUsers.delete(msg.author.id);
});
})

Related

How to use quick.db variable in client.users.fetch discordjs

I am trying to use a value (Discord User ID stored as a string) stored via quick.db in my code, but it returns me the error user_id: Value "[object Promise]" is not snowflake. I've spent hours trying to figure it out but it just doesn't work. If I store the ID directly in the code it works just fine and I can fetch the user.
This is my "ready.js" file. The cron package lets me define at what time of the day that part of code is being executed. I don't think it's a part of the problem.
const Discord = require("discord.js")
const cron = require('cron');
const path = require('path');
const { QuickDB } = require("quick.db");
const db = new QuickDB()
module.exports = client => {
console.log(`${client.user.username} ist online`)
client.user.setActivity('Online!', { type: 'PLAYING' });
let userid1 = db.get("id1.string");
let scheduledMessage = new cron.CronJob('00 00 08 * * *', () => {
client.users.fetch(userid1).then(user => {
user.send('Test').catch(err => {
let channel = client.channels.cache.get('998568073034465341')
channel.send(`${user} blocked the bot`)
})
})
})
scheduledMessage.start()
}
This is where I want to utilize a User ID stored via quick.db in "id1.string"
client.users.fetch(userid1).then(user => {
-> This doesn't work
client.users.fetch(400120540989227010).then(user => {
-> This is working fine
I've already tried using
`${userid1}`
but this also doesn't work
I'd be so happy if someone could help me with that.
db.get("id1.string") is an async function, meaning unless you put await behind it, it will returns a Promise who isn't finished yet. It's like wanting to send a discord message. You can't just get the message immediatly since because of your connection and the api's connection. It takes time. So to bypass this you have to use the await keyword before the .get method (and async the main function here) so that it won't execute the rest of the code until you get what you want from the database.
let userid1 = db.get("id1.string"); // What you have now
let userid1 = await db.get("id1.string"); // What you should do instead
module.exports = client => { // What you have now
module.exports = async client => { // What you should do instead

How to check the existence of a user in Discord.js without errors?

Explain
I am developing a user verification bot.
Within the requirements I need to remove the user reactions on a specific message, but removing the reactions discord.js triggers the messageReactionRemove event and causes another function to be executed that sets the roles to a user.
Problem/Error
The problem happens when a user leaves the server, discord.js removes the reactions but in turn triggers another event messageReactionRemove and my bot blows up with an error called "Uknow Member"
Codes
Listener when an user leaves the server:
// All this code works fine :)
client.on("guildMemberRemove", async (member) => {
warn(`El usuario ${member.user.username} abandono el servidor`);
try {
const channel = await client.channels.fetch(CHANNELS.VERIFICATION_CHANNEL);
const message = await channel.messages.fetch(
MESSAGES.BOT_VERIFICATION_MESSAGE_ID
);
message.reactions.cache.map((reaction) => {
// After removing the reaction, discord.js fires the event "messageReactionRemove"
reaction.users.remove(member.user.id);
});
} catch (err) {
console.error(err);
}
});
Listener when an user remove a reaction from a message:
if (!user.bot) {
try {
const channelId = reaction.message.channelId;
const messageId = reaction.message.id;
const emojiName = reaction.emoji.name;
const userExists = await getMember(user.id); // <-- This explodes in an error
if (!userExists) {
warn(`El usuario ${user.username} no existe en el servidor`);
return;
}
if (
channelId === CHANNELS.VERIFICATION_CHANNEL &&
messageId === MESSAGES.BOT_VERIFICATION_MESSAGE_ID &&
emojiName === EMOJIS.VERIFICATION_EMOJI
) {
addTownLoyalRoleToNewUsers(reaction, user);
}
// other messages validations ...
} catch (err) {
error(err.message, err);
}
}
Code for add the verification role:
const addTownLoyalRoleToNewUsers = async (reaction, user) => {
const member = await reaction.message.guild.members.fetch(user.id); // <-- This also blows up in an error
const townRoyalUsersLength = await getUsersLengthByRole(
ROLES.TOW_LOYAL_ROLE_ID
);
if (townRoyalUsersLength <= MAX_LENGTH_TOW_LOYAL_USERS) {
member.roles.add(ROLES.TOW_LOYAL_ROLE_ID);
} else {
member.roles.add(ROLES.VERIFIED_ROLE_ID);
}
const otherRoles = ROLES.AFTER_VERIFICATION();
if (otherRoles.length) member.roles.add(otherRoles);
};
Error images
Error when an user leaves a server
Interestingly when the user leaves, the guildMemberRemove event still owns the nonexistent user
If you want to only remove the member who is leaving, then this line of code should do the trick, add it to your guildMemberRemove event
client.channels.cache.get('ChannelIdWithMessage').messages.fetch('MessageIdToRemoveReactionsFrom').then((message) => {
message.reactions.cache.filter(reaction => reaction.users.remove(member.user.id))
})
There are 2 parts to discord.js you should know, there is the Discord API, which is where those errors come from, and then there are the objects, which make interacting with the Discord API easier.
I'm assuming that your getMember() function tries to fetch the member from the API by ID (guild.members.fetch(id)). Note the user is not "non-existent", it's the GuildMember. The object is simply data from the event, but you can't interact with it in the API. You seem to expect the getMember() function to return a falsy value, like null or undefined, if the member is not found. Instead, it gets a 404 and rejects.
Instead, use a .catch() like this:
const userExists = await getMember(user.id).catch(() => null); // null if rejected

Get count of member's messages in channel in discord.js

Is there any way how to count messages of specified user in specified Discord channel in discord.js? When I use:
const countMyMessages = async (channel, member) => {
const messages = await channel.messages.fetch()
const myMessages = message.filter(m => m.author.id === member.id)
console.log(myMessages.size)
}
Only 50 messages are fetched, so I can't count all messages of user. And option limit can have max value 100. /guilds/guild_id/messages/search API on the other hand is not available for bots.
You will need to use a storage system to keep this kind of statistics on Discord.
I recommend you to use SQLite at first (like Enmap npm package).
I can quickly draw a structure for you based on this one.
const Enmap = require("enmap");
client.messages = new Enmap("messages");
client.on("message", message => {
if (message.author.bot) return;
if (message.guild) {
const key = `${message.guild.id}-${message.author.id}`;
client.messages.ensure(key, {
user: message.author.id,
guild: message.guild.id,
messages: 0
});
client.messages.inc(key, "messages");
// Do your stuff here.
console.log(client.messages.get(key, "messages"))
}
});

What is going wrong with my express call? I need an array of ID's but its returning an empty array

Im guessing this problem is because I don't know how to use async await effectively. I still dont get it and I've been trying to understand for ages. sigh.
Anyway, heres my function:
app.post("/declineTrades", async (request, response) => {
//---------------------------------------------
const batch = db.batch();
const listingID = request.body.listingID;
const tradeOfferQuery = db
//---------------------------------------------
//Get trade offers that contain the item that just sold
//(therefore it cannot be traded anymore, I need to cancel all existing trade offers that contain the item because this item isn't available anymore)
//---------------------------------------------
.collection("tradeOffers")
.where("status", "==", "pending")
.where("itemIds", "array-contains", listingID);
//---------------------------------------------
//Function that gets all trade offers that contain the ID of the item.
async function getIdsToDecline() {
let tempArray = [];
tradeOfferQuery.get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
//For each trade offer found
let offerRef = db.collection("tradeOffers").doc(doc.id);
//Change the status to declined
batch.update(offerRef, { status: "declined" });
//Get the data from the trade offer because I want to send an email
//to the who just got their trade offer declined.
const offerGet = offerRef.get().then((offer) => {
const offerData = offer.data();
//Check the items that the receiving person had in this trade offer
const receiverItemIds = Array.from(
offerData.receiversItems
.reduce((set, { itemID }) => set.add(itemID), new Set())
.values()
);
//if the receiver item id's array includes this item that just sold, I know that
//I can get the sender ID (users can be sender or receiver, so i need to check which person is which)
if (receiverItemIds.includes(listingID)) {
tempArray.push(offerData.senderID);
}
});
});
});
//With the ID's now pushed, return the tempArray
return tempArray;
}
//---------------------------------------------
//Call the above function to get the ID's of people that got declined
//due to the item no longer being available
const peopleToDeclineArray = await getIdsToDecline();
//Update the trade offer objects to declined
const result = await batch.commit();
//END
response.status(201).send({
success: true,
result: result,
idArray: peopleToDeclineArray,
});
});
Im guessing that my return tempArray is in the wrong place? But I have tried putting it in other places and it still returns an empty array. Is my logic correct here? I need to run the forEach loop and add to the array before the batch.commit happens and before the response is sent.
TIA Guys!
As #jabaa pointed out in their comment, there are problems with an incorrectly chained Promise in your getIdsToDecline function.
Currently the function initializes an array called tempArray, starts executing the trade offer query and then returns the array (which is currently still empty) because the query hasn't finished yet.
While you could throw in await before tradeOfferQuery.get(), this won't solve your problem as it will only wait for the tradeOfferQuery to execute and the batch to be filled with entries, while still not waiting for any of the offerRef.get() calls to be completed to fill the tempArray.
To fix this, we need to make sure that all of the offerRef.get() calls finish first. To get all of these documents, you would use the following code to fetch each document, wait for all of them to complete and then pull out the snapshots:
const itemsToFetch = [ /* ... */ ];
const getAllItemsPromise = Promise.all(
itemsToFetch.map(item => item.get())
);
const fetchedItemSnapshots = await getAllItemsPromise;
For documents based on a query, you'd tweak this to be:
const querySnapshot = /* ... */;
const getSenderDocPromises = [];
querySnapshot.forEach((doc) => {
const senderID = doc.get("senderID");
const senderRef = db.collection("users").doc(senderID);
getSenderDocPromises.push(senderRef.get());
}
const getAllSenderDocPromise = Promise.all(getSenderDocPromises);
const fetchedSenderDataSnapshots = await getAllSenderDocPromise;
However neither of these approaches are necessary, as the document you are requesting using these offerRef.get() calls are already returned in your query so we don't even need to use get() here!
(doc) => {
let offerRef = db.collection("tradeOffers").doc(doc.id);
//Change the status to declined
batch.update(offerRef, { status: "declined" });
//Get the data from the trade offer because I want to send an email
//to the who just got their trade offer declined.
const offerGet = offerRef.get().then((offer) => {
const offerData = offer.data();
//Check the items that the receiving person had in this trade offer
const receiverItemIds = Array.from(
offerData.receiversItems
.reduce((set, { itemID }) => set.add(itemID), new Set())
.values()
);
//if the receiver item id's array includes this item that just sold, I know that
//I can get the sender ID (users can be sender or receiver, so i need to check which person is which)
if (receiverItemIds.includes(listingID)) {
tempArray.push(offerData.senderID);
}
});
}
could be replaced with just
(doc) => {
// Change the status to declined
batch.update(doc.ref, { status: "declined" });
// Fetch the IDs of items that the receiving person had in this trade offer
const receiverItemIds = Array.from(
doc.get("receiversItems") // <-- this is the efficient form of doc.data().receiversItems
.reduce((set, { itemID }) => set.add(itemID), new Set())
.values()
);
// If the received item IDs includes the listed item, add the
// sender's ID to the array
if (receiverItemIds.includes(listingID)) {
tempArray.push(doc.get("senderID"));
}
}
which could be simplified to just
(doc) => {
//Change the status to declined
batch.update(doc.ref, { status: "declined" });
// Check if any items that the receiving person had in this trade offer
// include the listing ID.
const receiversItemsHasListingID = doc.get("receiversItems")
.some(item => item.itemID === listingID);
// If the listing ID was found, add the sender's ID to the array
if (receiversItemsHasListingID) {
tempArray.push(doc.get("senderID"));
}
}
Based on this, getIdsToDecline actually queues declining the invalid trades and returns the IDs of those senders affected. Instead of using the batch and tradeOfferQuery objects that are outside of the function that make this even more unclear, you should roll them into the function and pull it out of the express handler. I'll also rename it to declineInvalidTradesAndReturnAffectedSenders.
async function declineInvalidTradesAndReturnAffectedSenders(listingID) {
const tradeOfferQuery = db
.collection("tradeOffers")
.where("status", "==", "pending")
.where("itemIds", "array-contains", listingID);
const batch = db.batch();
const affectedSenderIDs = [];
const querySnapshot = await tradeOfferQuery.get();
querySnapshot.forEach((offerDoc) => {
batch.update(offerDoc.ref, { status: "declined" });
const receiversItemsHasListingID = offerDoc.get("receiversItems")
.some(item => item.itemID === listingID);
if (receiversItemsHasListingID) {
affectedSenderIDs.push(offerDoc.get("senderID"));
}
}
await batch.commit(); // generally, the return value of this isn't useful
return affectedSenderIDs;
}
This then would change your route handler to:
app.post("/declineTrades", async (request, response) => {
const listingID = request.body.listingID;
const peopleToDeclineArray = await declineInvalidTradesAndReturnAffectedSenders(listingID);
response.status(201).send({
success: true,
result: result,
idArray: peopleToDeclineArray,
});
});
Then adding the appropriate error handling, swapping out the incorrect use of HTTP 201 Created for HTTP 200 OK, and using json() instead of send(); you now get:
app.post("/declineTrades", async (request, response) => {
try {
const listingID = request.body.listingID;
const affectedSenderIDs = await declineInvalidTradesAndReturnAffectedSenders(listingID);
response.status(200).json({
success: true,
idArray: affectedSenderIDs, // consider renaming to affectedSenderIDs
});
} catch (error) {
console.error(`Failed to decline invalid trades for listing ${listingID}`, error);
if (!response.headersSent) {
response.status(500).json({
success: false,
errorCode: error.code || "unknown"
});
} else {
response.end(); // forcefully end corrupt response
}
}
});
Note: Even after all these changes, you are still missing any form of authentication. Consider swapping the HTTPS Event Function out for a Callable Function where this is handled for you but requires using a Firebase Client SDK.

Creating a menu like structure (discord.js)

I wanted to create a menu of items that the bot can do and then the user can select the operation to perform.
For example:
When I say +Menu then the bot shows something like:
1. Time in NY
2. Movies currently running
3. Sports News
Then I wanted to take the user's input (1,2 or 3) and then based on their selection, the bot will execute the task.
But I am not sure how to read the user's input after the command (+Menu) and wanted to ask for help.
You are looking for a message collector. See the docs here
Personally I would create an embed with the options in it e.g.
const menuEmbed = new Discord.MessageEmbed()
.setTitle("Menu")
.addFields(
{ name: "1.", value: "Time in NY"},
{ name: "2.", value: "Movies currently running"},
{ name: "3.", value: "Sports News"}
);
message.channel.send(menuEmbed).then(() => {
const filter = (user) => {
return user.author.id === message.author.id //only collects messages from the user who sent the command
};
try {
let collected = await message.channel.awaitMessages(filter, { max: 1, time: 15000, errors: ['time'] });
let choice = collected.first().content; //takes user input and saves it
//do execution here
}
catch(e)
{
return message.channel.send(`:x: Setup cancelled - 0 messages were collected in the time limit, please try again`).then(m => m.delete({ timeout: 4000 }));
};
});
Then use a collector to let the user choose an option.
Bear in mind this is using async/await and must be in an async function.

Categories

Resources