MongoDB only using first entry - javascript

I made an adventure command that searches for a persons profile in a Mongo database, then uses that info to calculate xp, coins, etc. I have made several other commands that use the database that work perfectly. This one, however, starts out fine by finding the correct document, but then when I try to populate the command with info from the called up document, it only pulls info from the first document in the database which belongs to my profile. So, everytime someone does adventure, it calculates everything based on my profile, then goes and adds it to the correct one. So they keep earning xp and coins but not according to their own profile info. See code below.
const { SlashCommandBuilder } = require('#discordjs/builders');
const { Collection, MessageActionRow, MessageButton } = require("discord.js");
const econModel = require('../models/econModel');
const humanizeDuration = require('humanize-duration');
const adventureCooldowns = new Collection();
module.exports = {
data: new SlashCommandBuilder()
.setName("adventure")
.setDescription("Explore the world and find treasure"),
execute(interaction){
const Member = interaction.member;
const memberHasAdvCooldown = adventureCooldowns.has(Member.id);
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
if(memberHasAdvCooldown){
const remaining1 = adventureCooldowns.get(Member.id) - new Date();
const remainingFormatted1 = humanizeDuration(remaining1, {
language: 'en',
round: true,
units: ['h', 'm', 's'],
})
interaction.reply({ content: `You must wait ${remainingFormatted1} for the cooldown period to end before adventuring again.`, ephemeral: true});
}
if(!adventureCooldowns.has(Member.id)){
econModel.findOne({ MemberId: Member.id }, async (err, Account) => {
if(err) throw err;
if(Account) {
console.log(`ID: ${Member.id}\n${Member.nickname}`);
const curXp = Account.Exp;
console.log(`Current XP: ${curXp}`);
const curLvl = Account.Level;
console.log(`Curent Level: ${curLvl}`);
const coinBonus = Account.coinBonus;
console.log(`Coin Bonus: ${coinBonus}`);
const xpBonus = Account.expBonus;
console.log(`XP Bonus: ${xpBonus}`);
const xpBase = Math.floor(Math.random() * 10 + 50);
console.log(`Base XP: ${xpBase}`);
const xpLvlBonus = xpBase * curLvl;
console.log(`lvl XP: ${xpLvlBonus}`);
const xpTotal = Math.floor(xpLvlBonus * xpBonus);
console.log(`Total: ${xpTotal}`);
const Coins = getRandomInt(25, 200);
console.log(`Base coins: ${Coins}`);
const coinAdd = Math.floor(Coins * coinBonus);
console.log(`total coins: ${coinAdd}`);
const nextLvl = (500 * Math.pow(2, curLvl))/2;
console.log(`Next level: ${nextLvl}`);
const newExp = curXp + xpTotal;
if(nextLvl <= newExp){
econModel.collection.updateOne({ MemberID: Member.id }, { $inc: {Level: +1} });
econModel.collection.updateOne({ MemberID: Member.id }, { $inc: {Fowlercoins: +coinAdd} });
econModel.collection.updateOne({ MemberID: Member.id }, { $inc: {Exp: +xpTotal} });
interaction.reply({ content: `${Member.nickname}\nYou have leveled up! You are now level ${Account.Level + 1}!\nYou gained ${xpTotal} experience and found ${coinAdd} coins.`, components: [] });
econModel.collection.updateOne({ MemberID: Member.id }, { $set: {Next: nextLvl} });
adventureCooldowns.set(Member.id, Date.now() + 5000); //5000 is 5 seconds
setTimeout(() => adventureCooldowns.delete(Member.id), 5000);
} else{
econModel.collection.updateOne({ MemberID: Member.id }, { $inc: {Fowlercoins: +coinAdd} });
econModel.collection.updateOne({ MemberID: Member.id }, { $inc: {Exp: +xpTotal} });
interaction.reply({ content: `${Member.nickname}\nYou gained ${xpTotal} experience and found ${coinAdd} coins.`, components: [] })
econModel.collection.updateOne({ MemberID: Member.id }, { $set: {Next: nextLvl} });
adventureCooldowns.set(Member.id, Date.now() + 5000); //5000 is 5 seconds
setTimeout(() => adventureCooldowns.delete(Member.id), 5000);
}
} else{
interaction.reply({ embeds: [new MessageEmbed()
.setTitle("ECONOMY SYSTEM")
.setColor("#3be820")
.setDescription(`${interaction.member.nickname} does not have an account! To create one, use /economy start!`)
.setFooter({ text: 'Rum Bot 1.4.5' })]});
}
})
}
}
}
As you can see, the first thing it tries to use is Account.Exp from the called upon document, but it always fills it in with the first document in the database. Any idea how to stop this from happening? I have been trying for a week and have yet to make any progress.

