diff --git a/commands/calendar/remind.ts b/commands/calendar/remind.ts index ee650c6..c752bff 100644 --- a/commands/calendar/remind.ts +++ b/commands/calendar/remind.ts @@ -16,6 +16,7 @@ import * as chrono from "chrono-node"; interface Settings { client: Client; // Main Discord client object db: Sequelize; // Database access object + responseMode: string; publicChannel: string; // Channel to use if a reminder is public loopIntervalSec?: number; // Loop interval in seconds } @@ -48,43 +49,67 @@ class Reminder extends Model { class Plugin { settings: Settings; - publicChannel: TextChannel; + interval: NodeJS.Timeout; constructor(settings: Settings) { this.settings = settings; - this.publicChannel = getSendableTextChannel( - settings.client, - settings.publicChannel, - ); + } + + getChannel(reminder: Reminder) { + let channel: TextChannel | null = null; + const client = this.settings.client; + const publicChannel = this.settings.publicChannel; + // if (this.settings.responseMode === "private") { + // channel = getSendableTextChannel(client, reminder.requestChannel); + // } + if (this.settings.responseMode === "public" || !channel) { + channel = getSendableTextChannel(client, publicChannel); + } + if (!channel) { + throw Error("Cannot find valid channel to send reminder on"); + } + return channel; } async loop() { + console.debug("remind.js main loop"); const results = await Reminder.findAll({ // NOTE: 'trigger' is the time when the reminder should go off. If trigger - // now is positive, trigger is in the future. If trigger - now <= // 1.0, then we have less than one minute before the timer should go // off. where: sequelize.literal( - "(julianday(trigger) - julianday('now')) <= 1.0", + "isValid AND (julianday(trigger) - julianday('now')) <= 1.0", ), }); for (const reminder of results) { try { const now = new Date(Date.now()); - const wait = reminder.trigger.getTime() - now.getTime(); + const delay = reminder.trigger.getTime() - now.getTime(); console.log( - `Callback for Reminder ${reminder.get("id")} triggering in ${wait}ms`, - ); - setTimeout( - async () => await triggerReminder(this.publicChannel, reminder), - wait, + `Callback for Reminder ${reminder.get("id")} triggering in ${delay}ms`, ); + const channel = this.getChannel(reminder); + setTimeout(async () => await triggerReminder(channel, reminder), delay); } catch (error) { - console.error(`Unrecoverable error: ${error}`); + console.error( + `Error while processing Reminder ${reminder.id}: ${error}`, + ); console.trace(); } } } + + start() { + this.interval = setInterval( + this.loop.bind(this), + 1000 * (this.settings.loopIntervalSec ?? 60), + ); + } + + stop() { + clearInterval(this.interval); + } } async function initialize(settings: Settings) { @@ -115,23 +140,25 @@ async function initialize(settings: Settings) { ); await Reminder.sync(); - if ( - !(await getSendableTextChannel(settings.client, settings.publicChannel)) - ) { + // Try and determine how the bot will send reminders + console.debug(`Accessing channel ${settings.publicChannel}`); + if (!getSendableTextChannel(settings.client, settings.publicChannel)) { throw Error( "Invalid value for publicChannel; specify a channel the bot can access.", ); } - const plugin = new Plugin(settings); + + // Initialize the 'plugin' object which will store all state required to control the mainloop. if (settings.loopIntervalSec == null || settings.loopIntervalSec <= 0) { console.log("Setting plugin loop interval to default of 60 seconds"); settings.loopIntervalSec = 60; } - setInterval(plugin.loop, 1000 * settings.loopIntervalSec); + const plugin = new Plugin(settings); + plugin.start(); } function getSendableTextChannel(client: Client, chanId: string) { - console.log(`getSendableTextChannel(${client}, ${chanId})`); + // console.log(`getSendableTextChannel(${client}, ${chanId})`); const channel = client.channels.cache.get(chanId); if (!client.user) { throw Error("Can't check non-existent client"); @@ -146,27 +173,32 @@ function getSendableTextChannel(client: Client, chanId: string) { throw Error("Channel is not a guild channel"); } const permissions = channel?.permissionsFor(client.user); - if ( - !permissions?.has([ - PermissionsBitField.Flags.SendMessages, - PermissionsBitField.Flags.ViewChannel, - ]) - ) { + const requiredPerms = new PermissionsBitField([ + PermissionsBitField.Flags.SendMessages, + PermissionsBitField.Flags.ViewChannel, + ]); + if (!permissions?.has(requiredPerms)) { throw Error("Missing required permissions: SendMessages, ViewChannel"); } return channel; } async function triggerReminder(channel: TextChannel, reminder: Reminder) { - await channel.send({ - content: `Reminder for <@${reminder.userId}>: ${reminder.text}`, - }); - reminder.isValid = false; - await reminder.save(); + try { + await channel.send({ + content: `Reminder for <@${reminder.userId}>: ${reminder.text}`, + }); + reminder.isValid = false; + } catch (error) { + console.log(`Error trigggering Reminder ${reminder.id}: ${error}`); + } finally { + await reminder.save(); + } } async function execute(interaction: ChatInputCommandInteraction) { - const when = chrono.parseDate(interaction.options.getString("when") ?? "now"); + const whenString = interaction.options.getString("when") ?? "now"; + const when = chrono.parseDate(whenString); if (!when) { await interaction.reply(`Sorry, I don't understand '${when}' as a date`); return; @@ -190,7 +222,7 @@ export default function (settings: Settings) { return { data, execute, - initialize, + initialize: async () => await initialize(settings), Reminder, settings, }; diff --git a/index.ts b/index.ts index b7d72ea..a32be9d 100644 --- a/index.ts +++ b/index.ts @@ -89,12 +89,21 @@ const commands = new Collection(); import PingCommand from "./commands/calendar/ping"; import RemindCommand from "./commands/calendar/remind"; +import QuoteCommand from "./commands/quotes/quote"; + +console.debug(`${remindersChannelId}`); commands.set("ping", PingCommand({ client: client, db: sql })); commands.set( "remind", - RemindCommand({ client: client, db: sql, publicChannel: remindersChannelId }), + RemindCommand({ + client: client, + db: sql, + publicChannel: remindersChannelId, + responseMode: "public", + }), ); +commands.set("quote", QuoteCommand({})); async function syncCommands() { try {