I have a working countdown timer command with my discord bot and it works fairly well at the moment. The biggest issue that I am running into at this time is - if the command is executed again while there is already another instance running, it is extremely slow and will start to freeze up the command within discord.
I am expecting to be able to run this command more than once without any issue or lag. I am not sure what I can implement to make this command run faster when there is more than one instance of it running simultaneously.
const { SlashCommandBuilder, PermissionFlagsBits } = require("discord.js");
const moment = require("moment");
let eventMonth;
let eventDay;
let eventYear;
let time;
module.exports = {
data: new SlashCommandBuilder()
.setName("test-timer")
.setDescription("Timer testing command")
.setDefaultMemberPermissions(PermissionFlagsBits.ViewAuditLog)
.addIntegerOption((option) => option
.setName("event-month")
.setDescription("month of the event")
.setRequired(true)
)
.addIntegerOption((option) => option
.setName("event-day")
.setDescription("day of the event")
.setRequired(true)
)
.addIntegerOption((option) => option
.setName("event-year")
.setDescription("year of the event")
.setRequired(true)
)
.addStringOption((option) => option
.setName("event-time")
.setDescription("time of the event")
.setRequired(true)
),
async execute(interaction, client) {
const message = await interaction.reply({
content: `Days: 0 Hours: 0 - Mintues: 0 - Seconds: 0`,
fetchReply: true
});
eventMonth = interaction.options.getInteger("event-month").toString();
eventDay = interaction.options.getInteger("event-day").toString();
eventYear = interaction.options.getInteger("event-year").toString();
time = interaction.options.getString("event-time");
const convertTime12to24 = (time12h) => {
const [time, modifier] = time12h.split(' ');
let [hours, minutes] = time.split(':');
if (hours === '12') {
hours = '00';
}
if (modifier === 'PM') {
hours = parseInt(hours, 10) + 12;
}
return `${hours}:${minutes}`;
}
const timeFormat = `${convertTime12to24(time)}:00`
let interval;
const eventDayMoment = moment(`${eventYear}-${eventMonth}-${eventDay} ${timeFormat}`);
const second = 1000;
const minute = second * 60;
const hour = minute * 60;
const day = hour * 24;
const countDownFn = () => {
const today = moment();
const timeSpan = eventDayMoment.diff(today);
if (timeSpan <= -today) {
console.log("Past the event day");
clearInterval(interval);
return;
} else if (timeSpan <= 0) {
console.log("Today is the day of the event");
clearInterval(interval);
return;
} else {
const days = Math.floor(timeSpan / day);
const hours = Math.floor((timeSpan % day) / hour);
const minutes = Math.floor((timeSpan % hour) / minute);
const seconds = Math.floor((timeSpan % minute) / second);
message.edit({
content: `Days: ${days} Hours: ${hours} - Mintues: ${minutes} - Seconds: ${seconds}`
})
}
};
interval = setInterval(countDownFn, second);
}
};
Most APIs these days have ratelimits to keep people from spamming them and degrading service quality for everyone else. When you try to make requests after hitting the rate limit, your requests are blocked and you get a HTTP 429 Too Many Requests status code.
How Discord's Rate Limits work
Discord, specifically, has a global ratelimit of 50 requests per second for every bot. Some bots outgrow that limit, and have to contact their staff to get it increased.
On top of that, they also have per-resource rate limits, which are probaby the ones you're hitting here. This means that resources in different guilds or channels have different rate limits (and you'll probably be able to notice that your countdown timer doesn't slow down then ran in two different guilds at the same time).
And how Discord.js handles it
To help out a bit with this Rate Limiting madness, discord.js has a set of features that queue up your requests locally and only sends them to the Discord API whenever the Ratelimit allows it. This is why your countdown timer is slow when you have too many instances of it running simultaneously.
Instead of editing your message repeatedly you could use Unix timestamps (in seconds). [Docs]
Example:
<t:1672300000:R> => 17 hours ago
<t:1672400000:R> => in 11 hours
As a side effect of using the format the formatted and translated according to the user's preferences,
it shows the exact time and date when hovering over the timestamp
and automatically updates without spamming the discord api.
Related
I'm trying to write a firebase function that would schedule a push notification 6 hours before the start time of a party. I used chatGPT to help me write the function but this does not seem to work. All the fields(hostId, deviceToken) are available in their respective collections/documents. Here's the function:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.scheduleNotification = functions.firestore
.document("parties/{partyId}").onCreate((snapshot, context)=>{
// Get the hostId from the party document
const hostId = snapshot.get("hostId");
let deviceToken = "";
// Query the hosts collection to get the device token of the host
admin.firestore().collection("hosts").doc(hostId).get().then((snap)=>{
deviceToken = snap.data().deviceToken;
// Get the time when the party will start
const startTime = snapshot.get("timestamp");
// Calculate the time 6 hours before the start time of the party
const reminderTime = startTime - 6 * 60 * 60 * 1000;
// Convert the reminderTime from millisecondSinceEpoch to CronExpression
const date = new Date(reminderTime);
// eslint-disable-next-line quotes, max-len
const cronExpression = `${date.getSeconds()} ${date.getMinutes()} ${date.getHours()} ${date.getDate()} ${date.getMonth()} ${date.getDay()} ${date.getFullYear()}`;
// Create a notification at the reminder time
const notification = {
notification: {
title: "Reminder",
body: "Your scheduled party is 6 hours away",
},
android: {
priority: "high",
},
token: deviceToken,
};
// Schedule the notification to be sent at the reminder time
functions.pubsub.schedule(cronExpression).onRun((context)=>{
return admin.messaging().send(notification);
});
console.log("Notification created");
});
});
I don't come from Javascript background, so there may be a lot of issues in this function.
Note: The startTime is in milliSecondsSinceEpoch
Here is my code, I want it to send a message if the command has been used twice within like 10 seconds. Idk but it is very wrong
var bumpEmbedTwo = new Discord.MessageEmbed()
.setTitle('Cool Down!!!')
.setColor(0xFF0000)
.setDescription('Please waitt 30 more seconds before you use this command again')
setTimeout(() => {
message.channel.send(bumpEmbedTwo)
}, 5000)
var bumpEmbed = new Discord.MessageEmbed()
.setTitle('Time to Bump!')
.setColor(0xFF0000)
.setDescription('Please use the command `!d bump` to bump the server!!!')
setTimeout(() => {
message.channel.send('<#&812133021590880316>')
message.channel.send(bumpEmbed)
}, 1000)
The code you have provided isn't correct as your doing a set timeout without an await so it would completely ignore the purpose of the timeout. Also, whenever the .on('message') function is used, it would still carry out the command, what you have to do is to create an object: const cooldowns = new Discord.Collection(); and whenever the 'message' event is triggered, you would have to add whatever amount of cool down you want.
So here's the code:
const now = Date.now();
let cooldownAmount = 5 * 1000; // In this case, it is 5 seconds
if (cooldowns.has(message.author.id)) {
const expirationTime = cooldowns.get(message.author.id) + cooldownAmount;
if (now < expirationTime) {
const timeLeft = (expirationTime - now) / 1000;
return message.channel.send(`Please wait ${timeLeft.toFixed(1)} more second(s) before using another command`);
}
}
if(!cooldowns.has(message.author.id)){
<command>.run() // Be sure to edit <command to whatever the fetched command is> and add to the (), e.g. message, args, bot, etc.
}
cooldowns.set(message.author.id, now);
setTimeout(() => cooldowns.delete(message.author.id), cooldownAmount);
PS: the client in the arrow symbols are for you to change.
If there are any extra questions, you can comment in this post! Or dm me on discord: N̷i̷g̷h̷t̷m̷a̷r̷e̷ ̷ Jeff#2616
One way to approach this problem would be to save user+command+time in DB or in some global variable.
And then perform a check.
Pseudo-code:
var storage = {
"/command": {
"user-id-123": "22/02/2021 5:27pm"
"user-id-345": "22/02/2021 3:12pm"
}
};
Chat.on("/command", (user) => {
if (!storage["/command"].hasOwnProperty(user.id)) {
storage["/command"][user.id] = Date.now();
}
if (storage["/command"][user.id] > Date.now() + 10 seconds) {
return "Please wait more";
} else {
return "I'm running your command";
}
});
I've made a discord bot, here the code
const Discord = require("discord.js");
const client = new Discord.Client();
client.on("ready", () => {
console.log("I am ready!");
});
client.on("message", (message) => {
if (message.content.startsWith("!kevin")) {
message.channel.send("i'm kevin");
}
if (message.content.startsWith("!thomas")) {
message.channel.send("random text blabla");
}
Basically, when I type !something, my bot answer in the chat by the proper line, my current issue is my discord is kinda big nowadays, and I would like to restrict the bot to only X messages per minutes, but I can't find an easy function to do that
my question :
Is it possible to get a timer between 2 messages to send by the bot, because without that my bot is just spamming answer to each users typing !somethingsomething, I would like my bot to as soon as someone type !something, the bot lock itself of replying to any other !something for X amount of time
Example,
User 1 : !thomas
User 2 : !thomas
User 3 : !thomas
But the bot only reply to one of them and put a timer to himself before being able to send a new !message reply
So basically, is it any way to make the bot reply to X amounts of !cmd each minutes, or to limits the cooldown between 2 messages the bot send
here my script : https://i.imgur.com/Q7w98Rm.jpg ( i know its terrible especially since it have over 9000 differents !cmd , but i converted quickly a old MIRC script and im terrible at javascript)
Cooldown for the users and/or bot
So I found out from your comments that you want the bot to only run a command only every "X" amount of time. I don't recommend this, I recommend just preventing USERS from registering a cmd every "X" amount of time. I've included both in this example.
let lastCmdSentTime = {};
let waitTimeForUser = 60000 * 5; //Users can only run a command once every 5 minutes
let botLastSent = false;
let timeBetweenEachCmd = 60000; //Bot will only respond once a minute.
client.on("message", (message) => {
if(botLastSent !== false ? message.createdTimestamp - botLastSent < timeBetweenEachCmd : false) return; //don't let the bot run a cmd every [timeBetweenEachCmd]
let userLastSent = lastCmdSentTime[message.author.id] || false;
if(userLastSent !== false ? message.createdTimestamp - userLastSent < waitTimeForUser : false) return; //don't let the user run a cmd every [waitTimeForUser]
lastCmdSentTime[message.author.id] = message.createdTimestamp;
botLastSent = message.createdTimestamp;
//RUN COMMANDS
});
Cooldown for commands
Simply store the date the command was ran, then check if a certain amount has passed since the date was set.
Example:
Edited to support multiple commands
let date = false;
let commandsTimers = {
"!kevin":{
waitTime: 5 * 60000, // 5 minutes wait for this particular command.
lastUsed: false,
}
}
let defaultWaitTime = 60000 * 2; //User needs to wait 2 minutes for each command unless specified
client.on("message", (message) => {
let msgSentDate = Date.now();
let commandWaitTimer = commandsTimers[message.content.split(" ")[0]] || {waitTime:defaultWaitTime, lastUsed:false};
if((commandWaitTimer.lastUsed !== false ? msgSentDate - commandWaitTimer.lastUsed < commandWaitTimer.waitTime : false)){
console.log('User needs to wait: ' + (commandWaitTimer.waitTime - (msgSentDate - commandWaitTimer .lastUsed)) / 1000 + ' seconds');
return
}
commandsTimers[message.content.split(" ")[0]].lastUsed = msgSentDate;
if (message.content.startsWith("!thomas")) {
message.channel.send("random text blabla");
}
if (message.content.startsWith("!kevin")) {
message.channel.send("Kevin!");
}
});
I'm implementing a countdown in my react-native app, but something is not working properly.
It seems that the countdown loses 1 seconds every minute (as you can see in the gif, it jumps between 33 and 31)
this is the code:
import {
differenceInDays,
differenceInHours,
differenceInMinutes,
differenceInSeconds,
isBefore,
parseISO,
} from 'date-fns'
import { useEffect, useState } from 'react'
type CountdownResult = {
days: number
hours: number
minutes: number
seconds: number
}
const calculateInitialDuration = (endDate: string, today: Date): CountdownResult => {
const futureDate = new Date(endDate)
const days = differenceInDays(futureDate, today)
const hours = differenceInHours(futureDate, today) % 24
const minutes = differenceInMinutes(futureDate, today) % 60
const seconds = differenceInSeconds(futureDate, today) % 60
return { days, hours, minutes, seconds }
}
const EXPIREDRESULT: CountdownResult = { days: 0, hours: 0, minutes: 0, seconds: 0 }
// TODO: FIXME: sometimes the countdown jumps directly between 2 seconds
// even if the real time passed is 1 second
// this was happening before the refactor too
const useCountdown = (endDate: string): CountdownResult => {
const today = new Date()
const formattedEndDate = parseISO(endDate)
// doing this because at the beginning countdown seems stuck on the first second
// maybe there is a better solution for this problem
const initialCountdown = calculateInitialDuration(endDate, today)
initialCountdown.seconds++
const [time, setTime] = useState(isBefore(formattedEndDate, today) ? EXPIREDRESULT : initialCountdown)
useEffect(() => {
if (isBefore(formattedEndDate, today)) return
const intervalId = setInterval(() => {
setTime(calculateInitialDuration(endDate, today))
}, 1000)
return (): void => clearInterval(intervalId)
}, [time])
return time
}
export default useCountdown
endDate is a string following the ISO 8601 format.
I'm using date-fns but I also tried the basic javascript implementation, bug is still the same.
Another strange thing is that the countdown, at the beginning, is stuck for one second on the first second (that's the reason why I created the initialCountdown variable), but actually I don't like the solution.
Any tips? Where are the mistakes? Thanks in advance.
At the moment you are assuming that setInterval() triggers the callback every 1,000 milliseconds.
setInterval(() => {
setTime(calculateInitialDuration(endDate, today))
}, 1000)
Unfortunately, with everything else that browser has to do, there's no guarantee that it will.
What you will need to do to gain more accuracy is repeatedly use setTimeout() calculating how long to set the timeout for.
let timeout;
const start = (() => {
// IIFE because func needs to be able to reference itself!
let func = () => {
// Do whatever you need to do here
let now = new Date();
let timeToNextSecond = 1000 - (now.getTime() % 1000);
console.log('Now: ', now, 'TimeToNext: ', timeToNextSecond);
timeout = setTimeout(func, timeToNextSecond);
};
return func;
})();
const stop = () => clearTimeout(timeout);
start();
// wait 10 seconds(ish)
setTimeout(stop, 10000);
If you run this, you will see that subsequent timeouts run shortly after the start of the next second. Assuming that the browser isn't bogged down doing other stuff, it will run every second.
Thoughts: I imagine that setInterval does something like this behind the scenes, just with a fixed timeout causing the drift.
I'm trying to make the bot writing messages at specific times. Example:
const Discord = require("discord.js");
const client = new Discord.Client();
client.on("ready", () => {
console.log("Online!");
});
var now = new Date();
var hour = now.getUTCHours();
var minute = now.getUTCMinutes();
client.on("message", (message) => {
if (hour === 10 && minute === 30) {
client.channels.get("ChannelID").send("Hello World!");
}
});
Unfortunately, it only works once I trigger another command like:
if (message.content.startsWith("!ping")) {
message.channel.send("pong!");
}
my message: !ping (at 10:10 o'clock)
-> pong!
-> Hello World!
I guess it needs something that constantly checks the time variables.
I would use cron: with this package you can set functions to be executed if the date matches the given pattern.
When building the pattern, you can use * to indicate that it can be executed with any value of that parameter and ranges to indicate only specific values: 1-3, 7 indicates that you accept 1, 2, 3, 7.
These are the possible ranges:
Seconds: 0-59
Minutes: 0-59
Hours: 0-23
Day of Month: 1-31
Months: 0-11 (Jan-Dec)
Day of Week: 0-6 (Sun-Sat)
Here's an example:
var cron = require("cron");
function test() {
console.log("Action executed.");
}
let job1 = new cron.CronJob('01 05 01,13 * * *', test); // fires every day, at 01:05:01 and 13:05:01
let job2 = new cron.CronJob('00 00 08-16 * * 1-5', test); // fires from Monday to Friday, every hour from 8 am to 16
// To make a job start, use job.start()
job1.start();
// If you want to pause your job, use job.stop()
job1.stop();
In your case, I would do something like this:
const cron = require('cron');
client.on('message', ...); // You don't need to add anything to the message event listener
let scheduledMessage = new cron.CronJob('00 30 10 * * *', () => {
// This runs every day at 10:30:00, you can do anything you want
let channel = yourGuild.channels.get('id');
channel.send('You message');
});
// When you want to start it, use:
scheduledMessage.start()
// You could also make a command to pause and resume the job
As Federico stated, that is the right way to solve this problem but the syntax has changed and now with the new update of discord.js (v12) it would be like:
// Getting Discord.js and Cron
const Discord = require('discord.js');
const cron = require('cron');
// Creating a discord client
const client = new Discord.Client();
// We need to run it just one time and when the client is ready
// Because then it will get undefined if the client isn't ready
client.once("ready", () => {
console.log(`Online as ${client.user.tag}`);
let scheduledMessage = new cron.CronJob('00 30 10 * * *', () => {
// This runs every day at 10:30:00, you can do anything you want
// Specifing your guild (server) and your channel
const guild = client.guilds.cache.get('id');
const channel = guild.channels.cache.get('id');
channel.send('You message');
});
// When you want to start it, use:
scheduledMessage.start()
};
// You could also make a command to pause and resume the job
But still credits to Federico, he saved my life!