From b9bac2fb67f3e5c1698bb1c275aaa235331661ad Mon Sep 17 00:00:00 2001 From: NovaFox161 Date: Sun, 29 Aug 2021 19:09:37 -0600 Subject: [PATCH] Add `/addcal` slash command This is locked to devs only for right now until I finish the full flow that is planned in #113 I also cleaned up some code in a few places while I was messing with some stuff --- .../discal/client/commands/AddCalCommand.kt | 39 +++++++++ .../discal/core/entities/Calendar.kt | 61 +++++++++----- .../discal/core/entities/Event.kt | 2 + .../core/entities/google/GoogleCalendar.kt | 6 +- .../discal/core/extensions/discord4j/Guild.kt | 4 +- .../core/extensions/discord4j/RestGuild.kt | 79 +++++++++---------- core/src/main/resources/commands/addcal.json | 5 ++ .../i18n/command/addcal/addcal.properties | 8 +- .../src/main/resources/i18n/common.properties | 7 +- 9 files changed, 139 insertions(+), 72 deletions(-) create mode 100644 client/src/main/kotlin/org/dreamexposure/discal/client/commands/AddCalCommand.kt create mode 100644 core/src/main/resources/commands/addcal.json 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 new file mode 100644 index 000000000..dadcc2c49 --- /dev/null +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/AddCalCommand.kt @@ -0,0 +1,39 @@ +package org.dreamexposure.discal.client.commands + +import discord4j.core.`object`.entity.Guild +import discord4j.core.`object`.entity.Member +import discord4j.core.event.domain.interaction.SlashCommandEvent +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.hasElevatedPermissions +import org.dreamexposure.discal.core.utils.getCommonMsg +import org.springframework.stereotype.Component +import reactor.core.publisher.Mono + +@Component +class AddCalCommand : SlashCommand { + override val name = "addcal" + override val ephemeral = true + + override fun handle(event: SlashCommandEvent, settings: GuildSettings): Mono { + //TODO: Remove dev-only and switch to patron-only once this is completed + return if (settings.devGuild) { + Mono.justOrEmpty(event.interaction.member) + .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))) + .then() + } else { + Responder.followupEphemeral(event, getCommonMsg("error.disabled", settings)).then() + } + } + + private fun getLink(settings: GuildSettings): String { + return "${BotSettings.BASE_URL.get()}/dashboard/${settings.guildID.asString()}/calendar/new?type=1&step=0" + } +} diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/entities/Calendar.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/entities/Calendar.kt index 20a157bea..de9752d58 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/entities/Calendar.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/entities/Calendar.kt @@ -5,9 +5,11 @@ import discord4j.core.`object`.entity.Guild import org.dreamexposure.discal.core.`object`.BotSettings import org.dreamexposure.discal.core.`object`.calendar.CalendarData import org.dreamexposure.discal.core.`object`.web.WebCalendar +import org.dreamexposure.discal.core.entities.google.GoogleCalendar import org.dreamexposure.discal.core.entities.response.UpdateCalendarResponse import org.dreamexposure.discal.core.entities.spec.create.CreateEventSpec import org.dreamexposure.discal.core.entities.spec.update.UpdateCalendarSpec +import org.dreamexposure.discal.core.enums.calendar.CalendarHost import org.json.JSONObject import reactor.core.publisher.Flux import reactor.core.publisher.Mono @@ -143,7 +145,7 @@ interface Calendar { * @return A [Flux] of [events][Event] that are happening within the next 24-hour period from the start. */ fun getEventsInNext24HourPeriod(start: Instant): Flux = - getEventsInTimeRange(start, start.plus(1, ChronoUnit.DAYS)) + getEventsInTimeRange(start, start.plus(1, ChronoUnit.DAYS)) /** * Requests to retrieve all [events][Event] within the month starting at the supplied [Instant]. @@ -152,7 +154,7 @@ interface Calendar { * @return A [Flux] of [events][Event] that are happening in the supplied 1-month period. */ fun getEventsInMonth(start: Instant, daysInMonth: Int): Flux = - getEventsInTimeRange(start, start.plus(daysInMonth.toLong(), ChronoUnit.DAYS)) + getEventsInTimeRange(start, start.plus(daysInMonth.toLong(), ChronoUnit.DAYS)) /** * Requests to create an event with the supplied information. @@ -172,29 +174,46 @@ interface Calendar { */ fun toWebCalendar(): WebCalendar { return WebCalendar( - this.calendarId, - this.calendarAddress, - this.calendarNumber, - this.calendarData.host, - this.link, - this.name, - this.description, - this.timezone.id.replace("/", "___"), - this.external + this.calendarId, + this.calendarAddress, + this.calendarNumber, + this.calendarData.host, + this.link, + this.name, + this.description, + this.timezone.id.replace("/", "___"), + this.external ) } fun toJson(): JSONObject { return JSONObject() - .put("guild_id", guildId.asString()) - .put("calendar_id", calendarId) - .put("calendar_address", calendarAddress) - .put("calendar_number", calendarNumber) - .put("host", calendarData.host.name) - .put("external", external) - .put("name", name) - .put("description", description) - .put("timezone", timezone) - .put("link", link) + .put("guild_id", guildId.asString()) + .put("calendar_id", calendarId) + .put("calendar_address", calendarAddress) + .put("calendar_number", calendarNumber) + .put("host", calendarData.host.name) + .put("external", external) + .put("name", name) + .put("description", description) + .put("timezone", timezone) + .put("link", link) + } + + companion object { + /** + * Requests to retrieve the [Calendar] from the provided [CalendarData] + * If an error occurs, it is emitted through the [Mono] + * + * @param data The data object for the Calendar to be built with + * @return A [Mono] containing the [Calendar], if it does not exist, [empty][Mono.empty] is returned. + */ + fun from(data: CalendarData): Mono { + when (data.host) { + CalendarHost.GOOGLE -> { + return GoogleCalendar.get(data) + } + } + } } } diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/entities/Event.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/entities/Event.kt index 09ba9e3e5..0c294deed 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/entities/Event.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/entities/Event.kt @@ -173,4 +173,6 @@ interface Event { .put("rrule", recurrence.toRRule()) .put("image", eventData.imageLink) } + + } diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/entities/google/GoogleCalendar.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/entities/google/GoogleCalendar.kt index df5848e0b..e94ee52d2 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/entities/google/GoogleCalendar.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/entities/google/GoogleCalendar.kt @@ -31,10 +31,10 @@ class GoogleCalendar internal constructor( ) : Calendar { override val name: String - get() = baseCalendar.summary ?: "" + get() = baseCalendar.summary.orEmpty() override val description: String - get() = baseCalendar.description ?: "" + get() = baseCalendar.description.orEmpty() override val timezone: ZoneId get() = ZoneId.of(baseCalendar.timeZone) @@ -143,7 +143,7 @@ class GoogleCalendar internal constructor( confirmed.id, calendarNumber, spec.end.toEpochMilli(), - spec.image ?: "" + spec.image.orEmpty() ) return@flatMap DatabaseManager.updateEventData(data) diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/Guild.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/Guild.kt index 51c0926d4..0ee24cc4b 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/Guild.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/Guild.kt @@ -28,6 +28,8 @@ fun Guild.getSettings(): Mono = getRestGuild().getSettings() */ fun Guild.hasCalendar(): Mono = getRestGuild().hasCalendar() +fun Guild.canAddCalendar(): Mono = getRestGuild().canAddCalendar() + /** * Attempts to retrieve this [Guild]'s main [Calendar] (calendar 1, this guild's first/primary calendar) * If an error occurs, it is emitted through the [Mono] @@ -69,7 +71,7 @@ fun Guild.createCalendar(spec: CreateCalendarSpec): Mono = getRestGuil * If an error occurs, it is emitted through the Mono. * * @param id The ID of the announcement to check for - * @return A Mono, where upon successful completion, returns a boolean as to if the announcement exists or not + * @return A Mono, whereupon successful completion, returns a boolean as to if the announcement exists or not */ fun Guild.announcementExists(id: UUID): Mono = getRestGuild().announcementExists(id) diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/RestGuild.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/RestGuild.kt index 0c421b5db..2fad847b6 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/RestGuild.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/extensions/discord4j/RestGuild.kt @@ -25,15 +25,25 @@ fun RestGuild.getSettings(): Mono = DatabaseManager.getSettings(t //Calendars /** - * Attempts to request whether or not this [Guild] has at least one [Calendar]. + * Attempts to request whether this [Guild] has at least one [Calendar]. * If an error occurs, it is emitted through the [Mono] * - * @return A [Mono] containing whether or not this [Guild] has a [Calendar]. + * @return A [Mono] containing whether this [Guild] has a [Calendar]. */ fun RestGuild.hasCalendar(): Mono { return DatabaseManager.getAllCalendars(this.id).map(List::isNotEmpty) } +fun RestGuild.canAddCalendar(): Mono { + return getAllCalendars() + .count() + .map(Long::toInt) + .flatMap { current -> + if (current == 0) Mono.just(true) + else getSettings().map { current < it.maxCalendars } + } +} + /** * Attempts to retrieve this [Guild]'s main [Calendar] (calendar 1, this guild's first/primary calendar) * If an error occurs, it is emitted through the [Mono] @@ -51,13 +61,8 @@ fun RestGuild.getMainCalendar(): Mono = this.getCalendar(1) * returned. */ fun RestGuild.getCalendar(calNumber: Int): Mono { - return DatabaseManager.getCalendar(this.id, calNumber).flatMap { - when (it.host) { - CalendarHost.GOOGLE -> { - return@flatMap GoogleCalendar.get(it) - } - } - } + return DatabaseManager.getCalendar(this.id, calNumber) + .flatMap(Calendar.Companion::from) } /** @@ -68,14 +73,8 @@ fun RestGuild.getCalendar(calNumber: Int): Mono { */ fun RestGuild.getAllCalendars(): Flux { return DatabaseManager.getAllCalendars(this.id) - .flatMapMany { Flux.fromIterable(it) } - .flatMap { - when (it.host) { - CalendarHost.GOOGLE -> { - return@flatMap GoogleCalendar.get(it) - } - } - } + .flatMapMany { Flux.fromIterable(it) } + .flatMap(Calendar.Companion::from) } /** @@ -97,26 +96,26 @@ fun RestGuild.createCalendar(spec: CreateCalendarSpec): Mono { //Call google to create it CalendarWrapper.createCalendar(googleCal, credId, this.id) - .timeout(Duration.ofSeconds(30)) - .flatMap { confirmed -> - val data = CalendarData( - this.id, - spec.calNumber, - CalendarHost.GOOGLE, - confirmed.id, - confirmed.id, - credId - ) - - val rule = AclRule() - .setScope(AclRule.Scope().setType("default")) - .setRole("reader") - - Mono.`when`( - DatabaseManager.updateCalendar(data), - AclRuleWrapper.insertRule(rule, data) - ).thenReturn(GoogleCalendar(data, confirmed)) - } + .timeout(Duration.ofSeconds(30)) + .flatMap { confirmed -> + val data = CalendarData( + this.id, + spec.calNumber, + CalendarHost.GOOGLE, + confirmed.id, + confirmed.id, + credId + ) + + val rule = AclRule() + .setScope(AclRule.Scope().setType("default")) + .setRole("reader") + + Mono.`when`( + DatabaseManager.updateCalendar(data), + AclRuleWrapper.insertRule(rule, data) + ).thenReturn(GoogleCalendar(data, confirmed)) + } } } } @@ -128,7 +127,7 @@ fun RestGuild.createCalendar(spec: CreateCalendarSpec): Mono { * If an error occurs, it is emitted through the Mono. * * @param id The ID of the announcement to check for - * @return A Mono, where upon successful completion, returns a boolean as to if the announcement exists or not + * @return A Mono, whereupon successful completion, returns a boolean as to if the announcement exists or not */ fun RestGuild.announcementExists(id: UUID): Mono = this.getAnnouncement(id).hasElement() @@ -149,7 +148,7 @@ fun RestGuild.getAnnouncement(id: UUID): Mono = DatabaseManager.ge */ fun RestGuild.getAllAnnouncements(): Flux { return DatabaseManager.getAnnouncements(this.id) - .flatMapMany { Flux.fromIterable(it) } + .flatMapMany { Flux.fromIterable(it) } } /** @@ -160,7 +159,7 @@ fun RestGuild.getAllAnnouncements(): Flux { */ fun RestGuild.getEnabledAnnouncements(): Flux { return DatabaseManager.getEnabledAnnouncements(this.id) - .flatMapMany { Flux.fromIterable(it) } + .flatMapMany { Flux.fromIterable(it) } } fun RestGuild.createAnnouncement(ann: Announcement): Mono = DatabaseManager.updateAnnouncement(ann) diff --git a/core/src/main/resources/commands/addcal.json b/core/src/main/resources/commands/addcal.json new file mode 100644 index 000000000..821039a1e --- /dev/null +++ b/core/src/main/resources/commands/addcal.json @@ -0,0 +1,5 @@ +{ + "name": "addcal", + "description": "Used to add an external (already existing) calendar to DisCal", + "default_permissions": true +} diff --git a/core/src/main/resources/i18n/command/addcal/addcal.properties b/core/src/main/resources/i18n/command/addcal/addcal.properties index f070743f9..bd7253a2c 100644 --- a/core/src/main/resources/i18n/command/addcal/addcal.properties +++ b/core/src/main/resources/i18n/command/addcal/addcal.properties @@ -1,8 +1,4 @@ meta.description=Used to add an external calendar meta.example=/addcal (calendar-id) -response.started=Instructions on how to authorize DisCal and add your calendar have been DMed to you -success=Successfully linked your calendar! you can start creating events, announcements, and more! -failure.notStarted=Please start the process with `/addcal` before selecting a calendar to connect. -failure.invalid=The supplied ID was not recognized, are you sure it is correct? -failure.badArgs=Invalid arguments were supplied. See command info with `/help addcal` -error.hasCalendar=A calendar already exists. View it with `/linkcal` + +response.start=To add an external calendar, please visit this link and follow the provided instructions: {0} diff --git a/core/src/main/resources/i18n/common.properties b/core/src/main/resources/i18n/common.properties index 6211f066f..eb4f565c9 100644 --- a/core/src/main/resources/i18n/common.properties +++ b/core/src/main/resources/i18n/common.properties @@ -9,7 +9,9 @@ error.perms.elevated=This feature requires elevated access (admin/manage server error.perms.privileged=This feature requires privileged access (discal control role). Please contact the server owner\ \ if you believe this is a mistake. -error.disabled=This feature is currently disabled as it is in development. +error.disabled=This feature is currently disabled as it is in development. \n\n\ + Please contact the devs in the support server (`/help`) for more information. \n\ + Sorry for the inconvenience. error.patronOnly=This feature is patron-only at this time. Consider supporting at https://www.patreon.com/Novafox to \ gain access to early access/patron-only features. @@ -18,3 +20,6 @@ error.format.dateTime=The date/time was not formatted correctly, please try agai `yyyy/MM/dd-hh:mm:ss`. error.event.ended=That event has already ended! + +error.calendar.max=The max amount of calendars have already been created. Consider supporting at \ + https://patreon.com/Novafox to unlock a greater limit.