Refactoring Embed message generation for Discord Bot - javascript

I am trying to refactor the code that I am using to generate embeds (user input makes an API call for info, which I display through the embed) with my bot in Discord. I have both a slash command and prefix command version of the code (so that people can either ask the bot with a prefix command or use the slash commands) and I am trying to pull the duplicated code for both those requests into a helper file.
The helper file: (/utils/itemHelper.js)
const { MessageEmbed } = require('discord.js');
const { request } = require('undici');
const { ITEM_URL, BASE_URL } = require('./utils');
const { itemAliases } =require('./itemAliases')
async function getItem( itemName ){
if (itemName in itemAliases){ itemName = itemAliases[itemName] };
const { body } = await request(ITEM_URL + encodeURIComponent(itemName));
const { items } = await body.json();
const [itemAnswer] = items;
const itemEmbed = new MessageEmbed()
.setTitle(itemAnswer.name)
.setDescription('Mentor notes will go here.')
.setImage(BASE_URL + itemAnswer.screenshot)
console.log(itemEmbed);
}
module.exports = { getItem }
At this point, the console.log(itemEmbed) gives me what I want:
MessageEmbed {
type: 'rich',
title: 'Frost Brand',
description: 'Mentor notes will go here.',
url: null,
color: null,
timestamp: null,
fields: [],
thumbnail: null,
image: { url: 'https://dom5api.illwiki.com/items/14/screenshot' },
video: null,
author: null,
provider: null,
footer: null
}
But by the time I call the function within the file were I create the slash command, I get an error message (which is discord telling me that the embed I want to send is lacking the description field):
data.embeds[0].description: This field is required.
The slash command file where I call the async function: (/commands/item.js)
const { SlashCommandBuilder } = require('#discordjs/builders');
const { MessageEmbed } = require('discord.js');
const { getItem } = require('../utils/itemHelper')
module.exports = {
data: new SlashCommandBuilder()
.setName('item')
.setDescription('Replies with information about an item')
.addStringOption(option => option.setName('item_name').setDescription('Enter the name of the item').setRequired(true)),
async execute(interaction) {
let itemName = interaction.options.getString('item_name');
const itemEmbed = getItem( itemName );
console.log(`itemEmbed:`+ itemEmbed);
await interaction.reply({ embeds: [itemEmbed] });
},
};
The console.log output is
itemEmbed:[object Promise]
My hunch is that I am somehow misunderstanding/misapplying how async functions and promises work (and that the information I need is hidden somewhere inside [object Promise]). I have tried to do 'return itemEmbed' at the end of the helper file, but that didn't solve the issue. I have also considered if I might be missing a param in my async function, but I don't think I need another param, or if I did need one what role it would fill.
Thanks for any/all help in advance!

In your getItem function, there's two problems:
You made it async. When a function is asynchronous, it is considered as a Promise that you have to use the await keyword on await getItem(..) So that it will wait for the fetch to finish to finally continue executing your code.
You don't return anything on your function. You log the embed in your console.
Try using it like so :
const { MessageEmbed } = require('discord.js');
const { request } = require('undici');
const { ITEM_URL, BASE_URL } = require('./utils');
const { itemAliases } =require('./itemAliases')
function getItem( itemName ){
if (itemName in itemAliases){ itemName = itemAliases[itemName] };
const { body } = await request(ITEM_URL + encodeURIComponent(itemName));
const { items } = await body.json();
const [itemAnswer] = items;
const itemEmbed = new MessageEmbed()
.setTitle(itemAnswer.name)
.setDescription('Mentor notes will go here.')
.setImage(BASE_URL + itemAnswer.screenshot)
return itemEmbed
}
module.exports = { getItem }
const itemEmbed = await getItem(itemName);

Related

Discord.js bot won't recognize fetch as a function (using node-fetch)

