Discord stats bot adding old stats to embed - statistics

So the problem is I´m making a CS:GO stats Discord bot, and every time you use !stats + Steam ID it gives you your stats in an embed. The first time you do it, it works perfectly, but after that every person that uses the command gets back his stats and the stats of everyone else that has used it previously.Example
Essential code:
if (command === "stats") {
let stats = [];
if (stats) {
stats = [];
let steamID = args[0];
if (!steamID) return message.channel.send("Negative! You haven't added your Steam ID");
axios
.get(
`http://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/?appid=730&key=${steamWebApiKey}&steamid=${steamID}&format=json`
)
.then(async data => {
let stats = data.data.playerstats.stats;
let totalKills = stats[0].value;
let totalDeaths = stats[1].value;
let hsKills = stats[25].value;
let totalMVP = stats[95].value;
let KD = totalKills / totalDeaths;
let HS = totalKills / hsKills * 100;
let Knifekills = stats[10].value;
embedStats
.setTitle("CSGO Stats")
.setColor("#FF0000")
.addField("Total Kills", totalKills, true)
.addField("Total Deaths", totalDeaths, true)
.addField("Total HeadShot Kills", hsKills, true)
.addField("MVPS", totalMVP, true)
.addField("KD", KD.toFixed(2), true)
.addField("Knife kills", Knifekills, true)
})
.catch(e => {
if (e.response.status == "500") {
message.channel.send("Negative! I couldn't find your stats, possible solution: set your game details as public on your Steam profile.");
}
});
axios
.get(
`http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${steamWebApiKey}&steamids=${steamID}&format=json`
)
.then(async data => {
let user = data.data.response.players;
embedStats
.setThumbnail(user[0].avatarmedium)
.setDescription(`[${user[0].personaname}](${user[0].profileurl})`)
await message.channel.send(embedStats);
})
}
}

Your code doesn't seems to have a minimal reproducible example, as I can't find the definitions of embedStats and arg[0], but it seems on using the addField() method, you are just "inserting" new info in that embedStats variable, that is not being "reseted" on new requests of your Discord users. I would suggest try to "free" the variable after the execution of the command is finished, or instantiating a new one before all the addField() logic.

I´ve already found the solution, I was putting my stats variable "const embedStats = new Discord.MessageEmbed();" at the start of the code and not inside the command itself, it was that simple.

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.

Dynamic Slash Command Options List via Database Query?

