I'm trying to learn Node.js so I've been trying to write a simple Discord bot. It runs fine except when I try to write to a config file. It only writes to the file when I run the command twice or if another command is run right after it. I can't seem to figure out why it does that. It succeeds in posting a message in Discord after every command, but it's just the file that doesn't get updated. It doesn't output an error to the console either.
I have the code below and the config.json sample. Any help would be greatly appreciated.
config.json:
{
"ownerID": "1234567890",
"token": "insert-bot-token-here",
"prefix": "!",
"defaultStatus": "status-here"
}
const Discord = require("discord.js");
const client = new Discord.Client();
const fs = require("fs")
const config = require("./config.json");
client.on("ready", () => {
console.log("I am ready!");
client.user.setActivity(config.defaultStatus, {
type: 'WATCHING'
});
});
client.on("message", (message) => {
const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
if (!message.content.startsWith(config.prefix) || message.author.bot) return;
if (command === "ping") {
message.channel.send("pong!");
} else
if (message.author.id !== config.ownerID) return message.reply("you must be the bot owner to run this command.");
let configData = JSON.stringify(config, null, 2);
// Command to change the prefix for commands
if (command === "prefix") {
let newPrefix = message.content.split(" ").slice(1, 2)[0];
config.prefix = newPrefix;
fs.writeFile("./config.json", configData, (err) => console.error);
message.channel.send("Prefix has been updated to \"" + newPrefix + "\"");
}
// Command to change the bot status
if (command === "status") {
let game = args.slice(0).join(" ");
client.user.setActivity(game, {
type: 'WATCHING'
});
message.channel.send("Status has been updated to \"" + game + "\"");
}
// Command to change the default bot status
if (command === "defaultstatus") {
let newStatus = args.slice(0).join(" ");
config.defaultStatus = newStatus;
client.user.setActivity(newStatus, {
type: 'WATCHING'
});
fs.writeFile("./config.json", configData, (err) => console.error);
message.channel.send("Default status has been updated to \"" + newStatus + "\"");
}
});
client.login(config.token);
That works only the second time because you're declaring configData before you change the config object.
configData = JSON.stringify(config, null, 2); //saves the config as a string
// you do all your stuff, including changing the prefix, for example
config.prefix = '-'; //this is stored in config, but not in configData
// then you write the file with the old data
fs.writeFile("./config.json", configData, (err) => console.error);
That causes the changes to be delayed every time.
You should create the configData after changing config:
...
config.prefix = '-'; // inside the command
...
let configData = JSON.stringify(config, null, 2);
fs.writeFile('./config.json', configData, err => console.error);
Related
I'm using Node.js v18.12.1 and Discord.js v14. for developing the Discord bot. I need to read some data from Firebase. I'm confused because I'm used to how Java with Hibernate fetches data differently. Here, I need to use onValue() listener.
My onValue() acts strange. Instead of just reading the data from Firebase, it skips entirely, then it triggers multiple times, each time skipping the body block of its code, and then it actually does the code after.
I've read somewhere on this forum that this can happen because there are more onValue() listeners that are subscribed and they are all fired up. Someone mentioned I need to use the off() function somewhere "before" the onValue(). This confuses me because I'm using this listener in many locations. I need it in each command file, in execute(interaction) functions. You know, when you need to execute slash commands in Discord. I have it something like this:
async execute(interaction) {
const infographicRef = ref(db, '/infographics/arena/' + interaction.options.getString("arena-team"));
var imageUrl = null;
var postUrl = null;
onValue(infographicRef, (snapshot) => {
imageUrl = snapshot.child("image-url").val();
interaction.reply(imageUrl);
})
},
And I planned for each command, in each command.js file to have onValue(). I'm not sure exactly what to do.
Also, I tried to work around this with once() method, I see it in Firebase documentation, but I got the error: ref.once() is not a function.
It seems that after first triggering of onValue method when the body is not executed, my code in interactionCreate.js is triggered as well, it points for a command to be executed again:
const { Events } = require('discord.js');
module.exports = {
name: Events.InteractionCreate,
async execute(interaction) {
if (!interaction.isChatInputCommand()) return;
const command = interaction.client.commands.get(interaction.commandName);
if (!command) {
console.error(`No command matching ${interaction.commandName} was found.`);
return;
}
try {
await command.execute(interaction);
} catch (error) {
console.error(`Error executing ${interaction.commandName}`);
console.error(error);
}
},
};
my bot.js (which is in my case an index file)
const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, Events, GatewayIntentBits } = require('discord.js');
const { token } = require('./config.json');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const eventsPath = path.join(__dirname, 'events');
const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js'));
for (const file of eventFiles) {
const filePath = path.join(eventsPath, file);
const event = require(filePath);
if (event.once) {
client.once(event.name, (...args) => event.execute(...args));
} else {
client.on(event.name, (...args) => event.execute(...args));
}
}
client.commands = new Collection();
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
client.commands.set(command.data.name, command);
}
client.once(Events.ClientReady, () => {
console.log('Ready!');
});
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
const command = client.commands.get(interaction.commandName);
if (!command) return;
try {
await command.execute(interaction);
} catch (error) {
console.error(error);
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
}
});
client.login(token);
The onValue function registers a realtime listener, that continues to monitor the value on the database.
If you want to read a value once, that'd be done with get() function in v9 (which is the equivalent of the once method in earlier SDK versions). Have a look at the code sample in the documentation on reading data once.
Basically, my code worked completely fine in replit, but now it doesnt work in a vsc folder. my replit version also suddenly can’t send any messages anymore. it sends all the console.logs but the client.say stuff it just skips without an error.
const tmi = require('tmi.js');
// Define configuration options
const opts = {
identity: {
username: 'BormBot',
password: 'cut out for a reason'
},
channels: [
'Epicurious__'
]
};
// Create a client with our options
const client = new tmi.client(opts);
const jsonFile = require('./link.json');
const fs = require('fs');
const mongoose = require('mongoose');
// Register our event handlers (defined below)
client.on('connected', onConnectedHandler);
// Connect to Twitch:
client.connect();
client.on('message', (channel, tags, msg, self, target) => {
if (self) return;
//start of geoguessr commands
const link = {
"link": ""
};
if (msg.startsWith('!geolink')) {
if (tags.badges.broadcaster == 1) {
const arguments = msg.split(/[ ]+/)
if (arguments[1]) {
let link = arguments[1];
const data = JSON.stringify(link);
fs.writeFile('./link.json', data, (err) => {
if (err) {
throw err;
}
console.log("JSON data is saved.");
});
client.say(channel, link);
} else {
console.log("no args");
}
}
}
if (msg.startsWith('!game')) {
// read JSON object from file
fs.readFile('./link.json', 'utf-8', (err, data) => {
if (err) {
throw err;
}
// parse JSON object
const linkDone = JSON.parse(data.toString());
// print JSON object
client.say(channel, `The link for the current geoguessr game is: ${linkDone}`);
console.log(`${linkDone}`);
});
}
//end of geoguessr commands
});
// Called every time the bot connects to Twitch chat
function onConnectedHandler(addr, port) {
console.log(`* Connected to ${addr}:${port}`);
}
console
Also on twitch developer forum: 2
On twitch developer forum there hasn't been an answer yet, hence why I'm also putting it on here. Hopefully I can find an answer, also, maybe add a tag for tmi.js
So anytime I send an incorrect command like !pong (where!ping is the correct one) the bot returns with the following error:
(if(command.permissions.length){
^
TypeError: Cannot read property 'permissions' of undefined
even if I remove the whole permissions code it then gives me a
TypeError: Cannot read property 'users' of undefined
and it's only when I have a wrong command of that of !pong when there is no command or !me anything with the prefix that isn't a actual command. below is the code im using
Message.js
require('dotenv').config();
const cooldowns = new Map();
module.exports = (Discord, client, message) =>{
const prefix = process.env.PREFIX;
if(!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).split(/ +/);
const cmd = args.shift().toLowerCase();
const command = client.commands.get(cmd) || client.commands.find(a => a.aliases && a.aliases.includes(cmd));
const validPermissions = [
"CREATE_INSTANT_INVITE",
"KICK_MEMBERS",
"BAN_MEMBERS",
"ADMINISTRATOR",
"MANAGE_CHANNELS",
"MANAGE_GUILD",
"ADD_REACTIONS",
"VIEW_AUDIT_LOG",
"PRIORITY_SPEAKER",
"STREAM",
"VIEW_CHANNEL",
"SEND_MESSAGES",
"SEND_TTS_MESSAGES",
"MANAGE_MESSAGES",
"EMBED_LINKS",
"ATTACH_FILES",
"READ_MESSAGE_HISTORY",
"MENTION_EVERYONE",
"USE_EXTERNAL_EMOJIS",
"VIEW_GUILD_INSIGHTS",
"CONNECT",
"SPEAK",
"MUTE_MEMBERS",
"DEAFEN_MEMBERS",
"MOVE_MEMBERS",
"USE_VAD",
"CHANGE_NICKNAME",
"MANAGE_NICKNAMES",
"MANAGE_ROLES",
"MANAGE_WEBHOOKS",
"MANAGE_EMOJIS",
]
if(command.permissions.length){
let invalidPerms = []
for(const perm of command.permissions){
if(!validPermissions.includes(perm)){
return console.log(`Invalid Permissions ${perm}`);
}
if(!message.member.hasPermission(perm)){
invalidPerms.push(perm);
}
}
if (invalidPerms.length){
return message.channel.send(`Missing Permissions: \`${invalidPerms}\``);
}
}
if(!cooldowns.has(command.name)){
cooldowns.set(command.name, new Discord.Collection());
}
const current_time = Date.now();
const time_stamps = cooldowns.get(command.name);
const cooldown_ammount = (command.cooldown) * 1000;
if(time_stamps.has(message.author.id)){
const expiration_time = time_stamps.get(message.author.id) + cooldown_ammount;
if(current_time < expiration_time){
const time_left = (expiration_time - current_time) / 1000;
return message.reply(`Please wait ${time_left.toFixed(1)} more seconds before using ${command.name}`)
}
}
time_stamps.set(message.author.id, current_time);
setTimeout(() => time_stamps.delete(message.author.id), cooldown_ammount);
try{
command.execute(message,args, cmd, client, Discord);
} catch (err){
message.reply("There was an error trying to execute this command!");
console.log(err);
}
}
ping.js
module.exports = {
name: 'ping',
cooldown: 10,
permissions: ["SEND_MESSAGES",],
description: "this is a ping command!",
execute(client, message, args, cmd, Discord){
message.channel.send('pong!');
} }
In your message.js, after the declaration of command, you need to check if it’s undefined or null
const command = client.commands.get(cmd) || client.commands.find(a => a.aliases && a.aliases.includes(cmd));
if(!command) return;
Edit: your error comes from here:
command.execute(message,args, cmd, client, Discord);
//^^how you execute
execute(client, message, args, cmd, Discord){ message.channel.send('pong!'); }
//^^how you define it
So as you can see, you put them in the wrong order.
Hi i was making a per server prefix thing for my discord.js v12 bot using quick.db everything was going well but when using a command it throws this error
C:\Users\Aoshey\Desktop\Taco Bot V2\tacobot.js:50
let prefix = db.get(`prefix_${message.guild.ID}`);
^
TypeError: Cannot read property 'ID' of null
i don't quite understand since i'm new to coding so sorry if it was something stupid
the main bot file's code:
client.on('message', message => {
let prefix = db.get(`prefix_${message.guild.ID}`);
if(prefix === null) prefix = default_prefix;
if (!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).trim().split(/ +/);
const commandName = args.shift().toLowerCase();
const command = client.commands.get(commandName)
|| client.commands.find(cmd => cmd.aliases && cmd.aliases.includes(commandName));
if (!command) return;
if (command.guildOnly && message.channel.type === 'dm') {
return message.reply('I can\'t execute that command inside DMs!');
}
if (command.args && !args.length) {
let reply = `${message.author}, wrong usage`;
if (command.usage) {
reply += `\nThe proper usage would be: \`${prefix}${command.name} ${command.usage}\``;
}
return message.channel.send(reply);
}
try {
command.execute(message, args);
} catch (error) {
console.error(error);
message.reply('there was an error trying to execute that command! Please tell Aro#1221 about this.');
}
});
i noticed that the error only occurs when its used on only 1 command meanwhile other commands work well (also yes i'm using the command in a guild)
the code for the command which causes the error i think?
const fsn = require("fs-nextra");
const colors = require("colors");
module.exports = {
name: 'deleteorder',
description: 'deleting/denying an order.',
args: 'true',
usage: '<order id> <reason>',
aliases: ['od', 'delorder', 'do'],
execute(message, args) {
if (message.member.roles.cache.find(r => r.id === '745410836901789749')) {
let ticketID = args[0];
let reason = args[1];
fsn.readJSON("./orders.json").then((orderDB) => {
const order = orderDB[ticketID];
if(order === undefined) {
message.reply(`Couldn't find order \`${args[0]}\` Try again.`);
return;
}
if(reason === "") {
reason = "None Provided";
}
delete orderDB[ticketID];
fsn.writeJSON("./orders.json", orderDB, {
replacer: null,
spaces: 4
});
message.reply(`You deleted order \`${args[0]}\` with reason \`${args[1]}\``)
// Sends a message to the customer.
let customer = message.guild.members.cache.find(m => m.id === order.userID)
//message.users.cache.get(order.userID).send(`Sorry, but your order was cancelled by **${message.author.username}** due to the following reason: \`${reason}\`.`);
customer.send(`Sorry, but your order was cancelled by **${message.author.username}** due to the following reason: \`${reason}\`.`)
// Logs in console.
console.log(colors.red(`${message.author.username} deleted order ${order.orderID}.`));
});
} else {
message.reply("You aren't an employee.");
console.log(colors.red(`${message.author.username} did not have access to the delorder command.`));
}
}
}
Its just id in lowercase letters. ID in uppercase letters doesn't exist.
let prefix = db.get(`prefix_${message.guild.id}`);
Ok, so I searched for a while, but I couldn't find any information on how to delete all messages in a discord channel. And by all messages I mean every single message ever written in that channel. Any clues?
Try this
async () => {
let fetched;
do {
fetched = await channel.fetchMessages({limit: 100});
message.channel.bulkDelete(fetched);
}
while(fetched.size >= 2);
}
Discord does not allow bots to delete more than 100 messages, so you can't delete every message in a channel. You can delete less then 100 messages, using BulkDelete.
Example:
const Discord = require("discord.js");
const client = new Discord.Client();
const prefix = "!";
client.on("ready" () => {
console.log("Successfully logged into client.");
});
client.on("message", msg => {
if (msg.content.toLowerCase().startsWith(prefix + "clearchat")) {
async function clear() {
msg.delete();
const fetched = await msg.channel.fetchMessages({limit: 99});
msg.channel.bulkDelete(fetched);
}
clear();
}
});
client.login("BOT_TOKEN");
Note, it has to be in a async function for the await to work.
Here's my improved version that is quicker and lets you know when its done in the console but you'll have to run it for each username that you used in a channel (if you changed your username at some point):
// Turn on Developer Mode under User Settings > Appearance > Developer Mode (at the bottom)
// Then open the channel you wish to delete all of the messages (could be a DM) and click the three dots on the far right.
// Click "Copy ID" and paste that instead of LAST_MESSAGE_ID.
// Copy / paste the below script into the JavaScript console.
var before = 'LAST_MESSAGE_ID';
var your_username = ''; //your username
var your_discriminator = ''; //that 4 digit code e.g. username#1234
var foundMessages = false;
clearMessages = function(){
const authToken = document.body.appendChild(document.createElement`iframe`).contentWindow.localStorage.token.replace(/"/g, "");
const channel = window.location.href.split('/').pop();
const baseURL = `https://discordapp.com/api/channels/${channel}/messages`;
const headers = {"Authorization": authToken };
let clock = 0;
let interval = 500;
function delay(duration) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), duration);
});
}
fetch(baseURL + '?before=' + before + '&limit=100', {headers})
.then(resp => resp.json())
.then(messages => {
return Promise.all(messages.map((message) => {
before = message.id;
foundMessages = true;
if (
message.author.username == your_username
&& message.author.discriminator == your_discriminator
) {
return delay(clock += interval).then(() => fetch(`${baseURL}/${message.id}`, {headers, method: 'DELETE'}));
}
}));
}).then(() => {
if (foundMessages) {
foundMessages = false;
clearMessages();
} else {
console.log('DONE CHECKING CHANNEL!!!')
}
});
}
clearMessages();
The previous script I found for deleting your own messages without a bot...
// Turn on Developer Mode under User Settings > Appearance > Developer Mode (at the bottom)
// Then open the channel you wish to delete all of the messages (could be a DM) and click the three dots on the far right.
// Click "Copy ID" and paste that instead of LAST_MESSAGE_ID.
// Copy / paste the below script into the JavaScript console.
// If you're in a DM you will receive a 403 error for every message the other user sent (you don't have permission to delete their messages).
var before = 'LAST_MESSAGE_ID';
clearMessages = function(){
const authToken = document.body.appendChild(document.createElement`iframe`).contentWindow.localStorage.token.replace(/"/g, "");
const channel = window.location.href.split('/').pop();
const baseURL = `https://discordapp.com/api/channels/${channel}/messages`;
const headers = {"Authorization": authToken };
let clock = 0;
let interval = 500;
function delay(duration) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), duration);
});
}
fetch(baseURL + '?before=' + before + '&limit=100', {headers})
.then(resp => resp.json())
.then(messages => {
return Promise.all(messages.map((message) => {
before = message.id;
return delay(clock += interval).then(() => fetch(`${baseURL}/${message.id}`, {headers, method: 'DELETE'}));
}));
}).then(() => clearMessages());
}
clearMessages();
Reference: https://gist.github.com/IMcPwn/0c838a6248772c6fea1339ddad503cce
This will work on discord.js version 12.2.0
Just put this inside your client on message event
and type the command: !nuke-this-channel
Every message on channel will get wiped
then, a kim jong un meme will be posted.
if (msg.content.toLowerCase() == '!nuke-this-channel') {
async function wipe() {
var msg_size = 100;
while (msg_size == 100) {
await msg.channel.bulkDelete(100)
.then(messages => msg_size = messages.size)
.catch(console.error);
}
msg.channel.send(`<#${msg.author.id}>\n> ${msg.content}`, { files: ['http://www.quickmeme.com/img/cf/cfe8938e72eb94d41bbbe99acad77a50cb08a95e164c2b7163d50877e0f86441.jpg'] })
}
wipe()
}
This will work so long your bot has appropriate permissions.
module.exports = {
name: "clear",
description: "Clear messages from the channel.",
args: true,
usage: "<number greater than 0, less than 100>",
execute(message, args) {
const amount = parseInt(args[0]) + 1;
if (isNaN(amount)) {
return message.reply("that doesn't seem to be a valid number.");
} else if (amount <= 1 || amount > 100) {
return message.reply("you need to input a number between 1 and 99.");
}
message.channel.bulkDelete(amount, true).catch((err) => {
console.error(err);
message.channel.send(
"there was an error trying to prune messages in this channel!"
);
});
},
};
In case you didn't read the DiscordJS docs, you should have an index.js file that looks a little something like this:
const Discord = require("discord.js");
const { prefix, token } = require("./config.json");
const client = new Discord.Client();
client.commands = new Discord.Collection();
const commandFiles = fs
.readdirSync("./commands")
.filter((file) => file.endsWith(".js"));
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
client.commands.set(command.name, command);
}
//client portion:
client.once("ready", () => {
console.log("Ready!");
});
client.on("message", (message) => {
if (!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).split(/ +/);
const commandName = args.shift().toLowerCase();
if (!client.commands.has(commandName)) return;
const command = client.commands.get(commandName);
if (command.args && !args.length) {
let reply = `You didn't provide any arguments, ${message.author}!`;
if (command.usage) {
reply += `\nThe proper usage would be: \`${prefix}${command.name} ${command.usage}\``;
}
return message.channel.send(reply);
}
try {
command.execute(message, args);
} catch (error) {
console.error(error);
message.reply("there was an error trying to execute that command!");
}
});
client.login(token);
Another approach could be cloning the channel and deleting the one with the messages you want deleted:
// Clears all messages from a channel by cloning channel and deleting old channel
async function clearAllMessagesByCloning(channel) {
// Clone channel
const newChannel = await channel.clone()
console.log(newChannel.id) // Do with this new channel ID what you want
// Delete old channel
channel.delete()
}
I prefer this method rather than the ones listed on this thread because it most likely takes less time to process and (I'm guessing) puts the Discord API under less stress. Also, channel.bulkDelete() is only able to delete messages that are newer than two weeks, which means you won't be able to delete every channel message in case your channel has messages that are older than two weeks.
The possible downside is the channel changing id. In case you rely on storing ids in a database or such, don't forget to update those documents with the id of the newly cloned channel!
Here's #Kiyokodyele answer but with some changes from #user8690818 answer.
(async () => {
let deleted;
do {
deleted = await channel.bulkDelete(100);
} while (deleted.size != 0);
})();