Refactor to more consistent structure (e.g. "plugins")
This commit is contained in:
149
plugins/nag/index.ts
Normal file
149
plugins/nag/index.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { TextChannel } from 'discord.js';
|
||||
import { Op } from 'sequelize';
|
||||
import { BasePlugin, type Command, type Settings } from '../../plugin'
|
||||
import CheckinCommand from './checkin'
|
||||
import checkin from './checkin';
|
||||
import { CheckIn, initializeModels, Nag } from './models'
|
||||
import NagCommand from './nag'
|
||||
import UnnagCommand from './unnag'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param hour Number between 0-24 representing the hour the check-in is performed.
|
||||
* @param offset Number of days from today; this can be positive for future days or negative for past days.
|
||||
* @returns Date object representing the check-in time
|
||||
*/
|
||||
export function getCheckIn(hour: number, offset: number = 0) {
|
||||
const nDaysInMS = offset * 24 * 60 * 60 * 1000;
|
||||
const day = new Date(Date.now() + nDaysInMS);
|
||||
const checkInDate = new Date(
|
||||
day.getFullYear(),
|
||||
day.getMonth(),
|
||||
day.getDate(),
|
||||
hour,
|
||||
);
|
||||
return checkInDate;
|
||||
}
|
||||
|
||||
export async function findGuiltyNags() {
|
||||
const results = await Nag.findAll();
|
||||
const guiltyNags: Nag[] = [];
|
||||
const prevCheckIn = getCheckIn(9, -1);
|
||||
const currentCheckIn = getCheckIn(9, 0);
|
||||
|
||||
for (const nag of results) {
|
||||
const checkInResults = await CheckIn.findAll({
|
||||
where: {
|
||||
id: nag.id,
|
||||
lastCheckIn: {
|
||||
[Op.between]: [prevCheckIn, currentCheckIn],
|
||||
},
|
||||
},
|
||||
});
|
||||
if (checkInResults.length <= 0) {
|
||||
guiltyNags.push(nag);
|
||||
}
|
||||
}
|
||||
return guiltyNags;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The Date representing the next check-in, which will be either
|
||||
* today (if we are asking in the morning) or tomorrow (if we are asking
|
||||
* after the check-in time for today.)
|
||||
*/
|
||||
export function nextCheckInDate() {
|
||||
const todaysCheckInDate = getCheckIn(9, 0);
|
||||
if (Date.now() - todaysCheckInDate.getTime() < 0) {
|
||||
return getCheckIn(9, 0);
|
||||
} else {
|
||||
return getCheckIn(9, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function nextCheckInMs() {
|
||||
const delayMs = nextCheckInDate().getTime() - Date.now();
|
||||
if (delayMs <= 0) {
|
||||
// The value of nextCheckInDate is guaranteed to be in the future; if not, that's a bug in the program.
|
||||
throw Error('Invalid value for nextCheckInDate');
|
||||
}
|
||||
return delayMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle state for the various nag-related commands, mainly related to
|
||||
* tracking of timers and triggers.
|
||||
*/
|
||||
export class NagPlugin extends BasePlugin {
|
||||
// NOTE(DWM): I really hate the word 'Manager' when applied to code, but I
|
||||
// thought for quite a bit about what to name these classes and I just can't
|
||||
// come up with anything solid. (Pun intended.)
|
||||
|
||||
interval?: NodeJS.Timeout;
|
||||
|
||||
constructor(settings: Settings) {
|
||||
super(settings);
|
||||
initializeModels(settings.database);
|
||||
this.commands = [
|
||||
CheckinCommand(settings) as Command, // TODO Why, TS?...
|
||||
NagCommand(settings) as Command, // TODO Why, TS?...
|
||||
UnnagCommand(settings),
|
||||
]
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.interval = setTimeout(this.loop, nextCheckInMs());
|
||||
}
|
||||
|
||||
async triggerNag(nag: Nag) {
|
||||
const client = this.settings.client;
|
||||
// First, try and find the appropriate channel to send on
|
||||
const guild = client.guilds.cache.get(nag.guildId);
|
||||
if (!guild) {
|
||||
console.error(
|
||||
`No longer have access to Guild ${nag.guildId}; consider deleting this nag manually :(`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const channel = guild.channels.cache.get(nag.channelId);
|
||||
if (!channel) {
|
||||
console.error(
|
||||
`No longer have access to Channel ${nag.channelId}; consider deleting this nag manually :(`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!channel?.isSendable() || !(channel instanceof TextChannel)) {
|
||||
console.error("Somehow, we specified a channel which isn't sendable");
|
||||
return; // TODO
|
||||
}
|
||||
try {
|
||||
const failText =
|
||||
nag.failText ??
|
||||
`<@${nag.userId}> didn't complete "${nag.text}". Shame shame!`;
|
||||
const mentionHere = nag.mentionHere ? '<@here> ' : '';
|
||||
const msg = `${mentionHere}${failText}`;
|
||||
await channel.send(msg);
|
||||
} catch (error) {
|
||||
console.log('Error while creating Nag:', error); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
async loop() {
|
||||
// According to MDN we don't need to do this, but it makes me feel happier
|
||||
// knowing that we won't accidentally call clearInterval(...) on a timer
|
||||
// that isn't running anymore.
|
||||
this.interval = undefined;
|
||||
|
||||
console.debug('nag.js main loop');
|
||||
const guiltyNags = await findGuiltyNags();
|
||||
for (const nag of guiltyNags) {
|
||||
await this.triggerNag(nag);
|
||||
}
|
||||
this.interval = setTimeout(this.loop, nextCheckInMs());
|
||||
}
|
||||
|
||||
async stop() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user