I am using discordjs to build a bot and one slashcommand I want to use is a web scraping command to let users know about the latest releases on their favorite platform.
The issue at the moment is that I get an error code saying the function "fetch" doesn't exist after importing node-fetch.
It works as a script it self but not when used in the discord bot environment.
I would like to know if any of you ran into a similar issue and if so, what was the solution?
Thank you for your time.
const { load } = require("cheerio");
const { fetch } = require("node-fetch");
const fs = require("fs");
module.exports = {
data: new SlashCommandBuilder()
.setName("questnew")
.setDescription("Get Quest releases!"),
async execute(interaction, client) {
try {
const siteUrl = "http://www.vrdb.app/";
const response = await fetch(siteUrl);
const body = await response.text();
const games = [];
const $ = cheerio.load(body);
const elSelector = "#oculus-statistics > tbody > tr";
$(elSelector).each((parentIdx, parentEl) => {
if (parentIdx >= 0 && parentIdx <= 4) {
$(parentEl)
.children()
.each((childIdx, childEl) => {
if (childIdx === 0) {
games.push($(childEl).text());
}
});
}
});
await interaction.reply({
content: "There was an error while executing this command!" + games,
ephemeral: true,
});
} catch (error) {
await interaction.reply({
content: "There was an error while executing this command!" + error,
ephemeral: true,
});
}
},
};
Wrong syntax
const fetch = require("node-fetch")

js imported function error "getUser not a function"

I have three files
BotJobActions.js
TestDate.js
CreateCron.js
The BotJobActions file creates a function called getUser that returns the user connected to a specific job, then exports the getUser along with a bunch of other functions.
const getUser = async (jobId) =>{
await mongoConnect(process.env.DB_PWORD)
try {
const user = await User.findOne({pendingJobs:jobId})
return user
} catch (err) {
console.log(err)
}
}
module.exports = { newJob, getUserJobs, getUser, updateUserJob, destroyUserPendingJob, destroyUserCompletedJob, activateJob, deactivateJob, endJob }
TestDate defines a function called runBot which runs a bot Job. In runBot it also calls the getUser function, so I can make changes to a specific user. Then exports the function because it will be used in other files.
const { getUser } = require("../bot/botJobActions");
const runBot = async (todayJobs) =>{
// await mongoConnect(process.env.DB_PWORD)
for(const job of todayJobs){
const clubPassword = decryptToken(job.clubPassword.token, job.clubPassword.iv)
const user = await getUser(job.id)
if(job.proxy){
const proxyConfig = await getProxyConfig(user)
if(proxyConfig.status === "no proxy") console.log("[-] Proxy Config Retrival Error/Running Without Proxy")
// await startBot(member=job.member?job.member : null, proxy=proxyConfig.status === 'success'?proxyConfig:null, job.clubUsername, clubPassword, job.startTime, job.endTime, job.courseList, job.id)
await console.log(member=job.member?job.member : null, proxy=proxyConfig.status === 'success'?proxyConfig:null, job.clubUsername, clubPassword, job.startTime, job.endTime, job.courseList, job.id)
}else{
// await startBot(member=job.member?job.member : null, proxy=null, job.clubUsername, clubPassword, job.startTime, job.endTime, job.courseList, job.id)
await console.log(member=job.member?job.member : null, proxy=null, job.clubUsername, clubPassword, job.startTime, job.endTime, job.courseList, job.id)
}
}
return
}
module.exports = { runBot, getJobs }
CreateCron is a function that runs whenever a job is created with a specific start time. This function will create a cron job for that specified time to run the bot.
const schedule = require('node-schedule');
const { runBot } = require('./testDate');
const createCron = (job) =>{
const startDate = new Date(job.botStartDate)
const startTime = new Date(`09/19/2000 ${job.botStartTime}`)
startDate.setHours(startTime.getHours())
startDate.setMinutes(startTime.getMinutes())
console.log(startDate.toUTCString())
schedule.scheduleJob(startDate, async function(){
console.log('run job')
await runBot([job])
})
}
My problem thought is that whenever I run the createCron function, I get an error saying that the getUser is not a function. Even thought it is.
Any help is appreciated!!
I was able to fix the problem. All I had to do was use the absolute path to the function instead of the relative path. Then the functions worked. Hope this can help somebody!

Passing multiple Embeds through a helper function for Discord Bot

