Node-Cron: Running multiple schedules a day - node.js

Tags:
node-cron, ExpressJs, NodeJs, Replit, Uptimerobot
Situation:
Hey all!
I am trying to get my discord bot to send multiple messages every day on specific times.
I deployed my bot on Replit and use Uptimerobot to ping my app every 10 min to keep the bot live.
In my code I used node-cron shedules for each spicific time it should send a message:
imports
const express = require("express");
const router = express.Router();
const { Client, Intents, Guild } = require("discord.js");
const cron = require("node-cron");
const token = process.env['BOT_TOKEN']
const { promotions } = require("./promotions");
const { testServers } = require("./test-servers");
const { buildMessage } = require("./generateMessage");
Message generator
router.get("/", function(req, res, next) {
const client = new Client({
intents: [Intents.FLAGS.GUILDS],
allowedMentions: { parse: ["users", "roles"] }
});
const composeMessage = guilds => {
let thisGuild;
let discordChannel;
let role;
let holidays;
let start;
let end;
guilds.map((guild, key) => {
guild.channels.cache.map(channel => {
testServers.forEach((promo, index) => {
thisGuild = promo.guild_id;
discordChannel = promo.channel_id;
role = promo.role_id;
holidays = promo.holidays;
start = promo.start;
end = promo.end;
// All relevant promotions
if (discordChannel === channel.id.toString()) {
const notAHoliday = [];
const currentDate = new Date();
holidays.forEach(holiday => {
if (
currentDate >= holiday.start &&
currentDate.setUTCHours(23, 59, 59) <= holiday.end
) {
notAHoliday.push(false);
}
});
if (
notAHoliday.length === 0 &&
(currentDate >= promo.start &&
currentDate.setUTCHours(23, 59, 59) <= promo.end)
) {
const unfilteredMessage = buildMessage(role);
channel.send(unfilteredMessage);
}
}
});
});
});
};
When running the Bot
client.once("ready", () => {
console.log("READY!");
const guilds = client.guilds.cache.map(guild => guild);
cron.schedule("0 55 7 * * Mon,Tue,Wed,Thu,Fri", () => {
console.log("morning");
composeMessage(guilds);
});
cron.schedule("0 31 11 * * Mon,Tue,Wed,Thu,Fri", () => {
console.log("start lunch");
composeMessage(guilds);
});
cron.schedule("0 25 12 * * Mon,Tue,Wed,Thu,Fri", () => {
console.log("end lunch");
composeMessage(guilds);
});
cron.schedule("0 0 16 * * Mon,Tue,Wed,Thu,Fri", () => {
console.log("evening");
composeMessage(guilds);
});
});
client.login(token);
botStatus = "Active";
res.render('index', { status: botStatus, version: "1.0.0" })
});
module.exports = router;
Issue:
The timers work but every time it runs a schedule, I get back a bunch of messages (the longer between schedules, the more messages my bot sends)
I suspect that it has to do with the pinging and the schedules stocking those runs until the shedule runs active and releases it all..
But how should I fix this?
Thanks in advance!

Related

Get number of requisitions made by the user in a day

