Discord-xp / Canvacord Ranking system - node.js

I am having issues with my rank system, when I attempted to level up (by spamming), I started to notice that I don't actually reach the required amount of XP before leveling up (I have an image below to further explain this).
I've asked a few people about the issue, but they can't seem to know the problem, so here I am.. asking the wonderful people of stackoverflow!
I have tried a few things to try to fix the issue, but at this point I feel like its a dead-end, perhaps I'm doing something wrong, or just don't fully understand it, or maybe I am calculating the XP wrong, no idea at this time.
Below I have provided my code for the portion I have in the messageCreate event, and I've also provided my code the the entire rank command in hopes that I would get some assistance or a point in the right direction on what I need to do to solve this issue.
messageCreate.js
const Levels = require("discord-xp");
Levels.setURL("");
client.on("messageCreate", async (message) => {
if (!message.guild) return;
if (message.author.bot) return;
const xp = Math.floor(Math.random() * 29) + 1;
const hasLeveledUp = await Levels.appendXp(message.author.id, message.guild.id, xp);
if (hasLeveledUp) {
const user = await Levels.fetch(message.author.id, message.guild.id);
message.channel.send({ content: `<a:CL_Confetti:909438449910681680> ${message.author} has reached **level ${user.level}**!` });
}
...
});
rank.js
const { MessageEmbed, MessageAttachment } = require("discord.js");
const Levels = require("discord-xp");
const canvacord = require("canvacord");
module.exports = {
name: "rank",
aliases: [],
description: "Displays a users current rank!",
usage: "<user>",
run: async (client, message, args) => {
let target = message.mentions.users.first()
? message.mentions.users.first()
: message.author;
const user = await Levels.fetch(target.id, message.guild.id, true);
if (!user) return message.reply("That user does not have any xp.");
const rank = new canvacord.Rank()
.setAvatar(target.displayAvatarURL({ dynamic: false, format: "png", size: 512 }))
.setCurrentXP(user.cleanXp)
.setRequiredXP(Levels.xpFor(user.level + 1))
.setRank(user.position)
.setLevel(user.level)
.setStatus(message.member.presence.status)
.setProgressBar("#DC143C", "COLOR")
.setUsername(target.username)
.setDiscriminator(target.discriminator);
rank.build().then((data) => {
const attachment = new MessageAttachment(data, "rank.png");
message.reply({ files: [attachment] });
});
},
};
Image
I also have this image here of what it is doing inside discord, any help would be greatly appreciated as I have recently taken my first steps into canvacord, and am willing to learn from this issue!

I found the issue.
In messageCreate.js you have an XP mulitpleir. The problem is that its also not reading rank.js/its not required.
const xp = Math.floor(Math.random() * 29) + 1;
.setRequiredXP(Levels.xpFor(user.level + 1)) more or less your multiplier is surpassing the amount required in my opinion, and with the two different files your assigning 2 different values, so it gets called in rank.js and then it checks if it has surpassed the xp limit. rank.js added not messagecreate but messagecreative is updating the value by that multiplier without checking whatever updates the leveling in rank js which is basically 1.
You don't need messagecreate.js you can technically just check if someone has did whatever message.js is doing. and also take out.
ANYWAYS the real issue is that in messagejs its checking if it leveled up and then responding so you are leveling up without being told the rank embed to update nor the value of hasLeveled up when it does level up in messageCreate.

Related

DiscordJS v13 send to specific channel

