From fef80001011010405e337c044cf6bb1992c4f2f1 Mon Sep 17 00:00:00 2001 From: Chamath Wijesekera Date: Wed, 7 Feb 2024 12:11:35 -0500 Subject: [PATCH] Implement meeting reminder command --- src/index.ts | 2 +- src/listeners/commands/index.ts | 6 ++- .../commands/meetingReminderCommand.ts | 46 +++++++++++++++++++ src/listeners/index.ts | 6 ++- src/utils/eventReminders.ts | 28 +++++++---- 5 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 src/listeners/commands/meetingReminderCommand.ts diff --git a/src/index.ts b/src/index.ts index c57097f..5d42a7a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,7 +27,7 @@ const app = new App({ }); // Register listeners -registerListeners(app); +registerListeners(app, auth); // Schedule tasks scheduleTasks(app.client, auth); diff --git a/src/listeners/commands/index.ts b/src/listeners/commands/index.ts index e47ea41..19db108 100644 --- a/src/listeners/commands/index.ts +++ b/src/listeners/commands/index.ts @@ -1,9 +1,13 @@ // commands/index.ts import { App } from "@slack/bolt"; +import { OAuth2Client } from "google-auth-library"; + import { helpCommandHandler } from "./helpCommand"; +import { meetingReminderCommandHandler } from "./meetingReminderCommand"; -const register = (app: App): void => { +const register = (app: App, googleAuth: OAuth2Client): void => { app.command("/help", helpCommandHandler); + app.command("/meeting_reminder", (payload) => meetingReminderCommandHandler(payload, googleAuth)); // Other command registrations would go here }; diff --git a/src/listeners/commands/meetingReminderCommand.ts b/src/listeners/commands/meetingReminderCommand.ts new file mode 100644 index 0000000..1d3c3b7 --- /dev/null +++ b/src/listeners/commands/meetingReminderCommand.ts @@ -0,0 +1,46 @@ +import { AllMiddlewareArgs, SlackCommandMiddlewareArgs } from "@slack/bolt"; +import { OAuth2Client } from "google-auth-library"; + +import { filterEventsForChannels, getEvents, parseEvents } from "../../utils/googleCalendar"; +import { getAllSlackChannels } from "../../utils/channels"; +import { logCommandUsed } from "../../utils/logging"; +import { postEphemeralMessage } from "../../utils/slack"; +import { EventReminderType, remindUpcomingEvent } from "../../utils/eventReminders"; +import { SlackLogger } from "../../classes/SlackLogger"; + +export async function meetingReminderCommandHandler( + { command, ack, client }: SlackCommandMiddlewareArgs & AllMiddlewareArgs, + googleAuth: OAuth2Client, +): Promise { + ack(); + logCommandUsed(command); + + try { + const slackChannels = await getAllSlackChannels(client); + const fetchedEvents = await getEvents(googleAuth); + const events = parseEvents(fetchedEvents, slackChannels); + const eventsInChannel = filterEventsForChannels(events, [command.channel_name]); + + if (eventsInChannel.length === 0) { + await postEphemeralMessage(client, command.channel_id, command.user_id, "No upcoming events in this channel."); + } else { + const soonestEvent = eventsInChannel.sort((a, b) => a.start.getTime() - b.start.getTime())[0]; + + const commandText = command.text.trim().toLowerCase(); + + await remindUpcomingEvent( + soonestEvent, + client, + commandText == "ping" ? EventReminderType.MANUAL_PING : EventReminderType.MANUAL, + ); + await postEphemeralMessage( + client, + command.channel_id, + command.user_id, + "Manual reminder sent for next event in channel.", + ); + } + } catch (error) { + SlackLogger.getInstance().error("Failed to send manual meeting reminder", error); + } +} diff --git a/src/listeners/index.ts b/src/listeners/index.ts index af06c39..4e2f797 100644 --- a/src/listeners/index.ts +++ b/src/listeners/index.ts @@ -1,4 +1,6 @@ import { App } from "@slack/bolt"; +import { OAuth2Client } from "google-auth-library"; + import actions from "./actions"; import commands from "./commands"; import events from "./events"; @@ -6,9 +8,9 @@ import messages from "./messages"; import shortcuts from "./shortcuts"; import views from "./views"; -const registerListeners = (app: App): void => { +const registerListeners = (app: App, googleClient: OAuth2Client): void => { actions.register(app); - commands.register(app); + commands.register(app, googleClient); events.register(app); messages.register(app); shortcuts.register(app); diff --git a/src/utils/eventReminders.ts b/src/utils/eventReminders.ts index b69ecd6..40967e9 100644 --- a/src/utils/eventReminders.ts +++ b/src/utils/eventReminders.ts @@ -19,6 +19,8 @@ export const TIME_CHECK_INTERVAL = 1000 * 60 * 5; // 5 minutes in milliseconds * The types of event reminders that can be sent */ export enum EventReminderType { + MANUAL = 1, + MANUAL_PING = 2, FIVE_MINUTES = 1000 * 60 * 5, // 5 minutes in milliseconds SIX_HOURS = 1000 * 60 * 60 * 6, // 6 hours in milliseconds } @@ -59,12 +61,14 @@ export function getEventReminderType(event: CalendarEvent): EventReminderType | * Posts a reminder for the given event to the channel it is associated with * @param event The event to post a reminder for * @param client Slack Web API client + * @param reminderType The type of reminder to post * @param defaultSlackChannels The default Slack channels to post reminders to. If not provided, the default channels will be fetched from the Slack API * @param allSlackUsersInWorkspace All Slack users in the workspace. If not provided, the users will be fetched from the Slack API */ export async function remindUpcomingEvent( event: CalendarEvent, client: WebClient, + reminderType: EventReminderType | null, defaultSlackChannels?: SlackChannel[], allSlackUsersInWorkspace?: SlackUser[], ): Promise { @@ -73,9 +77,7 @@ export async function remindUpcomingEvent( return; } - const reminderType = getEventReminderType(event); - - if (!reminderType) { + if (reminderType == null) { return; } @@ -104,10 +106,15 @@ export async function remindUpcomingEvent( allSlackUsersInWorkspace, ); - const reminderTypeString = reminderType === EventReminderType.FIVE_MINUTES ? "5 minute" : "6 hour"; + const reminderTypeStrings = { + [EventReminderType.MANUAL]: "manually triggered", + [EventReminderType.MANUAL_PING]: "manually triggered (with ping)", + [EventReminderType.FIVE_MINUTES]: "5 minute", + [EventReminderType.SIX_HOURS]: "6 hour", + }; SlackLogger.getInstance().info( - `Sent ${reminderTypeString} reminder for event \`${ + `Sent ${reminderTypeStrings[reminderType]} reminder for event \`${ event.title }\` at \`${event.start.toISOString()}\` to channel \`${ event.minervaEventMetadata.channel.name @@ -206,7 +213,8 @@ export async function remindUpcomingEvents(client: WebClient, events: CalendarEv const allSlackUsersInWorkspace = await getAllSlackUsers(client); events.forEach((event) => { - remindUpcomingEvent(event, client, defaultSlackChannels, allSlackUsersInWorkspace); + const reminderType = getEventReminderType(event); + remindUpcomingEvent(event, client, reminderType, defaultSlackChannels, allSlackUsersInWorkspace); }); } @@ -217,9 +225,11 @@ export async function remindUpcomingEvents(client: WebClient, events: CalendarEv * @returns The generated reminder text */ export function generateEventReminderChannelText(event: CalendarEvent, reminderType: EventReminderType): string { - let message = `${reminderType == EventReminderType.FIVE_MINUTES ? "\n" : ""}Reminder: *${ - event.title - }* is occurring`; + let message = `${ + reminderType == EventReminderType.FIVE_MINUTES || reminderType == EventReminderType.MANUAL_PING + ? "\n" + : "" + }Reminder: *${event.title}* is occurring`; if (reminderType === EventReminderType.FIVE_MINUTES) { const timeUntilEvent = event.start.getTime() - new Date().getTime();