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];
Related
i have multiple commands that work perfectly fine but i always get this message in return.
here is the code for that command. it works perfectly fine i guess it just doesn't respond to the interaction even though i feel like it should be?
how can i get it to ignore this message or reply properly?
const Discord = require('discord.js');
const { EmbedBuilder, SlashCommandBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
// command name
.setName('totalfrozencheckouts')
// command description
.setDescription('Add up every message in the frozen checkouts channel after a specific message ID')
.addStringOption(option =>
option.setName('messageid')
.setDescription('The message ID')
.setRequired(true)),
async execute(interaction) {
const channel = '<#' + process.env.FROZENCHECKOUTS + '>';
const messageId = interaction.options.getString("messageid");
// Check if the channel mention is valid
if (!channel.startsWith('<#') || !channel.endsWith('>')) {
return interaction.channel.send(`Invalid channel mention. Please use the format: ${this.usage}`);
}
// Extract the channel ID from the channel mention
const channelId = channel.slice(2, -1);
// Try to fetch the messages from the requested channel and message ID
interaction.guild.channels.cache.get(channelId).messages.fetch({ after: messageId })
.then(messages => {
// Create an array of the message contents that are numbers
const numbers = messages.map(message => message.content).filter(content => !isNaN(content));
// Check if there are any messages
if (numbers.length === 0) {
return interaction.channel.send(`No messages were found in ${channel} after message ID https://discord.com/channels/1059607354678726666/1060019655663689770/${messageId}`);
}
// Adds up the messages
const sum = numbers.reduce((accumulator) => accumulator + 1, 1);
// Create an embed object
const embed = new EmbedBuilder()
.setColor(0x4bd8c1)
.setTitle(`Total Checkouts in #frozen-checkouts for today is:`)
.addFields(
{name: 'Total Checkouts', value: sum.toString() , inline: true},
)
.setThumbnail('https://i.imgur.com/7cmn8uY.png')
.setTimestamp()
.setFooter({ text: 'Frozen ACO', iconURL: 'https://i.imgur.com/7cmn8uY.png' });
// Send the embed object
interaction.channel.send({embeds: [embed]});
})
.catch(error => {
console.error(error);
interaction.channel.send('An error occurred while trying to fetch the messages. Please try again later.');
});
}
}
I don't really know what to try because it literally works I just don't know how to get it to either ignore that message or respond with nothing. It doesn't break the bot its just annoying to look at.
Use interaction.reply instead of interaction.channel.send to reply.
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 });
}
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);
I wanted to make a command to make the bot return its connection status to the database, but I got an error and I am a little confused now.
RangeError [MESSAGE_CONTENT_TYPE]: Message content must be a non-empty string.
const { MessageEmbed } = require('discord.js');
const quick = require('quick.db');
module.exports = {
name: 'ping',
aliases: [],
description: 'Get bot ping.',
permissions: [],
async execute(message, client) {
const ping = await getDBPingData();
const messagePing = Date.now();
const msg = await message.channel.send('Loading...');
const endMessagePing = Date.now() - messagePing;
const embed = new MessageEmbed()
.setDescription(
`
Database ping data:
- Fetch ping: \`${ping.endGet}ms\`
- Wright ping: \`${ping.endWright}ms\`
- Avrage ping: \`${ping.avarage}ms\`
Message ping: \`${endMessagePing}ms\`
`
)
.setColor('GREEN')
.setTimestamp();
msg.edit({
content: '',
embed,
});
},
};
async function getDBPingData() {
// get the fetch data ping
const startGet = Date.now();
await quick.get('QR=.');
const endGet = Date.now() - startGet;
// get the wright data ping
const startWright = Date.now();
await quick.set('QR=.', Buffer.from(startWright.toString()).toString('base64'));
const endWright = Date.now() - startWright;
// avrage ping time
const avarage = (endGet + endWright) / 2;
try {
quick.delete('QR=.');
} catch (error) {}
return { endGet, endWright, avarage };
}
I am using discord.js v13, and the packages in use for this command are: discord.js and quick.db.
In v13, messages sent by bots now support up to 10 embeds. As a result, the embed option was removed and replaced with an embeds array, which must be in the options object, so your message edit should be msg.edit({ embeds: [embed] }).
If you also want to remove the previous text (Loading...), you need to add content: null as providing an empty string ('') as the content will throw a RangeError.
msg.edit({
content: null,
embeds: [embed],
});
Try this:
// only need to edit embed in an embeds array
msg.edit({ embeds: [embed] })
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