diff --git a/build.gradle.kts b/build.gradle.kts index 2735e1e2..d695dce2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,7 +21,7 @@ buildscript { } } -val discord4jVersion = "3.2.0-SNAPSHOT" //Has to be here to show up in git properties task +val discord4jVersion = "3.2.0" //Has to be here to show up in git properties task @Suppress("UNUSED_VARIABLE") allprojects { //Project props diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/AddCalCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/AddCalCommand.kt index 1d7a35b1..d9ae008a 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/AddCalCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/AddCalCommand.kt @@ -3,10 +3,10 @@ package org.dreamexposure.discal.client.commands import discord4j.core.`object`.entity.Guild import discord4j.core.`object`.entity.Member import discord4j.core.event.domain.interaction.ChatInputInteractionEvent -import org.dreamexposure.discal.client.message.Responder import org.dreamexposure.discal.core.`object`.BotSettings import org.dreamexposure.discal.core.`object`.GuildSettings import org.dreamexposure.discal.core.extensions.discord4j.canAddCalendar +import org.dreamexposure.discal.core.extensions.discord4j.followupEphemeral import org.dreamexposure.discal.core.extensions.discord4j.hasElevatedPermissions import org.dreamexposure.discal.core.utils.getCommonMsg import org.springframework.stereotype.Component @@ -24,12 +24,12 @@ class AddCalCommand : SlashCommand { .filterWhen(Member::hasElevatedPermissions).flatMap { //Check if a calendar can be added since non-premium only allows 1 calendar. event.interaction.guild.filterWhen(Guild::canAddCalendar).flatMap { - Responder.followupEphemeral(event, getMessage("response.start", settings, getLink(settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.calendar.max", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.perms.elevated", settings))) + event.followupEphemeral(getMessage("response.start", settings, getLink(settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.calendar.max", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.perms.elevated", settings))) .then() } else { - Responder.followupEphemeral(event, getCommonMsg("error.disabled", settings)).then() + event.followupEphemeral(getCommonMsg("error.disabled", settings)).then() } } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/DevCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/DevCommand.kt index 9db3ac53..505a6b73 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/DevCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/DevCommand.kt @@ -3,11 +3,11 @@ package org.dreamexposure.discal.client.commands import discord4j.common.util.Snowflake import discord4j.core.`object`.command.ApplicationCommandInteractionOptionValue import discord4j.core.event.domain.interaction.ChatInputInteractionEvent -import org.dreamexposure.discal.client.message.Responder import org.dreamexposure.discal.core.`object`.GuildSettings import org.dreamexposure.discal.core.`object`.web.UserAPIAccount import org.dreamexposure.discal.core.crypto.KeyGenerator.csRandomAlphaNumericString import org.dreamexposure.discal.core.database.DatabaseManager +import org.dreamexposure.discal.core.extensions.discord4j.followupEphemeral import org.dreamexposure.discal.core.logger.LOGGER import org.dreamexposure.discal.core.utils.GlobalVal import org.springframework.stereotype.Component @@ -20,7 +20,7 @@ class DevCommand : SlashCommand { override fun handle(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { if (!GlobalVal.devUserIds.contains(event.interaction.user.id)) { - return Responder.followupEphemeral(event, getMessage("error.notDeveloper", settings)).then() + return event.followupEphemeral(getMessage("error.notDeveloper", settings)).then() } return when (event.options[0].name) { @@ -40,12 +40,11 @@ class DevCommand : SlashCommand { .flatMap { DatabaseManager.getSettings(it) } .doOnNext { settings.patronGuild = !settings.patronGuild } .flatMap { - DatabaseManager.updateSettings(it).then(Responder.followupEphemeral( - event, - getMessage("patron.success", settings, settings.patronGuild.toString()) + DatabaseManager.updateSettings(it).then(event.followupEphemeral( + getMessage("patron.success", settings, settings.patronGuild.toString()) )) }.doOnError { LOGGER.error("[cmd] patron failure", it) } - .onErrorResume { Responder.followupEphemeral(event, getMessage("patron.failure.badId", settings)) } + .onErrorResume { event.followupEphemeral(getMessage("patron.failure.badId", settings)) } .then() } @@ -56,12 +55,11 @@ class DevCommand : SlashCommand { .flatMap { DatabaseManager.getSettings(it) } .doOnNext { settings.devGuild = !settings.devGuild } .flatMap { - DatabaseManager.updateSettings(it).then(Responder.followupEphemeral( - event, + DatabaseManager.updateSettings(it).then(event.followupEphemeral( getMessage("dev.success", settings, settings.devGuild.toString()) )) }.doOnError { LOGGER.error("[cmd] dev failure", it) } - .onErrorResume { Responder.followupEphemeral(event, getMessage("dev.failure.badId", settings)) } + .onErrorResume { event.followupEphemeral(getMessage("dev.failure.badId", settings)) } .then() } @@ -74,13 +72,12 @@ class DevCommand : SlashCommand { val amount = event.options[0].getOption("amount").get().value.get().asLong().toInt() it.maxCalendars = amount }.flatMap { - DatabaseManager.updateSettings(it).then(Responder.followupEphemeral( - event, + DatabaseManager.updateSettings(it).then(event.followupEphemeral( getMessage("maxcal.success", settings, settings.maxCalendars.toString()) )) } .onErrorResume { - Responder.followupEphemeral(event, getMessage("maxcal.failure.badInput", settings)) + event.followupEphemeral(getMessage("maxcal.failure.badInput", settings)) }.then() } @@ -97,15 +94,12 @@ class DevCommand : SlashCommand { DatabaseManager.updateAPIAccount(acc).flatMap { success -> if (success) { - Responder.followupEphemeral( - event, - getMessage("apiRegister.success", settings, acc.APIKey) - ) + event.followupEphemeral(getMessage("apiRegister.success", settings, acc.APIKey)) } else { - Responder.followupEphemeral(event, getMessage("apiRegister.failure.unable", settings)) + event.followupEphemeral(getMessage("apiRegister.failure.unable", settings)) } } - }.switchIfEmpty(Responder.followupEphemeral(event, getMessage("apiRegister.failure.empty", settings))) + }.switchIfEmpty(event.followupEphemeral(getMessage("apiRegister.failure.empty", settings))) .then() } @@ -116,10 +110,10 @@ class DevCommand : SlashCommand { .map { it.copy(blocked = true) }.flatMap(DatabaseManager::updateAPIAccount) - .flatMap { Responder.followupEphemeral(event, getMessage("apiBlock.success", settings)) } - .switchIfEmpty(Responder.followupEphemeral(event, getMessage("apiBlock.failure.notFound", settings))) + .flatMap { event.followupEphemeral(getMessage("apiBlock.success", settings)) } + .switchIfEmpty(event.followupEphemeral(getMessage("apiBlock.failure.notFound", settings))) .onErrorResume { - Responder.followupEphemeral(event, getMessage("apiBlock.failure.other", settings)) + event.followupEphemeral(getMessage("apiBlock.failure.other", settings)) }.then() } } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/DiscalCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/DiscalCommand.kt index c5674437..214c2137 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/DiscalCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/DiscalCommand.kt @@ -1,9 +1,9 @@ package org.dreamexposure.discal.client.commands import discord4j.core.event.domain.interaction.ChatInputInteractionEvent -import org.dreamexposure.discal.client.message.Responder import org.dreamexposure.discal.client.message.embed.DiscalEmbed import org.dreamexposure.discal.core.`object`.GuildSettings +import org.dreamexposure.discal.core.extensions.discord4j.followup import org.springframework.stereotype.Component import reactor.core.publisher.Mono @@ -15,7 +15,7 @@ class DiscalCommand : SlashCommand { override fun handle(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { return event.interaction.guild .flatMap(DiscalEmbed::info) - .flatMap { Responder.followup(event, it) } + .flatMap(event::followup) .then() } } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/EventsCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/EventsCommand.kt index c0c01354..024f1423 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/EventsCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/EventsCommand.kt @@ -1,9 +1,9 @@ package org.dreamexposure.discal.client.commands import discord4j.core.event.domain.interaction.ChatInputInteractionEvent -import org.dreamexposure.discal.client.message.Responder import org.dreamexposure.discal.client.message.embed.EventEmbed import org.dreamexposure.discal.core.`object`.GuildSettings +import org.dreamexposure.discal.core.extensions.discord4j.followup import org.dreamexposure.discal.core.extensions.discord4j.getCalendar import org.dreamexposure.discal.core.utils.getCommonMsg import org.springframework.stereotype.Component @@ -34,169 +34,159 @@ class EventsCommand : SlashCommand { private fun upcomingEventsSubcommand(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { //Determine which calendar they want to use... val calNumMono = Mono.justOrEmpty(event.options[0].getOption("calendar").flatMap { it.value }) - .map { it.asLong().toInt() } - .defaultIfEmpty(1) + .map { it.asLong().toInt() } + .defaultIfEmpty(1) val amountMono = Mono.justOrEmpty(event.options[0].getOption("amount").flatMap { it.value }) - .map { it.asLong().toInt() } - .defaultIfEmpty(1) + .map { it.asLong().toInt() } + .defaultIfEmpty(1) return Mono.zip(calNumMono, amountMono).flatMap(TupleUtils.function { calNumb, amount -> if (amount < 1 || amount > 15) { - return@function Responder.followup(event, getMessage("upcoming.failure.outOfRange", settings)) + return@function event.followup(getMessage("upcoming.failure.outOfRange", settings)) } event.interaction.guild.flatMap { guild -> guild.getCalendar(calNumb).flatMap { cal -> cal.getUpcomingEvents(amount).collectList().flatMap { events -> if (events.isEmpty()) { - Responder.followup(event, getMessage("upcoming.success.none", settings)) + event.followup(getMessage("upcoming.success.none", settings)) } else if (events.size == 1) { - Responder.followup( - event, - getMessage("upcoming.success.one", settings), - EventEmbed.getFull(guild, settings, events[0]) + event.followup( + getMessage("upcoming.success.one", settings), + EventEmbed.getFull(guild, settings, events[0]) ) } else { - Responder.followup(event, getMessage("upcoming.success.many", settings, "${events.size}")) - .flatMapMany { - Flux.fromIterable(events) - }.flatMap { - Responder.followup(event, EventEmbed.getCondensed(guild, settings, it)) - }.then(Mono.just("")) + event.followup( + getMessage("upcoming.success.many", settings, "${events.size}") + ).flatMapMany { + Flux.fromIterable(events) + }.flatMap { + event.followup(EventEmbed.getCondensed(guild, settings, it)) + }.then(Mono.just("")) } } - }.switchIfEmpty(Responder.followup(event, getCommonMsg("error.notFound.calendar", settings))) + }.switchIfEmpty(event.followup(getCommonMsg("error.notFound.calendar", settings))) } }).then() } private fun ongoingEventsSubcommand(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { return Mono.justOrEmpty(event.options[0].getOption("calendar").flatMap { it.value }) - .map { it.asLong().toInt() } - .defaultIfEmpty(1).flatMap { calNum -> - event.interaction.guild.flatMap { guild -> - guild.getCalendar(calNum).flatMap { cal -> - cal.getOngoingEvents().collectList().flatMap { events -> - if (events.isEmpty()) { - Responder.followup( - event, - getMessage("ongoing.success.none", settings) - ) - } else if (events.size == 1) { - Responder.followup( - event, - getMessage("ongoing.success.one", settings), - EventEmbed.getFull(guild, settings, events[0]) - ) - } else { - Responder.followup(event, - getMessage("ongoing.success.many", settings, "${events.size}") - ).flatMapMany { - Flux.fromIterable(events) - }.flatMap { - Responder.followup(event, EventEmbed.getCondensed(guild, settings, it)) - }.then(Mono.just("")) - } - } - }.switchIfEmpty(Responder.followup(event, getCommonMsg("error.notFound.calendar", settings))) - } - }.then() + .map { it.asLong().toInt() } + .defaultIfEmpty(1).flatMap { calNum -> + event.interaction.guild.flatMap { guild -> + guild.getCalendar(calNum).flatMap { cal -> + cal.getOngoingEvents().collectList().flatMap { events -> + if (events.isEmpty()) { + event.followup(getMessage("ongoing.success.none", settings)) + } else if (events.size == 1) { + event.followup( + getMessage("ongoing.success.one", settings), + EventEmbed.getFull(guild, settings, events[0]) + ) + } else { + event.followup( + getMessage("ongoing.success.many", settings, "${events.size}") + ).flatMapMany { + Flux.fromIterable(events) + }.flatMap { + event.followup(EventEmbed.getCondensed(guild, settings, it)) + }.then(Mono.just("")) + } + } + }.switchIfEmpty(event.followup(getCommonMsg("error.notFound.calendar", settings))) + } + }.then() } private fun eventsTodaySubcommand(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { return Mono.justOrEmpty(event.options[0].getOption("calendar").flatMap { it.value }) - .map { it.asLong().toInt() } - .defaultIfEmpty(1).flatMap { calNum -> - event.interaction.guild.flatMap { guild -> - guild.getCalendar(calNum).flatMap { cal -> - cal.getEventsInNext24HourPeriod(Instant.now()).collectList().flatMap { events -> - if (events.isEmpty()) { - Responder.followup( - event, - getMessage("today.success.none", settings) - ) - } else if (events.size == 1) { - Responder.followup( - event, - getMessage("today.success.one", settings), - EventEmbed.getFull(guild, settings, events[0]) - ) - } else { - Responder.followup(event, - getMessage("today.success.many", settings, "${events.size}") - ).flatMapMany { - Flux.fromIterable(events) - }.flatMap { - Responder.followup(event, EventEmbed.getCondensed(guild, settings, it)) - }.then(Mono.just("")) - } - } - }.switchIfEmpty(Responder.followup(event, getCommonMsg("error.notFound.calendar", settings))) - } - }.then() + .map { it.asLong().toInt() } + .defaultIfEmpty(1).flatMap { calNum -> + event.interaction.guild.flatMap { guild -> + guild.getCalendar(calNum).flatMap { cal -> + cal.getEventsInNext24HourPeriod(Instant.now()).collectList().flatMap { events -> + if (events.isEmpty()) { + event.followup(getMessage("today.success.none", settings)) + } else if (events.size == 1) { + event.followup( + getMessage("today.success.one", settings), + EventEmbed.getFull(guild, settings, events[0]) + ) + } else { + event.followup( + getMessage("today.success.many", settings, "${events.size}") + ).flatMapMany { + Flux.fromIterable(events) + }.flatMap { + event.followup(EventEmbed.getCondensed(guild, settings, it)) + }.then(Mono.just("")) + } + } + }.switchIfEmpty(event.followup(getCommonMsg("error.notFound.calendar", settings))) + } + }.then() } private fun eventsRangeSubcommand(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { val gMono = event.interaction.guild.cache() val calMono = Mono.justOrEmpty(event.options[0].getOption("calendar").flatMap { it.value }) - .map { it.asLong().toInt() } - .defaultIfEmpty(1) - .flatMap { num -> - gMono.flatMap { - it.getCalendar(num) - } - }.cache() + .map { it.asLong().toInt() } + .defaultIfEmpty(1) + .flatMap { num -> + gMono.flatMap { + it.getCalendar(num) + } + }.cache() val sMono = Mono.justOrEmpty(event.options[0].getOption("start").flatMap { it.value }) - .map { it.asString() } - .flatMap { value -> - calMono.map { - val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd") + .map { it.asString() } + .flatMap { value -> + calMono.map { + val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd") - LocalDate.parse(value, formatter).atStartOfDay(it.timezone) - } - }.map(ZonedDateTime::toInstant) + LocalDate.parse(value, formatter).atStartOfDay(it.timezone) + } + }.map(ZonedDateTime::toInstant) val eMono = Mono.justOrEmpty(event.options[0].getOption("end").flatMap { it.value }) - .map { it.asString() } - .flatMap { value -> - calMono.map { - val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd") + .map { it.asString() } + .flatMap { value -> + calMono.map { + val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd") - //At end of day - LocalDate.parse(value, formatter).plusDays(1).atStartOfDay(it.timezone) - } - }.map(ZonedDateTime::toInstant) + //At end of day + LocalDate.parse(value, formatter).plusDays(1).atStartOfDay(it.timezone) + } + }.map(ZonedDateTime::toInstant) return Mono.zip(gMono, calMono, sMono, eMono).flatMap( - TupleUtils.function { guild, cal, start, end -> - cal.getEventsInTimeRange(start, end).collectList().flatMap { events -> - if (events.isEmpty()) { - Responder.followup(event, getMessage("range.success.none", settings)) - } else if (events.size == 1) { - Responder.followup( - event, - getMessage("range.success.one", settings), - EventEmbed.getFull(guild, settings, events[0])) - } else if (events.size > 15) { - Responder.followup( - event, - getMessage("range.success.tooMany", settings, "${events.size}", cal.link) - ) - } else { - Responder.followup(event, getMessage("range.success.many", settings, "${events.size}")) - .flatMapMany { - Flux.fromIterable(events) - }.flatMap { - Responder.followup(event, EventEmbed.getCondensed(guild, settings, it)) - }.then(Mono.just("")) - } - } - }).switchIfEmpty(Responder.followup(event, getCommonMsg("error.notFound.calendar", settings))) - .onErrorResume(DateTimeParseException::class.java) { - Responder.followup(event, getCommonMsg("error.format.date", settings)) - }.then() + TupleUtils.function { guild, cal, start, end -> + cal.getEventsInTimeRange(start, end).collectList().flatMap { events -> + if (events.isEmpty()) { + event.followup(getMessage("range.success.none", settings)) + } else if (events.size == 1) { + event.followup( + getMessage("range.success.one", settings), + EventEmbed.getFull(guild, settings, events[0]) + ) + } else if (events.size > 15) { + event.followup(getMessage("range.success.tooMany", settings, "${events.size}", cal.link)) + } else { + event.followup( + getMessage("range.success.many", settings, "${events.size}") + ).flatMapMany { + Flux.fromIterable(events) + }.flatMap { + event.followup(EventEmbed.getCondensed(guild, settings, it)) + }.then(Mono.just("")) + } + } + }).switchIfEmpty(event.followup(getCommonMsg("error.notFound.calendar", settings))) + .onErrorResume(DateTimeParseException::class.java) { + event.followup(getCommonMsg("error.format.date", settings)) + }.then() } } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/HelpCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/HelpCommand.kt index 6cb88757..7e46af38 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/HelpCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/HelpCommand.kt @@ -1,9 +1,9 @@ package org.dreamexposure.discal.client.commands import discord4j.core.event.domain.interaction.ChatInputInteractionEvent -import org.dreamexposure.discal.client.message.Responder import org.dreamexposure.discal.core.`object`.BotSettings import org.dreamexposure.discal.core.`object`.GuildSettings +import org.dreamexposure.discal.core.extensions.discord4j.followupEphemeral import org.springframework.stereotype.Component import reactor.core.publisher.Mono @@ -13,9 +13,8 @@ class HelpCommand : SlashCommand { override val ephemeral = true override fun handle(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { - return Responder.followupEphemeral( - event, - getMessage("error.workInProgress", settings, "${BotSettings.BASE_URL.get()}/commands") + return event.followupEphemeral( + getMessage("error.workInProgress", settings, "${BotSettings.BASE_URL.get()}/commands") ).then() } } 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 6e745b9a..8f1983c0 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 @@ -2,9 +2,9 @@ package org.dreamexposure.discal.client.commands import discord4j.core.`object`.command.ApplicationCommandInteractionOptionValue import discord4j.core.event.domain.interaction.ChatInputInteractionEvent -import org.dreamexposure.discal.client.message.Responder import org.dreamexposure.discal.client.message.embed.CalendarEmbed import org.dreamexposure.discal.core.`object`.GuildSettings +import org.dreamexposure.discal.core.extensions.discord4j.followup import org.dreamexposure.discal.core.utils.getCommonMsg import org.springframework.stereotype.Component import reactor.core.publisher.Mono @@ -16,16 +16,15 @@ class LinkCalendarCommand : SlashCommand { override fun handle(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { return Mono.justOrEmpty(event.getOption("number")) - .flatMap { Mono.justOrEmpty(it.value) } - .map(ApplicationCommandInteractionOptionValue::asLong) - .map(Long::toInt) - .defaultIfEmpty(1) - .flatMap { calNumber -> - event.interaction.guild.flatMap { guild -> - CalendarEmbed.getLinkCalEmbed(guild, settings, calNumber).flatMap { - Responder.followup(event, it) - } - }.switchIfEmpty(Responder.followup(event, getCommonMsg("error.notFound.calendar", settings))) - }.then() + .flatMap { Mono.justOrEmpty(it.value) } + .map(ApplicationCommandInteractionOptionValue::asLong) + .map(Long::toInt) + .defaultIfEmpty(1) + .flatMap { calNumber -> + event.interaction.guild.flatMap { guild -> + CalendarEmbed.getLinkCalEmbed(guild, settings, calNumber) + .flatMap(event::followup) + }.switchIfEmpty(event.followup(getCommonMsg("error.notFound.calendar", settings))) + }.then() } } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/RsvpCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/RsvpCommand.kt index 050e07c4..9f652120 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/RsvpCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/RsvpCommand.kt @@ -1,9 +1,9 @@ package org.dreamexposure.discal.client.commands import discord4j.core.event.domain.interaction.ChatInputInteractionEvent -import org.dreamexposure.discal.client.message.Responder import org.dreamexposure.discal.client.message.embed.RsvpEmbed import org.dreamexposure.discal.core.`object`.GuildSettings +import org.dreamexposure.discal.core.extensions.discord4j.followupEphemeral import org.dreamexposure.discal.core.extensions.discord4j.getCalendar import org.dreamexposure.discal.core.extensions.discord4j.hasControlRole import org.dreamexposure.discal.core.extensions.discord4j.hasElevatedPermissions @@ -52,16 +52,13 @@ class RsvpCommand : SlashCommand { .flatMap { calEvent.updateRsvp(it) } .flatMap { RsvpEmbed.list(guild, settings, calEvent) } .flatMap { - Responder.followupEphemeral(event, getMessage("onTime.success", settings), it) - }.switchIfEmpty(Responder.followupEphemeral( - event, - getMessage("onTime.failure.limit", settings) - )) + event.followupEphemeral(getMessage("onTime.success", settings), it) + }.switchIfEmpty(event.followupEphemeral(getMessage("onTime.failure.limit", settings))) } else { - Responder.followupEphemeral(event, getCommonMsg("error.event.ended", settings)) + event.followupEphemeral(getCommonMsg("error.event.ended", settings)) } - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.event", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.calendar", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.event", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings))) }).then() } @@ -86,16 +83,13 @@ class RsvpCommand : SlashCommand { .flatMap { calEvent.updateRsvp(it) } .flatMap { RsvpEmbed.list(guild, settings, calEvent) } .flatMap { - Responder.followupEphemeral(event, getMessage("late.success", settings), it) - }.switchIfEmpty(Responder.followupEphemeral( - event, - getMessage("late.failure.limit", settings) - )) + event.followupEphemeral(getMessage("late.success", settings), it) + }.switchIfEmpty(event.followupEphemeral(getMessage("late.failure.limit", settings))) } else { - Responder.followupEphemeral(event, getCommonMsg("error.event.ended", settings)) + event.followupEphemeral(getCommonMsg("error.event.ended", settings)) } - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.event", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.calendar", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.event", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings))) }).then() } @@ -119,13 +113,13 @@ class RsvpCommand : SlashCommand { .flatMap { calEvent.updateRsvp(it) } .then(RsvpEmbed.list(guild, settings, calEvent)) .flatMap { - Responder.followupEphemeral(event, getMessage("unsure.success", settings), it) + event.followupEphemeral(getMessage("unsure.success", settings), it) } } else { - Responder.followupEphemeral(event, getCommonMsg("error.event.ended", settings)) + event.followupEphemeral(getCommonMsg("error.event.ended", settings)) } - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.event", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.calendar", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.event", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings))) }).then() } @@ -149,13 +143,13 @@ class RsvpCommand : SlashCommand { .flatMap { calEvent.updateRsvp(it) } .then(RsvpEmbed.list(guild, settings, calEvent)) .flatMap { - Responder.followupEphemeral(event, getMessage("notGoing.success", settings), it) + event.followupEphemeral(getMessage("notGoing.success", settings), it) } } else { - Responder.followupEphemeral(event, getCommonMsg("error.event.ended", settings)) + event.followupEphemeral(getCommonMsg("error.event.ended", settings)) } - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.event", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.calendar", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.event", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings))) }).then() } @@ -178,13 +172,13 @@ class RsvpCommand : SlashCommand { .flatMap { calEvent.updateRsvp(it) } .then(RsvpEmbed.list(guild, settings, calEvent)) .flatMap { - Responder.followupEphemeral(event, getMessage("remove.success", settings), it) + event.followupEphemeral(getMessage("remove.success", settings), it) } } else { - Responder.followupEphemeral(event, getCommonMsg("error.event.ended", settings)) + event.followupEphemeral(getCommonMsg("error.event.ended", settings)) } - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.event", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.calendar", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.event", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings))) }).then() } @@ -201,9 +195,9 @@ class RsvpCommand : SlashCommand { cal.getEvent(eventId).flatMap { calEvent -> calEvent.getRsvp() .then(RsvpEmbed.list(guild, settings, calEvent)) - .flatMap { Responder.followupEphemeral(event, it) } - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.event", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.calendar", settings))) + .flatMap { event.followupEphemeral(it) } + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.event", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings))) }).then() } @@ -232,18 +226,14 @@ class RsvpCommand : SlashCommand { .flatMap { calEvent.updateRsvp(it) } .then(RsvpEmbed.list(guild, settings, calEvent)) .flatMap { - Responder.followupEphemeral( - event, - getMessage("limit.success", settings, "$limit"), - it - ) + event.followupEphemeral(getMessage("limit.success", settings, "$limit"), it) } } else { - Responder.followupEphemeral(event, getCommonMsg("error.event.ended", settings)) + event.followupEphemeral(getCommonMsg("error.event.ended", settings)) } - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.event", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.calendar", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.perms.privileged", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.event", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.perms.privileged", settings))) }).then() } @@ -264,7 +254,7 @@ class RsvpCommand : SlashCommand { return Mono.zip(gMono, mMono, cMono, eMono, rMono).flatMap(function { guild, member, calNum, eventId, role -> if (!settings.patronGuild || !settings.devGuild) { - return@function Responder.followupEphemeral(event, getCommonMsg("error.patronOnly", settings)) + return@function event.followupEphemeral(getCommonMsg("error.patronOnly", settings)) } member.hasElevatedPermissions().filter { it }.flatMap { @@ -277,19 +267,14 @@ class RsvpCommand : SlashCommand { .then(calEvent.updateRsvp(rsvp)) .then(RsvpEmbed.list(guild, settings, calEvent)) .flatMap { - Responder.followupEphemeral( - event, - getMessage("role.success.remove", settings), - it - ) + event.followupEphemeral(getMessage("role.success.remove", settings), it) } } else { rsvp.setRole(role) .then(calEvent.updateRsvp(rsvp)) .then(RsvpEmbed.list(guild, settings, calEvent)) .flatMap { - Responder.followupEphemeral( - event, + event.followupEphemeral( getMessage("role.success.set", settings, role.name), it ) @@ -297,11 +282,11 @@ class RsvpCommand : SlashCommand { } } } else { - Responder.followupEphemeral(event, getCommonMsg("error.event.ended", settings)) + event.followupEphemeral(getCommonMsg("error.event.ended", settings)) } - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.event", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.calendar", settings))) - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.perms.elevated", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.event", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.perms.elevated", settings))) }).then() } } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/SettingsCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/SettingsCommand.kt index d732b043..084be356 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/SettingsCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/SettingsCommand.kt @@ -2,12 +2,13 @@ package org.dreamexposure.discal.client.commands import discord4j.core.`object`.command.ApplicationCommandInteractionOptionValue import discord4j.core.event.domain.interaction.ChatInputInteractionEvent -import org.dreamexposure.discal.client.message.Responder import org.dreamexposure.discal.client.message.embed.SettingsEmbed import org.dreamexposure.discal.core.`object`.GuildSettings import org.dreamexposure.discal.core.database.DatabaseManager import org.dreamexposure.discal.core.enums.announcement.AnnouncementStyle import org.dreamexposure.discal.core.enums.time.TimeFormat +import org.dreamexposure.discal.core.extensions.discord4j.followup +import org.dreamexposure.discal.core.extensions.discord4j.followupEphemeral import org.dreamexposure.discal.core.extensions.discord4j.hasElevatedPermissions import org.dreamexposure.discal.core.utils.getCommonMsg import org.springframework.stereotype.Component @@ -32,7 +33,7 @@ class SettingsCommand : SlashCommand { else -> Mono.empty() //Never can reach this, makes compiler happy. } } else { - Responder.followupEphemeral(event, getCommonMsg("error.perms.elevated", settings)).then() + event.followupEphemeral(getCommonMsg("error.perms.elevated", settings)).then() } } } @@ -40,7 +41,7 @@ class SettingsCommand : SlashCommand { private fun viewSubcommand(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { return event.interaction.guild .flatMap { SettingsEmbed.getView(it, settings) } - .flatMap { Responder.followup(event, it) } + .flatMap(event::followup) .then() } @@ -50,8 +51,9 @@ class SettingsCommand : SlashCommand { .flatMap(ApplicationCommandInteractionOptionValue::asRole) .doOnNext { settings.controlRole = it.id.asString() } .flatMap { role -> - DatabaseManager.updateSettings(settings) - .then(Responder.followupEphemeral(event, getMessage("role.success", settings, role.name))) + DatabaseManager.updateSettings(settings).then( + event.followupEphemeral(getMessage("role.success", settings, role.name)) + ) }.then() } @@ -62,10 +64,7 @@ class SettingsCommand : SlashCommand { .doOnNext { settings.announcementStyle = it } .flatMap { DatabaseManager.updateSettings(settings) } .flatMap { - Responder.followupEphemeral( - event, - getMessage("style.success", settings, settings.announcementStyle.name) - ) + event.followupEphemeral(getMessage("style.success", settings, settings.announcementStyle.name)) }.then() } @@ -75,7 +74,7 @@ class SettingsCommand : SlashCommand { .map(ApplicationCommandInteractionOptionValue::asString) .doOnNext { settings.lang = it } .flatMap { DatabaseManager.updateSettings(settings) } - .then(Responder.followupEphemeral(event, getMessage("lang.success", settings))) + .then(event.followupEphemeral(getMessage("lang.success", settings))) .then() } @@ -86,7 +85,7 @@ class SettingsCommand : SlashCommand { .doOnNext { settings.timeFormat = it } .flatMap { DatabaseManager.updateSettings(settings) } .flatMap { - Responder.followupEphemeral(event, getMessage("format.success", settings, settings.timeFormat.name)) + event.followupEphemeral(getMessage("format.success", settings, settings.timeFormat.name)) }.then() } @@ -97,13 +96,12 @@ class SettingsCommand : SlashCommand { .map(ApplicationCommandInteractionOptionValue::asBoolean) .doOnNext { settings.branded = it } .flatMap { - DatabaseManager.updateSettings(settings).then(Responder.followupEphemeral( - event, - getMessage("brand.success", settings, "$it") - )) + DatabaseManager.updateSettings(settings).then( + event.followupEphemeral(getMessage("brand.success", settings, "$it")) + ) }.then() } else { - Responder.followupEphemeral(event, getCommonMsg("error.patronOnly", settings)).then() + event.followupEphemeral(getCommonMsg("error.patronOnly", settings)).then() } } } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/TimeCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/TimeCommand.kt index 0645e2b2..69df7288 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/TimeCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/TimeCommand.kt @@ -2,9 +2,9 @@ package org.dreamexposure.discal.client.commands import discord4j.core.`object`.command.ApplicationCommandInteractionOptionValue import discord4j.core.event.domain.interaction.ChatInputInteractionEvent -import org.dreamexposure.discal.client.message.Responder import org.dreamexposure.discal.client.message.embed.CalendarEmbed import org.dreamexposure.discal.core.`object`.GuildSettings +import org.dreamexposure.discal.core.extensions.discord4j.followupEphemeral import org.dreamexposure.discal.core.utils.getCommonMsg import org.springframework.stereotype.Component import reactor.core.publisher.Mono @@ -23,9 +23,9 @@ class TimeCommand : SlashCommand { .flatMap { calNumber -> event.interaction.guild.flatMap { guild -> CalendarEmbed.getTimeEmbed(guild, settings, calNumber).flatMap { - Responder.followupEphemeral(event, it) + event.followupEphemeral(it) } - }.switchIfEmpty(Responder.followupEphemeral(event, getCommonMsg("error.notFound.calendar", settings))) + }.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings))) }.then() } } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/listeners/discord/SlashCommandListener.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/listeners/discord/SlashCommandListener.kt index de1d3ba4..5956eb85 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/listeners/discord/SlashCommandListener.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/listeners/discord/SlashCommandListener.kt @@ -17,17 +17,17 @@ class SlashCommandListener(applicationContext: ApplicationContext) { } return Flux.fromIterable(cmds) - .filter { it.name == event.commandName } - .next() - .flatMap { command -> - val mono = - if (command.ephemeral) event.acknowledgeEphemeral() - else event.acknowledge() + .filter { it.name == event.commandName } + .next() + .flatMap { command -> + val mono = + if (command.ephemeral) event.deferReply().withEphemeral(true) + else event.deferReply() - mono.then(DatabaseManager.getSettings(event.interaction.guildId.get())) - .flatMap { command.handle(event, it) } - }.doOnError { - LOGGER.error("Unhandled slash command error", it) - }.onErrorResume { Mono.empty() } + mono.then(DatabaseManager.getSettings(event.interaction.guildId.get())) + .flatMap { command.handle(event, it) } + }.doOnError { + LOGGER.error("Unhandled slash command error", it) + }.onErrorResume { Mono.empty() } } } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/message/Responder.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/message/Responder.kt deleted file mode 100644 index 55c61a7a..00000000 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/message/Responder.kt +++ /dev/null @@ -1,66 +0,0 @@ -package org.dreamexposure.discal.client.message - -import discord4j.core.event.domain.interaction.InteractionCreateEvent -import discord4j.core.spec.EmbedCreateSpec -import discord4j.discordjson.json.MessageData -import discord4j.discordjson.json.WebhookExecuteRequest -import discord4j.rest.util.MultipartRequest -import reactor.core.publisher.Mono - -object Responder { - fun followup(event: InteractionCreateEvent, embed: EmbedCreateSpec): Mono { - val spec = WebhookExecuteRequest.builder() - .addEmbed(embed.asRequest()) - .build() - - return sendFollowup(event, spec) - } - - fun followup(event: InteractionCreateEvent, message: String): Mono { - val spec = WebhookExecuteRequest.builder() - .content(message) - .build() - - return sendFollowup(event, spec) - } - - fun followup(event: InteractionCreateEvent, message: String, embed: EmbedCreateSpec): Mono { - val spec = WebhookExecuteRequest.builder() - .content(message) - .addEmbed(embed.asRequest()) - .build() - - return sendFollowup(event, spec) - } - - fun followupEphemeral(event: InteractionCreateEvent, embed: EmbedCreateSpec): Mono { - val spec = WebhookExecuteRequest.builder() - .addEmbed(embed.asRequest()) - .build() - - return sendFollowupEphemeral(event, spec) - } - - fun followupEphemeral(event: InteractionCreateEvent, message: String): Mono { - val spec = WebhookExecuteRequest.builder() - .content(message) - .build() - - return sendFollowupEphemeral(event, spec) - } - - fun followupEphemeral(event: InteractionCreateEvent, message: String, embed: EmbedCreateSpec): Mono { - val spec = WebhookExecuteRequest.builder() - .content(message) - .addEmbed(embed.asRequest()) - .build() - - return sendFollowupEphemeral(event, spec) - } - - private fun sendFollowup(event: InteractionCreateEvent, request: WebhookExecuteRequest) = - event.interactionResponse.createFollowupMessage(MultipartRequest.ofRequest(request)) - - private fun sendFollowupEphemeral(event: InteractionCreateEvent, request: WebhookExecuteRequest) = - event.interactionResponse.createFollowupMessageEphemeral(MultipartRequest.ofRequest(request)) -} diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/enums/event/EventFrequency.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/enums/event/EventFrequency.kt index f6b84e9a..d4818b3b 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/enums/event/EventFrequency.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/enums/event/EventFrequency.kt @@ -1,5 +1,7 @@ package org.dreamexposure.discal.core.enums.event +import java.util.* + enum class EventFrequency { DAILY, WEEKLY, MONTHLY, YEARLY; @@ -12,7 +14,7 @@ enum class EventFrequency { } fun fromValue(value: String): EventFrequency { - return when (value.toUpperCase()) { + return when (value.uppercase(Locale.getDefault())) { "WEEKLY" -> WEEKLY "MONTHLY" -> MONTHLY "YEARLY" -> YEARLY diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/InteractionCreateEvent.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/InteractionCreateEvent.kt new file mode 100644 index 00000000..717d9e3f --- /dev/null +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/InteractionCreateEvent.kt @@ -0,0 +1,60 @@ +package org.dreamexposure.discal.core.extensions.discord4j + +import discord4j.core.`object`.entity.Message +import discord4j.core.event.domain.interaction.InteractionCreateEvent +import discord4j.core.spec.EmbedCreateSpec +import discord4j.core.spec.InteractionFollowupCreateSpec +import reactor.core.publisher.Mono + +fun InteractionCreateEvent.followup(embed: EmbedCreateSpec): Mono { + val spec = InteractionFollowupCreateSpec.builder() + .addEmbed(embed) + .build() + + return this.createFollowup(spec) +} + +fun InteractionCreateEvent.followup(message: String): Mono { + val spec = InteractionFollowupCreateSpec.builder() + .content(message) + .build() + + return this.createFollowup(spec) +} + +fun InteractionCreateEvent.followup(message: String, embed: EmbedCreateSpec): Mono { + val spec = InteractionFollowupCreateSpec.builder() + .content(message) + .addEmbed(embed) + .build() + + return this.createFollowup(spec) +} + +fun InteractionCreateEvent.followupEphemeral(embed: EmbedCreateSpec): Mono { + val spec = InteractionFollowupCreateSpec.builder() + .addEmbed(embed) + .ephemeral(true) + .build() + + return this.createFollowup(spec) +} + +fun InteractionCreateEvent.followupEphemeral(message: String): Mono { + val spec = InteractionFollowupCreateSpec.builder() + .content(message) + .ephemeral(true) + .build() + + return this.createFollowup(spec) +} + +fun InteractionCreateEvent.followupEphemeral(message: String, embed: EmbedCreateSpec): Mono { + val spec = InteractionFollowupCreateSpec.builder() + .content(message) + .addEmbed(embed) + .ephemeral(true) + .build() + + return this.createFollowup(spec) +} diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/object/web/WebGuild.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/object/web/WebGuild.kt index ec316f02..224cb43f 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/object/web/WebGuild.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/object/web/WebGuild.kt @@ -19,6 +19,7 @@ import org.dreamexposure.discal.core.`object`.GuildSettings import org.dreamexposure.discal.core.`object`.announcement.Announcement import org.dreamexposure.discal.core.database.DatabaseManager import org.dreamexposure.discal.core.exceptions.BotNotInGuildException +import org.dreamexposure.discal.core.extensions.discord4j.getMainCalendar import reactor.core.publisher.Mono import reactor.core.publisher.Mono.justOrEmpty import reactor.function.TupleUtils @@ -41,6 +42,7 @@ data class WebGuild( @SerialName("discal_role") var discalRole: Boolean = false, + //TODO: Support multi-cal val calendar: WebCalendar ) { val roles: MutableList = mutableListOf() @@ -79,10 +81,10 @@ data class WebGuild( val announcements = DatabaseManager.getAnnouncements(id) - val calendar = settings.flatMap { s -> - DatabaseManager.getMainCalendar(id) - .flatMap { d -> WebCalendar.fromCalendar(d, s) } - }.defaultIfEmpty(WebCalendar.empty()) + //TODO: Support multi-cal + val calendar = g.getMainCalendar() + .map { it.toWebCalendar() } + .defaultIfEmpty(WebCalendar.empty()) Mono.zip(botNick, settings, roles, webChannels, announcements, calendar) @@ -121,10 +123,10 @@ data class WebGuild( val announcements = DatabaseManager.getAnnouncements(g.id) - val calendar = settings.flatMap { s -> - DatabaseManager.getMainCalendar(Snowflake.of(id)) - .flatMap { d -> WebCalendar.fromCalendar(d, s) } - } + //TODO: Support multi-cal + val calendar = g.getMainCalendar() + .map { it.toWebCalendar() } + .defaultIfEmpty(WebCalendar.empty()) return Mono.zip(botNick, settings, roles, channels, announcements, calendar) .map(TupleUtils.function { bn, s, r, wc, a, c ->