I have a POST ENDPOINT in my API where i want to register all work journals a worker made.
export const registerPoint = async (req: Request, res: Response) => {
const user = res.locals.decoded;
const {id} = user;
const ponto = new Ponto;
ponto.datePoint = new Date();
ponto.user_id = id;
//this numPoint should restart every day for that user...
ponto.numPoint = 1;
res.json({ msg: "Register point Route", user, id });
return;
}
how can i control how many times this requisition was made by a worker?
I want to control the variable numPoint, when the user makes this requisition it should increase by 01 and then in the final of the day returns it to 0.
Anyone knows about a solution or about a npm package that can handle this?
EDIT: Im storing all the data with SEQUELIZE + MYSQL.
As a starting point you could use a simple database for storing the data such as https://www.npmjs.com/package/sqlite3 or MySQL.
Running jobs daily you could consider https://www.npmjs.com/package/node-cron , for example having a daily job (outside of an API call function);
var cron = require('node-cron');
cron.schedule('0 0 1 * *', () => {
console.log('running every minute to 1 from 5');
});
From what I understand, you need a logging/audit trail mechanism. Since you are using MySQL you can create a new table with columns like (datetime, user_id, action). Every time the user does any action, it will be logged here. Then you can easily find out the count by aggregation. You won't need to reset any count for any user, if data doesn't exist for a given date then it's count will be 0.
I've made a solution that it worked for what i want.
Here is the solution below:
export const registerPoint = async (req: Request, res: Response) => {
const decodedUser = res.locals.decoded;
const user = await User.findByPk(decodedUser.id);
const TODAY_START = new Date().setHours(0, 0, 0, 0);
const NOW = new Date();
const markedPointsOfDay = await Ponto.findAll({
where: {
datePoint: {
[Op.gt]: TODAY_START,
[Op.lt]: NOW
},
}
});
const markedPointsOfDayByUser = await Ponto.findAll({
where: {
datePoint: {
[Op.gt]: TODAY_START,
[Op.lt]: NOW
},
UserId: (user !== null) ? user.id : decodedUser.id
}
})
if (!markedPointsOfDay || markedPointsOfDay.length === 0) {
const ponto = new Ponto;
ponto.datePoint = new Date();
if (user) {
console.log(user)
ponto.UserId = user.id as number;
console.log(ponto.UserId);
}
if (markedPointsOfDayByUser) {
ponto.numPoint = markedPointsOfDayByUser.length + 1;
}
const newPoint = await ponto.save();
res.json({ msg: "Ponto registrado com sucesso", msg2: "Caiu no IF de quando nao encontrou ponto do DIA", newPoint })
return;
}
if (markedPointsOfDay) {
const ponto = new Ponto;
ponto.datePoint = new Date();
if (user) {
ponto.UserId = user.id as number;
}
if (markedPointsOfDayByUser) {
ponto.numPoint = markedPointsOfDayByUser.length + 1;
}
const newPoint = await ponto.save();
res.json({ msg: "ponto registrado", markedPoint: newPoint, markedPointsOfDayByUser });
return;
}
return;
}

Run Nodejs App Every 6 Hours with Aysnc Await

I want to run a Script every 6 Hours
const { IgApiClient } = require("instagram-private-api")
const ig = new IgApiClient()
const USERNAME = "abc"
const PASSWORD = "xyz"
ig.state.generateDevice(USERNAME)
const main = async () => {
var birthday = new Date(2069, 05, 14);
var today = new Date();
birthday.setFullYear(today.getFullYear());
if (today > birthday) {
birthday.setFullYear(today.getFullYear() + 1);
}
var daystill = Math.floor((birthday - today) / (1000*60*60*24))
await ig.simulate.preLoginFlow()
await ig.account.login(USERNAME, PASSWORD)
process.nextTick(async () => await ig.simulate.postLoginFlow())
await ig.account.setBiography(`${daystill} Days till my Birthday, Today is ${new Date().getDate()}/${new Date().getMonth()}/${new Date().getFullYear()}. (AutoGenerated)`)
}
main()
instagram-private-api
About Script: update my Instagram Bio with Async Await
Problem / Goal:
I Tried using node-cron, but It returns some Error (I think Async is causing the Problem), I also tried while loops and setInterval()s
I want this Script/File to run every 6 Hours, I have a heroku account (if that helps)
Error when i use node-cron:
node:internal/process/promises:288
triggerUncaughtException(err, true /* fromPromise */);
Code for node-cron:
cron.schedule('* * * * *', () => { // this is not every 6hrs
const main = async () => {
//same as above
}
main()
})
Doing it the async await way as the title says.
// used to measure time
import { performance } from 'perf_hooks';
const interval = 1000; // in ms
(async function main(){
let start_time = performance.now();
// do stuff
let stop_time = performance.now();
let timeout = interval - (stop_time - start_time);
setTimeout(main, timeout);
})();
edit:
To explain the syntax behind the main function.
()(); will automatically call the function inside of first braces on script start.