Background:
I am building a discord bot that operates as a Dungeons & Dragons DM of sorts. We want to store game data in a database and during the execution of certain commands, query data from said database for use in the game.
All of the connections between our Discord server, our VPS, and the VPS' backend are functional and we are now implementing slash commands since traditional ! commands are being removed from support in April.
We are running into problems making the slash commands though. We want to set them up to be as efficient as possible which means no hard-coded choices for options. We want to build those choice lists via data from the database.
The problem we are running into is that we can't figure out the proper way to implement the fetch to the database within the SlashCommandBuilder.
Here is what we currently have:
const {SlashCommandBuilder} = require('#discordjs/builders');
const fetch = require('node-fetch');
const {REST} = require('#discordjs/rest');
const test = require('../commonFunctions/test.js');
var options = async function getOptions(){
let x = await test.getClasses();
console.log(x);
return ['test','test2'];
}
module.exports = {
data: new SlashCommandBuilder()
.setName('get-test-data')
.setDescription('Return Class and Race data from database')
.addStringOption(option =>{
option.setName('class')
.setDescription('Select a class for your character')
.setRequired(true)
for(let op of options()){
//option.addChoice(op,op);
}
return option
}
),
async execute(interaction){
},
};
This code produces the following error when start the npm for our bot on our server:
options is not a function or its return value is not iterable
I thought that maybe the function wasn't properly defined, so I replaced the contents of it with just a simple array return and the npm started without errors and the values I had passed showed up in the server.
This leads me to think that the function call in the modules.exports block is immediatly attempting to get the return value of the function and as the function is async, it isn't yet ready and is either returning undefined or a promise or something else not iteratable.
Is there a proper way to implement the code as shown? Or is this way too complex for discord.js to handle?
Is there a proper way to implement the idea at all? Like creating a json object that contains the option data which is built and saved to a file at some point prior to this command being registered and then having the code above just pull in that file for the option choices?
Alright, I found a way. Ian Malcom would be proud (LMAO).
Here is what I had to do for those with a similar issues:
I had to basically re-write our entire application. It sucks, I know, but it works so who cares?
When you run your index file for your npm, make sure that you do the following things.
Note: you can structure this however you want, this is just how I set up my js files.
Setup a function that will setup the data you need, it needs to be an async function as does everything downstream from this point on relating to the creation and registration of the slash commands.
Create a js file to act as your application setup "module". "Module" because we're faking a real module by just using the module.exports method. No package.jsons needed.
In the setup file, you will need two requires. The first is a, as of yet, non-existent data manager file; we'll do that next. The second is a require for node:fs.
Create an async function in your setup file called setup and add it to your module.exports like so:
module.exports = { setup }
In your async setup function or in a function that it calls, make a call to the function in your still as of yet non-existent data manager file. Use await so that the application doesn't proceed until something is returned. Here is what mine looks like, note that I am writing my data to a file to read in later because of my use case, you may or may not have to do the same for yours:
async function setup(){
console.log('test');
//build option choice lists
let listsBuilt = await buildChoiceLists();
if (listsBuilt){
return true;
} else {
return false;
}
}
async function buildChoiceLists(){
let classListBuilt = await buildClassList();
return true;
}
async function buildClassList(){
let classData = await classDataManager.getClassData();
console.log(classData);
classList = classData;
await writeFiles();
return true;
}
async function writeFiles(){
fs.writeFileSync('./CommandData/classList.json', JSON.stringify(classList));
}
Before we finish off this file, if you want to store anything as a property in this file and then get it later on, you can do so. In order for the data to return properly though, you will need to define a getter function in your exports. Here is an example:
var classList;
module.exports={
getClassList: () => classList,
setup
};
So, with everything above you should have something that looks like this:
const classDataManager = require('./DataManagers/ClassData.js')
const fs = require('node:fs');
var classList;
async function setup(){
console.log('test');
//build option choice lists
let listsBuilt = await buildChoiceLists();
if (listsBuilt){
return true;
} else {
return false;
}
}
async function buildChoiceLists(){
let classListBuilt = await buildClassList();
return true;
}
async function buildClassList(){
let classData = await classDataManager.getClassData();
console.log(classData);
classList = classData;
await writeFiles();
return true;
}
async function writeFiles(){
fs.writeFileSync('./CommandData/classList.json', JSON.stringify(classList));
}
module.exports={
getClassList: () => classList,
setup
};
Next that pesky non-existent DataManager file. For mine, each data type will have its own, but you might want to just combine them all into a single .js file for yours.
Same with the folder name, I called mine DataManagers, if you're combining them all into one, you could just call the file DataManager and leave it in the same folder as your appSetup.js file.
For the data manager file all we really need is a function to get our data and then return it in the format we want it to be in. I am using node-fetch. If you are using some other module for data requests, write your code as needed.
Instead of explaining everything, here is the contents of my file, not much has to be explained here:
const fetch = require('node-fetch');
async function getClassData(){
return new Promise((resolve) => {
let data = "action=GetTestData";
fetch('http://xxx.xxx.xxx.xx/backend/characterHandler.php', {
method: 'post',
headers: { 'Content-Type':'application/x-www-form-urlencoded'},
body: data
}).then(response => {
response.json().then(res => {
let status = res.status;
let clsData = res.classes;
let rcData = res.races;
if (status == "Success"){
let text = '';
let classes = [];
let races = [];
if (Object.keys(clsData).length > 0){
for (let key of Object.keys(clsData)){
let cls = clsData[key];
classes.push({
"name": key,
"code": key.toLowerCase()
});
}
}
if (Object.keys(rcData).length > 0){
for (let key of Object.keys(rcData)){
let rc = rcData[key];
races.push({
"name": key,
"desc": rc.Desc
});
}
}
resolve(classes);
}
});
});
});
}
module.exports = {
getClassData
};
This file contacts our backend php and requests data from it. It queries the data then returns it. Then we format it into an JSON structure for use later on with option choices for the slash command.
Once all of your appSetup and data manager files are complete, we still need to create the commands and register them with the server. So, in your index file add something similar to the following:
async function getCommands(){
let cmds = await comCreator.appSetup();
console.log(cmds);
client.commands = cmds;
}
getCommands();
This should go at or near the top of your index.js file. Note that comCreator refers to a file we haven't created yet; you can name this require const whatever you wish. That's it for this file.
Now, the "comCreator" file. I named mine deploy-commands.js, but you can name it whatever. Once again, here is the full file contents. I will explain anything that needs to be explained after:
const {Collection} = require('discord.js');
const {REST} = require('#discordjs/rest');
const {Routes} = require('discord-api-types/v9');
const app = require('./appSetup.js');
const fs = require('node:fs');
const config = require('./config.json');
async function appSetup(){
console.log('test2');
let setupDone = await app.setup();
console.log(setupDone);
console.log(app.getClassList());
return new Promise((resolve) => {
const cmds = [];
const cmdFiles = fs.readdirSync('./commands').filter(f => f.endsWith('.js'));
for (let file of cmdFiles){
let cmd = require('./commands/' + file);
console.log(file + ' added to commands!');
cmds.push(cmd.data.toJSON());
}
const rest = new REST({version: '9'}).setToken(config.token);
rest.put(Routes.applicationGuildCommands(config.clientId, config.guildId), {body: cmds})
.then(() => console.log('Successfully registered application commands.'))
.catch(console.error);
let commands = new Collection();
for (let file of cmdFiles){
let cmd = require('./commands/' + file);
commands.set(cmd.data.name, cmd);
}
resolve(commands);
});
}
module.exports = {
appSetup
};
Most of this is boiler plate for slash command creation though I did combine the creation and registering of the commands into the same process. As you can see, we are grabbing our command files, processing them into a collection, registering that collection, and then resolving the promise with that variable.
You might have noticed that property, was used to then set the client commands in the index.js file.
Config just contains your connection details for your discord server app.
Finally, how I accessed the data we wrote for the SlashCommandBuilder:
data: new SlashCommandBuilder()
.setName('get-test-data')
.setDescription('Return Class and Race data from database')
.addStringOption(option =>{
option.setName('class')
.setDescription('Select a class for your character')
.setRequired(true)
let ops = [];
let data = fs.readFileSync('./CommandData/classList.json','utf-8');
ops = JSON.parse(data);
console.log('test data class options: ' + ops);
for(let op of ops){
option.addChoice(op.name,op.code);
}
return option
}
),
Hopefully this helps someone in the future!

