diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/LinkCalendarCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/LinkCalendarCommand.kt index f46d03b2..72d9c126 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/LinkCalendarCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/LinkCalendarCommand.kt @@ -17,6 +17,11 @@ class LinkCalendarCommand : SlashCommand { override val ephemeral = false override fun handle(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { + val showOverview = event.getOption("overview") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asBoolean) + .orElse(true) + val calendarNumber = event.getOption("calendar") .flatMap(ApplicationCommandInteractionOption::getValue) .map(ApplicationCommandInteractionOptionValue::asLong) @@ -24,7 +29,7 @@ class LinkCalendarCommand : SlashCommand { .orElse(1) return event.interaction.guild.flatMap { guild -> - CalendarEmbed.link(guild, settings, calendarNumber) + CalendarEmbed.link(guild, settings, calendarNumber, showOverview) .flatMap(event::followup) }.switchIfEmpty(event.followup(getCommonMsg("error.notFound.calendar", settings))) } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/AnnouncementEmbed.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/AnnouncementEmbed.kt index 70de720d..f9f4e0a0 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/AnnouncementEmbed.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/AnnouncementEmbed.kt @@ -8,6 +8,7 @@ import org.dreamexposure.discal.core.`object`.announcement.Announcement import org.dreamexposure.discal.core.entities.Event import org.dreamexposure.discal.core.enums.announcement.AnnouncementStyle import org.dreamexposure.discal.core.enums.announcement.AnnouncementType +import org.dreamexposure.discal.core.extensions.DiscordTimestampFormat.LONG_DATETIME import org.dreamexposure.discal.core.extensions.asDiscordTimestamp import org.dreamexposure.discal.core.extensions.discord4j.getSettings import org.dreamexposure.discal.core.extensions.embedFieldSafe @@ -40,12 +41,12 @@ object AnnouncementEmbed : EmbedMaker { builder.addField( getMessage("announcement", "full.field.start", settings), - event.start.asDiscordTimestamp(), + event.start.asDiscordTimestamp(LONG_DATETIME), true ) builder.addField( getMessage("announcement", "full.field.end", settings), - event.end.asDiscordTimestamp(), + event.end.asDiscordTimestamp(LONG_DATETIME), true ) @@ -84,7 +85,7 @@ object AnnouncementEmbed : EmbedMaker { builder.addField( getMessage("announcement", "simple.field.start", settings), - event.start.asDiscordTimestamp(), + event.start.asDiscordTimestamp(LONG_DATETIME), true ) @@ -116,12 +117,12 @@ object AnnouncementEmbed : EmbedMaker { builder.addField( getMessage("announcement", "event.field.start", settings), - event.start.asDiscordTimestamp(), + event.start.asDiscordTimestamp(LONG_DATETIME), true ) builder.addField( getMessage("announcement", "event.field.end", settings), - event.end.asDiscordTimestamp(), + event.end.asDiscordTimestamp(LONG_DATETIME), true ) diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/CalendarEmbed.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/CalendarEmbed.kt index 4985494c..cf984e04 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/CalendarEmbed.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/CalendarEmbed.kt @@ -6,21 +6,20 @@ import org.dreamexposure.discal.core.`object`.GuildSettings import org.dreamexposure.discal.core.`object`.calendar.PreCalendar import org.dreamexposure.discal.core.entities.Calendar import org.dreamexposure.discal.core.enums.time.TimeFormat +import org.dreamexposure.discal.core.extensions.* import org.dreamexposure.discal.core.extensions.discord4j.getCalendar -import org.dreamexposure.discal.core.extensions.embedDescriptionSafe -import org.dreamexposure.discal.core.extensions.embedFieldSafe -import org.dreamexposure.discal.core.extensions.embedTitleSafe -import org.dreamexposure.discal.core.extensions.toMarkdown import org.dreamexposure.discal.core.utils.GlobalVal.discalColor import org.dreamexposure.discal.core.utils.getCommonMsg import reactor.core.publisher.Mono +import java.time.Instant import java.time.LocalDateTime import java.time.format.DateTimeFormatter object CalendarEmbed : EmbedMaker { - fun link(guild: Guild, settings: GuildSettings, calNumber: Int): Mono { - return guild.getCalendar(calNumber).map { - link(guild, settings, it) + fun link(guild: Guild, settings: GuildSettings, calNumber: Int, overview: Boolean): Mono { + return guild.getCalendar(calNumber).flatMap { + if (overview) overview(guild, settings, it, false) + else Mono.just(link(guild, settings, it)) } } @@ -42,6 +41,54 @@ object CalendarEmbed : EmbedMaker { .build() } + fun overview(guild: Guild, settings: GuildSettings, calendar: Calendar, showUpdate: Boolean): Mono { + return calendar.getUpcomingEvents(15).collectList().map { it.groupByDate() }.map { events -> + val builder = defaultBuilder(guild, settings) + + //Handle optional fields + if (calendar.name.isNotBlank()) + builder.title(calendar.name.toMarkdown().embedTitleSafe()) + if (calendar.description.isNotBlank()) + builder.description(calendar.description.toMarkdown().embedDescriptionSafe()) + + // Show events + events.forEach { date -> + val fieldTitle = getMessage( + "calendar", "link.field.date", + settings, + Instant.from(date.key).asDiscordTimestamp(DiscordTimestampFormat.LONG_DATE) + ) + + val content = StringBuilder().append("```\n") + date.value.forEach { + content.append(it.start.asDiscordTimestamp(DiscordTimestampFormat.SHORT_TIME)) + .append(" - ") + .append(it.end.asDiscordTimestamp(DiscordTimestampFormat.SHORT_TIME)) + .append(" | ") + if (it.name.isNotBlank()) content.append(it.name).append(" | ") + content.append(it.eventId).append("\n") + } + content.append("```") + + builder.addField(fieldTitle, content.toString(), false) + } + + + // set footer + if (showUpdate) { + val lastUpdate = Instant.now().asDiscordTimestamp(DiscordTimestampFormat.RELATIVE_TIME) + builder.footer(getMessage("calendar", "link.footer.update", settings, lastUpdate), null) + } else builder.footer(getMessage("calendar", "link.footer.default", settings), null) + + // finish and return + builder.addField(getMessage("calendar", "link.field.timezone", settings), calendar.zoneName, true) + .addField(getMessage("calendar", "link.field.number", settings), "${calendar.calendarNumber}", true) + .url(calendar.link) + .color(discalColor) + .build() + } + } + fun time(guild: Guild, settings: GuildSettings, calNumber: Int): Mono { return guild.getCalendar(calNumber).map { cal -> diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/EventEmbed.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/EventEmbed.kt index a7a35535..801a45d7 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/EventEmbed.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/EventEmbed.kt @@ -6,6 +6,7 @@ import org.dreamexposure.discal.core.`object`.GuildSettings import org.dreamexposure.discal.core.`object`.event.PreEvent import org.dreamexposure.discal.core.entities.Event import org.dreamexposure.discal.core.extensions.* +import org.dreamexposure.discal.core.extensions.DiscordTimestampFormat.LONG_DATETIME import org.dreamexposure.discal.core.utils.getCommonMsg object EventEmbed : EmbedMaker { @@ -19,8 +20,15 @@ object EventEmbed : EmbedMaker { if (event.description.isNotBlank()) builder.description(event.description.toMarkdown().embedDescriptionSafe()) - builder.addField(getMessage("event", "full.field.start", settings), event.start.asDiscordTimestamp(), true) - builder.addField(getMessage("event", "full.field.end", settings), event.end.asDiscordTimestamp(), true) + builder.addField( + getMessage("event", "full.field.start", settings), + event.start.asDiscordTimestamp(LONG_DATETIME), + true) + builder.addField( + getMessage("event", "full.field.end", settings), + event.end.asDiscordTimestamp(LONG_DATETIME), + true + ) if (event.location.isNotBlank()) builder.addField( getMessage("event", "full.field.location", settings), @@ -44,7 +52,11 @@ object EventEmbed : EmbedMaker { if (event.name.isNotBlank()) builder.title(event.name.toMarkdown().embedTitleSafe()) - builder.addField(getMessage("event", "con.field.start", settings), event.start.asDiscordTimestamp(), true) + builder.addField( + getMessage("event", "con.field.start", settings), + event.start.asDiscordTimestamp(LONG_DATETIME), + true + ) if (event.location.isNotBlank()) builder.addField( getMessage("event", "con.field.location", settings), diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/DiscordTimestampFormat.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/DiscordTimestampFormat.kt new file mode 100644 index 00000000..cf5bf282 --- /dev/null +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/DiscordTimestampFormat.kt @@ -0,0 +1,11 @@ +package org.dreamexposure.discal.core.extensions + +enum class DiscordTimestampFormat(val value: String) { + SHORT_TIME("t"), + LONG_TIME("T"), + SHORT_DATE("d"), + LONG_DATE("D"), + SHORT_DATETIME("f"), + LONG_DATETIME("F"), + RELATIVE_TIME("R"), +} diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/InstantExtension.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/InstantExtension.kt index 4bdfe4ac..95622911 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/InstantExtension.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/InstantExtension.kt @@ -5,7 +5,7 @@ import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter -fun Instant.asDiscordTimestamp(): String = "" +fun Instant.asDiscordTimestamp(fmt: DiscordTimestampFormat) = "" fun Instant.humanReadableFull(timezone: ZoneId, format: TimeFormat): String { return DateTimeFormatter.ofPattern(format.full).withZone(timezone).format(this) diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/MutableList.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/MutableList.kt index 1245dd12..bf6bf4a7 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/MutableList.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/MutableList.kt @@ -1,5 +1,10 @@ package org.dreamexposure.discal.core.extensions +import org.dreamexposure.discal.core.entities.Event +import java.time.LocalDate +import java.time.temporal.TemporalAdjusters +import java.util.stream.Collectors + fun MutableList.asStringList(): String { val builder = StringBuilder() @@ -13,3 +18,13 @@ fun MutableList.asStringList(): String { return builder.toString() } + + +fun MutableList.groupByDate(): Map> { + return this.stream() + .collect(Collectors.groupingBy { + LocalDate.ofInstant(it.start, it.timezone) + .with(TemporalAdjusters.ofDateAdjuster { identity -> identity }) + }) + +} diff --git a/core/src/main/resources/commands/linkCal.json b/core/src/main/resources/commands/linkCal.json index 00d08bcc..22e77165 100644 --- a/core/src/main/resources/commands/linkCal.json +++ b/core/src/main/resources/commands/linkCal.json @@ -2,6 +2,12 @@ "name": "linkcal", "description": "Provides info and a link to the Guild's Calendar", "options": [ + { + "name": "overview", + "type": 5, + "description": "Whether to display the next 15 upcoming events in the response (default true).", + "required": false + }, { "name": "calendar", "type": 4, diff --git a/core/src/main/resources/i18n/embed/calendar.properties b/core/src/main/resources/i18n/embed/calendar.properties index f65baae9..66947e28 100644 --- a/core/src/main/resources/i18n/embed/calendar.properties +++ b/core/src/main/resources/i18n/embed/calendar.properties @@ -3,7 +3,9 @@ link.field.timezone=Time Zone link.field.host=Host link.field.number=Number link.field.id=ID -link.footer=View the entire calendar with events by clicking the title +link.field.date=%s (Local) +link.footer.default=View the entire calendar with events by clicking the title +link.footer.update=Last Updated \u22C4 %s wizard.title=Calendar Wizard wizard.field.name=* Name