How do I properly send emails to users exactly by 12am in their own time zone

I am working on a nodejs project that involves sending emails to users exactly by 12 am in their own timezone. I tried collecting the user's timezone during registration and storing it in the database. Then I tried looping through the users collection and mailing the user based on his/her timezone but it ended up mailing all users with the same time zone alongside users with no timezone and skipping those with a different timezone entirely.
I'm using expressjs, MongoDB for database management, and node-cron to schedule the email sending.
I will really appreciate your help guys.
email service file
const nodemailer = require("nodemailer");
const mg = require("nodemailer-mailgun-transport");
const handlebars = require("handlebars");
const fs = require("fs");
const path = require("path");
let cron = require("node-cron");
const { User } = require("../models/user");
const verses = require("kjv/json/verses-1769.json");
const store = require("store2");
const { bible_books } = require("./data");
const _ = require("lodash");
async function getUsersEmail() {
const users = await User.find().select(["email","timezone"]);
return users;
}
module.exports = async function () {
const emailTemplateSource = fs.readFileSync(
path.join(__dirname, "../views/template.hbs"),
"utf8"
);
const mailgunAuth = {
auth: {
api_key: process.env.mailgun_apikey,
domain: process.env.mailgun_domain,
},
};
const smtpTransport = nodemailer.createTransport(mg(mailgunAuth));
const template = handlebars.compile(emailTemplateSource);
(await getUsersEmail()).map(async function (value) {
cron.schedule("0 0 * * *", async function () {
console.log("running task every day at 12:00 am");
const bible_book = _.sampleSize(bible_books, 1);
store("bible", bible_book[0]);
let verse = store("bible");
let bible_passage = verse;
let bible_text = verses[bible_passage];
await User.findByIdAndUpdate(
value?._id,
{
bible: {
verse: bible_passage,
text: bible_text,
},
},
{ new: true }
);
const htmlToSend = template({
verse: bible_passage,
text: bible_text,
imageUrl:"https://world.png",
redirectUrl: "https://redirecturl.com/home",
year: new Date().getFullYear(),
});
const mailOptions = {
from: "from#domain.org",
to: value?.email,
subject: "Send",
html: htmlToSend,
};
smtpTransport.sendMail(mailOptions, function (error, response) {
if (error) {
console.log(error);
} else {
console.log(`Successfully sent email to ${mailOptions.to}.`);
}
});
}, {
scheduled: true,
timezone:value?.timezone
});
console.log(value?.timezone)
});
};
calling my service file in my index.js
require("./service/emailServiceFile")();
There are many approaches to do this but let's discuss your solution
The time is midnight[12:00] somewhere around the globe now, so the schedule should run every hour not day.
cron.schedule("0 * * * *", ...
You need to check if it is after 12:00 am in the user's timezone with something like this.
if (new Date(new Date().toLocaleString('en-us', { timeZone: value?.timezone })).getHours() == 0) { //Send Emails }

Command Cooldown 1 minute not working discord bot

Here I tried making a command cooldown so people wont be spamming and getting rich fast on my discord bot! The thing is though the code I wrote does not do what I want it to do! It just ignores talkRecently Here is my code:
var eco = require('discord-economy');
const editJsonFile = require("edit-json-file");
const prefix = '.';
const talkedRecently = new Set();
exports.run = async (client, message, args, ops) => {
if (talkedRecently.has(message.author.id)) {
message.channel.send("Wait 1 minute before getting typing this again. - " + message.author);
} else {
var job2 = ['actor/actress','astronaut','baker','barber','biologist','chef','doctor','dentist','farmer','nurse','jounalist','police officer','vet','vocalist','zoologist','waiter/waitress'];
const user = message.author.id;
var number = Math.floor(Math.random() * (500 + 1));
let check = eco.FetchBalance(user);
if(!check) {
eco.SetBalance(user, 0);
}
eco.AddToBalance(user, number)
var job = job2[Math.floor(Math.random() * job2.length)];
message.channel.send(`You have worked as a **${job}** and you have earned ${number}`);
}
talkedRecently.add(message.author.id);
setTimeout(() => {
// Removes the user from the set after a minute
talkedRecently.delete(message.author.id);
}, 60000);
}
Excluding boilerplate code
var arrayOfIntervalUsed = []
var arrOfIds = [];
client.on("message", (msg) => {
arrOfIds.push(msg.author.id)
if(arrOfIntervalUsed.indexOf(msg.author.id) === -1){
setInterval(() => {
arrOfIntervalUsed.push(msg.author.id) // so the bot isn't duplicating itself
if(arrOfIds.filter(() => {
return msg.author.id
}).length > 10){
msg.reply("Stop spamming")
arrOfIds = arrOfIds.filter(() => {
return !msg.author.id
})
}
}, 60000)
}
})
EDIT:
If you want none of the commands to work if they spam add a variable by default true which has to be true in your if statement for what command it is and in the code above below the message about stop spamming make it false but if you want it to come back on add another setInterval outside of that that a set amount of miliseconds it is restored.

How to make a command randomly fail

Okay so on my bot I have a beg command but the peoblem is I want it to sometimes fail.
For example one time it might say "Someone gave you $5" but if it fails it would say "Famous Person: NOPE"
The code below chooses a random text from a file where all the text is.
Is there anyway to mke it randomly fail so a user does not get money?
const { RichEmbed } = require("discord.js");
const { stripIndents } = require("common-tags");
const { prefix } = require("../../botconfig.json");
const db = require('quick.db')
let bal = require("../../database/balance.json");
const fs = require('fs');
const cooldowns = new Map();
const humanizeDuration = require('humanize-duration');
//Set cooldown
module.exports = {
name: "beg",
aliases: [],
category: "economy",
description: "Gets you money",
usage: "[command | alias]",
run: async (client, message, args) => {
const cooldown = cooldowns.get(message.author.id);
if (cooldown) {
const remaining = humanizeDuration(cooldown - Date.now(),{ units: ['s'],round: true });
let cEmbed = new RichEmbed()
.setColor("RANDOM")
.setTitle("Slow down, cmon!")
.setDescription(`You dont want to be like a cry baby! You will be able to beg in \`${remaining}\` just you wait!\n\nWhile you wait why not follow our [Twitter](https://twitter.com/switchoffical)`)
return message.channel.send(cEmbed)
.catch(console.error);
} else {
if(!bal[message.author.id]){
bal[message.author.id] = {
balance: 0
};
}
const Jwork = require('../../beg.json');
const JworkR = Jwork[Math.floor(Math.random() * Jwork.length)];
var random = Math.floor(Math.random() * 20) + 3;
let curBal = bal[message.author.id].balance
bal[message.author.id].balance = curBal + random;
fs.writeFile('././database/balance.json', JSON.stringify(bal, null, 2), (err) => {
let embed = new RichEmbed()
.setColor("RANDOM")
.setDescription(`**\ ${message.author.username}**, ${JworkR} 💵 **${random}**`)
message.channel.send(embed)
if (err) console.log(err)
});
//Adds the user to the set so that they can't talk for a minute
cooldowns.set(message.author.id, Date.now() + 10000);
setTimeout(() => cooldowns.delete(message.author.id), 10000);
}
}
}
I just don't know how to make it fail
So what you can do is run a Math.floor((Math.random()-0.001)*4) store it to a variable. Now you have a random number from 0 to 3 (4 different numbers/outcomes). And then check whether your new variable equals 0. if(failChance === 0) if it's true just don't do the add bal cmd.
Example:
...
} else {
var failChance = Math.floor((Math.random()-0.001)*4);
if(failChance === 0){
message.channel.send('FAILURE');
return;
}
if(!bal[message.author.id]){
...

Resources