nodejs discord get nickname from a users ID

My goals are to obtain the users nickname by using their ID.
Their ID's are stored as variables which are being collected from a reaction collector.
I have tried a few methods and failed, most of which either return nothing or errors.
The below code returns nothing, the getnames() function is empty. This method was recommended to me buy 2 people from a nodejs discord server which aims to help solve issues, similar to here.
// returns player ID's
function getPlayers() {
let players = [];
players.push(queue.tank[0]); // First (1) in TANK queue
players.push(queue.heal[0]); // First (1) in HEAL queue
players.push(queue.dps[0]); // First (2) in DPS queue
players.push(queue.dps[1]);
return players;
}
// get nick names from ID's
function getnames() {
let players = getPlayers();
let playerNicks = [];
let newPlayer = "";
players.forEach(async player => {
newPlayer = await message.guild.members.fetch(player).then(function (user) {return user.displayName });
playerNicks.push(newPlayer)
return playerNicks;
})}
//formats nicknames into string
function formatnicknames() {
let formatted_string2 = '';
let playerNicks = getnames();
if (playerNicks)
formatted_string2 = `${playerNicks[0]} \n${playerNicks[1]} \n${playerNicks[2]} \n${playerNicks[3]}`;
return formatted_string2;
}
I have also tried a few variations of the below code, still unable to obtain nickname.
message.guild.members.cache.get(user.id)
Edit #1
now tried the following code with no success. (boost1ID contains the ID of 1 user)
var mem1 = message.guild.members.fetch(boost1ID).nickname
Edit #2
tried a new method of obtaining displayname from ID.
var guild = client.guilds.cache.get('guildid');
var mem1 = guild.member(boost1ID);
var mem2 = guild.member(boost2ID);
var mem3 = guild.member(boost3ID);
var mem4 = guild.member(boost4ID);
var nickname1 = mem1 ? mem1.displayName : null;
var nickname2 = mem2 ? mem2.displayName : null;
var nickname3 = mem3 ? mem3.displayName : null;
var nickname4 = mem4 ? mem4.displayName : null;
var Allnicknames = `${nickname1} ${nickname2} ${nickname3} ${nickname4}`
message.channel.send(`testing nicknames: ${Allnicknames}`)
I managed to only return my own name since i dont have a nickname on this server, but the other three users who does have a nickname returned null.
This is the simplest solution:
// your users ids
const IDs = [ '84847448498748974', '48477847847844' ];
const promises = IDs.map((userID) => {
return new Promise(async (resolve) => {
const member = message.guild.member(userID) || await message.guild.members.fetch(userID);
resolve(member.displayName || member.user.username);
});
});
const nicknames = await Promise.all(promises);
// you now have access to ALL the nicknames, even if the members were not cached!
The members you are trying to get the nicknames of are not necessarily cached, and this fixes that.
I made an example that could help you.
let testUsers = [];
module.exports = class extends Command {
constructor(...args) {
super(...args, {
description: 'Testing.',
category: "Information",
});
}
async run(message) {
function getNicknames(userArr, guild) {
let playerNicks = [];
for(var i = 0; i < userArr.length; i++) {
playerNicks.push(guild.member(userArr[i]).displayName);
}
return playerNicks;
}
let testUser = message.guild.members.cache.get(message.author.id);
testUsers.push(testUser);
let guild = message.guild;
console.log(getNicknames(testUsers, guild));
}
}
I created a function getNicknames that takes in two parameters. The first one is an Array of users (as you get one from your function getPlayers()) and the second one is the guild you are playing in. You need to provide the guild, because every user should be a GuildMember, because you want to use .displayName. I created a user Array outside of my command code, because otherwise there will only be one user in the Array everytime you use the command. Inside of the getNicknames() function I have created a new Array playerNicks that I basically fill with the user nicknames we get from our provided user Array.
Now you have to implement that into your code.
The call of the function getNicknames(), for your code should look like this:
getNicknames(getPlayers(), message.guild);