So I am trying to send an embed to a different channel than the command was used in as a log channel how ever I have tried a few different methods however where I am at now is the error i get is qChann.send is not a function it is yelling at the .send part.
This is the part referenced in index.js Yes I know it is bad practice to reference and export from main file but I am desperate for a solution
client.on('ready', async () => {
console.log('Online');
const qChann = client.channels.cache.get('960425106964885535');
console.log(qChann);
})
The second part is in a command file call deposit.js
It uses a 2 part collector ignore date and time stuff thats from express
I am also using mongoDB and it works completely fine how ever that last little statement is giving me a hard time qChann.send({embed: steelEmbed}); also qChann is included as const qChann = require('../index');
if (collected.content.toLowerCase() === 'steel') {
message.reply('Enter the amount youd like to deposit');
collector.stop('user entered steel');
const steelCollector = message.channel.createMessageCollector({
filter,
max: 1,
time: 1000 * 20,
});
steelCollector.on('collect', async (collected) => {
const steelAmount = collected.content;
await steelSchema.findOneAndUpdate({
$inc: {
steel: +steelAmount,
}
})
steelNewCount = await steelSchema.findOne({steelSchema}, {_id: 0});
const steelEmbed = new MessageEmbed()
.setColor('#4278f5')
.setTitle(`Ammo11 Stash Update`)
.setDescription(`Steel Count Updated`)
.addField(`Stash Updated`, `by <#${message.author.id}>`, true)
.addField(`Date Log`, `${date} / ${month} / ${year}`, true)
.addField(`Steel Deposited`, `${steelAmount}`, true)
.addField(`New Steel Count`, `${steelNewCount}`, true )
.setTimestamp()
.setFooter({text:'THIS MESSAGE IS NOT TO BE DELETED'});
qChann.send({embed: steelEmbed});
})
}
You need to define channel and then export it. You didn't export so you can't take it from another file as i know.
// outside of events
const qChann = client.channels.cache.get("960425106964885535")
module.exports = { qChann }
After this edit on your main file, you can access your channel with importing it with require in your command files.

Helping Finding A friend bot

My friend has a discord server called Find A Friend and he wants me to code a bot to type a command to ask a series of questions and put it in a channel. People can see if anybody interests them and becomes friends with them. I have already coded part of it.
One of the questions it asks is: What games do you like?
My friend wants to make it able that the bot searches the other answers to this same question and dm the user if they have a game in common. I want to do the same for another question: What are some shows, movies, and anime that you like?
Here is my code for the series of questions:
const { Client, Message, MessageEmbed } = require("discord.js")
module.exports = {
name: "faf",
/**
* #param {Client} client
* #param {Message} message
* #param {String[]} args
*/
run: async (client, message, args) => {
message.delete()
const questions = [
"What is your usernames for any games you have including discord (is fine if you dont put them all but you MUST include your discord)",
"What is your age (not required)",
"What is your gender",
"Do you talk in Voicechannel or through text?",
"What games do you like?",
"What are some shows, movies, and anime that you like",
"Where are you from (not required)",
"Please state anything else you want to say"
]
let collectCounter = 0;
let endCounter = 0;
const filter = (m) => m.author.id === message.author.id;
const appStart = await message.author.send(questions[collectCounter++]);
const channel = appStart.channel;
const collector = channel.createMessageCollector(filter);
collector.on("collect", () => {
if(collectCounter < questions.length) {
channel.send(questions[collectCounter++])
} else {
channel.send('Your Find A Friend thread has been sent, Good Luck!')
collector.stop("fulfilled");
}
});
const appsChannel = client.channels.cache.get("831292911437086760");
collector.on('end', (collected, reason) => {
if(reason === 'fulfilled') {
let index = 1;
const mappedResponses = collected
.map((msg) => {
return `${index++}) ${questions[endCounter++]}\n-> ${msg.content}`;
}).join('\n\n')
appsChannel.send(
new MessageEmbed()
.setAuthor(
message.author.tag,
message.author.displayAvatarURL({ dynamic: true })
)
.setTitle('Friend Thread')
.setDescription(mappedResponses)
.setColor('RED')
.setTimestamp()
)
}
});
},
};
TL;DR I currently have a message collector bot that records responses to questions. How can I make the bot recognize when two people share common interests, such as games, movies, shows, or anime, based on their responses?

Pub/Sub Cloud Function does not Update Document in Subcollection

