Skip to content

Commit

Permalink
💫 New Cron System
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashu11-A committed Feb 20, 2024
1 parent 4d87739 commit cf894af
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 84 deletions.
22 changes: 21 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@
"chalk": "^4.1.2",
"colors": "^1.4.0",
"cors": "^2.8.5",
"discord.js": "^14.13.0",
"cron-parser": "^4.9.0",
"discord.js": "^14.14.1",
"dotenv": "^16.3.1",
"eslint": "^8.46.0",
"express": "^4.18.2",
Expand Down
168 changes: 168 additions & 0 deletions src/classes/Crons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import colors from 'colors'
import { EventEmitter } from 'events'
import { randomUUID } from 'crypto'
import cronParser, { type CronExpression } from 'cron-parser'
import { core } from '@/app'

/**
* Configuration object for defining recurring cron jobs.
*/
export interface CronsConfigurations<Metadata = undefined> {
/**
* Identifier for the Cron job.
*/
name: string
/**
* Cron argument, e.g., "* * * * * *" for every second.
*/
cron: string
/**
* Function to be executed when the cron job is triggered.
*/
// eslint-disable-next-line @typescript-eslint/method-signature-style
exec(cron: CronsConfigurations<Metadata>, interval: CronExpression): any
/**
* Metadata for the cron job, for specific information.
*/
metadata?: Metadata
/**
* Indicates whether the cron job should run only once.
*/
once?: boolean
}

/**
* Configuration object for defining a unique cron job to run only once.
*/
export interface UniqueCron<MetaArgs> {
/**
* Identifier for the unique cron job.
*/
name: string
/**
* Cron argument, e.g., "* * * * * *" for every second.
*/
cron: string
/**
* Function to be executed when the unique cron job is triggered.
*/
// eslint-disable-next-line @typescript-eslint/method-signature-style
exec(cron: UniqueCron<MetaArgs>): any
/**
* Metadata for the unique cron job, for specific information.
*/
metadata?: MetaArgs
}

/**
* Extended configuration interface for cron jobs, including a UUID.
*/
export interface CronsConfigurationsSystem<Metadata> extends CronsConfigurations<Metadata> {
uuid: string
}