My discord.js help command doesn't exactly work

First things first, my help command does work but not in the way I would like it to work.
My first issue is that the commands are being sent in separate messages which is kind of annoying when you have a lot of commands.
My second issue is that when the message is sent in an embed, it shows up like this:
Command
Description
Usage
Undefined
I tried multiple ways to get rid of 'Undefined'.
My code:
const fs = require("fs");
const Discord = require("discord.js");
module.exports.run = async(bot, message, args, con) => {
fs.readdir("./commands/", (err, files) => {
if(err) console.error(err);
let jsfiles = files.filter(f => f.split(".").pop() === "js");
if(jsfiles.length <= 0) {
console.log("No commands to load!");
return;
}
var namelist = "";
var desclist = "";
var usage = "";
let result = jsfiles((f, i) => {
let props = require(`./${f}`);
namelist = props.help.name;
desclist = props.help.description;
usage = props.help.usage;
// send help text
let helpembed = new Discord.RichEmbed()
.setTitle("Commands")
.setFooter("Please report any bugs to Vati#1662")
.setColor("RANDOM")
.addField(`**${namelist}** \n${desclist} \n${usage}`)
message.author.sendEmbed(helpembed);
});
})
}
module.exports.help = {
name: "help",
description: "shows all commands",
usage: "help"
}
When you use RichEmbed.addField() it expects at least two arguments: the title of the field and its value.
.addField(`**${namelist}** \n${desclist} \n${usage}`) // this has only the title argument
Try putting the three "sections" in three different fields.
.addField("Name:", namelist, true) // the 'true' means that they're inline fileds
.addField("Usage:", usage, true) // they will try to fit on the same line
.addField("Description:", desclist) // if there's no third argument, the default is 'false'
The commands are sent in different messages because you're running the whole code for every command, and not only adding the fields for every command. If you don't want to spend time on all this stuff, you can use the discord.js-commando library: it's a framework that deals with the commands and also handles errors, incomplete commands, and a lot of other stuff. If you want to check it out, you can find the docs here.

I can not click on a link with phantom-node

I tell you what I want to do. I am doing a scraping of tematika.com site to be able to create a base of books to me. The problem is that I need to go from page to page but I can not, so I always get the same books. I think with the code they will understand it better.
const phantom = require("phantom");
const cheerio = require("cheerio");
let _instance, _page;
let _books = [];
phantom.create().then(instance => {
_instance = instance;
return _instance.createPage();
}).then(page => {
_page = page;
return _page.open('http://www.tematika.com/catalogo/libros/arte__arquitectura_y_diseno--9.htm');
}).then(status => {
console.log(status);
return _page.property('content')
}).then(html => {
// I transform the html into an object to manipulate it with cheerio
let $ = cheerio.load(html);
//I get all the books and pass them to an array to go through them and extract the info I'm looking for
let books = $('.Gcentro').find('.moduleproductob').toArray();
//As always there are 10 pages I make a for it it itere 10 times
for(let i=0;i<10;i++){
books.forEach(book => {
let _book = {};
_book.imageMin = $(book).find('.Gimagesproductos').attr('src');
_book.linkBook = $(book).find('.FProductos').attr('href');
_books.push(_book);
});
//In this part what I look for is to click on the next button on the page and it is what is not working for me
//What ends up happening is that it never goes back to the page. So always bring me the info of the same 10 books.
_page.evaluate(function() {
var evObj = document.createEvent('Events');
evObj.initEvent('click', true, false);
document.getElementById('catalogNext').dispatchEvent(evObj);
});
}
console.log(_books);
_page.close();
_instance.exit();
}).catch(e => console.log(e));
What I need is to be able to click on next and be able to bring me the books that are showing. If it is not with phantom-node and cheerio can be with another tool, all I need is that it works on node.
Thank you! :)

Resources