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.
Related
I am working on creating a telegram bot, I want to make an anti-spam system, that is, when a person presses a button too many times, the bot will freeze for him for a certain number of seconds, it is possible to write a message about blocking. I just started learning JavaScript.
I use node-telegram-bot-api.
import {
bot
} from '../token.js';
import {
keyboardMain
} from '../keyboards/keyboardsMain.js';
export function commands() {
bot.on('message', msg => {
const text = msg.text;
const chatId = msg.chat.id;
if (text === '/start') {
return bot.sendMessage(chatId, 'hello', keyboardMain);
}
return bot.sendMessage(chatId, 'error');
});
}
You can create a user throttler using Javascript Map
/*
* #param {number} waitTime Seconds to wait
*/
function throttler(waitTime) {
const users = new Map()
return (chatId) => {
const now = parseInt(Date.now()/1000)
const hitTime = users.get(chatId)
if (hitTime) {
const diff = now - hitTime
if (diff < waitTime) {
return false
}
users.set(chatId, now)
return true
}
users.set(chatId, now)
return true
}
}
How to use: You'll get the user's chatId from telegram api. You can use that id as an identifier and stop the user for given specific time.
For instance I'm gonna stop the user for 10seconds once the user requests.
// global 10 second throttler
const throttle = throttler(10) // 10 seconds
// in your code
const allowReply = throttle(chatId) // chatId obtained from telegram
if (allowReply) {
// reply to user
} else {
// dont reply
}
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)
}
});
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!
Now, I have my shop and all the items already. I want the user so when they buy the FishingRod, they get put in a new Set();, and once they are in that set they can use the fish command. Here's my code for the 'buy' command:
else if (command === 'buy') {
const args = message.content.slice(PREFIX.length).trim().split(' ');
if (isDead.has(message.author.id)) {
message.channel.send('You\'re dead right now lmao you need to wait 5 more minutes before using any more currency and game related commands');
}
const item = await CurrencyShop.findOne({ where: { name: { [Op.like]: commandArgs } } });
if (!item) return message.channel.send('That item doesn\'t exist.');
if (args === 'FishingRod') {
hasRod.add(message.author.id);
}
if (item.cost > currency.getBalance(message.author.id)) {
return message.channel.send(`You don't have enough currency, ${message.author}`);
}
const user = await Users.findOne({ where: { user_id: message.author.id } });
currency.add(message.author.id, -item.cost);
await user.addItem(item);
message.channel.send(`You've bought a ${item.name}`);
}
As you can see, I've already made it so when the args are 'FishingRod', it puts them in the Set. The problem is that when this happens, and I try running the fish command, it still says I haven't got a FishingRod and need to buy one. Any help would be appreciated.
Okay, you're trying to compare the command arguments (which is an array) to a string. You could just do args[0] to get the first argument. Also, you're making it so that it has to be "FishingRod" and can't be lowercase. Try args[0].toLowerCase() === "fishingrod".
const Discord = require("discord.js"),
bot = new Discord.Client();
let pre = "?"
bot.on("message", async msg => {
var msgArray = msg.content.split(" ");
var args = msgArray.slice(1);
var prisonerRole = msg.guild.roles.find("name", "Prisoner");
let command = msgArray[0];
if (command == `${pre}roll`) {
if (!msg.member.roles.has(prisonerRole.id)) {
roll = Math.floor(Math.random()*6)+1;
msg.reply(`You rolled a ${roll}`)
} else {
msg.reply(`HaHa NOOB, you're in prison you don't get priveleges!`)
}
}
if (command == `${pre}kick`) {
var leaderRole = msg.guild.roles.find("name", "LEADER");
var co_leaderRole = msg.guild.roles.find("name", "CO-LEADER");
if (msg.member.roles.has(leaderRole.id) ||
msg.member.roles.has(co_leaderRole.id)) {
var kickUser = msg.guild.member(msg.mentions.users.first());
var kickReason = args.join(" ").slice(22);
msg.guild.member(kickUser).kick();
msg.channel.send(`${msg.author} has kicked ${kickUser}\nReason: ${kickReason}`);
} else {
return msg.reply("Ya pleb, you can't kick people!");
}
}
})
bot.login("token").then(function() {
console.log('Good!')
}, function(err) {
console.log('Still good, as long as the process now exits.')
bot.destroy()
})
Everything works except actually kicking the person. The message sends nut it doesn't kick people. For example, when I type in ?kick #BobNuggets#4576 inactive, it says
#rishabhase has kicked #BobNuggets
Reason: inactive
But it doesn't actually kick the user, which is weird, can you help me?
Change
msg.guild.member(kickUser).kick();
to
kickUser.kick();
also, make sure the bot is elevated in hierarchy
Use kickUser.kick();
I recommend using a command handler to neaten up your code. You don't want all your commands in one .js file.
Try something like this for the Ban command itself. I use this for my Bot:
client.on("message", (message) => {
if (message.content.startsWith("!ban")) {
if(!message.member.roles.find("name", "Role that can use this bot"))
return;
// Easy way to get member object though mentions.
var member= message.mentions.members.first();
// ban
member.ban().then((member) => {
// Successmessage
message.channel.send(":wave: " + member.displayName + " has been successfully banned :point_right: ");
}).catch(() => {
// Failmessage
message.channel.send("Access Denied");
});
}
});
That should work, set the role you want to use it (cAsE sEnSiTiVe) and change !ban to whatever you feel like using. If you change all "ban"s in this to kick, it will have the same effect. If this helped you, mark this as the answer so others can find it, if not, keep looking :)