/**
* Class representing a collection of cron jobs.
*/
export class Crons<Metadata> {
/**
* Array containing all defined cron jobs.
*/
public static all: Array<CronsConfigurationsSystem<InstanceType<typeof Crons>['data']['metadata']>> = []
/**
* EventEmitter used for managing cron job events.
*/
public static set = new EventEmitter()
public static timeouts = new Map()

/**
* Starts the specified cron job.
* @param cron - Configuration for the cron job.
*/
public static start (cron: CronsConfigurationsSystem<InstanceType<typeof Crons>['data']['metadata']>): void {
const interval = cronParser.parseExpression(cron.cron)
const nextScheduledTime = interval.next().getTime()
const currentTime = Date.now()
const delay = nextScheduledTime - currentTime

// Updates the interval and schedules the next execution
this.timeouts.set(cron.uuid, setTimeout(() => {
Crons.set.emit(cron.uuid, cron, interval)
if (!(cron.once ?? false)) Crons.start(cron)
}, delay))
}

/**
* Configures unique cron jobs that run only once.
* @return Returns the setTimeout ID, which can be used for cancellation.
*/
public static once<MetaArgs>(cron: UniqueCron<MetaArgs>): NodeJS.Timeout {
const interval = cronParser.parseExpression(cron.cron)
const nextScheduledTime = interval.next().getTime()
const currentTime = Date.now()
const delay = nextScheduledTime - currentTime

// Schedules the unique cron job and logs success
console.log(`| ${colors.green('Unique Cron')} - ${colors.blue(cron.name)} added successfully.`)

return setTimeout(() => {
cron.exec(cron)
}, delay)
}

/**
* Updates or adds a cron job.
* @param cron - Configuration for the cron job.
*/
public static post (cron: CronsConfigurationsSystem<InstanceType<typeof Crons>['data']['metadata']>): void {
if ((cron?.uuid).length > 0) {
const index = Crons.all.findIndex(c => c.uuid === cron.uuid)
if (index !== -1) {
if (cron === Crons.all[index]) return
Crons.all[index] = cron
console.log(`${colors.green('Crons')} - ${colors.blue(cron.name)} | updated successfully.`)
}
} else {
// Generates a new UUID if not provided
const newcron = {
...cron,
uuid: randomUUID().replaceAll('-', '')
}
newcron.once = cron.once ?? false
Crons.all.push(newcron)
console.log(`${colors.green('Crons')} - ${colors.blue(cron.name)} | added successfully.`)
}
}

/**
* Generates a cron expression for a specific date.
* @param date - Expiration date.
* @returns Returns the cron expression.
*/
public static date (date: Date): string {
const seconds = date.getSeconds()
const minutes = date.getMinutes()
const hours = date.getHours()
const dayOfMonth = date.getDate()
let month = date.getMonth() + 1 // Months start from zero in JavaScript
if (month === 13) {
month = 12
}
return `${seconds} ${minutes} ${hours} ${dayOfMonth} ${month} *`
}

/**
* Constructor for the Crons class.
* @param data - Configuration for the cron job.
*/
constructor (public data: CronsConfigurations<Metadata>) {
core.log(`${colors.green('Schedules')} - ${colors.blue(data.name)} | configured successfully.`)

const cron = {
...data,
uuid: randomUUID().replaceAll('-', '')
}
cron.once = data.once ?? false
Crons.all.push(cron as CronsConfigurationsSystem<InstanceType<typeof Crons>['data']['metadata']>)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { EmbedBuilder, type TextChannel } from 'discord.js'
import axios from 'axios'
import { setIntervalAsync } from 'set-interval-async'
import { client, db } from '@/app'
import { Crons } from '@/classes/Crons'
import { Pterodactyl } from '@/classes/pterodactyl'
import axios from 'axios'
import { EmbedBuilder, type TextChannel } from 'discord.js'

interface NodeData {
id: number
Expand Down Expand Up @@ -49,6 +49,7 @@ export async function genEmbeds (options: {

// Obtém as informações de estatísticas dos nós
async function getNodeStats (): Promise<NodeData[] | undefined> {
if (token === undefined || url === undefined) return
const pteroConect = new Pterodactyl({ token, url })
const nodesList = await pteroConect.getNodes()
if (nodesList === undefined || axios.isAxiosError(nodesList)) return
Expand Down Expand Up @@ -150,20 +151,21 @@ export async function genEmbeds (options: {
return embeds
}

export default async function statusPtero (): Promise<void> {
const guilds = client.guilds.cache

for (const [, guild] of guilds.entries()) {
const timeout = (await db.system.get(`${guild.id}.pterodactyl.timeout`)) ?? 15000
new Crons({
name: 'statusPtero',
cron: '*/15 * * * * *',
once: false,
async exec (cron, interval) {
if (interval === undefined) return
const guilds = client.guilds.cache

// Set an interval
setIntervalAsync(async () => {
for (const [, guild] of guilds.entries()) {
const enabled = await db.system.get(`${guild.id}.status.PteroStatus`)
if (enabled === false) return
try {
const embeds: EmbedBuilder[] = []
const now = new Date()
const futureTime = new Date(now.getTime() + parseInt(timeout))
const futureTime = new Date(now.getTime() + 15000)
const futureTimeString = `<t:${Math.floor(futureTime.getTime() / 1000)}:R>`

embeds.push(
Expand Down Expand Up @@ -195,6 +197,6 @@ export default async function statusPtero (): Promise<void> {
} catch (err) {
console.log(err)
}
}, parseInt(timeout))
}
}
}
})
34 changes: 34 additions & 0 deletions src/crons/authUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { core } from '@/app'
import { Crons } from '@/classes/Crons'
import { PaymentBot } from '@/classes/PaymentBot'

Check failure on line 3 in src/crons/authUpdate.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Cannot find module '@/classes/PaymentBot' or its corresponding type declarations.

Check failure on line 3 in src/crons/authUpdate.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Cannot find module '@/classes/PaymentBot' or its corresponding type declarations.
import getSettings from '@/functions/getSettings'
import internalDB from '@/settings/settings.json'

Check failure on line 5 in src/crons/authUpdate.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Cannot find module '@/settings/settings.json' or its corresponding type declarations.

Check failure on line 5 in src/crons/authUpdate.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Cannot find module '@/settings/settings.json' or its corresponding type declarations.

new Crons({
name: 'Auth - Start',
cron: '* * * * * *',
once: true,
async exec (cron, interval) {
if (interval === undefined) return
await authUpdate()
}
})

new Crons({
name: 'Auth',
cron: '0 */1 * ? * *',
once: false,
async exec (cron, interval) {
if (interval === undefined) return
await authUpdate()
}
})

async function authUpdate (): Promise<void> {
const { Auth } = getSettings()

Check failure on line 28 in src/crons/authUpdate.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Property 'Auth' does not exist on type 'Config'.

Check failure on line 28 in src/crons/authUpdate.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Property 'Auth' does not exist on type 'Config'.
if (Auth?.email === undefined || Auth.password === undefined || Auth.uuid === undefined) { core.warn('Sistema de autenticação não configurado'); return }
const PaymentAuth = new PaymentBot({ url: internalDB.API })

await PaymentAuth.login({ email: Auth.email, password: Auth.password })
await PaymentAuth.validate({ uuid: Auth.uuid })
}
67 changes: 67 additions & 0 deletions src/crons/statusPresence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ActivityType, type PresenceStatusData } from 'discord.js'
import { client, db } from '@/app'
import axios from 'axios'
import { Crons } from '@/classes/Crons'

new Crons({
name: 'statusPresence',
cron: '*/30 * * * * *',
once: false,
async exec (cron, interval) {
if (interval === undefined) return
const guilds = client.guilds.cache

for (const guild of guilds.values()) {
const enabled = await db.system.get(`${guild.id}.status.systemStatus`)
if (enabled !== undefined && enabled === false) return

const type = (await db.system.get(`${guild.id}.status.systemStatusType`)) as PresenceStatusData
const typeStatus = await db.system.get(`${guild.id}.status.systemStatusMinecraft`)

if (typeStatus !== undefined && typeStatus === true) {
const ip = await db.guilds.get(`${guild.id}.minecraft.ip`)
await mineStatus(ip, type)
} else if (typeStatus === undefined || typeStatus === false) {
const messages = await db.messages.get(`${guild.id}.system.status.messages`)

if (messages?.[0] !== undefined) {
let currentMessage = await db.messages.get(`${guild.id}.system.status.currentMessage`)

if (currentMessage >= messages?.length || currentMessage === undefined) {
currentMessage = 0
await db.messages.set(`${guild.id}.system.status.currentMessage`, 0)
}

const newStatus = messages[currentMessage]
client?.user?.setPresence({
activities: [{ name: newStatus, type: ActivityType.Playing }],
status: type
})
await db.messages.add(`${guild.id}.system.status.currentMessage`, 1)
}
}
}
}
})

async function mineStatus (ip: string, type: PresenceStatusData): Promise<void> {
try {
const res = await axios.get(`https://api.mcsrvstat.us/3/${ip}`)
const formatRes = `${ip} | Status: ${
res.data.online === true
? `Online | Players: ${res.data.players.online ?? 0}/${res.data.players.max ?? 0}`
: 'Offline'
}`

client?.user?.setPresence({
activities: [{ name: formatRes, type: ActivityType.Playing }],
status: `${type ?? 'online'}`
})
} catch (err) {
console.error(err)
client?.user?.setPresence({
activities: [{ name: 'API Error', type: ActivityType.Playing }],
status: 'idle'
})
}
}
2 changes: 1 addition & 1 deletion src/discord/commands/configs/config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { db } from '@/app'
import { genEmbeds } from '@/crons/SystemStatus'
import { Command } from '@/discord/base'
import { setSystem } from '@/discord/commands/configs/utils/setSystem'
import { MpModalconfig } from '@/discord/components/config/modals/mpModal'
import { sendEmbed } from '@/discord/components/payments'
import { genEmbeds } from '@/discord/events/ready/statusPtero/SystemStatus'
import { Database, validarURL } from '@/functions'
import { CustomButtonBuilder, Discord } from '@/functions/Discord'
import {
Expand Down
Loading

0 comments on commit cf894af

Please sign in to comment.