I am trying to update a field in my document in Firestore. The general location of the document would be "/games/{userId}/userGames/{gameId}. And in this game, there is a property called "status" which changes accordingly to the games start and end time.
As you can guess, the if the start time is bigger than the "now" timestamp and the status is "TO_BE_PLAYED", the game will begin and the status will be 1, "BEING_PLAYED". Also, if the end time is bigger than the "now" timestamp and the status is "BEING_PLAYED", the game will end, therefore the status will be 2, "PLAYED". I want to create a cloud function that is capable to do so.
However, even if the function logs output 'ok', the values are never updated. Unfortunately, I do not have that much experience in Javascript too.
THE CODE
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const STATUS_PLAYED = 2;
const STATUS_BEING_PLAYED = 1;
const STATUS_TO_BE_PLAYED = 0;
exports.handleBeingPlayedGames = functions.runWith({memory: "2GB"}).pubsub.schedule('* * * * *')
.timeZone('Europe/Istanbul') // Users can choose timezone - default is America/Los_Angeles
.onRun(async () => {
// current time & stable
// was Timestamp.now();
const now = admin.firestore.Timestamp.fromDate( new Date());
const querySnapshot = await db.collection("games").get();
const promises = [];
querySnapshot.forEach( doc => {
const docRef = doc.ref;
console.log(docRef);
promises.push(docRef.collection("userGames").where("status", "==", STATUS_BEING_PLAYED).where("endtime", "<", now).get());
});
const snapshotArrays = await Promise.all(promises);
const promises1 = [];
snapshotArrays.forEach( snapArray => {
snapArray.forEach(snap => {
promises1.push(snap.ref.update({
"status": STATUS_PLAYED,
}));
});
});
return Promise.all(promises1);
});
exports.handleToBePlayedGames = functions.runWith({memory: "2GB"}).pubsub.schedule('* * * * *')
.onRun(async () => {
// current time & stable
// was Timestamp.now();
const now = admin.firestore.Timestamp.fromDate(new Date());
const querySnapshot = await db.collection("games").get();
const promises = [];
querySnapshot.forEach( async doc => {
const docData = await doc.ref.collection("userGames").where("status", "==", STATUS_TO_BE_PLAYED).where("startTime", ">", now).get();
promises.push(docData);
});
const snapshotArrays = await Promise.all(promises);
const promises1 = [];
snapshotArrays.forEach( snapArray => {
snapArray.forEach(snap => {
promises1.push(snap.ref.update({
"status": STATUS_BEING_PLAYED,
}));
});
});
return Promise.all(promises1);
});
Okay, so this answer goes to lurkers trying to solve this problem.
First I tried to solve this problem by brute force and not including much thinking and tried to acquire the value in subcollection. However, as I searched, I've found that denormalizing (flattening) data actually solves the problem a bit.
I created a new directory under /status/{gameId} with the properties
endTime, startTime, and status field and I actually did it on a single level by using promises. Sometimes denormalizing data can be your savior.
How can startTime be greater than now? Is it set by default to a date in the future?
My current assumption is that a game cannot set it's status to STATUS_BEING_PLAYED because of the inconsistency with startTime. Moreover, a game cannot have the status STATUS_PLAYED because it depends on having STATUS_BEING_PLAYED, which cannot have.
My recommendation would be to set the field startTime and endTime to null by default. If you do so you can check if a game has to be set to STATUS_BEING_PLAYED with this:
doc.ref.collection("userGames")
.where("status", "==", STATUS_TO_BE_PLAYED)
.where("startTime", "<", now)
.where("endTime", "==", null)
.get();
You could check if a game has to be on STATUS_PLAYED with this (exactly as you did):
docRef.collection("userGames")
.where("status", "==", STATUS_BEING_PLAYED)
.where("endtime", "<", now)
.get();
Now there's something that you should wonder, is this the best approach to change a game's status? You are querying the whole game library of a user every single minute as you know read operations are charged so this approach would imply meaningful charges. Maybe you should simply use update the game's status when the game is started and closed.
Also notice that the equals operation is ==, not =.

How can I resolve this problem (node.js, discord.js)

