replace scheduler with round robin every x minutes

This commit is contained in:
2026-01-11 12:19:43 -05:00
parent b222d4a5d7
commit d00d5bb313
9 changed files with 77 additions and 59 deletions

View File

@@ -4,15 +4,16 @@
"alerts": "YOUR DISCORD USER ID", "alerts": "YOUR DISCORD USER ID",
"torn": "TORN API KEY", "torn": "TORN API KEY",
"httpPort": 3000, "httpPort": 3000,
"taskWaitMinutes": 5,
"channels": { "channels": {
"ocAlert": "000000000000000000" "ocAlert": "000000000000000000"
}, },
"upgradeColors": { "upgradeColors": {
"core": "#FFFFFF", "core": "#FFFFFF",
"peace": "#FFFFFF", "peace": "#FFFFFF",
"peaceDim": "#AAAAAA", "peaceDim": "#AAAAAA",
"war": "#FFFFFF", "war": "#FFFFFF",
"warDim": "#AAAAAA", "warDim": "#AAAAAA",
"background": "#0A2472" "background": "#0A2472"
} }
} }

View File

@@ -1,4 +1,4 @@
const cron = require('node-cron');
const fs = require('fs'); const fs = require('fs');
const path = require('node:path'); const path = require('node:path');
const torn = require('./torn.js'); const torn = require('./torn.js');
@@ -19,9 +19,9 @@ try {
} catch { } catch {
console.log("Core: No state file found, creating one.") console.log("Core: No state file found, creating one.")
state = { state = {
"ocAlertLast": "2025-01-01T00:00:00.000Z", "ocAlertLast": "2025-01-01T00:00:00.000Z",
"payoutAlertLast": "2025-01-01T00:00:00.000Z", "payoutAlertLast": "2025-01-01T00:00:00.000Z",
"itemAlertLast": "2025-01-01T00:00:00.000Z" "itemAlertLast": "2025-01-01T00:00:00.000Z"
} }
fs.writeFileSync('./state.json', JSON.stringify(state)); fs.writeFileSync('./state.json', JSON.stringify(state));
stateWasCreated = true; stateWasCreated = true;
@@ -32,18 +32,18 @@ try {
const { Client, Collection, Events, GatewayIntentBits, EmbedBuilder, Partials, MessageFlags } = require('discord.js'); const { Client, Collection, Events, GatewayIntentBits, EmbedBuilder, Partials, MessageFlags } = require('discord.js');
const client = new Client({ const client = new Client({
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessages,
GatewayIntentBits.DirectMessages, GatewayIntentBits.DirectMessages,
GatewayIntentBits.MessageContent GatewayIntentBits.MessageContent
], ],
partials: [ partials: [
Partials.Channel, Partials.Channel,
Partials.Message Partials.Message
] ]
}); });
client.once(Events.ClientReady, readyClient => { client.once(Events.ClientReady, readyClient => {
console.log(`Discord: Connected as ${readyClient.user.tag}`); console.log(`Discord: Connected as ${readyClient.user.tag}`);
torn.readyCheck(config.torn); torn.readyCheck(config.torn);
}); });
client.login(config.token); client.login(config.token);
@@ -52,23 +52,50 @@ client.tasks = {};
fs.readdir('./tasks/', (err, files) => { fs.readdir('./tasks/', (err, files) => {
if (err) return console.log(err); if (err) return console.log(err);
const taskNames = [];
files.forEach(file => { files.forEach(file => {
const taskFile = require(`./tasks/${file}`); const taskFile = require(`./tasks/${file}`);
const taskName = file.split('.')[0]; const taskName = file.split('.')[0];
client.tasks[taskName] = taskFile; client.tasks[taskName] = taskFile;
if (taskFile.schedule) { taskNames.push(taskName);
console.debug(`Tasks: Scheduling "${taskName}" for ${taskFile.schedule}`); console.debug(`Tasks: Registered "${taskName}"`);
cron.schedule(taskFile.schedule, () => { taskFile(client, torn, config, state); });
} else {
console.debug(`Tasks: Registered "${taskName}"`);
}
}); });
// Round-robin scheduler
let currentTaskIndex = 0;
const runNextTask = () => {
if (taskNames.length === 0) return;
const taskName = taskNames[currentTaskIndex];
const taskFile = client.tasks[taskName];
const now = new Date();
const dateString = now.toLocaleTimeString('en-US', { hour12: false }) + ' ' + now.toLocaleDateString('en-US');
try {
console.debug(`Tasks: Executing "${taskName}" at ${dateString}`);
taskFile(client, torn, config, state);
} catch (error) {
console.error(`Tasks: Error executing "${taskName}" at ${dateString}:`, error);
}
currentTaskIndex = (currentTaskIndex + 1) % taskNames.length;
const waitMinutes = config.taskWaitMinutes || 5;
setTimeout(runNextTask, waitMinutes * 60 * 1000);
};
// Start the loop with an initial delay
if (taskNames.length > 0) {
const waitMinutes = config.taskWaitMinutes || 5;
console.log(`Tasks: Scheduler started. First task will run in ${waitMinutes} minutes.`);
setTimeout(runNextTask, waitMinutes * 60 * 1000);
}
}); });
// discord command stuff also yoinked // discord command stuff also yoinked
const foldersPath = path.join(__dirname, 'commands'); const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath); const commandFolders = fs.readdirSync(foldersPath);
for (const folder of commandFolders) { for (const folder of commandFolders) {
const commandsPath = path.join(foldersPath, folder); const commandsPath = path.join(foldersPath, folder);
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) { for (const file of commandFiles) {
@@ -94,8 +121,8 @@ client.on(Events.ClientReady, async () => {
if (cmd && typeof cmd.execute === 'function') { if (cmd && typeof cmd.execute === 'function') {
console.debug('Startup: Generating upgrades image (missing or first run)'); console.debug('Startup: Generating upgrades image (missing or first run)');
const mockInteraction = { const mockInteraction = {
deferReply: async () => {}, deferReply: async () => { },
editReply: async () => {} editReply: async () => { }
}; };
try { try {
await cmd.execute(mockInteraction); await cmd.execute(mockInteraction);
@@ -109,7 +136,7 @@ client.on(Events.ClientReady, async () => {
console.error('Startup: error while ensuring upgrades image', err); console.error('Startup: error while ensuring upgrades image', err);
} }
}); });
client.on(Events.InteractionCreate, async interaction => { client.on(Events.InteractionCreate, async interaction => {
if (interaction.isButton()) { if (interaction.isButton()) {
if (interaction.customId === 'delete_message') { if (interaction.customId === 'delete_message') {

24
package-lock.json generated
View File

@@ -12,8 +12,7 @@
"canvas": "^3.2.0", "canvas": "^3.2.0",
"discord.js": "^14.18.0", "discord.js": "^14.18.0",
"express": "^5.2.1", "express": "^5.2.1",
"fs": "^0.0.1-security", "fs": "^0.0.1-security"
"node-cron": "^3.0.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.24.0", "@eslint/js": "^9.24.0",
@@ -1835,18 +1834,6 @@
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-cron": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
"license": "ISC",
"dependencies": {
"uuid": "8.3.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -2547,15 +2534,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vary": { "node_modules/vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@@ -20,8 +20,7 @@
"canvas": "^3.2.0", "canvas": "^3.2.0",
"discord.js": "^14.18.0", "discord.js": "^14.18.0",
"express": "^5.2.1", "express": "^5.2.1",
"fs": "^0.0.1-security", "fs": "^0.0.1-security"
"node-cron": "^3.0.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.24.0", "@eslint/js": "^9.24.0",

View File

@@ -1,5 +1,5 @@
module.exports = async (client, torn, config) => { module.exports = async (client, torn, config) => {
console.debug("Task: Executing autoUpdateUpgrades");
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const renderer = require('../utils/UpgradeRenderer.js'); const renderer = require('../utils/UpgradeRenderer.js');

View File

@@ -1,5 +1,5 @@
module.exports = async (client, torn, config) => { module.exports = async (client, torn, config) => {
console.debug("Task: Executing noItemOC");
const fs = require('fs'); const fs = require('fs');
const channel = client.channels.resolve(config.channels.ocAlert); const channel = client.channels.resolve(config.channels.ocAlert);
const now = new Date(); const now = new Date();

View File

@@ -1,5 +1,5 @@
module.exports = async (client, torn, config) => { module.exports = async (client, torn, config) => {
console.debug("Task: Executing unavailableOC");
const { EmbedBuilder } = require('discord.js'); const { EmbedBuilder } = require('discord.js');
const fs = require('fs'); const fs = require('fs');
const channel = client.channels.resolve(config.channels.ocAlert); const channel = client.channels.resolve(config.channels.ocAlert);
@@ -41,6 +41,10 @@ module.exports = async (client, torn, config) => {
let embed = new EmbedBuilder() let embed = new EmbedBuilder()
.setTitle('Crime Availability Check') .setTitle('Crime Availability Check')
await torn.faction.crimes({ category: 'recruiting', offset: 0, sort: 'DESC' }).then(crimeList => { await torn.faction.crimes({ category: 'recruiting', offset: 0, sort: 'DESC' }).then(crimeList => {
if (!crimeList) {
console.error("unavailableOC: API returned no crimes.");
return;
}
const data = { crimes: crimeList }; const data = { crimes: crimeList };
data.crimes.forEach(crime => { data.crimes.forEach(crime => {
crimes.difficulty[crime.difficulty - 1].count++ crimes.difficulty[crime.difficulty - 1].count++

View File

@@ -1,12 +1,17 @@
module.exports = async (client, torn, config) => { module.exports = async (client, torn, config) => {
console.debug("Task: Executing unpaidOC");
const { EmbedBuilder } = require('discord.js'); const { EmbedBuilder } = require('discord.js');
const fs = require('fs'); const fs = require('fs');
const channel = client.channels.resolve(config.channels.ocAlert); const channel = client.channels.resolve(config.channels.ocAlert);
const now = new Date(); const now = new Date();
const state = require('../state.json'); const state = require('../state.json');
let embeds = []; let embeds = [];
const data = { crimes: await torn.faction.crimes({ category: 'successful', from: now.getTime() / 1000 - 7 * 24 * 60 * 60, sort: 'DESC' }) }; const crimesList = await torn.faction.crimes({ category: 'successful', from: now.getTime() / 1000 - 7 * 24 * 60 * 60, sort: 'DESC' });
if (!crimesList) {
console.error("unpaidOC: API returned no crimes.");
return;
}
const data = { crimes: crimesList };
for (const crime of data.crimes) { for (const crime of data.crimes) {
if (!crime.rewards.payout) { if (!crime.rewards.payout) {
console.debug(`unpaidOC: Found unpaid crime: ${crime.name}:${crime.id}`); console.debug(`unpaidOC: Found unpaid crime: ${crime.name}:${crime.id}`);

14
torn.js
View File

@@ -97,7 +97,11 @@ async function getCached(collectionName, id, fetchFn, ttl) {
async function fetchApi(path) { async function fetchApi(path) {
const glue = path.includes('?') ? '&' : '?'; const glue = path.includes('?') ? '&' : '?';
const response = await fetch(`${path}${glue}key=${config.torn}`); const response = await fetch(`${path}${glue}key=${config.torn}`);
return response.json(); const data = await response.json();
if (data.error) {
console.error(`Torn API Error on ${path}:`, JSON.stringify(data.error));
}
return data;
} }
const api = { const api = {
@@ -199,12 +203,12 @@ const api = {
}, },
async crimes(options = {}) { async crimes(options = {}) {
let params = new URLSearchParams(); let params = new URLSearchParams();
let category = '';
if (typeof options === 'string') { if (typeof options === 'string') {
category = options; params.append('cat', options);
} else { } else {
if (options.category) category = options.category; if (options.category) params.append('cat', options.category);
if (options.from) params.append('from', options.from); if (options.from) params.append('from', options.from);
if (options.to) params.append('to', options.to); if (options.to) params.append('to', options.to);
if (options.limit) params.append('limit', options.limit); if (options.limit) params.append('limit', options.limit);
@@ -213,7 +217,7 @@ const api = {
if (options.initiator) params.append('initiator', options.initiator); if (options.initiator) params.append('initiator', options.initiator);
} }
const endpoint = category ? `https://api.torn.com/v2/faction/crimes/${category}` : `https://api.torn.com/v2/faction/crimes`; const endpoint = `https://api.torn.com/v2/faction/crimes`;
const queryString = params.toString() ? `?${params.toString()}` : ''; const queryString = params.toString() ? `?${params.toString()}` : '';
const data = await fetchApi(`${endpoint}${queryString}`); const data = await fetchApi(`${endpoint}${queryString}`);