I have refactored my code to remove some redundancy, part of which was making helper functions for the generation of embeds that my bot sends as a reply. For one of the slash commands, the bot is supposed to send back multiple embeds.
mercHelper.js (Houses the helper function inside the utils folder)
const { MessageEmbed } = require('discord.js');
const { request } = require('undici');
const { MERC_URL, BASE_URL } = require('./utils');
const { mercAliases } =require('./mercAliases')
async function getMerc( mercName ){
if (mercName in mercAliases){ mercName = mercAliases[mercName] };
const { body } = await request(MERC_URL + encodeURIComponent(mercName));
const { mercs } = await body.json();
const [mercAnswer] = mercs;
const mercEmbed = new MessageEmbed()
.setTitle(mercAnswer.name)
.setDescription('Mentor notes will go here.')
.setImage(BASE_URL + mercAnswer.screenshot)
const mercLeaderEmbed = new MessageEmbed()
.setImage(BASE_URL+'/commanders/'+ mercAnswer.commander_id+'/screenshot')
.setDescription('Name of mercenary group leader: '+ mercAnswer.bossname)
const mercTroopEmbed = new MessageEmbed()
.setImage(BASE_URL+'/commanders/'+ mercAnswer.unit_id+'/screenshot')
.setDescription('Number of units: '+ mercAnswer.nrunits)
return [mercEmbed, mercLeaderEmbed, mercTroopEmbed];
}
module.exports = { getMerc }
merc.js (slash command file)
const { SlashCommandBuilder } = require('#discordjs/builders');
const { getMerc } = require('../utils/mercHelper');
module.exports = {
data: new SlashCommandBuilder()
.setName('merc')
.setDescription('Replies with information about a merc')
.addStringOption(option => option.setName('merc_name').setDescription('Enter the name of the mercenary').setRequired(true)),
async execute(interaction) {
let mercName = interaction.options.getString('merc_name');
const mercEmbed = await getMerc( mercName );
const mercLeaderEmbed = await getMerc( mercName );
console.log('mercLeaderEmbed'+mercLeaderEmbed)
const mercTroopEmbed = await getMerc( mercName );
console.log('mercTroopEmbed'+mercTroopEmbed)
await interaction.reply({ embeds: [mercEmbed, mercLeaderEmbed, mercTroopEmbed] });
},
};
When I run the code I have currently, I get the following error message: DiscordAPIError: Invalid Form Body and then empty data.embeds.
I assume this is happening because when I 'return' embeds from the helper function, it is now going through somehow. I added console.logs to the helper file - all the information was present.
When I tried to do the same in the slash command file (merc.js), I get mercLeaderEmbed[object Object],[object Object],[object Object] and the same for the second console.log.
Maybe the return is working properly, but I am not calling the information correctly, which is resulting in the return of [object Object], but I am not sure on how to solve that issue/how to rewrite the code to avoid this.
You're calling await getMerc(mercName) three times and assign the same value to three different variables (mercEmbed, mercLeaderEmbed, and mercTroopEmbed). If you return an array of the three embeds ([mercEmbed, mercLeaderEmbed, mercTroopEmbed]) from getMerc, you don't need to call it more than once.
When you log 'mercLeaderEmbed'+mercLeaderEmbed, you receive mercLeaderEmbed[object Object],[object Object],[object Object] because mercLeaderEmbed is already an array of the three objects (and they get stringified because you concatenate the array with a string).
I think what you want is either destructuring the returned array like this:
async execute(interaction) {
let mercName = interaction.options.getString('merc_name');
let [mercEmbed, mercLeaderEmbed, mercTroopEmbed] = await getMerc(mercName);
await interaction.reply({
embeds: [mercEmbed, mercLeaderEmbed, mercTroopEmbed],
});
}
Or just sending the returned array of embeds like this:
async execute(interaction) {
let mercName = interaction.options.getString('merc_name');
let embeds = await getMerc(mercName);
await interaction.reply({ embeds });
}

Using REST API for a Discord bot

