diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/global/EventCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/global/EventCommand.kt index cce5f21a..c7e45ae8 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/global/EventCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/global/EventCommand.kt @@ -172,6 +172,10 @@ class EventCommand(val wizard: Wizard, val staticMessageSrv: StaticMes .map(Long::toInt) .map { it.coerceAtLeast(0).coerceAtMost(59) } .orElse(0) + val keepDuration = event.options[0].getOption("keep-duration") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asBoolean) + .orElse(settings.eventKeepDuration) return Mono.justOrEmpty(event.interaction.member).filterWhen(Member::hasControlRole).flatMap { val pre = wizard.get(settings.guildID) @@ -197,8 +201,13 @@ class EventCommand(val wizard: Wizard, val staticMessageSrv: StaticMes } } else { // Event end already set, make sure everything is in order - if (pre.end!!.isAfter(start)) { + val originalDuration = if (pre.start != null) Duration.between(pre.start, pre.end) else null + val shouldChangeDuration = keepDuration && originalDuration != null + + if (pre.end!!.isAfter(start) || shouldChangeDuration) { pre.start = start + if (shouldChangeDuration) pre.end = start.plus(originalDuration) + if (pre.start!!.isAfter(Instant.now())) { event.interaction.guild .map { EventEmbed.pre(it, settings, pre) } @@ -252,6 +261,10 @@ class EventCommand(val wizard: Wizard, val staticMessageSrv: StaticMes .map(Long::toInt) .map { it.coerceAtLeast(0).coerceAtMost(59) } .orElse(0) + val keepDuration = event.options[0].getOption("keep-duration") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asBoolean) + .orElse(settings.eventKeepDuration) return Mono.justOrEmpty(event.interaction.member).filterWhen(Member::hasControlRole).flatMap { val pre = wizard.get(settings.guildID) @@ -277,8 +290,13 @@ class EventCommand(val wizard: Wizard, val staticMessageSrv: StaticMes } } else { // Event start already set, make sure everything is in order - if (pre.start!!.isBefore(end)) { + val originalDuration = if (pre.end != null) Duration.between(pre.start, pre.end) else null + val shouldChangeDuration = keepDuration && originalDuration != null + + if (pre.start!!.isBefore(end) || shouldChangeDuration) { pre.end = end + if (shouldChangeDuration) pre.start = end.minus(originalDuration) + if (pre.end!!.isAfter(Instant.now())) { event.interaction.guild .map { EventEmbed.pre(it, settings, pre) } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/global/SettingsCommand.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/global/SettingsCommand.kt index c7796346..04587cfd 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/commands/global/SettingsCommand.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/commands/global/SettingsCommand.kt @@ -1,18 +1,18 @@ package org.dreamexposure.discal.client.commands.global +import discord4j.core.event.domain.interaction.ChatInputInteractionEvent import discord4j.core.`object`.command.ApplicationCommandInteractionOption import discord4j.core.`object`.command.ApplicationCommandInteractionOptionValue import discord4j.core.`object`.entity.Message -import discord4j.core.event.domain.interaction.ChatInputInteractionEvent import org.dreamexposure.discal.client.commands.SlashCommand 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.`object`.GuildSettings import org.dreamexposure.discal.core.utils.getCommonMsg import org.springframework.stereotype.Component import reactor.core.publisher.Mono @@ -32,6 +32,7 @@ class SettingsCommand : SlashCommand { "announcement-style" -> announcementStyleSubcommand(event, settings) "language" -> languageSubcommand(event, settings) "time-format" -> timeFormatSubcommand(event, settings) + "event-keep-duration" -> eventKeepDurationSubcommand(event, settings) "branding" -> brandingSubcommand(event, settings) else -> Mono.empty() //Never can reach this, makes compiler happy. } @@ -99,6 +100,18 @@ class SettingsCommand : SlashCommand { .flatMap { event.followupEphemeral(getMessage("format.success", settings, timeFormat.name)) } } + private fun eventKeepDurationSubcommand(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { + val keepDuration = event.options[0].getOption("value") + .flatMap(ApplicationCommandInteractionOption::getValue) + .map(ApplicationCommandInteractionOptionValue::asBoolean) + .get() + + settings.eventKeepDuration = keepDuration + + return DatabaseManager.updateSettings(settings) + .flatMap { event.followupEphemeral(getMessage("eventKeepDuration.success.$keepDuration", settings)) } + } + private fun brandingSubcommand(event: ChatInputInteractionEvent, settings: GuildSettings): Mono { return if (settings.patronGuild) { val useBranding = event.options[0].getOption("use") diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/SettingsEmbed.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/SettingsEmbed.kt index d38956bd..70c94e10 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/SettingsEmbed.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/SettingsEmbed.kt @@ -3,8 +3,8 @@ package org.dreamexposure.discal.client.message.embed import discord4j.core.`object`.entity.Guild import discord4j.core.`object`.entity.Role import discord4j.core.spec.EmbedCreateSpec -import org.dreamexposure.discal.core.`object`.GuildSettings import org.dreamexposure.discal.core.extensions.discord4j.getControlRole +import org.dreamexposure.discal.core.`object`.GuildSettings import reactor.core.publisher.Mono object SettingsEmbed : EmbedMaker { @@ -16,6 +16,7 @@ object SettingsEmbed : EmbedMaker { .addField(getMessage("settings", "view.field.role", settings), roleName, false) .addField(getMessage("settings", "view.field.style", settings), settings.announcementStyle.name, true) .addField(getMessage("settings", "view.field.format", settings), settings.timeFormat.name, true) + .addField(getMessage("settings", "view.field.eventKeepDuration", settings), "${settings.eventKeepDuration}", true) .addField(getMessage("settings", "view.field.lang", settings), settings.getLocale().displayName, false) .addField(getMessage("settings", "view.field.patron", settings), "${settings.patronGuild}", true) .addField(getMessage("settings", "view.field.dev", settings), "${settings.devGuild}", true) diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/database/DatabaseManager.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/database/DatabaseManager.kt index 602e2496..b19c801b 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/database/DatabaseManager.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/database/DatabaseManager.kt @@ -129,7 +129,7 @@ object DatabaseManager { CONTROL_ROLE = ?, ANNOUNCEMENT_STYLE = ?, TIME_FORMAT = ?, LANG = ?, PREFIX = ?, PATRON_GUILD = ?, DEV_GUILD = ?, MAX_CALENDARS = ?, DM_ANNOUNCEMENTS = ?, - BRANDED = ? WHERE GUILD_ID = ? + BRANDED = ?, event_keep_duration = ? WHERE GUILD_ID = ? """.trimMargin() Mono.from( @@ -144,7 +144,8 @@ object DatabaseManager { .bind(7, settings.maxCalendars) .bind(8, settings.getDmAnnouncementsString()) .bind(9, settings.branded) - .bind(10, settings.guildID.asLong()) + .bind(10, settings.eventKeepDuration) + .bind(11, settings.guildID.asLong()) .execute() ).flatMap { res -> Mono.from(res.rowsUpdated) } .hasElement() @@ -152,8 +153,8 @@ object DatabaseManager { } else { val insertCommand = """INSERT INTO ${Tables.GUILD_SETTINGS} (GUILD_ID, CONTROL_ROLE, ANNOUNCEMENT_STYLE, TIME_FORMAT, LANG, PREFIX, - PATRON_GUILD, DEV_GUILD, MAX_CALENDARS, DM_ANNOUNCEMENTS, BRANDED) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + PATRON_GUILD, DEV_GUILD, MAX_CALENDARS, DM_ANNOUNCEMENTS, BRANDED, event_keep_duration) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """.trimMargin() Mono.from( @@ -169,6 +170,7 @@ object DatabaseManager { .bind(8, settings.maxCalendars) .bind(9, settings.getDmAnnouncementsString()) .bind(10, settings.branded) + .bind(11, settings.eventKeepDuration) .execute() ).flatMap { res -> Mono.from(res.rowsUpdated) } .hasElement() @@ -550,10 +552,11 @@ object DatabaseManager { val maxCals = row["MAX_CALENDARS", Int::class.java]!! val dmAnnouncementsString = row["DM_ANNOUNCEMENTS", String::class.java]!! val branded = row["BRANDED", Boolean::class.java]!! + val eventKeepDuration = row["event_keep_duration", Boolean::class.java]!! val settings = GuildSettings( guildId, controlRole, announcementStyle, timeFormat, - lang, prefix, patron, dev, maxCals, branded + lang, prefix, patron, dev, maxCals, branded, eventKeepDuration, ) settings.dmAnnouncements.setFromString(dmAnnouncementsString) diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/object/GuildSettings.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/object/GuildSettings.kt index 1dc7c2d3..7071f57d 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/object/GuildSettings.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/object/GuildSettings.kt @@ -11,29 +11,32 @@ import java.util.* @Serializable data class GuildSettings( - @Serializable(with = SnowflakeAsStringSerializer::class) + @Serializable(with = SnowflakeAsStringSerializer::class) @SerialName("guild_id") val guildID: Snowflake, - @SerialName("control_role") + @SerialName("control_role") var controlRole: String = "everyone", - @SerialName("announcement_style") + @SerialName("announcement_style") var announcementStyle: AnnouncementStyle = AnnouncementStyle.EVENT, - @SerialName("time_format") + @SerialName("time_format") var timeFormat: TimeFormat = TimeFormat.TWENTY_FOUR_HOUR, - var lang: String = "ENGLISH", - var prefix: String = "!", + var lang: String = "ENGLISH", + var prefix: String = "!", - @SerialName("patron_guild") - var patronGuild: Boolean = false, - @SerialName("dev_guild") - var devGuild: Boolean = false, - @SerialName("max_calendars") - var maxCalendars: Int = 1, + @SerialName("patron_guild") + var patronGuild: Boolean = false, + @SerialName("dev_guild") + var devGuild: Boolean = false, + @SerialName("max_calendars") + var maxCalendars: Int = 1, - var branded: Boolean = false, + var branded: Boolean = false, + + @SerialName("event_keep_duration") + var eventKeepDuration: Boolean = false, ) { @SerialName("dm_announcements") val dmAnnouncements: MutableList = mutableListOf() diff --git a/core/src/main/resources/commands/global/event.json b/core/src/main/resources/commands/global/event.json index 618ec133..ca74f5c5 100644 --- a/core/src/main/resources/commands/global/event.json +++ b/core/src/main/resources/commands/global/event.json @@ -250,6 +250,12 @@ "required": false, "min_value": 0, "max_value": 59 + }, + { + "name": "keep-duration", + "type": 5, + "description": "Adjusts the end time to keep the same duration (overrides guild-level setting)", + "required": false } ] }, @@ -440,6 +446,12 @@ "required": false, "min_value": 0, "max_value": 59 + }, + { + "name": "keep-duration", + "type": 5, + "description": "Adjusts the start time to keep the same duration (overrides guild-level setting)", + "required": false } ] }, diff --git a/core/src/main/resources/commands/global/settings.json b/core/src/main/resources/commands/global/settings.json index e8563da4..24427472 100644 --- a/core/src/main/resources/commands/global/settings.json +++ b/core/src/main/resources/commands/global/settings.json @@ -94,6 +94,19 @@ } ] }, + { + "name": "keep-event-duration", + "type": 1, + "description": "Toggles whether to keep an event's duration when changing the start or end time (default false)", + "options": [ + { + "name": "value", + "type": 5, + "description": "Whether to keep an event's duration", + "required": true + } + ] + }, { "name": "branding", "type": 1, diff --git a/core/src/main/resources/db/migration/V26__GuildSettings_KeepDuration.sql b/core/src/main/resources/db/migration/V26__GuildSettings_KeepDuration.sql new file mode 100644 index 00000000..9455d55a --- /dev/null +++ b/core/src/main/resources/db/migration/V26__GuildSettings_KeepDuration.sql @@ -0,0 +1,3 @@ +ALTER TABLE guild_settings + ADD COLUMN event_keep_duration BIT NOT NULL DEFAULT 0 + AFTER announcement_style; diff --git a/core/src/main/resources/i18n/command/settings/settings.properties b/core/src/main/resources/i18n/command/settings/settings.properties index 0291c439..4b10aec3 100644 --- a/core/src/main/resources/i18n/command/settings/settings.properties +++ b/core/src/main/resources/i18n/command/settings/settings.properties @@ -12,3 +12,7 @@ style.success=Announcement Style successfully changed to `{0}`. lang.success=Successfully changed default language! format.success=Time Format successfully changed to `{0}`. brand.success=Server branding has been set to `{0}`. +eventKeepDuration.success.true=Events will keep their duration by default when adjusting the start or end of the event.\n\ + This can be overridden when changing an event's start/end. +eventKeepDuration.success.false=Events will *not* keep their duration by default when adjusting the start or end of the event.\n\ + This can be overridden when changing an event's start/end. diff --git a/core/src/main/resources/i18n/embed/settings.properties b/core/src/main/resources/i18n/embed/settings.properties index 750c7012..05b3cea8 100644 --- a/core/src/main/resources/i18n/embed/settings.properties +++ b/core/src/main/resources/i18n/embed/settings.properties @@ -2,6 +2,7 @@ view.title=DisCal Guild Settings view.field.role=Control Role view.field.style=Announcement Style view.field.format=Time Format +view.field.eventKeepDuration=Keep Event Duration view.field.lang=Language view.field.patron=Patron Guild view.field.dev=Dev Guild