I'm basically creating a bot that does a loop, and I want it to end when you say *parar but i don't know how to make it.
Here is a bit of code to explain my problem
module.exports = {
name: 'epico',
execute(message, args, Discord, client){
//this is the loop
var interval = setInterval(function(){...}, 1000)
}
The loop starts when I put *epico and I want it to stop when user sends *parar
I was trying something like this:
client.on('message', message =>{
if(message.content.startsWith('parar')){clearInterval(interval)}
}
But this keeps working until I shut down the bot (I want it to just work 1 time)
Try something like the following: Basically what you want to do is save your interval to a variable that is accessible later on in order to stop your interval again.
const Discord = require("discord.js");
const client = new Discord.Client();
let interval;
client.on("message", async (message) => {
if (message.content.startsWith("*epico")) {
return (interval = setInterval(() => {
console.log("do something");
}, 1000));
}
if (message.content.startsWith("*parar")) {
clearInterval(interval);
return console.log("stopped interval");
}
});
client.login("your-token");
I assume your are using multiple commands in several different files. If that is the case I would simply save the interval to the client object in your *epico command file since you pass the client to your execute function anyways.
module.exports = {
name: "epico",
execute(message, args, Discord, client) {
return (client.interval = setInterval(() => {
console.log("do something");
}, 1000));
},
};
And then just clear the interval in your *parar command. Also don't forget to check if client.interval is even set ;)
module.exports = {
name: "parar",
execute(message, args, Discord, client) {
client.interval && clearInterval(client.interval);
return console.log("stopped interval");
},
};
maybe you have a typo???
client.on('message', message => {
if (message.content.startsWith('parar')){
clearInterval(interval)
}
});
Related
I'm trying to implement cooldown to my command handler in my Discord bot, but I'm stuck.
The cooldown itself works, but when I try to use a command that is different from the one that I executed before, that cooldown gets replaced with the last one. Here's the code:
const cooldowns = new Map();
try {
const cooldown = cooldowns.get(interaction.user.id);
if (cooldown && cooldowns.get("command") == command.data.name) {
const remaining = ms(cooldown - Date.now(), {till: "second"});
return interaction.reply({ content: `Please wait until cooldown expires! Remaining: ${codeLine(remaining)}`});
}
await command.execute(client, interaction, Discord, profileData);
let startTime = Date.now() + command.cooldown;
cooldowns.set(interaction.user.id, startTime);
cooldowns.set("command", command.data.name);
setTimeout(async () => cooldowns.delete(interaction.user.id, "command"), command.cooldown);
}
catch (error) {
console.log(`The interaction error is ${error}`);
await interaction.reply({ content: "There was an error trying to execute this command. 🍐", ephemeral: true});
}
}
So for example when I use the command /hello, with a cooldown of 30 seconds, the 1st time it gets executed, and if I try to execute it again the bot responds with the cooldown saying that I can't use the command until the cooldown expires, which is correct, but if I use another command, for example /world with 0 cooldown, the command gets executed correctly, but the cooldown for the /hello command gets canceled, and it can be used again.
I'm assuming that the interaction.user.id and the "command" items get replaced with new ones, and I can't find a method to create new ones.
UPDATED
According to your requirement, I created a data structure like below
{
"userA": {
"/hello": startTime
"/world": startTime
},
"userB": {
"/hello": startTime
"/world": startTime
}
}
userA is for user id
/hello and /world are commands
startTime is the cooldown for each command
You can check this implementation with some comments
const cooldowns = {}; //similar to Map but simpler
try {
//cannot find any user in cooldowns map
if (!cooldowns[interaction.user.id]) {
cooldowns[interaction.user.id] = {} //create map to keep commands
}
//check if the cooldown exists in the commands
const cooldown = cooldowns[interaction.user.id][command.data.name];
if (cooldown) {
const remaining = ms(cooldown - Date.now(), {
till: "second"
});
if (remaining > 0) {
return interaction.reply({
content: `Please wait until cooldown expires! Remaining: ${codeLine(remaining)}`
});
}
}
await command.execute(client, interaction, Discord, profileData);
let startTime = Date.now() + command.cooldown;
cooldowns[interaction.user.id][command.data.name] = startTime
setTimeout(async () => delete cooldowns[interaction.user.id][command.data.name], command.cooldown);
} catch (error) {
console.log(`The interaction error is ${error}`);
await interaction.reply({
content: "There was an error trying to execute this command. 🍐",
ephemeral: true
});
}
OLD ANSWER
if (cooldown && cooldowns.get("command") == command.data.name) {
const remaining = ms(cooldown - Date.now(), {till: "second"});
return interaction.reply({ content: `Please wait until cooldown expires! Remaining: ${codeLine(remaining)}`});
}
I think this part is causing the problem cooldowns.get("command") == command.data.name. If your new command matches with the previous command, you check the cooldown.
BUT whenever your new command does not match with the previous one, you bypass that condition which is the system won't check the remaining time.
The possible fix should be
if (cooldown) {
const remaining = ms(cooldown - Date.now(), {till: "second"});
return interaction.reply({ content: `Please wait until cooldown expires! Remaining: ${codeLine(remaining)}`});
}
You only need to check the cooldown from the user id, and don't need to check command matching.
I'm trying to make an automemes command, I can get it to send memes automatically, but when I try to disable it, it sends the Automemes disabled! command, but it still sends them. Here's the code:
const { SlashCommandBuilder } = require("#discordjs/builders");
const fetch = (...args) => import("node-fetch").then(({default: fetch}) => fetch(...args));
module.exports = {
data: new SlashCommandBuilder()
.setName("automemes")
.setDescription("Sends random memes every 5 minutes (from r/memes)")
.addBooleanOption(option =>
option.setName("enabled")
.setDescription("Set the automemes to on/off")
.setRequired(true)),
async execute(client, interaction, Discord) {
let isEnabled = interaction.options.get("enabled").value;
switch (isEnabled) {
case true: interaction.reply("Automemes enabled! " + ENV.CATKISS)
break;
case false: isEnabled = false;
break;
}
async function sendMemes() {
fetch("https://meme-api.herokuapp.com/gimme/memes")
.then(res => res.json())
.then(async json => {
const Embed = new Discord.MessageEmbed()
.setTitle(json.title)
.setImage(json.url)
if (Embed.title.length > 256) return;
await interaction.channel.send({embeds: [Embed]});
});
}
isEnabled? setInterval(() => sendMemes(), 10000) : interaction.reply("Automemes disabled! " + ENV.CATKISS);
}
}
I see what you're trying to accomplish but, as stated above by #Zsolt Meszaros, your interval is never getting cleared. That means that if that interval gets activated once, it will continue perpetually until your bot shuts down. Try declaring your interval as a constant so you can enable and disable it however you choose.
const myInterval = setInterval(sendMemes, 10000) //declaring interval
async fucntion startInterval() {
myInterval; //function to start interval
}
async function disableInterval() {
clearInterval(myInterval); //function to clear interval
interaction.reply(interaction.reply("Automemes disabled! " + ENV.CATKISS));
}
isEnabled? startInterval() : disableInterval(); //added starter and stopper functions
}
}
I am trying to make a bot that deletes a channel ('general') after 10 min after calling execcmd() function but if the msg is sent by someone having 'idofsomeuser' then it should cancel the previous timeout and start the timer again. But it's not working.
function execcmd(msg) {
let ID = msg.author.id
let chan = msg.guild.channels.cache.find(
(channel) => channel.name === 'general'
);
x = 'idofsomeuser'
if (ID == x) {
clearTimeout(x);
ID = setTimeout(() => {
chan.delete();
}, 600000); //10min
} else {
x = setTimeout(() => {
chan.delete();
}, 600000);
}
}
execcmd(msg); //msg is received by client and passed here
Create a closure function in which you store the timeout variable. Doing this enables you to store the timeout safely without having to create a global variable to store it in.
Inside the callback function first check if the id of the author matches. If it does, clear the timeout and set it to null. Then check if the timeout is equal to null and start the timer.
So now the timer will start when the function is called for the first time, and is restarted everytime the user with the id you're looking for is found.
function createClearChannel(channelName, authorId) {
let timeout = null;
const timeoutDuration = 1000 * 60 * 10;
return function (msg) {
const isAuthor = authorId === msg.author.id;
const channel = msg.guild.channels.cache.find(
channel => channel.name === channelName
);
if (isAuthor) {
clearTimeout(timeout);
timeout = null;
}
if (timeout === null) {
timeout = setTimeout(channel.delete, timoutDuration);
}
};
}
To set this up, first call the createClearChannel function and pass the arguments below. Store the result in execcmd and call that function when a message enters.
(You could always omit these parameters and simply hardcode the channel name and id of the user, but by doing this you make the function more flexible so it can be used for other cases as well.)
Important: The createClearChannel should only be called ONCE. So don't create a new execcmd when receives each message, but use the same one everytime.
const execcmd = createClearChannel('general', 'idofsomeuser');
execcmd(msg);
A user is spamming my commands, how do I make it so all command has the same cooldown for that specific user?
Here is my current code
if (talkedRecently.has(msg.author.id)) {
msg.channel.send("Wait before getting typing this again. - " + msg.author);
} else {
message
channel.send('Cooldown over')
talkedRecently.add(msg.author.id);
setTimeout(() => {
talkedRecently.delete(msg.author.id);
}, 6000);
}
It wont work because only 1 command and I want the cool down for all commamnds for that specific user.
You could create a function to execute when a user runs a command such as:
var usersOnCooldown = {};
function setCooldown(userId){
usersOnCooldown[userId] = true;
setTimeout(() => {
usersOnCooldown[userId] = false;
}, 5000);
}
function checkUserCooldown(userId){
return !(userId in usersOnCooldown) || !usersOnCooldown[userId];
}
You could call setCooldown() after every command. And checkUserCooldown() before every command.
So I'm messing around with creating a discord bot that repeatedly pings a user until they respond/say anything in the chat (annoying, right?). The amount of times to ping the user and the time between each ping can also be adjusted if necessary. However, I can't seem to find a way to detect if the pinged user actually says something in the chat, and a way to stop the loop.
The actual pinging part of the code is in this for loop:
const ping = async () => {
for(var i = 1; i <= pingAmount; i++){
//the wait() command
await new Promise(r => setTimeout(r, pingTime * 1000));
//the actual ping
message.channel.send(`hey <#${userID}> let\'s play minecraft`);
}
//sends a message once pinging is finished
message.channel.send("Pinging Complete.");
};
I've tried nesting the following code inside that loop, but I get no results.
client.on('message', message =>{
if(message.author == taggedUser) {
message.channel.send('User has replied. Stopping pings.')
return;
}
});
Any help is appreciated!
full code below:
module.exports = {
name: 'Ping',
description: "Pings specified user until they appear",
execute(message, args, Discord){
//initialize variables
const client = new Discord.Client();
const taggedUser = message.mentions.users.first();
const userID = message.mentions.users.first().id;
//splits the command
const slicedString = message.content.split(' ');
//grabs specific numbers from command as input
const pingAmount = slicedString.slice(4,5);
const pingTime = slicedString.slice(5);
//display confirmation info in chat
message.channel.send(`So, ${message.author.username}, you want to annoy ${taggedUser.username}? Alright then lol`);
message.channel.send(`btw ${taggedUser.username}\'s user ID is ${userID} lmao`);
message.channel.send(`amount of times to ping: ${pingAmount}`);
message.channel.send(`time between pings: ${pingTime} seconds`);
//checks to make sure pingTime isnt too short
if(pingTime < 5){
if(pingTime == 1){
message.channel.send(`1 second is too short!`);
return;
} else {
message.channel.send(`${pingTime} seconds is too short!`);
return;
}
}
//timer and loop using pingAmount and pingTime as inputs
const ping = async () => {
for(var i = 1; i <= pingAmount; i++){
//the wait() command
await new Promise(r => setTimeout(r, pingTime * 1000));
//the actual ping
message.channel.send(`hey <#${userID}> let\'s play minecraft`);
const pingedUsers = [taggedUser];
// doodle message
const msg = {author: {id:1}};
// message event
const onMessage = (message) => {
if (pingedUsers.indexOf(message.author.id) != -1) {
console.log("user replied");
}
}
onMessage(msg); // nothing
pingedUsers.push(msg.author.id); // push the author id
onMessage(msg); // he replied!
}
//sends a message once pinging is finished
message.channel.send("Pinging Complete.");
};
//runs the ping function
ping();
}
}
You should be comparing the author's snowflake (id) in this case.
You can put the pinged users in a list and see if the message author is in that list.
const pingedUsers = [];
// doodle message
const msg = {author: {id:1}};
// message event
const onMessage = (message) => {
if (pingedUsers.indexOf(message.author.id) != -1) {
console.log("user replied");
}
}
onMessage(msg); // nothing
pingedUsers.push(msg.author.id); // push the author id
onMessage(msg); // he replied!