I'm trying to make a discord bot that gives you weekly reminders. Im using momentjs to get the time. As well as using discord.js-commando. I found the best way to call a function multiple times is to use setInterval.
const moment = require('moment');
const Commando = require('discord.js-commando');
const bot = new Commando.Client();
bot.on('ready', function () {
var day = moment().format('dddd');
var time = moment().format('h:mm a');
function Tuesday() {
if (day == 'Tuesday' && time == '6:30 pm') {
const channel = bot.channels.get('933047404645724234');
console.log('Sending Message');
channel.send('Reminder: You have a week to complete your To-Do List!');
} else {
console.log('Not Sending Message');
}
}
console.log('Bot is now ready!');
setInterval(Tuesday, 100);
});
I noticed the problem with the setInterval is that it is only called once. I also tried using an async function but that failed as well.
Well executing a function every 100ms is not quite optimal. I don't know what is the reason to run just once (it should run infinitely) but there is a better way to do what you need.
You will need the package called "node-schedule". It's really useful for such things.
Here's an example:
const schedule = require("node-schedule");
schedule.scheduleJob({hour: 18, minute: 30, dayOfWeek: 2}, function(){
// your code here
});
You can read more in the documentation of node-schedule here.
Related
I have a website that allows users to send themselves a message at a date they choose, but I have no idea how to send it at that specific time. I know there exist CronJobs, but here, I'm not doing anything recurring. It's a one-time event trigger that I need.
I first tried using the native setTimeout like this:
const dueTimestamp = ...;
const timeLeft = dueTimestamp - Date().now();
const timeoutId = setTimeout(() => sendMessage(message), timeLeft);
It works perfectly for short periods, however, I'm not sure if it is reliable for long periods such as years or even decades. Moreover, it doesn't offer much control because if I'd like to modify the dueDate or the message's content, I'd have to stop the Timeout and start a new one.
Is there any package, a library, or a service that allows you to run a NodeJS function at a scheduled time? or do you have any solutions? I've heard of Google Cloud Schedule or Cronhooks, but I'm not sure.
You can use node-schedule library. for example :
you want to run a funcation at 5:30am on December 21, 2022.
const schedule = require('node-schedule');
const date = new Date(2022, 11, 21, 5, 30, 0);
const job = schedule.scheduleJob(date, function(){
console.log('The world is going to end today.');
});
As recommended by user3425506, I simply used a Cron job to fetch the messages from a database and to send the message of those whose timestamps have passed.
Dummy representation:
import { CronJob } from "cron";
import { fakeDB } from "./fakeDB";
const messages = fakeDB.messages;
const job = new CronJob("* * * * * *", () => {
const currentTimestamp = new Date().getTime();
messages.forEach((message, index) => {
if (message.timestamp > currentTimestamp) return;
console.log(message.message);
messages.splice(index, 1);
});
});
job.start();
I'm trying to make a bot that sends a message every day at 5 am EST so I'm trying to create a Cron job. This is what I have but every time I run it, it sends a message straight away instead of the time I want it to send at. Here's my code. I have it at 5 am in the code but I change the time when I'm testing it out.
Thank you.
const e = require('express')
const client = new Discord.Client()
const config = require('./config.json')
const privateMessage = require('./private-message')
const cron = require('node-cron');
const express = require('express');
client.on('ready', () => {
console.log('running');
})
cron.schedule('0 5 * * *', function() {
console.log('cron is working');
}, {
scheduled: true,
timezone: "America/New_York"
});
client.login(config.token).then(() => {
console.log('sending');
client.users
.fetch('749097582227357839').then((user) => {
user.send(`hello`,);
})
console.log("nope");
client.destroy();
});
client.login(config.token)
You have two client.login functions for some reason, please remove the first one.
You dont seem to use cron in the correct way - the cron.schedule function is where you put the code to repeat, not below it.
That is why your bot is sending the message immediately - the code is simply doing what you are asking it to do right after it schedules a cron job.
If you have everything else correct, your bot will actually log cron is working to the console at 5am every morning with your current code - the below code should achieve what you need.
To sum up:
//declare packages here
cron.schedule('0 5 * * *', function() {
console.log('sending');
client.users.fetch('749097582227357839').send('hello');
}, {
scheduled: true,
timezone: "America/New_York"
});
client.login(config.token); //only ever use one of these events, it causes issues if you use multiple
there is also no need to do client.destroy - it shouldn't make any difference
This is quite hard problem to describe.
I have a koajs app with a function which is created in multiple instances (10-1000 range) every 2 minutes. this scheduled job created on app startup. I use koajs because i need a few simple api endpoints for this app. It is running well for first 3-5 hours and then the count of created instances starts to decrease and some of the log output disappears.
Here is the minimal sample based on actual code:
server.ts
const bootstrap = async () => {
process.setMaxListeners(0); //(node:7310) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 uncaughtException listeners added to [process]. Use emitter.setMaxListeners() to increase limit
//appears on app startup (however seems like this setMaxListeners(0) doesnt affect anything since the warning persist)
const app = new Koa();
app.use(async ctx => {
ctx.body = "Welcome to my Server!";
});
app.listen(port);
new Main().run();
};
bootstrap();
main.ts (tried: cron npm package, node-scheduler, setInterval, recursive setTimeout) to run the scheduledJobWrapper.
isStarting: boolean = false;
async run() {
logger.info(`running the app, every 2 minutes`);
//let that = this;
// new CronJob(`*/2 * * * *`, function () {
// that.scheduledJobWrapper();
// }, null, true, 'America/Los_Angeles');
const interval = 2 * 60 * 1000;
setInterval(() => {
this.scheduledJobWrapper();
}, interval);
}
async scheduledJobWrapper() {
logger.info("here scheduledJobWrapper");
let args = {};
//some irrelevant logic to set the arguments
await this.scheduledJob(args);
}
async scheduledJob(args) {
try {
logger.info("starting");
if (!this.isStarting) {
this.isStarting = true;
const runningCount = Executor.tasks.length; //Executor.tasks is a singleton containing some info about tasks. details are irrelevant. the point is it contains the active tasks.
const tasksLimit = 100;
if (runningCount < tasksLimit) {
for await (const i of Array(tasksLimit - runningCount).keys()) {
if (Executor.tasks.length > 20)
await global.sleep(5 * 1000);
this.startWrapper(args); //calling main task here
}
}
this.isStarting = false;
logger.info(`Started: ${Executor.tasks.length - runningCount}`);
}
} catch (e) {
logger.error("Error running scheduled job: " + e.toString());
}
}
In this example the problem manifests as following:
All work as expected first 3-5 hours, later for each time the scheduled function called:
logger.info("here scheduledJobWrapper"); does now show any output.
logger.info("starting"); not in the output
this.startWrapper does run and the code inside it is being executed.
Despite that the code inside of this.startWrapper is still running, the count of newly created jobs is slowly decreasing.
Hardware (RAM/CPU) is not getting any significant load (CPU under 10%, RAM under 20%)
Any clue on possible reason?
nodejs: 12.6.0
Thanks!
UPDATE
it seems like that with the usage of setInterval the app is running OK for a longer time (6-24 hours), but after that the problem still starts.
The issue is with setInterval function. It gets slow down with the time. It has wierd behavior too. You can create custom setInterval using setTimeout or use third-party module and give try.
Sample setInterval Implementation.
const intervals = new Map();
function setInterval(fn, time, context, ...args) {
const id = new Date().getTime() + "" + Math.floor(Math.random() * 10000);
intervals.set(
id,
setTimeout(function next() {
intervals.set(id, setTimeout(next, time));
fn.apply(context, args);
}, time)
);
return id;
}
function clearInterval(id) {
clearTimeout(intervals.get(id));
}
setInterval(console.log, 100, console, "hi");
You can also enhance, by adding delta time loss in next setTimeout.
Meaning if time loss, run next setTimeout earlier.
First of all, It will be better to move instance of Main() in listen scope:
app.listen(port, () => {
new Main().run();
});
I don't know how good idea is to run setInterval function in the backend side. It's better to extract this logic and move it in cron job.
Are we sure that the machine can run 100 tasks? Please count the tasks by order and see when the problem starts. Probably you can not schedule 100 tasks and exists one limit somewhere
I was trying to run an API call at a scheduled time. I researched through sites and found this package called node-schedule from npmjs. This is working as expected by calling the code at required time. The issue I am having is :
Suppose I have a list of times eg: ["10:00","11:00","13:00"]
Once I start the server, it will get executed at required times. But what if I want to change the time list dynamically?
Exactly what I am trying to do:
Call API and get times from Database
Setup cron-schedule for each of these times.
Dynamically adding new time to database
What I want: Dynamically add this newly added time to the cron-schedule
index.js
const express = require('express');
const schedule = require('node-schedule');
const app = express();
const port = 5000;
var date = new Date(2019, 5, 04, 14, 05, 20);// API call here
var j = schedule.scheduleJob(date, function(){
console.log('The world is going to end today.');
});
app.get('/test', (req, res) => {
var date = new Date(2019, 5, 04, 14, 11, 0); // Will call API here
var q = schedule.scheduleJob(date, function(){
console.log('Hurray!!');
});
res.send('hello there');
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
The code written above is what I have and its pretty messed up. What I am conveying there is that, while running the index.js file API is called and the cron-schedule gets executed. Now if there is some new values added to DB I want to rerun this.
Rerunning index.js is another option I have, but I don't think its the right thing to do. The next option I have in mind is to call another endpoint which is mentioned as /test above, which would eventually run the cron again.
Please let me know some suggestions or some sort of solution to this so I can rectify my mistakes.
With this code I think you could do what you want, although you must adapt it to your needs in terms of defining the functions that will execute the tasks or if you need to specify the times in which they will be executed in another way (setting specific days of the week, for example).
var times = [];
var tasks = [];
function addTask(time, fn) {
var timeArr = time.split(':');
var cronString = timeArr[1] + ' ' + timeArr[0] + ' * * *';
// according to https://github.com/node-schedule/node-schedule#cron-style-scheduling
var newTask = schedule.scheduleJob(cronString, fn);
// check if there was a task defined for this time and overwrite it
// this code would not allow inserting two tasks that are executed at the same time
var idx = times.indexOf(time);
if (idx > -1) tasks[idx] = newTask;
else {
times.push(time);
tasks.push(newTask);
}
}
function cancelTask(time) {
// https://github.com/node-schedule/node-schedule#jobcancelreschedule
var idx = times.indexOf(time);
if (idx > -1) {
tasks[idx].cancel();
tasks.splice(idx, 1);
times.splice(idx, 1);
}
}
function init(tasks) {
for (var i in tasks){
addTask(i, tasks[i]);
}
}
init({
"10:00": function(){ console.log("It's 10:00"); },
"11:00": function(){ console.log("It's 11:00"); },
"13:00": function(){ console.log("It's 13:00"); }
});
app.post('/addTask', (req, res) => {
if (!req.body.time.match(/^(0[0-9]|1[0-9]|2[0-3]|[0-9]):[0-5][0-9]$/)) {
// regex from https://stackoverflow.com/a/7536768/8296184
return res.status(400).json({'success': false, 'code': 'ERR_TIME'});
}
function fn() {
// I suppose you will not use this feature just to do console.logs
// and not sure how you plan to do the logic to create new tasks
console.log("It's " + req.body.time);
}
addTask(req.body.time, fn);
res.status(200).json({'success': true});
});
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!