if (!args[1]) return message.channel.send('You need to specify a person!')
if (!args[2]) return message.channel.send('Please specify a time for how long the ban council will last.')
var tim = args[2]
const sweden = message.mentions.users.first()
message.react('👍').then(() => message.react('👎'))
const mappo = ['👍', '👎']
if (!args[1]) return message.channel.send('Please specify a person!')
if(message.guild){ //this will check if the command is being called from a server
const embad = new Discord.MessageEmbed()
.setTitle('Ban Council')
.addField('The Convicted:', `${sweden.tag}`)
.addField('Rules:', 'Vote on whether the convicted is guilty or not with the prompted reactions. The ban will end automatically in 5 seconds.')
message.channel.send(embad)
setTimeout(function(){
if(sweden){
const lyft = message.guild.member(sweden)
if(lyft){
if(message.reactions.cache.map(r => `${'👍'} ${r.users.cache.size}`)[0] > message.reactions.cache.map(r => `${'👍'} ${r.users.cache.size}`)[1]){
lyft.ban({ ression: 'Majority has exiled you from server. '}).then(() => {
message.reply(`The user ${december.tag} was banned as a result of a majority vote.`)
})
} else {
message.channel.send('The ban was cancelled.')
}
}else{
message.reply('The user is not in this server.')
}
}else{
message.reply('You need to specify a person!')
}
}, tim)
} else {
message.channel.send('Banning does not work here!')
}
It sends the "Ban cancelled" before it actually has the chance to take input. I've tried collectors, and it doesn't work because of the max: part, how can I resolve this problem? (Also it returns no errors)
This is in a case. (appending onto what feedback I got)
Firstly you should beautify your code before you post on stackoverflow, you could have removed the break keyword and just explain it was inside of a switch case
The reason it does not work is because you are checking the message's reaction, and not the embed that you send, so to fix this you need to assign a variable to message.channel.send(embad), but this is a promise so you need to await it, which requires an async function
lastly awaitReactions and createReactionCollector are probably better options,
So here's the new code:
(async () => {
if (!args[1]) return message.channel.send('You need to specify a person!');
if (!args[2]) return message.channel.send('Please specify a time for how long the ban council will last.')
if (!message.guild) return message.channel.send('Banning does not work here');
var tim = args[2]
const sweden = message.mentions.member.first()
const mappo = ['👍', '👎']
message.react('👍').then(() => message.react('👎'))
const embad = new Discord.MessageEmbed()
.setTitle('Ban Council')
.addField('The Convicted:', `${sweden.tag}`)
.addField('Rules:', 'Vote on whether the convicted is guilty or not with the prompted reactions. The ban will end automatically in 5 seconds.')
const embedMessage = await message.channel.send(embad);
setTimeout(function () {
if (!sweden) return message.reply('You need to mention a person');
const filter = (reaction) => mappo.includes(reaction.emoji.name);
embad.awaitReactions(filter, { time: 5000 })
.then(collection => {
//not the most optimal way to do it
const emoji1 = collection.find(e => e.emoji.name === '👍');
const emoji2 = collection.find(e => e.emoji.name === '👎');
//both default to 0
const upvotes = emoji1 && emoji1.users.size || 0;
const downvotes = emoji2 && emoji2.users.size || 0;
if (upvotes > downvotes) {
lyft.ban({ reason: 'Majority has exiled you from server. ' })
.then(() => message.reply(`The user ${december.tag} was banned as a result of a majority vote.`));
} else {
message.channel.send('The ban was cancelled.');
}
})
.catch(console.error);
}, tim);
})();
It's been around 8 months since I made this post, and I found the answer, and an even more effective way to do it. I won't post the entire code, as it's pretty long and not very neat. However, this is a much more effective way of counting reactions.
My Original Code was:
const upvotes = message.reactions.cache.map(r => ${'👍'} ${r.users.cache.size})[0]
const downvotes = message.reactions.cache.map(r => ${'👎'} ${r.users.cache.size})[1]
It doesn't work very well either.
const [upvoteReaction, downvoteReaction] = message.reactions.cache.first(2);
const upvotes = upvoteReaction.users.cache.size;
const downvotes = downvoteReaction.users.cache.size;
It takes the number of reactions from the first two reactions on the message. Seeing as how the only reactions are thumbs up and thumbs down, it will get the numbers from both of them. From there, you can just compare the two.

Get all messages from AWS SQS in NodeJS