The additional information from the tests in the comment is very helpful and informative.
It sounds like the actual data in your system is not as you expect it to be. So the system is probably behaving correctly, and the unexpected behavior is a consequence of the current data. Walking through parts of the comment in order will help this answer.
I used the .count method as well as the .findOne() method. The count brought back 10 documents, despite telling it to find the one with a specific ID
The phrasing of "despite telling it to find the one [document] with a specific ID" here is interesting. Is there a unique index in the econModel collection on the MemberId field? If not, then there is no constraint in the database itself to enforce the implied claim that there should only be one document with a given value for the MemberId field.
The findOne query acted differently, though. Despite asking for a specific document (one that wasn't the first one in the collection) it would still return with the first document. Seems like the findOne query isn't operating correctly.
Why are you assuming that the output of findOne({MemberId: <val>}) is wrong? Did the document that was returned have a different value for its MemberId field than the one you queried with?
Based on your testing, I am under the impression that there are 10 documents in your econModel collection that have a value for their MemberId field that matches the one you are searching against. As next steps I would recommend examining all of them (.find({MemberId: <val>})), updating the values if they are not as expected, and creating a unique index on the field if you want the database to enforce uniqueness on that field going forward. May also need to also double check that there is not some bug in the code (as opposed to manual testing) which caused the discrepancy in the first place.

Related

Find and update dynamic values MongoDB

I'm coding a Discord bot in java script right now. I use MongoDB to store a "heat" value for every user in my server. (The "heat-system" adds a certain amount to a percentage whenever the user does something wrong and takes it off again after some time, to be more specific: -5 percent every minute in my example).
To do that, I want to use Model.updateMany() in a loop. As you see below, I used the filter parameter to find every document related to my server. The question now is how I can take these 5% off the stored value because it's not static, but dynamic.
const { Client } = require('discord.js');
const mongoose = require('mongoose');
//using modules and event handlers
module.exports = {
name: 'ready',
/**
* #param {Client} client
*/
async execute(client) {
const heatdb = require('/app/models/heatDB.js');
//how often the loop should pe repeated
const interval = 10 * 60 * 1000;
console.log('Client ready.')
//connnecting my data
mongoose.connect(
'I put the mongoose link here', {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => {
console.log('Data connected.')
}).catch((err) => {
console.log(err)
});
//loop begins here
setInterval(function(){
//filtered so that it's only searching for documents having the guild ID in it
heatdb.updateMany({ GuildID: "000000000000000000"}, {Heat: parseInt(/*needed value*/) - 5}})
}, interval);
},
};
And also see how my model is built below:
const { Schema, model } = require('mongoose');
module.exports = model("heatDB", new Schema({
GuildID: String,
UserID: String,
Heat: String,
}))
If you need anything else to help me, please let me know.
Thank you in advance,
Hydra
If your Heat value is a Number in Schema instead of String then you can try this-
heatdb.updateMany({ GuildID: "000000000000000000"},{$mul:{"Heat":0.95}})
Explanation:- you want to reduce Heat every time 5% percent then you can use mul
operator & set your heat value to 95% of current value. It will give you 5% deduction every time.

My discord bot won't stop spamming a respond to my command

I added a //search command to my bot. But when I tried running it, it gave me the options on where to search, but after a while, it starts spamming messages, and every time I type in the channel, it responds to that too. I haven't even answered the question yet and it immediately starts replying and giving me coins. Is there a way I can fix this? I've scanned the code multiple times and found no typos or errors; in fact, there are no errors logging into my console either.
If the code is needed, here:
const profileModel = require("../models/profileSchema");
module.exports = {
name: "search",
aliases: [],
permissions: [],
cooldowns: 30,
description: "Search for some coin!",
async execute(message, args, cmd, client, Discord, profileData) {
const locations = [
"Dragonspine",
"Windrise",
"Qingyun Peak",
"Mt. Hulao",
"Mondstadt City",
"Springvale",
"Kamisato Estate",
"Guyun Stone Forest",
"Fort Mumei",
"Watatsumi Island",
];
const chosenLocations = locations
.sort(() => Math.random() - Math.random())
.slice(0, 3);
const filter = ({ author, content }) =>
message.author == author &&
chosenLocations.some(
(location) => location.toLowerCase() == content.toLowerCase()
);
const collector = message.channel.createMessageCollector(filter, {
max: 1,
time: 30000,
});
const earnings = Math.floor(Math.random() * (1000 - 100 + 1)) + 100;
collector.on("collect", async (m) => {
message.channel.send(`You found ${earnings} primogems !`);
await profileModel.findOneAndUpdate(
{
userID: message.author.id,
},
{
$inc: {
primogems: earnings,
},
}
);
});
collector.on("end", (collected, reason) => {
if (reason == "time") {
message.channel.send("You ran out of time!");
}
});
message.channel.send(
`<#${
message.author.id
}> **Which location would you like to search?\n** Type the location in this channel\n \`${chosenLocations.join(
"` `"
)}\``
);
},
};
Super easy fix! The thing is you're creating a message collector on your TextChannel and then sending your message, of which location then user would want to pick, what you simply have to do is that move the collector to the Message object instead of TextChannel after sending the message.

I'm trying to create a channel named like user args. [DISCORD.JS V12]

It just gives me an error that the function message.guild.channels.create does not work because it's not a correct name.
My intention is to create a command where you will be asked how the channel you want to create be named. So it's ask you this. After this you send the wanted name for the channel. Now from this the bot should name the channel.
(sorry for bad english and low coding skills, im a beginner)
module.exports = {
name: "setreport",
description: "a command to setup to send reports or bugs into a specific channel.",
execute(message, args) {
const Discord = require('discord.js')
const cantCreate = new Discord.MessageEmbed()
.setColor('#f07a76')
.setDescription(`Can't create channel.`)
const hasPerm = message.member.hasPermission("ADMINISTRATOR");
const permFail = new Discord.MessageEmbed()
.setColor('#f07a76')
.setDescription(`${message.author}, you don't have the permission to execute this command. Ask an Admin.`)
if (!hasPerm) {
message.channel.send(permFail);
}
else if (hasPerm) {
const askName = new Discord.MessageEmbed()
.setColor(' #4f6abf')
.setDescription(`How should the channel be called?`)
message.channel.send(askName);
const collector = new Discord.MessageCollector(message.channel, m => m.author.id === message.author.id, { max: 1, time: 10000 });
console.log(collector)
var array = message.content.split(' ');
array.shift();
let channelName = array.join(' ');
collector.on('collect', message => {
const created = new Discord.MessageEmbed()
.setColor('#16b47e')
.setDescription(`Channel has been created.`)
message.guild.channels.create(channelName, {
type: "text",
permissionOverwrites: [
{
id: message.guild.roles.everyone,
allow: ['VIEW_CHANNEL','READ_MESSAGE_HISTORY'],
deny: ['SEND_MESSAGES']
}
],
})
.catch(message.channel.send(cantCreate))
})
}
else {
message.channel.send(created)
}
}
}
The message object currently refers to the original message posted by the user. You're not declaring it otherwise, especially seeing as you're not waiting for a message to be collected before defining a new definition / variable for your new channel's name.
NOTE: In the following code I will be using awaitMessages() (Message Collector but promise dependent), as I see it more fitting for this case (Seeing as you're more than likely not hoping for it to be asynchronous) and could clean up the code a little bit.
const filter = m => m.author.id === message.author.id
let name // This variable will later be used to define our channel's name using the user's input.
// Starting our collector below:
try {
const collected = await message.channel.awaitMessages(filter, {
max: 1,
time: 30000,
errors: ['time']
})
name = collected.first().content /* Getting the collected message and declaring it as the variable 'name' */
} catch (err) { console.error(err) }
await message.guild.channels.create(name, { ... })

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.

Reaction collector, display percentage (used for suggestions)

So I've basically set up my suggestion bot, it's a very basis one but I'm looking to add a cool feature that will collect the positive and negative reactions and display a percentage. If the positive votes are more it would display 100%, if it's 1 positive and 1 negative it would display 50% and if it's negative it's 1 negative and nothing else it would display -100%. It's very simple but I'm struggling to understand how to do it. Any ideas?
For handle reaction you can use method createReactionCollector, but the 1 one problem is: method not triggered on reaction remove. So you need use some interval to check message reaction.
time: 120000 - its time to await reaction of millisecond, change it to whats you need.
If bot go restart hadling reactions will stop...
client.on('message', message => {
if (message.content.startsWith('test')) {
let suggestion = message.content.substring(0, 4) //test length
let embed = new Discord.MessageEmbed();
embed.setAuthor(message.author.tag, message.author.displayAvatarURL({
dynamic:true,
format: "png"
}))
embed.setTitle('Suggestion')
embed.setColor('GOLD')
embed.setDescription(suggestion)
embed.setTimestamp()
message.channel.send(embed).then(msg => {
msg.react('šŸ‘').then(() => msg.react('šŸ‘Ž'))
const filter = (reaction, user) => {
return [`šŸ‘`, 'šŸ‘Ž'].includes(reaction.emoji.name)
};
let check = setInterval(handleReaction, 5000, message, msg, suggestion)
const collector = msg.createReactionCollector(filter, {
time: 120000,
});
collector.on('collect', (reaction, reactionCollector) => {
handleReaction(message, msg, suggestion)
});
collector.on('end', (reaction, reactionCollector) => {;
clearInterval(check)
});
})
}
})
function handleReaction (message, msg, suggestion) {
let embed = new Discord.MessageEmbed();
let positiveReaction = msg.reactions.cache.get('šŸ‘')
let negativeReaction = msg.reactions.cache.get('šŸ‘Ž')
let negativeCount = negativeReaction ? negativeReaction.count : 0
let positiveCount = positiveReaction ? positiveReaction.count : 0
embed.setAuthor(message.author.tag, message.author.displayAvatarURL({
dynamic:true,
format: "png"
}))
embed.setTitle('Suggestion')
embed.setColor('GOLD')
embed.setDescription(suggestion)
embed.addField('Votes', `šŸ‘ - ${(positiveCount / (positiveCount + negativeCount) * 100).toFixed(2)}%\nšŸ‘Ž - ${(negativeCount / (positiveCount + negativeCount) * 100).toFixed(2)}%`)
embed.setTimestamp()
msg.edit(embed)
}
I Don't Really Now If This Is What You Were Looking For Directly But Here Is My Old Code From An Old Bot I Had
const sayMessage = args.join(" ");
if (!args.length) {
return message.channel.send(`You didn't provide any text! <:angry:713422454688710717>`);
}
const sentMessage = await client.channels.get(< CHANNEL ID HERE >).send({embed: {
color: 700000,
author: {
name: client.user.username,
},
title: "Suggestion",
description: sayMessage,
footer: {
timestamp: new Date(),
icon_url: client.user.avatarURL,
text: "Suggestion"
}
}});
message.react('šŸ‘');
sentMessage.react('šŸ‘').then(sentMessage.react('šŸ‘Ž'))
message.channel.send("To vote for a suggestion, join my server. It should just be ..server!")
What It Does Is It Takes The Users Message And Puts It In An Embed And Reacts With Thumbs Up And Thumbs Down In The Channel Of Your Choice
But It May Need To Be Updated A Bit If Your Using V.12

Categories

Resources