Centralize periodic checks for mutes, tempbans, vcalerts, reminders, and scheduled posts

This should result in a significant performance improvement.
The new method is also more precise than the old one, allowing
the aforementioned checks to be performed with second-precision.
This commit is contained in:
Dragory 2021-09-25 21:33:59 +03:00
parent c84d1a0be1
commit c7751a9da1
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
55 changed files with 883 additions and 366 deletions

View file

@ -6,7 +6,8 @@ import { RemindCmd } from "./commands/RemindCmd";
import { RemindersCmd } from "./commands/RemindersCmd";
import { RemindersDeleteCmd } from "./commands/RemindersDeleteCmd";
import { ConfigSchema, RemindersPluginType } from "./types";
import { postDueRemindersLoop } from "./utils/postDueRemindersLoop";
import { onGuildEvent } from "../../data/GuildEvents";
import { postReminder } from "./functions/postReminder";
const defaultOptions: PluginOptions<RemindersPluginType> = {
config: {
@ -46,16 +47,16 @@ export const RemindersPlugin = zeppelinGuildPlugin<RemindersPluginType>()({
state.reminders = GuildReminders.getGuildInstance(guild.id);
state.tries = new Map();
state.unloaded = false;
state.postRemindersTimeout = null;
},
afterLoad(pluginData) {
postDueRemindersLoop(pluginData);
pluginData.state.unregisterGuildEventListener = onGuildEvent(pluginData.guild.id, "reminder", (reminder) =>
postReminder(pluginData, reminder),
);
},
beforeUnload(pluginData) {
clearTimeout(pluginData.state.postRemindersTimeout);
pluginData.state.unregisterGuildEventListener();
pluginData.state.unloaded = true;
},
});

View file

@ -5,6 +5,7 @@ import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { convertDelayStringToMS, messageLink } from "../../../utils";
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
import { remindersCmd } from "../types";
import { registerUpcomingReminder } from "../../../data/loops/upcomingRemindersLoop";
export const RemindCmd = remindersCmd({
trigger: ["remind", "remindme", "reminder"],
@ -50,7 +51,7 @@ export const RemindCmd = remindersCmd({
}
const reminderBody = args.reminder || messageLink(pluginData.guild.id, msg.channel.id, msg.id);
await pluginData.state.reminders.add(
const reminder = await pluginData.state.reminders.add(
msg.author.id,
msg.channel.id,
reminderTime.clone().tz("Etc/UTC").format("YYYY-MM-DD HH:mm:ss"),
@ -58,6 +59,8 @@ export const RemindCmd = remindersCmd({
moment.utc().format("YYYY-MM-DD HH:mm:ss"),
);
registerUpcomingReminder(reminder);
const msUntilReminder = reminderTime.diff(now);
const timeUntilReminder = humanizeDuration(msUntilReminder, { largest: 2, round: true });
const prettyReminderTime = (await timeAndDate.inMemberTz(msg.author.id, reminderTime)).format(

View file

@ -2,6 +2,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { sorter } from "../../../utils";
import { remindersCmd } from "../types";
import { clearUpcomingReminder } from "../../../data/loops/upcomingRemindersLoop";
export const RemindersDeleteCmd = remindersCmd({
trigger: ["reminders delete", "reminders d"],
@ -21,6 +22,7 @@ export const RemindersDeleteCmd = remindersCmd({
}
const toDelete = reminders[args.num - 1];
clearUpcomingReminder(toDelete);
await pluginData.state.reminders.delete(toDelete.id);
sendSuccessMessage(pluginData, msg.channel, "Reminder deleted");

View file

@ -0,0 +1,41 @@
import { GuildPluginData } from "knub";
import { RemindersPluginType } from "../types";
import { Reminder } from "../../../data/entities/Reminder";
import { Snowflake, TextChannel } from "discord.js";
import moment from "moment-timezone";
import { disableLinkPreviews } from "knub/dist/helpers";
import { DBDateFormat, SECONDS } from "../../../utils";
import humanizeDuration from "humanize-duration";
export async function postReminder(pluginData: GuildPluginData<RemindersPluginType>, reminder: Reminder) {
const channel = pluginData.guild.channels.cache.get(reminder.channel_id as Snowflake);
if (channel && (channel.isText() || channel.isThread())) {
try {
// Only show created at date if one exists
if (moment.utc(reminder.created_at).isValid()) {
const createdAtTS = Math.floor(moment.utc(reminder.created_at, DBDateFormat).valueOf() / 1000);
await channel.send({
content: disableLinkPreviews(
`Reminder for <@!${reminder.user_id}>: ${reminder.body} \nSet <t:${createdAtTS}:R>`,
),
allowedMentions: {
users: [reminder.user_id as Snowflake],
},
});
} else {
await channel.send({
content: disableLinkPreviews(`Reminder for <@!${reminder.user_id}>: ${reminder.body}`),
allowedMentions: {
users: [reminder.user_id as Snowflake],
},
});
}
} catch (err) {
// If we were unable to post the reminder, we'll try again later
console.warn(`Error when posting reminder for ${reminder.user_id} in guild ${reminder.guild_id}: ${String(err)}`);
return;
}
}
await pluginData.state.reminders.delete(reminder.id);
}

View file

@ -14,7 +14,8 @@ export interface RemindersPluginType extends BasePluginType {
reminders: GuildReminders;
tries: Map<number, number>;
postRemindersTimeout;
unregisterGuildEventListener: () => void;
unloaded: boolean;
};
}

View file

@ -1,56 +0,0 @@
import { Snowflake, TextChannel } from "discord.js";
import humanizeDuration from "humanize-duration";
import { GuildPluginData } from "knub";
import { disableLinkPreviews } from "knub/dist/helpers";
import moment from "moment-timezone";
import { SECONDS } from "../../../utils";
import { RemindersPluginType } from "../types";
const REMINDER_LOOP_TIME = 10 * SECONDS;
const MAX_TRIES = 3;
export async function postDueRemindersLoop(pluginData: GuildPluginData<RemindersPluginType>) {
const pendingReminders = await pluginData.state.reminders.getDueReminders();
for (const reminder of pendingReminders) {
const channel = pluginData.guild.channels.cache.get(reminder.channel_id as Snowflake);
if (channel && channel instanceof TextChannel) {
try {
// Only show created at date if one exists
if (moment.utc(reminder.created_at).isValid()) {
const target = moment.utc();
const diff = target.diff(moment.utc(reminder.created_at, "YYYY-MM-DD HH:mm:ss"));
const result = humanizeDuration(diff, { largest: 2, round: true });
await channel.send({
content: disableLinkPreviews(
`Reminder for <@!${reminder.user_id}>: ${reminder.body} \n\`Set at ${reminder.created_at} (${result} ago)\``,
),
allowedMentions: {
users: [reminder.user_id as Snowflake],
},
});
} else {
await channel.send({
content: disableLinkPreviews(`Reminder for <@!${reminder.user_id}>: ${reminder.body}`),
allowedMentions: {
users: [reminder.user_id as Snowflake],
},
});
}
} catch {
// Probably random Discord internal server error or missing permissions or somesuch
// Try again next round unless we've already tried to post this a bunch of times
const tries = pluginData.state.tries.get(reminder.id) || 0;
if (tries < MAX_TRIES) {
pluginData.state.tries.set(reminder.id, tries + 1);
continue;
}
}
}
await pluginData.state.reminders.delete(reminder.id);
}
if (!pluginData.state.unloaded) {
pluginData.state.postRemindersTimeout = setTimeout(() => postDueRemindersLoop(pluginData), REMINDER_LOOP_TIME);
}
}