From 5d30cc761f8cb995b58c72c1fb0e5e45594e3145 Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Mon, 28 Apr 2025 19:32:44 -0400 Subject: [PATCH 01/13] Added selected attribute to event interface --- src/commands/general/calendar.ts | 2 +- src/commands/general/calreminder.ts | 190 ++++++++++++++-------------- src/lib/utils/calendarUtils.ts | 1 + 3 files changed, 98 insertions(+), 95 deletions(-) diff --git a/src/commands/general/calendar.ts b/src/commands/general/calendar.ts index e999f26c..0df3f421 100644 --- a/src/commands/general/calendar.ts +++ b/src/commands/general/calendar.ts @@ -116,7 +116,7 @@ export default class extends Command { return; } retrivedEvents.forEach((retrivedEvent) => { - const newEvent: Event = { calEvent: retrivedEvent, calendarName: calendar.calendarName }; + const newEvent: Event = { calEvent: retrivedEvent, calendarName: calendar.calendarName, selected: false }; if (!newEvent.calEvent.location) { newEvent.calEvent.location = '`Location not specified for this event`'; diff --git a/src/commands/general/calreminder.ts b/src/commands/general/calreminder.ts index d8185100..b7870637 100644 --- a/src/commands/general/calreminder.ts +++ b/src/commands/general/calreminder.ts @@ -1,6 +1,6 @@ -import { DB } from "@root/config"; -import { Command } from "@root/src/lib/types/Command"; -import { Reminder } from "@root/src/lib/types/Reminder"; +import { DB } from '@root/config'; +import { Command } from '@root/src/lib/types/Command'; +import { Reminder } from '@root/src/lib/types/Reminder'; import { ActionRowBuilder, ApplicationCommandOptionData, @@ -8,25 +8,26 @@ import { ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, - ComponentType, -} from "discord.js"; -import parse from "parse-duration"; -import { retrieveEvents } from "@root/src/lib/auth"; -import { calendar_v3 as calendarV3 } from "googleapis"; -import { MongoClient } from "mongodb"; -import { PagifiedSelectMenu } from "@root/src/lib/types/PagifiedSelect"; -const MONGO_URI = process.env.DB_CONN_STRING || ""; + ComponentType +} from 'discord.js'; +import parse from 'parse-duration'; +import { retrieveEvents } from '@root/src/lib/auth'; +import { calendar_v3 as calendarV3 } from 'googleapis'; +import { MongoClient } from 'mongodb'; +import { PagifiedSelectMenu } from '@root/src/lib/types/PagifiedSelect'; +const MONGO_URI = process.env.DB_CONN_STRING || ''; export default class extends Command { - name = "calreminder"; - description = "Setup reminders for calendar events"; + + name = 'calreminder'; + description = 'Setup reminders for calendar events'; options: ApplicationCommandOptionData[] = [ { - name: "classname", - description: "Course ID", + name: 'classname', + description: 'Course ID', type: ApplicationCommandOptionType.String, - required: true, - }, + required: true + } ]; async run(interaction: ChatInputCommandInteraction): Promise { @@ -34,7 +35,7 @@ export default class extends Command { let offsetMenu: PagifiedSelectMenu; function generateMessage( - repeatInterval: "every_event" | null, + repeatInterval: 'every_event' | null, chosenEvent?: calendarV3.Schema$Event, chosenOffset?: number, renderMenus = false, @@ -44,19 +45,19 @@ export default class extends Command { if (renderMenus) { eventMenu = new PagifiedSelectMenu(); eventMenu.createSelectMenu({ - customId: "select_event", - placeHolder: "Select an event", + customId: 'select_event', + placeHolder: 'Select an event', minimumValues: 1, - maximumValues: 1, + maximumValues: 1 }); let defaultSet = false; filteredEvents.forEach((event, index) => { if (!event.start?.dateTime) return; - const isDefault = - !defaultSet && - chosenEvent?.start?.dateTime === event.start?.dateTime; + const isDefault + = !defaultSet + && chosenEvent?.start?.dateTime === event.start?.dateTime; if (isDefault) defaultSet = true; @@ -66,7 +67,7 @@ export default class extends Command { description: `Starts at: ${new Date( event.start.dateTime ).toLocaleString()}`, - default: isDefault, + default: isDefault }); }); @@ -74,33 +75,33 @@ export default class extends Command { // Create offset select menu const offsetOptions = [ - { label: "At event", value: "0" }, - { label: "10 minutes before", value: "10m" }, - { label: "30 minutes before", value: "30m" }, - { label: "1 hour before", value: "1h" }, - { label: "1 day before", value: "1d" }, + { label: 'At event', value: '0' }, + { label: '10 minutes before', value: '10m' }, + { label: '30 minutes before', value: '30m' }, + { label: '1 hour before', value: '1h' }, + { label: '1 day before', value: '1d' } ]; offsetMenu = new PagifiedSelectMenu(); offsetMenu.createSelectMenu({ - customId: "select_offset", - placeHolder: "Select reminder offset", + customId: 'select_offset', + placeHolder: 'Select reminder offset', minimumValues: 1, - maximumValues: 1, + maximumValues: 1 }); let offsetDefaultSet = false; offsetOptions.forEach((option) => { - const isDefault = - !offsetDefaultSet && - chosenOffset === parse(option.value); + const isDefault + = !offsetDefaultSet + && chosenOffset === parse(option.value); if (isDefault) offsetDefaultSet = true; offsetMenu.addOption({ label: option.label, value: option.value, - default: isDefault, + default: isDefault }); }); @@ -115,22 +116,22 @@ export default class extends Command { // 3) Generate repeat button const toggleRepeatButton = new ButtonBuilder() - .setCustomId("toggle_repeat") + .setCustomId('toggle_repeat') .setLabel( - repeatInterval === "every_event" - ? "Repeat: On" - : "Repeat: Off" + repeatInterval === 'every_event' + ? 'Repeat: On' + : 'Repeat: Off' ) .setStyle(ButtonStyle.Secondary); // 4) Generate set reminder button const setReminder = new ButtonBuilder() - .setCustomId("set_reminder") - .setLabel("Set Reminder") + .setCustomId('set_reminder') + .setLabel('Set Reminder') .setStyle(ButtonStyle.Success); - const setReminderAndRepeatRow = - new ActionRowBuilder().addComponents( + const setReminderAndRepeatRow + = new ActionRowBuilder().addComponents( toggleRepeatButton, setReminder ); @@ -138,18 +139,18 @@ export default class extends Command { return [ ...eventMenuRows, ...offsetMenuRows, - setReminderAndRepeatRow, + setReminderAndRepeatRow ]; } const courseCode = interaction.options - .getString("classname") + .getString('classname') ?.toUpperCase(); if (!courseCode) { await interaction.reply({ - content: "❗ You must specify a class name.", - ephemeral: true, + content: '❗ You must specify a class name.', + ephemeral: true }); return; } @@ -158,15 +159,15 @@ export default class extends Command { let calendar: { calendarId: string; calendarName: string }; try { const client = new MongoClient(MONGO_URI, { - useUnifiedTopology: true, + useUnifiedTopology: true }); await client.connect(); - const db = client.db("CalendarDatabase"); - const collection = db.collection("calendarIds"); + const db = client.db('CalendarDatabase'); + const collection = db.collection('calendarIds'); const calendarInDB = await collection.findOne({ - calendarName: { $regex: `^${courseCode}$`, $options: "i" }, + calendarName: { $regex: `^${courseCode}$`, $options: 'i' } }); await client.close(); @@ -174,20 +175,20 @@ export default class extends Command { if (!calendarInDB) { await interaction.reply({ content: `⚠️ There are no matching calendars with course code **${courseCode}**.`, - ephemeral: true, + ephemeral: true }); return; } calendar = { calendarId: calendarInDB.calendarId, - calendarName: calendarInDB.calendarName, + calendarName: calendarInDB.calendarName }; } catch (error) { - console.error("Calendar lookup failed:", error); + console.error('Calendar lookup failed:', error); await interaction.reply({ content: `❌ Database error while fetching calendar for **${courseCode}**.`, - ephemeral: true, + ephemeral: true }); return; } @@ -198,8 +199,8 @@ export default class extends Command { if (!events || events.length === 0) { await interaction.reply({ content: - "⚠️ Failed to fetch calendar events or no events found.", - ephemeral: true, + '⚠️ Failed to fetch calendar events or no events found.', + ephemeral: true }); return; } @@ -208,7 +209,7 @@ export default class extends Command { let chosenEvent: calendarV3.Schema$Event = null; let chosenOffset: number = null; - let repeatInterval: "every_event" = null; + let repeatInterval: 'every_event' = null; let activeReminderId: string = null; const initialComponents = generateMessage( @@ -223,24 +224,24 @@ export default class extends Command { const replyMessage = await interaction.reply({ components: initialComponents, - ephemeral: true, + ephemeral: true }); // Main collector for event & offset const collector = replyMessage.createMessageComponentCollector({ componentType: ComponentType.StringSelect, - time: 60_000, + time: 60_000 }); - collector.on("collect", async (i) => { - if (i.customId === "select_event") { - const [, indexStr] = i.values[0].split("::"); + collector.on('collect', async (i) => { + if (i.customId === 'select_event') { + const [, indexStr] = i.values[0].split('::'); const selectedIndex = parseInt(indexStr); chosenEvent = filteredEvents[selectedIndex]; await i.deferUpdate(); - } else if (i.customId === "select_offset") { + } else if (i.customId === 'select_offset') { const rawOffsetStr = i.values[0]; - chosenOffset = rawOffsetStr === "0" ? 0 : parse(rawOffsetStr); + chosenOffset = rawOffsetStr === '0' ? 0 : parse(rawOffsetStr); await i.deferUpdate(); } }); @@ -248,12 +249,12 @@ export default class extends Command { // Button collector for Cancel and Set Reminder const buttonCollector = replyMessage.createMessageComponentCollector({ componentType: ComponentType.Button, - time: 300_000, // 5 minutes + time: 300_000 // 5 minutes }); - buttonCollector.on("collect", async (btnInt) => { - if (btnInt.customId === "toggle_repeat") { - repeatInterval = repeatInterval ? null : "every_event"; + buttonCollector.on('collect', async (btnInt) => { + if (btnInt.customId === 'toggle_repeat') { + repeatInterval = repeatInterval ? null : 'every_event'; const updatedComponents = generateMessage( repeatInterval, @@ -265,9 +266,9 @@ export default class extends Command { ); await btnInt.update({ - components: updatedComponents, + components: updatedComponents }); - } else if (btnInt.customId === "set_reminder") { + } else if (btnInt.customId === 'set_reminder') { // If user hasn’t selected both fields, just silently acknowledge if (!chosenEvent || chosenOffset === null) { if (!btnInt.deferred && !btnInt.replied) { @@ -286,8 +287,8 @@ export default class extends Command { if (remindDate.getTime() <= Date.now()) { await btnInt.editReply({ content: - "⏰ That reminder time is in the past. No reminder was set.", - components: [], + '⏰ That reminder time is in the past. No reminder was set.', + components: [] }); collector.stop(); buttonCollector.stop(); @@ -305,11 +306,11 @@ export default class extends Command { const reminder: Reminder = { owner: btnInt.user.id, content: eventInfo, - mode: "public", + mode: 'public', expires: repeatInterval ? new Date(remindDate.getTime() + EXPIRE_BUFFER_MS) // give repeat reminders more time : remindDate, // one-time reminders - repeat: repeatInterval, + repeat: repeatInterval }; let result; @@ -319,11 +320,11 @@ export default class extends Command { .insertOne(reminder); activeReminderId = result.insertedId; } catch (err) { - console.error("Failed to insert reminder:", err); + console.error('Failed to insert reminder:', err); await btnInt.editReply({ content: - "❌ Failed to save reminder. Please try again later.", - components: [], + '❌ Failed to save reminder. Please try again later.', + components: [] }); buttonCollector.stop(); return; @@ -331,12 +332,12 @@ export default class extends Command { // Build Cancel button row const cancelButton = new ButtonBuilder() - .setCustomId("cancel_reminder") - .setLabel("Cancel Reminder") + .setCustomId('cancel_reminder') + .setLabel('Cancel Reminder') .setStyle(ButtonStyle.Danger); - const buttonRow = - new ActionRowBuilder().addComponents( + const buttonRow + = new ActionRowBuilder().addComponents( cancelButton ); @@ -348,11 +349,11 @@ export default class extends Command { repeatInterval ? `\n🔁 Repeats every event (for up to 180 days) ` - : "" + : '' }`, - components: [buttonRow], + components: [buttonRow] }); - } else if (btnInt.customId === "cancel_reminder") { + } else if (btnInt.customId === 'cancel_reminder') { try { // 1) Defer *a new reply* (ephemeral) if (!btnInt.deferred && !btnInt.replied) { @@ -368,22 +369,22 @@ export default class extends Command { // 3) Send brand new ephemeral follow-up await btnInt.followUp({ - content: "❌ Your reminder has been canceled.", - ephemeral: true, + content: '❌ Your reminder has been canceled.', + ephemeral: true }); // 4) Stop the collector buttonCollector.stop(); } catch (err) { - console.error("Failed to cancel reminder:", err); + console.error('Failed to cancel reminder:', err); } } const actions: Record void> = { - "next_button:select_event": () => eventMenu.currentPage++, - "prev_button:select_event": () => eventMenu.currentPage--, - "next_button:select_offset": () => offsetMenu.currentPage++, - "prev_button:select_offset": () => offsetMenu.currentPage--, + 'next_button:select_event': () => eventMenu.currentPage++, + 'prev_button:select_event': () => eventMenu.currentPage--, + 'next_button:select_offset': () => offsetMenu.currentPage++, + 'prev_button:select_offset': () => offsetMenu.currentPage-- }; const action = actions[btnInt.customId]; @@ -403,4 +404,5 @@ export default class extends Command { } }); } + } diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index d016dea6..b56203ce 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -8,6 +8,7 @@ import * as fs from 'fs'; export interface Event { calEvent: calendar_v3.Schema$Event; calendarName: string; + selected: boolean; } export interface Filter { From 29b1939c40429a22c661c157e9d439a325c6e65c Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Mon, 28 Apr 2025 19:51:05 -0400 Subject: [PATCH 02/13] Added new calendar embed interface to keep track of what events are in each embed --- src/commands/general/calendar.ts | 1 + src/lib/utils/calendarUtils.ts | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/commands/general/calendar.ts b/src/commands/general/calendar.ts index 0df3f421..ba595131 100644 --- a/src/commands/general/calendar.ts +++ b/src/commands/general/calendar.ts @@ -246,6 +246,7 @@ export default class extends Command { if (btnInt.customId.startsWith('toggle-')) { const eventIndex = Number(btnInt.customId.split('-')[1]) - 1; const event = filteredEvents[(currentPage * EVENTS_PER_PAGE) + eventIndex]; + event.selected = !event.selected; if (selectedEvents.includes(event)) { selectedEvents = selectedEvents.filter(e => e !== event); const m = await dm.send(`➖ Removed **${event.calEvent.summary}**`); diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index b56203ce..29f89505 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -20,6 +20,11 @@ export interface Filter { condition: (newValues: string[], event: Event) => boolean; } +export interface CalendarEmbed { + embed: EmbedBuilder; + events: Event[]; +} + /** * This function will filter out events based on the given filter array * @@ -56,9 +61,8 @@ export async function filterCalendarEvents(events: Event[], filters: Filter[]): * @param {number} itemsPerPage The number of events you want to display on one embed * @returns {EmbedBuilder[]} Embeds containing all of the calendar events */ -export function generateCalendarEmbeds(events: Event[], itemsPerPage: number): EmbedBuilder[] { - const embeds: EmbedBuilder[] = []; - let embed: EmbedBuilder; +export function generateCalendarEmbeds(events: Event[], itemsPerPage: number): CalendarEmbed[] { + const embeds: CalendarEmbed[] = []; // There can only be up to 25 fields in an embed, so this is just a check to make sure nothing breaks if (itemsPerPage > 25) { @@ -79,6 +83,8 @@ export function generateCalendarEmbeds(events: Event[], itemsPerPage: number): E .setTitle(`Events - ${pageIndex + 1} of ${maxPages}`) .setColor('Green'); + const newCalendarEmbed: CalendarEmbed = { embed: newEmbed, events: [] }; + page.forEach((event, eventIndex) => { newEmbed.addFields({ name: `**${eventIndex + 1}. ${event.calEvent.summary}**`, @@ -87,19 +93,21 @@ export function generateCalendarEmbeds(events: Event[], itemsPerPage: number): E Location: ${event.calEvent.location} Email: ${event.calEvent.creator.email}\n` }); + newCalendarEmbed.events.push(event); }); - embeds.push(newEmbed); + embeds.push(newCalendarEmbed); }); } else { - embed = new EmbedBuilder() + const emptyEmbed = new EmbedBuilder() .setTitle('No Events Found') .setColor('Green') .addFields({ name: 'Try adjusting your filters', value: 'No events match your selections, please change them!' }); - embeds.push(embed); + const newCalendarEmbed: CalendarEmbed = { embed: emptyEmbed, events: [] }; + embeds.push(newCalendarEmbed); } return embeds; } @@ -189,7 +197,7 @@ export function generateEventSelectButtons(embed: EmbedBuilder, events: Event[]) const selectEvent = new ButtonBuilder() .setCustomId(`toggle-${i}`) .setLabel(`Select #${i}`) - .setStyle(ButtonStyle.Secondary); + .setStyle(events[i].selected ? ButtonStyle.Primary : ButtonStyle.Secondary); selectEventButtons.push(selectEvent); } From 4a1160c6ce294ec47520893428ec575d72d03197 Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Mon, 28 Apr 2025 20:17:29 -0400 Subject: [PATCH 03/13] Updated colors and label for select button when event is selected --- src/commands/general/calendar.ts | 8 ++++---- src/lib/utils/calendarUtils.ts | 16 +++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/commands/general/calendar.ts b/src/commands/general/calendar.ts index ba595131..f657b112 100644 --- a/src/commands/general/calendar.ts +++ b/src/commands/general/calendar.ts @@ -164,7 +164,7 @@ export default class extends Command { let message: Message; try { message = await dm.send({ - embeds: [embeds[currentPage]], + embeds: [embeds[currentPage].embed], components: initialComponents }); } catch (error) { @@ -206,7 +206,7 @@ export default class extends Command { } message.edit({ - embeds: [embeds[currentPage]], + embeds: [embeds[currentPage].embed], components: newComponents }); }, interaction, dm, content); @@ -299,7 +299,7 @@ export default class extends Command { } await message.edit({ - embeds: [embeds[currentPage]], + embeds: [embeds[currentPage].embed], components: newComponents }); } catch (error) { @@ -334,7 +334,7 @@ export default class extends Command { } message.edit({ - embeds: [embeds[currentPage]], + embeds: [embeds[currentPage].embed], components: newComponents }); }); diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index 29f89505..12cd9be1 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -178,26 +178,28 @@ export function generateCalendarFilterMessage(filters: Filter[]): PagifiedSelect /** * This function will generate select buttons for each event on the given embed (up to 5 events) * - * @param {EmbedBuilder} embed The embed to generate buttons for + * @param {EmbedBuilder} calendarEmbed The embed to generate buttons for * @param {Event[]} events All of the events retrieved from the google calendar * @returns {ActionRowBuilder} An action row containing all of the select butttons */ -export function generateEventSelectButtons(embed: EmbedBuilder, events: Event[]): ActionRowBuilder | void { +export function generateEventSelectButtons(calendarEmbed: CalendarEmbed, events: Event[]): ActionRowBuilder | void { const selectEventButtons: ButtonBuilder[] = []; + const { embed } = calendarEmbed; + const emebdEvents = calendarEmbed.events; if (events.length && embed) { // This is to ensure that the number of buttons does not exceed to the limit per row - let eventsInEmbed = embed.data.fields.length; + let eventsInEmbed = emebdEvents.length; if (eventsInEmbed > 5) { eventsInEmbed = 5; } // Create buttons for each event on the page (up to 5) - for (let i = 1; i <= eventsInEmbed; i++) { + for (let i = 0; i < eventsInEmbed; i++) { const selectEvent = new ButtonBuilder() - .setCustomId(`toggle-${i}`) - .setLabel(`Select #${i}`) - .setStyle(events[i].selected ? ButtonStyle.Primary : ButtonStyle.Secondary); + .setCustomId(`toggle-${i + 1}`) + .setLabel(emebdEvents[i].selected ? `Remove #${i + 1}` : `Select #${i + 1}`) + .setStyle(emebdEvents[i].selected ? ButtonStyle.Danger : ButtonStyle.Secondary); selectEventButtons.push(selectEvent); } From b7c857319859d96481934c9392c1536526439344 Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Tue, 29 Apr 2025 13:30:43 -0400 Subject: [PATCH 04/13] Added spacing between calendar message and filter message --- src/commands/general/calendar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/general/calendar.ts b/src/commands/general/calendar.ts index f657b112..bb8b1f60 100644 --- a/src/commands/general/calendar.ts +++ b/src/commands/general/calendar.ts @@ -177,7 +177,7 @@ export default class extends Command { } // Create pagified select menus based on the filters - let content = '**Select Filters**'; + let content = '**\nSelect Filters**'; const filterComponents = generateCalendarFilterMessage(filters); // Separate single page menus and pagified menus. Send pagified menus in a separate message From e581733903de40d1b17b2304e28344857f7443be Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Tue, 29 Apr 2025 13:47:45 -0400 Subject: [PATCH 05/13] Bot should now edit inital reply to inform user that DM was sent --- src/commands/general/calendar.ts | 3 +++ src/lib/utils/calendarUtils.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands/general/calendar.ts b/src/commands/general/calendar.ts index bb8b1f60..8fa16677 100644 --- a/src/commands/general/calendar.ts +++ b/src/commands/general/calendar.ts @@ -167,6 +167,9 @@ export default class extends Command { embeds: [embeds[currentPage].embed], components: initialComponents }); + initalReply.edit({ + content: `I sent you a DM with the calendar for **${courseCode.toUpperCase()}**` + }); } catch (error) { console.error('Failed to send DM:', error); await interaction.followUp({ diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index 12cd9be1..c1b9aaf9 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -135,7 +135,7 @@ export function generateCalendarButtons(currentPage: number, maxPage: number, do const downloadButton = new ButtonBuilder() .setCustomId('download') - .setLabel(`Download ${downloadCount ? `${downloadCount} event(s)` : 'All'}`) + .setLabel(`Download ${downloadCount ? `${downloadCount} event(s)` : 'Every Event'}`) .setStyle(ButtonStyle.Success); return new ActionRowBuilder().addComponents( From d617b7b16e70b3e52c92b5b8c1368c880e370e71 Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Tue, 29 Apr 2025 14:39:55 -0400 Subject: [PATCH 06/13] Event numbers and select buttons will only be displayed once download button has ben pressed --- src/commands/general/calendar.ts | 99 ++++++++++++++++++++------------ src/lib/utils/calendarUtils.ts | 17 ++++-- 2 files changed, 74 insertions(+), 42 deletions(-) diff --git a/src/commands/general/calendar.ts b/src/commands/general/calendar.ts index 8fa16677..581aaebf 100644 --- a/src/commands/general/calendar.ts +++ b/src/commands/general/calendar.ts @@ -51,6 +51,7 @@ export default class extends Command { async run(interaction: ChatInputCommandInteraction): Promise { // Local variables let currentPage = 0; + let downloadPressed = false; let selectedEvents: Event[] = []; const courseCode = interaction.options.getString(this.options[0].name, this.options[0].required); const filters: Filter[] = [ @@ -153,11 +154,7 @@ export default class extends Command { // Create initial componenets const initialComponents: ActionRowBuilder[] = []; - const selectButtons = generateEventSelectButtons(embeds[currentPage], filteredEvents); - initialComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length)); - if (selectButtons) { - initialComponents.push(selectButtons); - } + initialComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length, downloadPressed)); // Send intital dm const dm = await interaction.user.createDM(); @@ -202,10 +199,12 @@ export default class extends Command { maxPage = embeds.length; const newComponents: ActionRowBuilder[] = []; - const newSelectButtons = generateEventSelectButtons(embeds[currentPage], filteredEvents); - newComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length)); - if (newSelectButtons) { - newComponents.push(newSelectButtons); + newComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length, downloadPressed)); + if (downloadPressed) { + const newSelectButtons = generateEventSelectButtons(embeds[currentPage], filteredEvents); + if (newSelectButtons) { + newComponents.push(newSelectButtons); + } } message.edit({ @@ -268,37 +267,59 @@ export default class extends Command { // Single Download button, context‑aware } else if (btnInt.customId === 'download') { - // Decide whether to download selected events or all of them - const toDownload = selectedEvents.length > 0 - ? selectedEvents - : filteredEvents; - if (toDownload.length === 0) { - await dm.send('⚠️ No events to download!'); - return; - } - - const prep = await dm.send(`⏳ Preparing ${toDownload.length} event(s)…`); - try { - // downloadEvents writes to './events.ics' - await downloadEvents(toDownload, calendar, interaction); - await prep.edit({ - content: `📥 Here are your ${toDownload.length} event(s):`, - files: ['./events.ics'] + if (downloadPressed) { + // Decide whether to download selected events or all of them + const toDownload = selectedEvents.length > 0 + ? selectedEvents + : filteredEvents; + if (toDownload.length === 0) { + await dm.send('⚠️ No events to download!'); + return; + } + + const prep = await dm.send(`⏳ Preparing ${toDownload.length} event(s)…`); + try { + // downloadEvents writes to './events.ics' + await downloadEvents(toDownload, calendar, interaction); + await prep.edit({ + content: `📥 Here are your ${toDownload.length} event(s):`, + files: ['./events.ics'] + }); + fs.unlinkSync('./events.ics'); + } catch (e) { + console.error('Download failed:', e); + await prep.edit('⚠️ Failed to generate calendar file.'); + } + downloadPressed = false; + embeds.forEach((embed) => { + const { fields } = embed.embed.data; + if (fields) { + fields.forEach((field) => { + [, field.name] = field.name.split(/\*\*\d+\.\*\*\s/); + }); + } + }); + } else { + downloadPressed = true; + embeds.forEach((embed) => { + const { fields } = embed.embed.data; + if (fields) { + fields.forEach((field, index) => { + field.name = `**${index + 1}.** ${field.name}`; + }); + } }); - fs.unlinkSync('./events.ics'); - } catch (e) { - console.error('Download failed:', e); - await prep.edit('⚠️ Failed to generate calendar file.'); } - return; // Skip the re‑render below } // Re‑render embed & buttons for toggles / pagination const newComponents: ActionRowBuilder[] = []; - const newSelectButtons = generateEventSelectButtons(embeds[currentPage], filteredEvents); - newComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length)); - if (newSelectButtons) { - newComponents.push(newSelectButtons); + newComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length, downloadPressed)); + if (downloadPressed) { + const newSelectButtons = generateEventSelectButtons(embeds[currentPage], filteredEvents); + if (newSelectButtons) { + newComponents.push(newSelectButtons); + } } await message.edit({ @@ -330,10 +351,12 @@ export default class extends Command { maxPage = embeds.length; const newComponents: ActionRowBuilder[] = []; - const newSelectButtons = generateEventSelectButtons(embeds[currentPage], filteredEvents); - newComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length)); - if (newSelectButtons) { - newComponents.push(newSelectButtons); + newComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length, downloadPressed)); + if (downloadPressed) { + const newSelectButtons = generateEventSelectButtons(embeds[currentPage], filteredEvents); + if (newSelectButtons) { + newComponents.push(newSelectButtons); + } } message.edit({ diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index c1b9aaf9..3509c7e8 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -85,9 +85,9 @@ export function generateCalendarEmbeds(events: Event[], itemsPerPage: number): C const newCalendarEmbed: CalendarEmbed = { embed: newEmbed, events: [] }; - page.forEach((event, eventIndex) => { + page.forEach((event) => { newEmbed.addFields({ - name: `**${eventIndex + 1}. ${event.calEvent.summary}**`, + name: `**${event.calEvent.summary}**`, value: `Date: ${new Date(event.calEvent.start.dateTime).toLocaleDateString()} Time: ${new Date(event.calEvent.start.dateTime).toLocaleTimeString()} - ${new Date(event.calEvent.end.dateTime).toLocaleTimeString()} Location: ${event.calEvent.location} @@ -118,9 +118,10 @@ export function generateCalendarEmbeds(events: Event[], itemsPerPage: number): C * @param {number} currentPage The current embed page * @param {number} maxPage The total number of embeds * @param {number} downloadCount The number of selected events to be downloaded + * @param {boolean} downloadPressed ... * @returns {ActionRowBuilder} All of the needed buttons to control the calendar embeds */ -export function generateCalendarButtons(currentPage: number, maxPage: number, downloadCount: number): ActionRowBuilder { +export function generateCalendarButtons(currentPage: number, maxPage: number, downloadCount: number, downloadPressed: boolean): ActionRowBuilder { const nextButton = new ButtonBuilder() .setCustomId('next') .setLabel('Next') @@ -133,9 +134,17 @@ export function generateCalendarButtons(currentPage: number, maxPage: number, do .setStyle(ButtonStyle.Primary) .setDisabled(currentPage === 0); + let downloadLabel = 'Download Events'; + if (downloadPressed) { + downloadLabel = 'Download Every Event'; + if (downloadCount) { + downloadLabel = `Download ${downloadCount} event(s)`; + } + } + const downloadButton = new ButtonBuilder() .setCustomId('download') - .setLabel(`Download ${downloadCount ? `${downloadCount} event(s)` : 'Every Event'}`) + .setLabel(downloadLabel) .setStyle(ButtonStyle.Success); return new ActionRowBuilder().addComponents( From 0d9978238f925f17eb53862d16e811e937ef8891 Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Tue, 29 Apr 2025 14:58:44 -0400 Subject: [PATCH 07/13] Added helper functon to update calendar fileds --- src/commands/general/calendar.ts | 21 ++++----------------- src/lib/utils/calendarUtils.ts | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/commands/general/calendar.ts b/src/commands/general/calendar.ts index 581aaebf..a47a2993 100644 --- a/src/commands/general/calendar.ts +++ b/src/commands/general/calendar.ts @@ -25,7 +25,8 @@ import generateCalendarEmbeds, generateEventSelectButtons, generateCalendarFilterMessage, - Event } from '@root/src/lib/utils/calendarUtils'; + Event, + updateCalendarEmbed } from '@root/src/lib/utils/calendarUtils'; // Global constants const MONGO_URI = process.env.DB_CONN_STRING || ''; @@ -291,24 +292,10 @@ export default class extends Command { await prep.edit('⚠️ Failed to generate calendar file.'); } downloadPressed = false; - embeds.forEach((embed) => { - const { fields } = embed.embed.data; - if (fields) { - fields.forEach((field) => { - [, field.name] = field.name.split(/\*\*\d+\.\*\*\s/); - }); - } - }); + embeds = updateCalendarEmbed(embeds, false); } else { downloadPressed = true; - embeds.forEach((embed) => { - const { fields } = embed.embed.data; - if (fields) { - fields.forEach((field, index) => { - field.name = `**${index + 1}.** ${field.name}`; - }); - } - }); + embeds = updateCalendarEmbed(embeds, true); } } diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index 3509c7e8..5e76cfa6 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -54,6 +54,29 @@ export async function filterCalendarEvents(events: Event[], filters: Filter[]): return filteredEvents; } +export function updateCalendarEmbed(embeds: CalendarEmbed[], add: boolean): CalendarEmbed[] { + if (add) { + embeds.forEach((embed) => { + const { fields } = embed.embed.data; + if (fields) { + fields.forEach((field, index) => { + field.name = `**${index + 1}.** ${field.name}`; + }); + } + }); + } else { + embeds.forEach((embed) => { + const { fields } = embed.embed.data; + if (fields) { + fields.forEach((field) => { + [, field.name] = field.name.split(/\*\*\d+\.\*\*\s/); + }); + } + }); + } + return embeds; +} + /** * This function will create embeds to contain all the events passed into the function * From 6c8cce309b46ed6bf2e4fcb8efefae35f3ff524a Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Tue, 29 Apr 2025 15:02:51 -0400 Subject: [PATCH 08/13] Updated helper function documentation in calendar utils --- src/lib/utils/calendarUtils.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index 5e76cfa6..8e165bff 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -54,6 +54,13 @@ export async function filterCalendarEvents(events: Event[], filters: Filter[]): return filteredEvents; } +/** + * This is a helper function update the calendar embed fields when the download button is pressed + * + * @param {CalendarEmbed[]} embeds The embeds that you want to update + * @param {boolean} add Whether or not you want to add or remove from the calendar fields + * @returns {CalendarEmbed[]} The updated embeds + */ export function updateCalendarEmbed(embeds: CalendarEmbed[], add: boolean): CalendarEmbed[] { if (add) { embeds.forEach((embed) => { @@ -141,7 +148,7 @@ export function generateCalendarEmbeds(events: Event[], itemsPerPage: number): C * @param {number} currentPage The current embed page * @param {number} maxPage The total number of embeds * @param {number} downloadCount The number of selected events to be downloaded - * @param {boolean} downloadPressed ... + * @param {boolean} downloadPressed Whether or not the download button has been pressed * @returns {ActionRowBuilder} All of the needed buttons to control the calendar embeds */ export function generateCalendarButtons(currentPage: number, maxPage: number, downloadCount: number, downloadPressed: boolean): ActionRowBuilder { From 322a74fc4fd1f80fe3e137c44e730628a5f626c8 Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Mon, 5 May 2025 11:58:09 -0400 Subject: [PATCH 09/13] Moved calendar interface to their own file in types folder --- src/commands/general/calendar.ts | 15 +++++++-------- src/lib/types/Calendar.d.ts | 20 +++++++++++++++++++ src/lib/utils/calendarUtils.ts | 33 +++++++------------------------- 3 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 src/lib/types/Calendar.d.ts diff --git a/src/commands/general/calendar.ts b/src/commands/general/calendar.ts index a47a2993..e43aba02 100644 --- a/src/commands/general/calendar.ts +++ b/src/commands/general/calendar.ts @@ -19,14 +19,13 @@ import * as fs from 'fs'; import { retrieveEvents } from '@root/src/lib/auth'; import { downloadEvents, - Filter, filterCalendarEvents, generateCalendarButtons, generateCalendarEmbeds, generateEventSelectButtons, generateCalendarFilterMessage, - Event, updateCalendarEmbed } from '@root/src/lib/utils/calendarUtils'; +import { CalendarEvent, Filter } from '@root/src/lib/types/Calendar'; // Global constants const MONGO_URI = process.env.DB_CONN_STRING || ''; @@ -53,7 +52,7 @@ export default class extends Command { // Local variables let currentPage = 0; let downloadPressed = false; - let selectedEvents: Event[] = []; + let selectedEvents: CalendarEvent[] = []; const courseCode = interaction.options.getString(this.options[0].name, this.options[0].required); const filters: Filter[] = [ { @@ -62,7 +61,7 @@ export default class extends Command { values: ['In Person', 'Virtual'], newValues: [], flag: true, - condition: (newValues: string[], event: Event) => { + condition: (newValues: string[], event: CalendarEvent) => { const valuesToCheck = ['virtual', 'online', 'zoom']; const summary = event.calEvent.summary?.toLowerCase() || ''; const location = event.calEvent.location?.toLowerCase() || ''; @@ -76,7 +75,7 @@ export default class extends Command { values: WEEKDAYS, newValues: [], flag: true, - condition: (newValues: string[], event: Event) => { + condition: (newValues: string[], event: CalendarEvent) => { if (!event.calEvent.start?.dateTime) return false; const dt = new Date(event.calEvent.start.dateTime); const weekdayIndex = dt.getDay(); // 0 = Sunday, 1 = Monday, etc. @@ -112,13 +111,13 @@ export default class extends Command { } // Retrieve events from selected calendar - const events: Event[] = []; + const events: CalendarEvent[] = []; const retrivedEvents = await retrieveEvents(calendar.calendarId, interaction); if (retrivedEvents === null) { return; } retrivedEvents.forEach((retrivedEvent) => { - const newEvent: Event = { calEvent: retrivedEvent, calendarName: calendar.calendarName, selected: false }; + const newEvent: CalendarEvent = { calEvent: retrivedEvent, calendarName: calendar.calendarName, selected: false }; if (!newEvent.calEvent.location) { newEvent.calEvent.location = '`Location not specified for this event`'; @@ -147,7 +146,7 @@ export default class extends Command { ); // Create a filtered events variable to keep the original array intact - let filteredEvents: Event[] = events; + let filteredEvents: CalendarEvent[] = events; // Create initial embed let embeds = generateCalendarEmbeds(filteredEvents, EVENTS_PER_PAGE); diff --git a/src/lib/types/Calendar.d.ts b/src/lib/types/Calendar.d.ts new file mode 100644 index 00000000..9f20afbd --- /dev/null +++ b/src/lib/types/Calendar.d.ts @@ -0,0 +1,20 @@ +/* eslint-disable camelcase */ +export interface CalendarEvent { + calEvent: calendar_v3.Schema$Event; + calendarName: string; + selected: boolean; +} + +export interface Filter { + customId: string; + placeholder: string, + values: string[]; + newValues: string[]; + flag: boolean; + condition: (newValues: string[], event: CalendarEvent) => boolean; +} + +export interface CalendarEmbed { + embed: EmbedBuilder; + events: CalendarEvent[]; +} diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index 8e165bff..278be562 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -4,26 +4,7 @@ import { calendar_v3 } from 'googleapis'; import { retrieveEvents } from '../auth'; import { PagifiedSelectMenu } from '../types/PagifiedSelect'; import * as fs from 'fs'; - -export interface Event { - calEvent: calendar_v3.Schema$Event; - calendarName: string; - selected: boolean; -} - -export interface Filter { - customId: string; - placeholder: string, - values: string[]; - newValues: string[]; - flag: boolean; - condition: (newValues: string[], event: Event) => boolean; -} - -export interface CalendarEmbed { - embed: EmbedBuilder; - events: Event[]; -} +import { CalendarEmbed, CalendarEvent, Filter } from '../types/Calendar'; /** * This function will filter out events based on the given filter array @@ -32,8 +13,8 @@ export interface CalendarEmbed { * @param {Filter[]} filters The filters that you want to use to filter the events * @returns {Promise} This function will return an async promise of the filtered events in an array */ -export async function filterCalendarEvents(events: Event[], filters: Filter[]): Promise { - const filteredEvents: Event[] = []; +export async function filterCalendarEvents(events: CalendarEvent[], filters: Filter[]): Promise { + const filteredEvents: CalendarEvent[] = []; let allFiltersFlags = true; @@ -91,7 +72,7 @@ export function updateCalendarEmbed(embeds: CalendarEmbed[], add: boolean): Cale * @param {number} itemsPerPage The number of events you want to display on one embed * @returns {EmbedBuilder[]} Embeds containing all of the calendar events */ -export function generateCalendarEmbeds(events: Event[], itemsPerPage: number): CalendarEmbed[] { +export function generateCalendarEmbeds(events: CalendarEvent[], itemsPerPage: number): CalendarEmbed[] { const embeds: CalendarEmbed[] = []; // There can only be up to 25 fields in an embed, so this is just a check to make sure nothing breaks @@ -101,7 +82,7 @@ export function generateCalendarEmbeds(events: Event[], itemsPerPage: number): C if (events.length) { // Pagify events array - const pagifiedEvents: Event[][] = []; + const pagifiedEvents: CalendarEvent[][] = []; for (let i = 0; i < events.length; i += itemsPerPage) { pagifiedEvents.push(events.slice(i, i + itemsPerPage)); } @@ -221,7 +202,7 @@ export function generateCalendarFilterMessage(filters: Filter[]): PagifiedSelect * @param {Event[]} events All of the events retrieved from the google calendar * @returns {ActionRowBuilder} An action row containing all of the select butttons */ -export function generateEventSelectButtons(calendarEmbed: CalendarEmbed, events: Event[]): ActionRowBuilder | void { +export function generateEventSelectButtons(calendarEmbed: CalendarEmbed, events: CalendarEvent[]): ActionRowBuilder | void { const selectEventButtons: ButtonBuilder[] = []; const { embed } = calendarEmbed; const emebdEvents = calendarEmbed.events; @@ -270,7 +251,7 @@ function formatTime(dateTimeString: string): string { * @param {{calendarId: string, calendarName: string}} calendar An arry of all of the calendars retrived from MongoDB * @param {ChatInputCommandInteraction} interaction The interaction created by calling /calendar */ -export async function downloadEvents(selectedEvents: Event[], calendar: {calendarId: string, calendarName: string}, interaction: ChatInputCommandInteraction): Promise { +export async function downloadEvents(selectedEvents: CalendarEvent[], calendar: {calendarId: string, calendarName: string}, interaction: ChatInputCommandInteraction): Promise { const formattedEvents: string[] = []; const parentEvents: calendar_v3.Schema$Event[] = await retrieveEvents(calendar.calendarId, interaction, false); const recurrenceRules: Record = Object.fromEntries(parentEvents.map((event) => [event.id, event.recurrence])); From 4272e41331f35cb8d467cf8cd3aa5710a85fabc6 Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Mon, 5 May 2025 12:01:38 -0400 Subject: [PATCH 10/13] Updated JSDocs to reflect new type name --- src/lib/utils/calendarUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index 278be562..a059edee 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -9,7 +9,7 @@ import { CalendarEmbed, CalendarEvent, Filter } from '../types/Calendar'; /** * This function will filter out events based on the given filter array * - * @param {Event[]} events The events that you want to filter + * @param {CalendarEvent[]} events The events that you want to filter * @param {Filter[]} filters The filters that you want to use to filter the events * @returns {Promise} This function will return an async promise of the filtered events in an array */ @@ -68,7 +68,7 @@ export function updateCalendarEmbed(embeds: CalendarEmbed[], add: boolean): Cale /** * This function will create embeds to contain all the events passed into the function * - * @param {Event[]} events The events you want to display in the embed + * @param {CalendarEvent[]} events The events you want to display in the embed * @param {number} itemsPerPage The number of events you want to display on one embed * @returns {EmbedBuilder[]} Embeds containing all of the calendar events */ @@ -199,7 +199,7 @@ export function generateCalendarFilterMessage(filters: Filter[]): PagifiedSelect * This function will generate select buttons for each event on the given embed (up to 5 events) * * @param {EmbedBuilder} calendarEmbed The embed to generate buttons for - * @param {Event[]} events All of the events retrieved from the google calendar + * @param {CalendarEvent[]} events All of the events retrieved from the google calendar * @returns {ActionRowBuilder} An action row containing all of the select butttons */ export function generateEventSelectButtons(calendarEmbed: CalendarEmbed, events: CalendarEvent[]): ActionRowBuilder | void { @@ -247,7 +247,7 @@ function formatTime(dateTimeString: string): string { /** * Creates an ics file containing all of the selected events * - * @param {Event[]} selectedEvents The selected events to download + * @param {CalendarEvent[]} selectedEvents The selected events to download * @param {{calendarId: string, calendarName: string}} calendar An arry of all of the calendars retrived from MongoDB * @param {ChatInputCommandInteraction} interaction The interaction created by calling /calendar */ From 96d8a126eb3e65a2414e4069ac067747c9bdaf79 Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Mon, 5 May 2025 13:20:56 -0400 Subject: [PATCH 11/13] Selected events should be removed from array once downloaded --- src/commands/general/calendar.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/commands/general/calendar.ts b/src/commands/general/calendar.ts index e43aba02..2798addf 100644 --- a/src/commands/general/calendar.ts +++ b/src/commands/general/calendar.ts @@ -291,6 +291,10 @@ export default class extends Command { await prep.edit('⚠️ Failed to generate calendar file.'); } downloadPressed = false; + selectedEvents.forEach((event) => { + event.selected = false; + }); + selectedEvents = []; embeds = updateCalendarEmbed(embeds, false); } else { downloadPressed = true; From 076b7d9da196fc0893efec7b759a19c432576b63 Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Mon, 5 May 2025 18:12:12 -0400 Subject: [PATCH 12/13] Download button is now more clear when no events are selected --- src/commands/general/calendar.ts | 9 +++++---- src/lib/utils/calendarUtils.ts | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/commands/general/calendar.ts b/src/commands/general/calendar.ts index 2798addf..fdc8bce4 100644 --- a/src/commands/general/calendar.ts +++ b/src/commands/general/calendar.ts @@ -154,7 +154,7 @@ export default class extends Command { // Create initial componenets const initialComponents: ActionRowBuilder[] = []; - initialComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length, downloadPressed)); + initialComponents.push(generateCalendarButtons(filteredEvents, selectedEvents, currentPage, maxPage, downloadPressed)); // Send intital dm const dm = await interaction.user.createDM(); @@ -199,7 +199,7 @@ export default class extends Command { maxPage = embeds.length; const newComponents: ActionRowBuilder[] = []; - newComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length, downloadPressed)); + newComponents.push(generateCalendarButtons(filteredEvents, selectedEvents, currentPage, maxPage, downloadPressed)); if (downloadPressed) { const newSelectButtons = generateEventSelectButtons(embeds[currentPage], filteredEvents); if (newSelectButtons) { @@ -272,6 +272,7 @@ export default class extends Command { const toDownload = selectedEvents.length > 0 ? selectedEvents : filteredEvents; + if (toDownload.length === 0) { await dm.send('⚠️ No events to download!'); return; @@ -304,7 +305,7 @@ export default class extends Command { // Re‑render embed & buttons for toggles / pagination const newComponents: ActionRowBuilder[] = []; - newComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length, downloadPressed)); + newComponents.push(generateCalendarButtons(filteredEvents, selectedEvents, currentPage, maxPage, downloadPressed)); if (downloadPressed) { const newSelectButtons = generateEventSelectButtons(embeds[currentPage], filteredEvents); if (newSelectButtons) { @@ -341,7 +342,7 @@ export default class extends Command { maxPage = embeds.length; const newComponents: ActionRowBuilder[] = []; - newComponents.push(generateCalendarButtons(currentPage, maxPage, selectedEvents.length, downloadPressed)); + newComponents.push(generateCalendarButtons(filteredEvents, selectedEvents, currentPage, maxPage, downloadPressed)); if (downloadPressed) { const newSelectButtons = generateEventSelectButtons(embeds[currentPage], filteredEvents); if (newSelectButtons) { diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index a059edee..3a6dd694 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -126,13 +126,20 @@ export function generateCalendarEmbeds(events: CalendarEvent[], itemsPerPage: nu /** * Generates pagification buttons and download buttons for the calendar embeds * + * @param {CalendarEvent[]} filteredEvents ... + * @param {CalendarEvent[]} selectedEvents ... * @param {number} currentPage The current embed page * @param {number} maxPage The total number of embeds - * @param {number} downloadCount The number of selected events to be downloaded * @param {boolean} downloadPressed Whether or not the download button has been pressed * @returns {ActionRowBuilder} All of the needed buttons to control the calendar embeds */ -export function generateCalendarButtons(currentPage: number, maxPage: number, downloadCount: number, downloadPressed: boolean): ActionRowBuilder { +export function generateCalendarButtons( + filteredEvents: CalendarEvent[], + selectedEvents: CalendarEvent[], + currentPage: number, + maxPage: number, + downloadPressed: boolean +): ActionRowBuilder { const nextButton = new ButtonBuilder() .setCustomId('next') .setLabel('Next') @@ -147,9 +154,9 @@ export function generateCalendarButtons(currentPage: number, maxPage: number, do let downloadLabel = 'Download Events'; if (downloadPressed) { - downloadLabel = 'Download Every Event'; - if (downloadCount) { - downloadLabel = `Download ${downloadCount} event(s)`; + downloadLabel = `Download Every Event (${filteredEvents.length})`; + if (selectedEvents.length) { + downloadLabel = `Download ${selectedEvents.length} event(s)`; } } From 7e3fd74d6c6b7f53a670bde49b6008ff755a23cd Mon Sep 17 00:00:00 2001 From: TheLuckyman30 Date: Mon, 5 May 2025 18:13:21 -0400 Subject: [PATCH 13/13] Updated documentation for functions --- src/lib/utils/calendarUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/utils/calendarUtils.ts b/src/lib/utils/calendarUtils.ts index 3a6dd694..7b4a7a96 100644 --- a/src/lib/utils/calendarUtils.ts +++ b/src/lib/utils/calendarUtils.ts @@ -126,8 +126,8 @@ export function generateCalendarEmbeds(events: CalendarEvent[], itemsPerPage: nu /** * Generates pagification buttons and download buttons for the calendar embeds * - * @param {CalendarEvent[]} filteredEvents ... - * @param {CalendarEvent[]} selectedEvents ... + * @param {CalendarEvent[]} filteredEvents All of the filtered events + * @param {CalendarEvent[]} selectedEvents The events selected from the filtered events array (if any) * @param {number} currentPage The current embed page * @param {number} maxPage The total number of embeds * @param {boolean} downloadPressed Whether or not the download button has been pressed