I am trying to make my own discord bot (in discord.js v13). I found the official discord.js guide on this topic and their example code, and I am trying to use/reuse the code from there to build my own bot.
The idea is to take a user input after the slash command "item" (so /item: firebrand), pass that user input into the URL (which allows you to get the information about that item from the backend/API) and use the response that is received to populate the various fields of an embed, which are sent back to the user.
I am able to pass the user input and add it to the URL, which returns a usable link, but the code breaks before the embed can be created. I put in a few console.log commands into my code to see where things are breaking.
See the location of console.log in the code below.
Console log 1: {"items":[{"id":14,"name":"Frost Brand","type":"1-h wpn","constlevel":4,"mainlevel":1,"mpath":"W1","gemcost":"5W","screenshot":"/items/14/screenshot"}],"similarity":-0}
Console log 2: https://dom5api.illwiki.com/items?match=fuzzy&name=frostbrand
Console log 3: [object Object]
Console log 4: [object Object]
Console log 5: undefined - TypeError: Cannot read properties of undefined (reading 'length')
My guess is that I am getting the TypeError because { list } is undefined. That is probably because itemSearchResult.body is giving back [object Object] as a response, but I have no clue how to fix that.
const { SlashCommandBuilder } = require('#discordjs/builders');
const { MessageEmbed } = require('discord.js');
const { request } = require('undici');
const { ITEM_URL, BASE_URL } = require('../utils/utils');
module.exports = {
data: new SlashCommandBuilder()
.setName('item')
.setDescription('Replies with information about an item')
.addStringOption(option => option.setName('item_name').setDescription('Enter the name of the item')),
async execute(interaction) {
async function getJSONResponse(body) {
let fullBody = '';
for await (const data of body) {
fullBody += data.toString();
console.log(`Console log 1: `+fullBody);
}
return JSON.parse(fullBody);
}
const itemName = interaction.options.getString('item_name');
const itemSearchResult = await request(ITEM_URL + encodeURIComponent(itemName));
console.log(`Console log 2: `+ITEM_URL + encodeURIComponent(itemName));
console.log(`Console log 3: `+itemSearchResult.body);
console.log(`Console log 4: `+itemSearchResult.body.toString())
const { list } = await getJSONResponse(itemSearchResult.body);
console.log(`Console log 5: `+list)
if (!list.length) {
await interaction.reply(`No results found for **${itemName}**.`);
}
const [answer] = list;
const itemEmbed = new MessageEmbed()
.setColor('#000000')
.setTitle(answer.name)
.setURL('X')
.setAuthor({ name: 'Author' })
.setDescription('Lot of hassle, but hey, it was working!')
.setImage(BASE_URL + answer.screenshot)
.setTimestamp()
.setFooter({ text: 'A small step for X, a giant leap for X' });
await interaction.reply({ embeds: [itemEmbed] });
},
};
I tried searching for the error message/similar cases online and on StackOverflow, but found nothing. Any help is greatly appreciated!
As you mentioned in your answer, there is no list item in the returned object. It should be items. Also, you received [object Object] because you implicitly coerced the returned object to a string (i.e. console.log(`Console log 3: `+itemSearchResult.body`).
The reason I'm answering though is that you could get rid of that ugly getJSONResponse() function and use a built-in body mixin to simplify your code:
async execute(interaction) {
const itemName = interaction.options.getString('item_name');
const { body } = await request(ITEM_URL + encodeURIComponent(itemName));
const { items } = await body.json();
if (!items.length)
await interaction.reply(`No results found for **${itemName}**.`);
const [answer] = items;
const itemEmbed = new MessageEmbed()
.setColor('#000000')
.setTitle(answer.name)
.setAuthor({ name: 'Author' })
.setDescription('Lot of hassle, but hey, it was working!')
.setImage(BASE_URL + answer.screenshot)
.setTimestamp()
.setFooter({ text: 'A small step for X, a giant leap for X' });
await interaction.reply({ embeds: [itemEmbed] });
},
Had a friend take a look at my code and help out.
Issue was with const { list } = await getJSONResponse(itemSearchResult.body); because the API wasn't passing a list.
The working/correct code is:
const itemName = interaction.options.getString('item_name');
const itemSearchResult = await request(ITEM_URL + encodeURIComponent(itemName));
const response = await getJSONResponse(itemSearchResult.body);
const items = response.items;
const firstItem = response.items[0];

Make a dog command in Discord.JS

I'm trying to figure out how to make a command that makes a GET request to this dog API and return the image in an embed. Here's the code I've tried:
const Discord = require('discord.js');
const fetch = require('node-fetch');
module.exports = {
name: 'afv!dog',
description: 'Grab a cute doggo from dog.ceo',
execute(msg, args, bot) {
const prevmsg = msg
const fetchEmbed = new Discord.MessageEmbed()
.setColor('#e3dcd3')
.setTitle(':dog: Woof! Let me find you a doggo! <a:AFVloading:748218375909539923>')
.setDescription("This shouldn't take long...")
msg.reply(fetchEmbed).then(msg => {
const { message } = await fetch('https://dog.ceo/api/breeds/image/random').then(response => response.text());
console.log(message)
const doneEmbed = new Discord.MessageEmbed()
.setColor('#e3dcd3')
.setTitle(':dog: Woof! Found one!')
.setImage(message)
msg.delete();
prevmsg.channel.send(doneEmbed);
})
},
};
This happens when I try to run index.js:
const { message } = await fetch('https://dog.ceo/api/breeds/image/random').then(response => response.json());
^^^^^
SyntaxError: await is only valid in async function
You need to use response.json() instead of response.text(). .text() is for standard html plain text, however the dog API you are using returns an object.
You're not awaiting prevmsg.channel.send(doneEmbed);, if I see it correctly
Edit: I am wrong

Categories

Resources