I have the following function that gets a message from aws SQS, the problem is I get one at a time and I wish to get all of them, because I need to check the ID for each message:
function getSQSMessages() {
const params = {
QueueUrl: 'some url',
};
sqs.receiveMessage(params, (err, data) => {
if(err) {
console.log(err, err.stack)
return(err);
}
return data.Messages;
});
};
function sendMessagesBack() {
return new Promise((resolve, reject) => {
if(Array.isArray(getSQSMessages())) {
resolve(getSQSMessages());
} else {
reject(getSQSMessages());
};
});
};
The function sendMessagesBack() is used in another async/await function.
I am not sure how to get all of the messages, as I was looking on how to get them, people mention loops but I could not figure how to implement it in my case.
I assume I have to put sqs.receiveMessage() in a loop, but then I get confused on what do I need to check and when to stop the loop so I can get the ID of each message?
If anyone has any tips, please share.
Thank you.
I suggest you to use the Promise api, and it will give you the possibility to use async/await syntax right away.
const { Messages } = await sqs.receiveMessage(params).promise();
// Messages will contain all your needed info
await sqs.sendMessage(params).promise();
In this way, you will not need to wrap the callback API with Promises.
SQS doesn't return more than 10 messages in the response. To get all the available messages, you need to call the getSQSMessages function recursively.
If you return a promise from getSQSMessages, you can do something like this.
getSQSMessages()
.then(data => {
if(!data.Messages || data.Messages.length === 0){
// no messages are available. return
}
// continue processing for each message or push the messages into array and call
//getSQSMessages function again.
});
You can never be guaranteed to get all the messages in a queue, unless after you get some of them, you delete them from the queue - thus ensuring that the next requests returns a different selection of records.
Each request will return 'upto' 10 messages, if you don't delete them, then there is a good chance that the next request for 'upto' 10 messages will return a mix of messages you have already seen, and some new ones - so you will never really know when you have seen them all.
It maybe that a queue is not the right tool to use in your use case - but since I don't know your use case, its hard to say.
I know this is a bit of a necro but I landed here last night while trying to pull some all messages from a dead letter queue in SQS. While the accepted answer, "you cannot guarantee to get all messages" from the queue is absolutely correct I did want to drop an answer for anyone that may land here as well and needs to get around the 10 message limit per request from AWS.
Dependencies
In my case I have a few dependencies already in my project that I used to make life simpler.
lodash - This is something we use in our code for help making things functional. I don't think I used it below but I'm including it since it's in the file.
cli-progress - This gives you a nice little progress bar on your CLI.
Disclaimer
The below was thrown together during troubleshooting some production errors integrating with another system. Our DLQ messages contain some identifiers that I need in order to formulate cloud watch queries for troubleshooting. Given that these are two different GUIs in AWS switching back and forth is cumbersome given that our AWS session are via a form of federation and the session only lasts for one hour max.
The script
#!/usr/bin/env node
const _ = require('lodash');
const aswSdk = require('aws-sdk');
const cliProgress = require('cli-progress');
const queueUrl = 'https://[put-your-url-here]';
const queueRegion = 'us-west-1';
const getMessages = async (sqs) => {
const resp = await sqs.receiveMessage({
QueueUrl: queueUrl,
MaxNumberOfMessages: 10,
}).promise();
return resp.Messages;
};
const main = async () => {
const sqs = new aswSdk.SQS({ region: queueRegion });
// First thing we need to do is get the current number of messages in the DLQ.
const attributes = await sqs.getQueueAttributes({
QueueUrl: queueUrl,
AttributeNames: ['All'], // Probably could thin this down but its late
}).promise();
const numberOfMessage = Number(attributes.Attributes.ApproximateNumberOfMessages);
// Next we create a in-memory cache for the messages
const allMessages = {};
let running = true;
// Honesty here: The examples we have in existing code use the multi-bar. It was about 10PM and I had 28 DLQ messages I was looking into. I didn't feel it was worth converting the multi-bar to a single-bar. Look into the docs on the github page if this is really a sticking point for you.
const progress = new cliProgress.MultiBar({
format: ' {bar} | {name} | {value}/{total}',
hideCursor: true,
clearOnComplete: true,
stopOnComplete: true
}, cliProgress.Presets.shades_grey);
const progressBar = progress.create(numberOfMessage, 0, { name: 'Messages' });
// TODO: put in a time limit to avoid an infinite loop.
// NOTE: For 28 messages I managed to get them all with this approach in about 15 seconds. When/if I cleanup this script I plan to add the time based short-circuit at that point.
while (running) {
// Fetch all the messages we can from the queue. The number of messages is not guaranteed per the AWS documentation.
let messages = await getMessages(sqs);
for (let i = 0; i < messages.length; i++) {
// Loop though the existing messages and only copy messages we have not already cached.
let message = messages[i];
let data = allMessages[message.MessageId];
if (data === undefined) {
allMessages[message.MessageId] = message;
}
}
// Update our progress bar with the current progress
const discoveredMessageCount = Object.keys(allMessages).length;
progressBar.update(discoveredMessageCount);
// Give a quick pause just to make sure we don't get rate limited or something
await new Promise((resolve) => setTimeout(resolve, 1000));
running = discoveredMessageCount !== numberOfMessage;
}
// Now that we have all the messages I printed them to console so I could copy/paste the output into LibreCalc (excel-like tool). I split on the semicolon for rows out of habit since sometimes similar scripts deal with data that has commas in it.
const keys = Object.keys(allMessages);
console.log('Message ID;ID');
for (let i = 0; i < keys.length; i++) {
const message = allMessages[keys[i]];
const decodedBody = JSON.parse(message.Body);
console.log(`${message.MessageId};${decodedBody.id}`);
}
};
main();

Resources