diff --git a/src/main/java/io/github/freya022/botcommands/api/localization/DefaultMessages.java b/src/main/java/io/github/freya022/botcommands/api/localization/DefaultMessages.java
index 041965fcdb..ea7724c61f 100644
--- a/src/main/java/io/github/freya022/botcommands/api/localization/DefaultMessages.java
+++ b/src/main/java/io/github/freya022/botcommands/api/localization/DefaultMessages.java
@@ -42,7 +42,10 @@
*
Refer to {@link Localization} for more details.
*
* @see Localization
+ *
+ * @deprecated This has been replaced by {@link io.github.freya022.botcommands.api.core.messages.DefaultBotCommandsMessages DefaultBotCommandsMessages}
*/
+@Deprecated(since = "3.1.0-beta.1", forRemoval = true)
public final class DefaultMessages {
private final Localization localization;
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/commands/ratelimit/handler/DefaultRateLimitHandler.kt b/src/main/kotlin/io/github/freya022/botcommands/api/commands/ratelimit/handler/DefaultRateLimitHandler.kt
index 69ba9ff6a4..4b70301bfa 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/api/commands/ratelimit/handler/DefaultRateLimitHandler.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/commands/ratelimit/handler/DefaultRateLimitHandler.kt
@@ -7,22 +7,25 @@ import io.github.freya022.botcommands.api.commands.application.ApplicationComman
import io.github.freya022.botcommands.api.commands.ratelimit.RateLimitScope
import io.github.freya022.botcommands.api.commands.text.TextCommandInfo
import io.github.freya022.botcommands.api.core.BContext
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.getService
import io.github.freya022.botcommands.api.core.utils.awaitCatching
import io.github.freya022.botcommands.api.core.utils.namedDefaultScope
import io.github.freya022.botcommands.api.core.utils.runIgnoringResponse
-import io.github.freya022.botcommands.api.localization.DefaultMessages
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel
+import net.dv8tion.jda.api.events.Event
+import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent
import net.dv8tion.jda.api.events.interaction.command.GenericCommandInteractionEvent
import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.interactions.callbacks.IMessageEditCallback
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback
import net.dv8tion.jda.api.requests.ErrorResponse
-import net.dv8tion.jda.api.utils.TimeFormat
+import net.dv8tion.jda.api.utils.messages.MessageCreateData
+import java.time.Instant
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.nanoseconds
@@ -35,7 +38,7 @@ private val deleteScope = namedDefaultScope("Rate limit message delete", 1)
* then it is sent to the user's DMs, or returns if not possible.
* - Interactions are simply replying an ephemeral message to the user.
*
- * All messages sent to the user are localized messages from [DefaultMessages] and will be deleted when expired.
+ * All messages sent to the user are localized messages from [BotCommandsMessages] and will be deleted when expired.
*
* **Note:** The rate limit message won't be deleted in a private channel,
* or if the [refill delay][ConsumptionProbe.nanosToWaitForRefill] is longer than 10 minutes.
@@ -59,8 +62,8 @@ class DefaultRateLimitHandler(
event.guildChannel.canTalk() -> event.channel
else -> event.author.openPrivateChannel().await()
}
- val messages = context.getService().get(event)
- val content = getRateLimitMessage(messages, probe)
+ val messages = context.getService().get(event)
+ val content = getRateLimitMessage(event, messages, probe)
runIgnoringResponse(ErrorResponse.CANNOT_SEND_TO_USER) {
val messageId = channel.sendMessage(content).await().idLong
@@ -80,7 +83,7 @@ class DefaultRateLimitHandler(
commandInfo: ApplicationCommandInfo,
probe: ConsumptionProbe
) where T : GenericCommandInteractionEvent, T : IReplyCallback {
- onRateLimit(context, event, probe)
+ onRateLimit0(context, event, probe)
}
override suspend fun onRateLimit(
@@ -88,12 +91,17 @@ class DefaultRateLimitHandler(
event: T,
probe: ConsumptionProbe
) where T : GenericComponentInteractionCreateEvent, T : IReplyCallback, T : IMessageEditCallback {
- onRateLimit(context, event as IReplyCallback, probe)
+ onRateLimit0(context, event, probe)
}
- private suspend fun onRateLimit(context: BContext, event: IReplyCallback, probe: ConsumptionProbe) {
- val messages = context.getService().get(event)
- val content = getRateLimitMessage(messages, probe)
+ private suspend fun onRateLimit0(
+ context: BContext,
+ event: T,
+ probe: ConsumptionProbe
+ ) where T : GenericInteractionCreateEvent,
+ T : IReplyCallback {
+ val messages = context.getService().get(event)
+ val content = getRateLimitMessage(event, messages, probe)
val hook = event.reply(content).setEphemeral(true).await()
// Only schedule delete if the interaction hook doesn't expire before
// Technically this is supposed to be 15 minutes but, just to be safe
@@ -106,16 +114,17 @@ class DefaultRateLimitHandler(
}
private fun getRateLimitMessage(
- messages: DefaultMessages,
+ event: Event,
+ messages: BotCommandsMessages,
probe: ConsumptionProbe
- ): String {
- val timestamp = TimeFormat.RELATIVE.atTimestamp(System.currentTimeMillis() + probe.nanosToWaitForRefill.floorDiv(1_000_000))
+ ): MessageCreateData {
+ val deadline = Instant.now().plusNanos(probe.nanosToWaitForRefill)
return when (scope) {
- RateLimitScope.USER -> messages.getUserRateLimitMsg(timestamp)
- RateLimitScope.USER_PER_GUILD -> messages.getUserRateLimitMsg(timestamp)
- RateLimitScope.USER_PER_CHANNEL -> messages.getUserRateLimitMsg(timestamp)
- RateLimitScope.GUILD -> messages.getGuildRateLimitMsg(timestamp)
- RateLimitScope.CHANNEL -> messages.getChannelRateLimitMsg(timestamp)
+ RateLimitScope.USER -> messages.userRateLimited(event, deadline)
+ RateLimitScope.USER_PER_GUILD -> messages.userRateLimited(event, deadline)
+ RateLimitScope.USER_PER_CHANNEL -> messages.userRateLimited(event, deadline)
+ RateLimitScope.GUILD -> messages.guildRateLimited(event, deadline)
+ RateLimitScope.CHANNEL -> messages.channelRateLimited(event, deadline)
}
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BTextConfig.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BTextConfig.kt
index a548157652..8253734b03 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BTextConfig.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/config/BTextConfig.kt
@@ -4,9 +4,9 @@ import dev.freya02.jda.emojis.unicode.Emojis
import io.github.freya022.botcommands.api.commands.text.IHelpCommand
import io.github.freya022.botcommands.api.commands.text.TextPrefixSupplier
import io.github.freya022.botcommands.api.commands.text.annotations.RequiresTextCommands
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
import io.github.freya022.botcommands.api.core.service.annotations.InjectedService
import io.github.freya022.botcommands.api.core.utils.toImmutableList
-import io.github.freya022.botcommands.api.localization.DefaultMessages
import io.github.freya022.botcommands.internal.core.config.ConfigDSL
import io.github.freya022.botcommands.internal.core.config.ConfigurationValue
import net.dv8tion.jda.api.entities.emoji.Emoji
@@ -78,7 +78,7 @@ interface BTextConfig {
/**
* Emoji used to indicate a user that their DMs are closed.
*
- * This is only used if [the closed DMs error message][DefaultMessages.getClosedDMErrorMsg] can't be sent.
+ * This is only used if [the closed DMs error message][BotCommandsMessages.closedDirectMessages] can't be sent.
*
* Default: `mailbox_closed`
*
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/BotCommandsMessages.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/BotCommandsMessages.kt
new file mode 100644
index 0000000000..f805db00ac
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/BotCommandsMessages.kt
@@ -0,0 +1,107 @@
+package io.github.freya022.botcommands.api.core.messages
+
+import io.github.freya022.botcommands.api.commands.application.slash.options.SlashCommandOption
+import io.github.freya022.botcommands.api.commands.text.TopLevelTextCommandInfo
+import net.dv8tion.jda.api.Permission
+import net.dv8tion.jda.api.entities.channel.attribute.IAgeRestrictedChannel
+import net.dv8tion.jda.api.events.GenericEvent
+import net.dv8tion.jda.api.utils.messages.MessageCreateData
+import java.time.Instant
+
+/**
+ * Returns the messages used by the framework, instances are produced by [BotCommandsMessagesFactory].
+ *
+ * @see BotCommandsMessagesFactory
+ */
+interface BotCommandsMessages {
+
+ /**
+ * @return Message to display when an uncaught exception occurs
+ */
+ fun uncaughtException(event: GenericEvent?): MessageCreateData
+
+ /**
+ * @return Message to display when the user is missing [permissions][io.github.freya022.botcommands.api.commands.annotations.UserPermissions]
+ */
+ fun missingUserPermissions(event: GenericEvent?, permissions: Set): MessageCreateData
+
+ /**
+ * @return Message to display when the bot is missing [permissions][io.github.freya022.botcommands.api.commands.annotations.BotPermissions]
+ */
+ fun missingBotPermissions(event: GenericEvent?, permissions: Set): MessageCreateData
+
+ /**
+ * @return Message to display when a text command is [only usable by the owner][io.github.freya022.botcommands.api.commands.text.annotations.RequireOwner]
+ */
+ fun ownerOnly(event: GenericEvent?): MessageCreateData
+
+ /**
+ * @return Message to display when a user has exceeded a command's [rate limit][io.github.freya022.botcommands.api.commands.annotations.RateLimit]
+ */
+ fun userRateLimited(event: GenericEvent?, deadline: Instant): MessageCreateData
+
+ /**
+ * @return Message to display when a channel has exceeded a command's [rate limit][io.github.freya022.botcommands.api.commands.annotations.RateLimit]
+ */
+ fun channelRateLimited(event: GenericEvent?, deadline: Instant): MessageCreateData
+
+ /**
+ * @return Message to display when a guild has exceeded a command's [rate limit][io.github.freya022.botcommands.api.commands.annotations.RateLimit]
+ */
+ fun guildRateLimited(event: GenericEvent?, deadline: Instant): MessageCreateData
+
+ /**
+ * @return Message to display when application commands are not loaded on the guild yet
+ */
+ fun applicationCommandsNotAvailable(event: GenericEvent?): MessageCreateData
+
+ /**
+ * @return Message to display when the command is not found
+ */
+ fun commandNotFound(event: GenericEvent?, suggestions: Collection): MessageCreateData
+
+ /**
+ * @return Message to display when a channel parameter could not be resolved
+ */
+ fun resolverChannelNotFound(event: GenericEvent?, channelId: Long): MessageCreateData
+
+ /**
+ * @return Message to display when a channel parameter could be resolved but is not accessible (such as private threads)
+ */
+ fun resolverChannelMissingAccess(event: GenericEvent?, channelId: Long): MessageCreateData
+
+ /**
+ * @return Message to display when a user parameter could not be resolved
+ */
+ fun resolverUserNotFound(event: GenericEvent?, userId: Long): MessageCreateData
+
+ /**
+ * @return Message to display when a slash command option is unresolvable (only in slash command interactions)
+ */
+ fun slashCommandUnresolvableOption(event: GenericEvent?, option: SlashCommandOption): MessageCreateData
+
+ /**
+ * @return Message to display when a User's DMs are closed (when sending help content for example)
+ */
+ fun closedDirectMessages(event: GenericEvent?): MessageCreateData
+
+ /**
+ * @return Message to display when a command is used in a NSFW [IAgeRestrictedChannel] (see [@NSFW][io.github.freya022.botcommands.api.commands.text.annotations.NSFW])
+ */
+ fun nsfwOnly(event: GenericEvent?): MessageCreateData
+
+ /**
+ * @return Message to display when a user tries to use a component it isn't allowed to interact with
+ */
+ fun componentNotAllowed(event: GenericEvent?): MessageCreateData
+
+ /**
+ * @return Message to display when a user tries to use a component which does not exist anymore
+ */
+ fun componentExpired(event: GenericEvent?): MessageCreateData
+
+ /**
+ * @return Message to display when a user tries to use a modal which has reached timeout
+ */
+ fun modalExpired(event: GenericEvent?): MessageCreateData
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/BotCommandsMessagesFactory.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/BotCommandsMessagesFactory.kt
new file mode 100644
index 0000000000..1cfaa5f566
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/BotCommandsMessagesFactory.kt
@@ -0,0 +1,43 @@
+@file:Suppress("DEPRECATION")
+
+package io.github.freya022.botcommands.api.core.messages
+
+import io.github.freya022.botcommands.api.core.service.annotations.InterfacedService
+import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
+import io.github.freya022.botcommands.api.localization.interaction.UserLocaleProvider
+import io.github.freya022.botcommands.api.localization.text.TextCommandLocaleProvider
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent
+import net.dv8tion.jda.api.interactions.Interaction
+import java.util.*
+
+/**
+ * Factory of [BotCommandsMessages], the default implementation is [DefaultBotCommandsMessagesFactory], or,
+ * if a non-default [DefaultMessagesFactory] exists, an adapter is used.
+ *
+ * ### Complete customization
+ *
+ * Returning a [BotCommandsMessagesFactory] from a service factory will disable the default implementation,
+ * this will let you return a completely custom instance,
+ * in which you can craft entirely custom messages in any way you see fit.
+ */
+@InterfacedService(acceptMultiple = false)
+interface BotCommandsMessagesFactory {
+ /**
+ * Retrieves a [BotCommandsMessages] instance for the given locale.
+ */
+ fun get(locale: Locale): BotCommandsMessages
+
+ /**
+ * Retrieves a [BotCommandsMessages] instance, with the locale derived from this event.
+ *
+ * By default, this uses [TextCommandLocaleProvider] to get the locale.
+ */
+ fun get(event: MessageReceivedEvent): BotCommandsMessages
+
+ /**
+ * Retrieves a [BotCommandsMessages] instance, with the locale derived from this interaction.
+ *
+ * By default, this uses [UserLocaleProvider] to get the locale.
+ */
+ fun get(event: Interaction): BotCommandsMessages
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/DefaultBotCommandsMessages.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/DefaultBotCommandsMessages.kt
new file mode 100644
index 0000000000..187593966d
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/DefaultBotCommandsMessages.kt
@@ -0,0 +1,122 @@
+package io.github.freya022.botcommands.api.core.messages
+
+import io.github.freya022.botcommands.api.commands.application.slash.options.SlashCommandOption
+import io.github.freya022.botcommands.api.commands.text.TopLevelTextCommandInfo
+import io.github.freya022.botcommands.api.core.messages.exceptions.MissingMessageTemplateException
+import io.github.freya022.botcommands.api.localization.Localization
+import io.github.freya022.botcommands.api.localization.LocalizationService
+import io.github.freya022.botcommands.api.localization.LocalizationTemplate
+import io.github.freya022.botcommands.api.localization.PermissionLocalization
+import io.github.freya022.botcommands.api.localization.localize
+import io.github.freya022.botcommands.internal.utils.throwArgument
+import net.dv8tion.jda.api.Permission
+import net.dv8tion.jda.api.events.GenericEvent
+import net.dv8tion.jda.api.utils.TimeFormat
+import net.dv8tion.jda.api.utils.messages.MessageCreateData
+import java.time.Instant
+import java.util.*
+import kotlin.to
+
+/**
+ * Default implementation of [BotCommandsMessages],
+ * see [DefaultBotCommandsMessagesFactory] for more details.
+ *
+ * @see DefaultBotCommandsMessagesFactory
+ */
+open class DefaultBotCommandsMessages(
+ protected val permissionLocalization: PermissionLocalization,
+ localizationService: LocalizationService,
+ protected val locale: Locale,
+ bundleName: String,
+) : BotCommandsMessages {
+
+ protected val localization: Localization = localizationService.getInstance(bundleName, locale)
+ ?: throwArgument("Could not find localization files for '$bundleName'")
+
+ override fun uncaughtException(event: GenericEvent?): MessageCreateData {
+ return getLocalizationTemplate("uncaught_exception").localize().toMessage()
+ }
+
+ override fun missingUserPermissions(event: GenericEvent?, permissions: Set): MessageCreateData {
+ val localizedPermissions = permissions.joinToString(separator = ", ") { permissionLocalization.localize(it, locale) }
+ return getLocalizationTemplate("missing.permissions.user").localize("permissions" to localizedPermissions).toMessage()
+ }
+
+ override fun missingBotPermissions(event: GenericEvent?, permissions: Set): MessageCreateData {
+ val localizedPermissions = permissions.joinToString(separator = ", ") { permissionLocalization.localize(it, locale) }
+ return getLocalizationTemplate("missing.permissions.bot").localize("permissions" to localizedPermissions).toMessage()
+ }
+
+ override fun ownerOnly(event: GenericEvent?): MessageCreateData {
+ return getLocalizationTemplate("owner_only").localize().toMessage()
+ }
+
+ override fun userRateLimited(event: GenericEvent?, deadline: Instant): MessageCreateData {
+ val args = "timestamp" to TimeFormat.RELATIVE.atInstant(deadline)
+ return getLocalizationTemplate("ratelimited.user").localize(args).toMessage()
+ }
+
+ override fun channelRateLimited(event: GenericEvent?, deadline: Instant): MessageCreateData {
+ val timestamp = TimeFormat.RELATIVE.atInstant(deadline)
+ return getLocalizationTemplate("ratelimited.channel").localize("timestamp" to timestamp).toMessage()
+ }
+
+ override fun guildRateLimited(event: GenericEvent?, deadline: Instant): MessageCreateData {
+ val timestamp = TimeFormat.RELATIVE.atInstant(deadline)
+ return getLocalizationTemplate("ratelimited.guild").localize("timestamp" to timestamp).toMessage()
+ }
+
+ override fun applicationCommandsNotAvailable(event: GenericEvent?): MessageCreateData {
+ return getLocalizationTemplate("commands.application.not_available").localize().toMessage()
+ }
+
+ override fun commandNotFound(event: GenericEvent?, suggestions: Collection): MessageCreateData {
+ val suggestionsStr = suggestions.joinToString(separator = "**, **", prefix = "**", postfix = "**") { it.name }
+ return getLocalizationTemplate("commands.text.not_found").localize("suggestions" to suggestionsStr).toMessage()
+ }
+
+ override fun resolverChannelNotFound(event: GenericEvent?, channelId: Long): MessageCreateData {
+ return getLocalizationTemplate("resolver.channel.not_found").localize("channel_id" to channelId).toMessage()
+ }
+
+ override fun resolverChannelMissingAccess(event: GenericEvent?, channelId: Long): MessageCreateData {
+ return getLocalizationTemplate("resolver.channel.missing_access").localize("channel_id" to channelId).toMessage()
+ }
+
+ override fun resolverUserNotFound(event: GenericEvent?, userId: Long): MessageCreateData {
+ return getLocalizationTemplate("resolver.user.not_found").localize("user_id" to userId).toMessage()
+ }
+
+ override fun slashCommandUnresolvableOption(event: GenericEvent?, option: SlashCommandOption): MessageCreateData {
+ return getLocalizationTemplate("commands.slash.option.unresolvable").localize("option_name" to option.discordName).toMessage()
+ }
+
+ override fun closedDirectMessages(event: GenericEvent?): MessageCreateData {
+ return getLocalizationTemplate("direct_messages.closed").localize().toMessage()
+ }
+
+ override fun nsfwOnly(event: GenericEvent?): MessageCreateData {
+ return getLocalizationTemplate("nsfw_only").localize().toMessage()
+ }
+
+ override fun componentNotAllowed(event: GenericEvent?): MessageCreateData {
+ return getLocalizationTemplate("components.not_allowed").localize().toMessage()
+ }
+
+ override fun componentExpired(event: GenericEvent?): MessageCreateData {
+ return getLocalizationTemplate("components.expired").localize().toMessage()
+ }
+
+ override fun modalExpired(event: GenericEvent?): MessageCreateData {
+ return getLocalizationTemplate("modals.expired").localize().toMessage()
+ }
+
+ protected fun getLocalizationTemplate(path: String): LocalizationTemplate {
+ val template = localization[path]
+ ?: throw MissingMessageTemplateException("Template '$path' could not be found, available keys: ${localization.keys}")
+
+ return template
+ }
+
+ protected fun String.toMessage(): MessageCreateData = MessageCreateData.fromContent(this)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/DefaultBotCommandsMessagesFactory.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/DefaultBotCommandsMessagesFactory.kt
new file mode 100644
index 0000000000..9ab28e83b0
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/DefaultBotCommandsMessagesFactory.kt
@@ -0,0 +1,59 @@
+package io.github.freya022.botcommands.api.core.messages
+
+import io.github.freya022.botcommands.api.localization.Localization
+import io.github.freya022.botcommands.api.localization.LocalizationService
+import io.github.freya022.botcommands.api.localization.PermissionLocalization
+import io.github.freya022.botcommands.api.localization.interaction.UserLocaleProvider
+import io.github.freya022.botcommands.api.localization.text.TextCommandLocaleProvider
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent
+import net.dv8tion.jda.api.interactions.Interaction
+import java.util.*
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+/**
+ * Default implementation of [BotCommandsMessagesFactory].
+ *
+ * Instances returned by this factory are [DefaultBotCommandsMessages],
+ * which uses [LocalizationService] to get a localization bundle using the provided [bundleName].
+ *
+ * By default, an instance of this factory is registered using the default bundle name (`BotCommandsMessages`),
+ * you can override the default factory by providing a corresponding service factory.
+ *
+ * ### Light customization / Supporting more locales
+ *
+ * With the default values, the localization templates would be loaded from `/bc_localization/BotCommandsMessages-default.json`,
+ * you may change the values by creating a new `BotCommandsMessages.json`,
+ * you can also support more locales following by appending an underscore and the [language tag][Locale.toLanguageTag],
+ * such as `BotCommandsMessages_fr.json`.
+ *
+ * The localization paths must be identical to those used by [DefaultBotCommandsMessages],
+ * but the placeholders can be modified but must keep the same names, they can also be removed.
+ *
+ * Refer to [Localization] for mode customization details.
+ *
+ * @see Localization
+ */
+class DefaultBotCommandsMessagesFactory(
+ private val permissionLocalization: PermissionLocalization,
+ private val localizationService: LocalizationService,
+ private val textCommandLocaleProvider: TextCommandLocaleProvider,
+ private val userLocaleProvider: UserLocaleProvider,
+ private val bundleName: String = "BotCommandsMessages",
+) : BotCommandsMessagesFactory {
+
+ private val cache: MutableMap = hashMapOf()
+ private val lock = ReentrantLock()
+
+ override fun get(locale: Locale): DefaultBotCommandsMessages {
+ cache[locale]?.let { return it }
+
+ return lock.withLock {
+ cache.getOrPut(locale) { DefaultBotCommandsMessages(permissionLocalization, localizationService, locale, bundleName) }
+ }
+ }
+
+ override fun get(event: MessageReceivedEvent): DefaultBotCommandsMessages = get(textCommandLocaleProvider.getLocale(event))
+
+ override fun get(event: Interaction): DefaultBotCommandsMessages = get(userLocaleProvider.getLocale(event))
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/exceptions/MissingMessageTemplateException.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/exceptions/MissingMessageTemplateException.kt
new file mode 100644
index 0000000000..b17b6c19e6
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/messages/exceptions/MissingMessageTemplateException.kt
@@ -0,0 +1,7 @@
+package io.github.freya022.botcommands.api.core.messages.exceptions
+
+/**
+ * Exception thrown when a localization template could not be found
+ * by a [BotCommandsMessages][io.github.freya022.botcommands.api.core.messages.BotCommandsMessages] instance.
+ */
+class MissingMessageTemplateException(message: String) : IllegalArgumentException(message)
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/service/annotations/InterfacedService.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/service/annotations/InterfacedService.kt
index 253377a60c..107fafee43 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/api/core/service/annotations/InterfacedService.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/service/annotations/InterfacedService.kt
@@ -14,8 +14,8 @@ import io.github.freya022.botcommands.api.components.ComponentInteractionFilter
import io.github.freya022.botcommands.api.core.*
import io.github.freya022.botcommands.api.core.db.ConnectionSupplier
import io.github.freya022.botcommands.api.core.db.query.ParametrizedQueryFactory
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.ServiceContainer
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.api.localization.arguments.factories.FormattableArgumentFactory
import io.github.freya022.botcommands.api.localization.providers.LocalizationMapProvider
import io.github.freya022.botcommands.api.localization.readers.LocalizationMapReader
@@ -37,7 +37,7 @@ import io.github.freya022.botcommands.api.localization.readers.LocalizationMapRe
* @see ICoroutineEventManagerSupplier
* @see JDAService
*
- * @see DefaultMessagesFactory
+ * @see BotCommandsMessagesFactory
*
* @see GlobalExceptionHandler
*
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/core/utils/JDA.kt b/src/main/kotlin/io/github/freya022/botcommands/api/core/utils/JDA.kt
index 8696177c57..92362e47f5 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/api/core/utils/JDA.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/core/utils/JDA.kt
@@ -1,3 +1,5 @@
+@file:Suppress("removal", "DEPRECATION")
+
package io.github.freya022.botcommands.api.core.utils
import dev.minn.jda.ktx.coroutines.await
@@ -5,7 +7,7 @@ import dev.minn.jda.ktx.messages.InlineMessage
import dev.minn.jda.ktx.messages.MessageCreate
import dev.minn.jda.ktx.messages.MessageEdit
import io.github.freya022.botcommands.api.core.exceptions.InvalidChannelTypeException
-import io.github.freya022.botcommands.api.localization.DefaultMessages
+import io.github.freya022.botcommands.api.localization.PermissionLocalization
import io.github.freya022.botcommands.internal.utils.deferredRestAction
import io.github.freya022.botcommands.internal.utils.takeIfFinite
import net.dv8tion.jda.api.JDA
@@ -240,9 +242,9 @@ inline fun suppressContentWarning(block: () -> R): R {
/**
* Computes the missing permissions from the specified permission holder,
- * If you plan on showing them, be sure to use [DefaultMessages.getPermission]
+ * if you plan on showing them, be sure to use [PermissionLocalization.localize].
*
- * @see DefaultMessages.getPermission
+ * @see PermissionLocalization.localize
*/
fun getMissingPermissions(requiredPerms: EnumSet, permissionHolder: IPermissionHolder, channel: GuildChannel): Set =
EnumSet.copyOf(requiredPerms).also { it.removeAll(permissionHolder.getPermissions(channel)) }
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/localization/DefaultMessagesFactory.kt b/src/main/kotlin/io/github/freya022/botcommands/api/localization/DefaultMessagesFactory.kt
index 0b2064aa7b..0a52848b2c 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/api/localization/DefaultMessagesFactory.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/localization/DefaultMessagesFactory.kt
@@ -1,3 +1,5 @@
+@file:Suppress("removal", "DEPRECATION")
+
package io.github.freya022.botcommands.api.localization
import io.github.freya022.botcommands.api.core.service.annotations.BService
@@ -15,6 +17,7 @@ import java.util.*
*
* @see InterfacedService @InterfacedService
*/
+@Deprecated("Replaced by BotCommandsMessagesFactory")
@InterfacedService(acceptMultiple = false)
interface DefaultMessagesFactory {
/**
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/localization/DefaultPermissionLocalization.kt b/src/main/kotlin/io/github/freya022/botcommands/api/localization/DefaultPermissionLocalization.kt
new file mode 100644
index 0000000000..20cc7ddd2b
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/localization/DefaultPermissionLocalization.kt
@@ -0,0 +1,44 @@
+package io.github.freya022.botcommands.api.localization
+
+import net.dv8tion.jda.api.Permission
+import java.util.*
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * Default implementation for [PermissionLocalization].
+ *
+ * The translations are queried from [bundleName],
+ * and the keys used are the enum name of the permission.
+ *
+ * The returned translations will try to use the nearest available locale,
+ * with the last fallback being [Permission.getName].
+ *
+ * ### Light customization / Supporting more locales
+ *
+ * To support more locales, (here we supposed the [bundleName] is `Permissions`) you may create a new `Permissions.json`
+ * following by appending an underscore and the [language tag][Locale.toLanguageTag],
+ * such as `Permissions_fr.json`.
+ *
+ * The localization paths must be equal to [Permission.getName].
+ *
+ * Refer to [Localization] for mode customization details.
+ */
+open class DefaultPermissionLocalization(
+ protected val localizationService: LocalizationService,
+ protected val bundleName: String = "Permissions",
+) : PermissionLocalization {
+
+ private val cache: MutableMap = ConcurrentHashMap()
+
+ override fun localize(permission: Permission, locale: Locale): String {
+ val key = CacheKey(permission, locale)
+ return cache.getOrPut(key) {
+ val permissionsLocalization: Localization? = localizationService.getInstance(bundleName, locale)
+
+ @Suppress("UsePropertyAccessSyntax") // `permission.name` targets Enum#name() which is definitely not the same
+ permissionsLocalization?.get(permission.name)?.localize() ?: permission.getName()
+ }
+ }
+
+ private data class CacheKey(val permission: Permission, val locale: Locale)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/localization/LocalizableAction.kt b/src/main/kotlin/io/github/freya022/botcommands/api/localization/LocalizableAction.kt
index 257a4e2623..7ec9e0c85d 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/api/localization/LocalizableAction.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/localization/LocalizableAction.kt
@@ -1,6 +1,8 @@
package io.github.freya022.botcommands.api.localization
import io.github.freya022.botcommands.api.core.config.BLocalizationConfig
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.localization.context.LocalizationContext
import io.github.freya022.botcommands.api.localization.context.PairEntry
import io.github.freya022.botcommands.api.localization.context.mapToEntries
@@ -43,8 +45,17 @@ interface LocalizableAction {
*
* @see DefaultMessagesFactory
*/
+ @Suppress("DEPRECATION", "removal")
+ @Deprecated("Replaced with getBotCommandsMessages()")
fun getDefaultMessages(): DefaultMessages
+ /**
+ * Retrieves a [BotCommandsMessages] instance, using a locale suitable for messages sent to the user.
+ *
+ * @see BotCommandsMessagesFactory
+ */
+ fun getBotCommandsMessages(): BotCommandsMessages
+
/**
* Returns the localized message at the following [path][localizationPath],
* using the provided locale and parameters.
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/localization/PermissionLocalization.kt b/src/main/kotlin/io/github/freya022/botcommands/api/localization/PermissionLocalization.kt
new file mode 100644
index 0000000000..c06b9b2dc7
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/localization/PermissionLocalization.kt
@@ -0,0 +1,21 @@
+package io.github.freya022.botcommands.api.localization
+
+import io.github.freya022.botcommands.api.core.service.annotations.InterfacedService
+import net.dv8tion.jda.api.Permission
+import java.util.*
+
+/**
+ * Utility service to translate [Permission] names, a [DefaultPermissionLocalization] instance is available by default,
+ * but can be overridden if necessary, using a service factory.
+ */
+@InterfacedService(acceptMultiple = false)
+interface PermissionLocalization {
+
+ /**
+ * Returns the given permission's name with the requested locale.
+ *
+ * If no translation is available for the requested locale,
+ * it is permitted to return fallbacks.
+ */
+ fun localize(permission: Permission, locale: Locale): String
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/api/parameters/resolvers/SlashParameterResolver.kt b/src/main/kotlin/io/github/freya022/botcommands/api/parameters/resolvers/SlashParameterResolver.kt
index 1ed134d94e..436128aaae 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/api/parameters/resolvers/SlashParameterResolver.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/api/parameters/resolvers/SlashParameterResolver.kt
@@ -6,7 +6,7 @@ import io.github.freya022.botcommands.api.commands.application.slash.annotations
import io.github.freya022.botcommands.api.commands.application.slash.autocomplete.annotations.AutocompleteHandler
import io.github.freya022.botcommands.api.commands.application.slash.options.SlashCommandOption
import io.github.freya022.botcommands.api.commands.application.slash.options.builder.SlashCommandOptionBuilder
-import io.github.freya022.botcommands.api.localization.DefaultMessages
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
import io.github.freya022.botcommands.api.parameters.ParameterResolver
import net.dv8tion.jda.api.entities.Guild
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent
@@ -57,7 +57,7 @@ interface SlashParameterResolver : IParameterResolver
* and you should reply if this is a [SlashCommandInteractionEvent].
*
* If the interaction is not replied to,
- * the handler sends an [unresolvable option error message][DefaultMessages.getSlashCommandUnresolvableOptionMsg].
+ * the handler sends an [unresolvable option error message][BotCommandsMessages.slashCommandUnresolvableOption].
*
* @param option The option currently being resolved
* @param event The corresponding event, could be a [SlashCommandInteractionEvent] or a [CommandAutoCompleteInteractionEvent]
@@ -74,7 +74,7 @@ interface SlashParameterResolver : IParameterResolver
* and you should reply if this is a [SlashCommandInteractionEvent].
*
* If the interaction is not replied to,
- * the handler sends an [unresolvable option error message][DefaultMessages.getSlashCommandUnresolvableOptionMsg].
+ * the handler sends an [unresolvable option error message][BotCommandsMessages.slashCommandUnresolvableOption].
*
* @param option The option currently being resolved
* @param event The corresponding event, could be a [SlashCommandInteractionEvent] or a [CommandAutoCompleteInteractionEvent]
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/commands/application/ApplicationCommandListener.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/commands/application/ApplicationCommandListener.kt
index a3ae3f41ab..76d0243ea7 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/commands/application/ApplicationCommandListener.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/commands/application/ApplicationCommandListener.kt
@@ -1,6 +1,5 @@
package io.github.freya022.botcommands.internal.commands.application
-import dev.minn.jda.ktx.messages.reply_
import io.github.freya022.botcommands.api.commands.Usability.UnusableReason
import io.github.freya022.botcommands.api.commands.application.ApplicationCommandFilter
import io.github.freya022.botcommands.api.commands.application.annotations.RequiresApplicationCommands
@@ -16,10 +15,11 @@ import io.github.freya022.botcommands.api.core.BContext
import io.github.freya022.botcommands.api.core.annotations.BEventListener
import io.github.freya022.botcommands.api.core.checkFilters
import io.github.freya022.botcommands.api.core.entities.inputUser
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.annotations.BService
import io.github.freya022.botcommands.api.core.service.getService
import io.github.freya022.botcommands.api.core.utils.getMissingPermissions
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.internal.commands.application.cache.factory.ApplicationCommandsCacheFactory
import io.github.freya022.botcommands.internal.commands.application.context.message.MessageCommandInfoImpl
import io.github.freya022.botcommands.internal.commands.application.context.user.UserCommandInfoImpl
@@ -40,6 +40,8 @@ import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionE
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+import net.dv8tion.jda.api.interactions.Interaction
+import net.dv8tion.jda.api.utils.messages.MessageCreateData
private val logger = KotlinLogging.logger { }
@@ -48,7 +50,7 @@ private val logger = KotlinLogging.logger { }
internal class ApplicationCommandListener internal constructor(
private val context: BContext,
private val applicationCommandsBuilder: ApplicationCommandsBuilder,
- private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
private val localizableInteractionFactory: LocalizableInteractionFactory,
private val rateLimitHandler: RateLimitHandler,
filters: List,
@@ -144,7 +146,7 @@ internal class ApplicationCommandListener internal constructor(
} else {
logger.debug { "Ignored '${event.fullCommandName}' as guild (${guild!!.id}) commands could not be updated" }
}
- return event.reply_(defaultMessagesFactory.get(event).applicationCommandsNotAvailableMsg, ephemeral = true).queue()
+ return event.reply(messagesFactory.get(event).applicationCommandsNotAvailable(event)).setEphemeral(true).queue()
}
//This is done so warnings are printed after the exception
@@ -225,29 +227,31 @@ internal class ApplicationCommandListener internal constructor(
exceptionHandler.handleException(event, e, "application command '${event.commandString}'", emptyMap(), logLevel)
if (e is InsufficientPermissionException) {
- event.replyExceptionMessage(defaultMessagesFactory.get(event).getBotPermErrorMsg(setOf(e.permission)))
+ event.replyExceptionMessage(messagesFactory.get(event).missingBotPermissions(event, setOf(e.permission)))
} else {
- event.replyExceptionMessage(defaultMessagesFactory.get(event).generalErrorMsg)
+ event.replyExceptionMessage(messagesFactory.get(event).uncaughtException(event))
}
}
private suspend fun canRun(event: GenericCommandInteractionEvent, applicationCommand: ApplicationCommandInfoImpl): Boolean {
val usability = applicationCommand.getUsability(event.inputUser, event.messageChannel)
if (usability.isNotUsable) {
- val errorMessage: String = when (usability.bestReason) {
- UnusableReason.OWNER_ONLY -> defaultMessagesFactory.get(event).ownerOnlyErrorMsg
- UnusableReason.USER_PERMISSIONS -> {
- val member = event.member ?: throwInternal("USER_PERMISSIONS got checked even if guild is null")
- val missingPermissions = getMissingPermissions(applicationCommand.userPermissions, member, event.guildChannel)
- defaultMessagesFactory.get(event).getUserPermErrorMsg(missingPermissions)
- }
- UnusableReason.BOT_PERMISSIONS -> {
- val guild = event.guild ?: throwInternal("BOT_PERMISSIONS got checked even if guild is null")
- val missingPermissions = getMissingPermissions(applicationCommand.botPermissions, guild.selfMember, event.guildChannel)
- defaultMessagesFactory.get(event).getBotPermErrorMsg(missingPermissions)
+ val errorMessage = fromMessages(event) {
+ when (usability.bestReason) {
+ UnusableReason.OWNER_ONLY -> ownerOnly(event)
+ UnusableReason.USER_PERMISSIONS -> {
+ val member = event.member ?: throwInternal("USER_PERMISSIONS got checked even if guild is null")
+ val missingPermissions = getMissingPermissions(applicationCommand.userPermissions, member, event.guildChannel)
+ missingUserPermissions(event, missingPermissions)
+ }
+ UnusableReason.BOT_PERMISSIONS -> {
+ val guild = event.guild ?: throwInternal("BOT_PERMISSIONS got checked even if guild is null")
+ val missingPermissions = getMissingPermissions(applicationCommand.botPermissions, guild.selfMember, event.guildChannel)
+ missingBotPermissions(event, missingPermissions)
+ }
+ UnusableReason.NSFW_ONLY -> throwInternal("Discord already handles NSFW commands")
+ UnusableReason.HIDDEN -> throwInternal("Application commands can't be hidden")
}
- UnusableReason.NSFW_ONLY -> throwInternal("Discord already handles NSFW commands")
- UnusableReason.HIDDEN -> throwInternal("Application commands can't be hidden")
}
reply(event, errorMessage)
return false
@@ -268,10 +272,15 @@ internal class ApplicationCommandListener internal constructor(
return true
}
- private fun reply(event: GenericCommandInteractionEvent, msg: String) {
- event.reply_(msg, ephemeral = true)
+ private fun reply(event: GenericCommandInteractionEvent, message: MessageCreateData) {
+ event.reply(message)
+ .setEphemeral(true)
.queue(null) { throwable ->
exceptionHandler.handleException(event, throwable, "interaction reply", emptyMap())
}
}
+
+ private inline fun fromMessages(event: Interaction, crossinline block: BotCommandsMessages.() -> MessageCreateData): MessageCreateData {
+ return messagesFactory.get(event).run(block)
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/commands/application/slash/SlashCommandInfoImpl.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/commands/application/slash/SlashCommandInfoImpl.kt
index 6e82e6bc07..95959175ca 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/commands/application/slash/SlashCommandInfoImpl.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/commands/application/slash/SlashCommandInfoImpl.kt
@@ -1,14 +1,13 @@
package io.github.freya022.botcommands.internal.commands.application.slash
-import dev.minn.jda.ktx.messages.reply_
import io.github.freya022.botcommands.api.commands.INamedCommand
import io.github.freya022.botcommands.api.commands.application.slash.GlobalSlashEvent
import io.github.freya022.botcommands.api.commands.application.slash.GuildSlashEvent
import io.github.freya022.botcommands.api.commands.application.slash.SlashCommandInfo
import io.github.freya022.botcommands.api.core.BContext
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.getService
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.internal.*
import io.github.freya022.botcommands.internal.commands.application.ApplicationCommandInfoImpl
import io.github.freya022.botcommands.internal.commands.application.options.ApplicationGeneratedOption
@@ -156,11 +155,10 @@ private fun onUnresolvableOption(
else -> {
//Only use the generic message if the user didn't handle this situation
if (!event.isAcknowledged && event is SlashCommandInteractionEvent) {
- val defaultMessages = option.context.getService().get(event)
- event.reply_(
- defaultMessages.getSlashCommandUnresolvableOptionMsg(option.discordName),
- ephemeral = true
- ).queue()
+ val defaultMessages = option.context.getService().get(event)
+ event.reply(defaultMessages.slashCommandUnresolvableOption(event, option))
+ .setEphemeral(true)
+ .queue()
}
InsertOptionResult.ABORT
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/commands/ratelimit/handler/RateLimitHandler.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/commands/ratelimit/handler/RateLimitHandler.kt
index ab3f2d1052..a1067f024a 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/commands/ratelimit/handler/RateLimitHandler.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/commands/ratelimit/handler/RateLimitHandler.kt
@@ -1,14 +1,13 @@
package io.github.freya022.botcommands.internal.commands.ratelimit.handler
-import dev.minn.jda.ktx.messages.reply_
import io.github.bucket4j.Bucket
import io.github.freya022.botcommands.api.commands.ratelimit.CancellableRateLimit
import io.github.freya022.botcommands.api.core.BContext
import io.github.freya022.botcommands.api.core.BotOwners
import io.github.freya022.botcommands.api.core.config.BConfig
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.annotations.BService
import io.github.freya022.botcommands.api.core.utils.loggerOf
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.internal.commands.application.ApplicationCommandInfoImpl
import io.github.freya022.botcommands.internal.commands.ratelimit.CancellableRateLimitImpl
import io.github.freya022.botcommands.internal.commands.ratelimit.NullCancellableRateLimit
@@ -28,7 +27,7 @@ internal class RateLimitHandler internal constructor(
private val context: BContext,
private val botOwners: BotOwners,
private val rateLimitContainer: RateLimitContainer,
- private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
config: BConfig,
) {
private val enableOwnerBypass = config.enableOwnerBypass
@@ -91,8 +90,7 @@ internal class RateLimitHandler internal constructor(
val rateLimitInfo = rateLimitContainer[group]
?: run {
componentsListenerLogger.warn { "Could not find a rate limiter named '$group'" }
- val defaultMessages = defaultMessagesFactory.get(event)
- event.reply_(defaultMessages.componentExpiredErrorMsg, ephemeral = true).queue()
+ event.reply(messagesFactory.get(event).componentExpired(event)).setEphemeral(true).queue()
return
}
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/commands/text/HelpCommand.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/commands/text/HelpCommand.kt
index f761a19e5f..507af8c933 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/commands/text/HelpCommand.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/commands/text/HelpCommand.kt
@@ -7,6 +7,7 @@ import io.github.freya022.botcommands.api.commands.text.annotations.RequiresText
import io.github.freya022.botcommands.api.commands.text.provider.TextCommandManager
import io.github.freya022.botcommands.api.commands.text.provider.TextCommandProvider
import io.github.freya022.botcommands.api.core.config.BTextConfig
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.ConditionalServiceChecker
import io.github.freya022.botcommands.api.core.service.ServiceContainer
import io.github.freya022.botcommands.api.core.service.annotations.BService
@@ -14,7 +15,6 @@ import io.github.freya022.botcommands.api.core.service.annotations.ConditionalSe
import io.github.freya022.botcommands.api.core.service.getInterfacedServices
import io.github.freya022.botcommands.api.core.service.getService
import io.github.freya022.botcommands.api.core.utils.*
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.internal.commands.text.TextUtils.getSpacedPath
import io.github.freya022.botcommands.internal.core.BContextImpl
import io.github.freya022.botcommands.internal.utils.reference
@@ -44,14 +44,14 @@ internal open class BuiltInHelpCommandProvider {
@ConditionalOnMissingBean(IHelpCommand::class)
internal open fun builtInHelpCommand(
context: BContextImpl,
- defaultMessagesFactory: DefaultMessagesFactory,
+ messagesFactory: BotCommandsMessagesFactory,
textCommandsContext: TextCommandsContext,
helpBuilderConsumer: HelpBuilderConsumer?,
- ) = HelpCommand(context, defaultMessagesFactory, textCommandsContext, helpBuilderConsumer)
+ ) = HelpCommand(context, messagesFactory, textCommandsContext, helpBuilderConsumer)
}
internal class HelpCommand internal constructor(
private val context: BContextImpl,
- private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
private val textCommandsContext: TextCommandsContext,
private val helpBuilderConsumer: HelpBuilderConsumer?
) : IHelpCommand, TextCommandProvider {
@@ -118,7 +118,7 @@ internal class HelpCommand internal constructor(
// Ignore and reply in channel/react if we can't send to DMs
.handle(ErrorResponse.CANNOT_SEND_TO_USER) {
if (event.channel.canTalk())
- event.respond(defaultMessagesFactory.get(event).closedDMErrorMsg).await()
+ event.channel.sendMessage(messagesFactory.get(event).closedDirectMessages(event)).await()
else if (hasReactionPermissions)
// May throw REACTION_BLOCKED
event.message.addReaction(context.textConfig.dmClosedEmoji).await()
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/commands/text/TextCommandsListener.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/commands/text/TextCommandsListener.kt
index 493a42b3ad..3d47aaa461 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/commands/text/TextCommandsListener.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/commands/text/TextCommandsListener.kt
@@ -10,6 +10,8 @@ import io.github.freya022.botcommands.api.core.JDAService
import io.github.freya022.botcommands.api.core.annotations.BEventListener
import io.github.freya022.botcommands.api.core.checkFilters
import io.github.freya022.botcommands.api.core.config.BTextConfig
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.ConditionalServiceChecker
import io.github.freya022.botcommands.api.core.service.ServiceContainer
import io.github.freya022.botcommands.api.core.service.annotations.BService
@@ -17,7 +19,6 @@ import io.github.freya022.botcommands.api.core.service.annotations.ConditionalSe
import io.github.freya022.botcommands.api.core.service.getService
import io.github.freya022.botcommands.api.core.service.getServiceOrNull
import io.github.freya022.botcommands.api.core.utils.*
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.internal.commands.ratelimit.handler.RateLimitHandler
import io.github.freya022.botcommands.internal.commands.text.TextCommandsListener.Status.*
import io.github.freya022.botcommands.internal.core.ExceptionHandler
@@ -32,6 +33,7 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException
import net.dv8tion.jda.api.requests.ErrorResponse
import net.dv8tion.jda.api.requests.GatewayIntent
+import net.dv8tion.jda.api.utils.messages.MessageCreateData
private val logger = KotlinLogging.logger { }
private val spacePattern = Regex("\\s+")
@@ -42,7 +44,7 @@ private val spacePattern = Regex("\\s+")
@ConditionalService(TextCommandsListener.ActivationCondition::class)
internal class TextCommandsListener internal constructor(
private val context: BContext,
- private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
private val textCommandsContext: TextCommandsContextImpl,
private val localizableTextCommandFactory: LocalizableTextCommandFactory,
private val rateLimitHandler: RateLimitHandler,
@@ -132,9 +134,9 @@ internal class TextCommandsListener internal constructor(
private suspend fun handleException(event: MessageReceivedEvent, e: Throwable, msg: String) {
exceptionHandler.handleException(event, e, "text command '$msg'", mapOf("Message" to event.jumpUrl))
if (e is InsufficientPermissionException) {
- replyError(event, defaultMessagesFactory.get(event).getBotPermErrorMsg(setOf(e.permission)))
+ replyError(event, messagesFactory.get(event).missingBotPermissions(event, setOf(e.permission)))
} else {
- replyError(event, defaultMessagesFactory.get(event).generalErrorMsg)
+ replyError(event, messagesFactory.get(event).uncaughtException(event))
}
}
@@ -175,19 +177,22 @@ internal class TextCommandsListener internal constructor(
val usability = commandInfo.getUsability(member, event.guildChannel)
if (usability.isNotUsable) {
- val errorMessage: String = when (usability.bestReason) {
- UnusableReason.HIDDEN -> throwInternal("Hidden commands should have been ignored by ${TextCommandsListener::findCommandWithArgs.shortSignature}")
- UnusableReason.OWNER_ONLY -> defaultMessagesFactory.get(event).ownerOnlyErrorMsg
- UnusableReason.USER_PERMISSIONS -> {
- val missingPermissions = getMissingPermissions(commandInfo.userPermissions, member, event.guildChannel)
- defaultMessagesFactory.get(event).getUserPermErrorMsg(missingPermissions)
- }
- UnusableReason.BOT_PERMISSIONS -> {
- val missingPermissions = getMissingPermissions(commandInfo.botPermissions, event.guild.selfMember, event.guildChannel)
- defaultMessagesFactory.get(event).getBotPermErrorMsg(missingPermissions)
+ val errorMessage = fromMessages(event) {
+ when (usability.bestReason) {
+ UnusableReason.HIDDEN -> throwInternal("Hidden commands should have been ignored by ${TextCommandsListener::findCommandWithArgs.shortSignature}")
+ UnusableReason.OWNER_ONLY -> ownerOnly(event)
+ UnusableReason.USER_PERMISSIONS -> {
+ val missingPermissions = getMissingPermissions(commandInfo.userPermissions, member, event.guildChannel)
+ missingUserPermissions(event, missingPermissions)
+ }
+ UnusableReason.BOT_PERMISSIONS -> {
+ val missingPermissions = getMissingPermissions(commandInfo.botPermissions, event.guild.selfMember, event.guildChannel)
+ missingBotPermissions(event, missingPermissions)
+ }
+ UnusableReason.NSFW_ONLY -> nsfwOnly(event)
}
- UnusableReason.NSFW_ONLY -> defaultMessagesFactory.get(event).nsfwOnlyErrorMsg
}
+
replyError(event, errorMessage)
return false
}
@@ -218,13 +223,13 @@ internal class TextCommandsListener internal constructor(
return ExecutionResult.OK
}
- private suspend fun replyError(event: MessageReceivedEvent, msg: String) {
+ private suspend fun replyError(event: MessageReceivedEvent, message: MessageCreateData) {
val channel = when {
event.guildChannel.canTalk() -> event.channel
else -> event.author.openPrivateChannel().await()
}
- channel.sendMessage(msg)
+ channel.sendMessage(message)
.awaitCatching()
.handle(ErrorResponse.CANNOT_SEND_TO_USER) {
event.message.addReaction(context.textConfig.dmClosedEmoji).await()
@@ -240,11 +245,14 @@ internal class TextCommandsListener internal constructor(
val suggestions = suggestionSupplier.getSuggestions(commandName, candidates)
if (suggestions.isNotEmpty()) {
- val suggestionsStr = suggestions.joinToString("**, **", "**", "**") { it.name }
- replyError(event, defaultMessagesFactory.get(event).getCommandNotFoundMsg(suggestionsStr))
+ replyError(event, messagesFactory.get(event).commandNotFound(event, suggestions))
}
}
+ private inline fun fromMessages(event: MessageReceivedEvent, crossinline block: BotCommandsMessages.() -> MessageCreateData): MessageCreateData {
+ return messagesFactory.get(event).run(block)
+ }
+
internal enum class Status {
/** Disabled by config */
DISABLED,
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/components/controller/ComponentsListener.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/components/controller/ComponentsListener.kt
index 20b1fac4ed..6d59872149 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/components/controller/ComponentsListener.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/components/controller/ComponentsListener.kt
@@ -1,6 +1,5 @@
package io.github.freya022.botcommands.internal.components.controller
-import dev.minn.jda.ktx.messages.reply_
import io.github.freya022.botcommands.api.commands.ratelimit.CancellableRateLimit
import io.github.freya022.botcommands.api.components.ComponentInteractionFilter
import io.github.freya022.botcommands.api.components.Components
@@ -13,9 +12,9 @@ import io.github.freya022.botcommands.api.core.Filter
import io.github.freya022.botcommands.api.core.annotations.BEventListener
import io.github.freya022.botcommands.api.core.checkFilters
import io.github.freya022.botcommands.api.core.config.BComponentsConfigBuilder
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.annotations.BService
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.internal.commands.ratelimit.handler.RateLimitHandler
import io.github.freya022.botcommands.internal.components.data.ActionComponentData
import io.github.freya022.botcommands.internal.components.data.PersistentComponentData
@@ -36,7 +35,7 @@ private val logger = KotlinLogging.logger { }
@RequiresComponents
internal class ComponentsListener(
private val context: BContext,
- private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
private val localizableInteractionFactory: LocalizableInteractionFactory,
private val rateLimitHandler: RateLimitHandler,
filters: List,
@@ -61,13 +60,13 @@ internal class ComponentsListener(
ComponentController.parseComponentId(id)
}
val component = componentController.getActiveComponent(componentId)
- ?: return@launch event.reply_(defaultMessagesFactory.get(event).componentExpiredErrorMsg, ephemeral = true).queue()
+ ?: return@launch event.reply(messagesFactory.get(event).componentExpired(event)).setEphemeral(true).queue()
if (component !is ActionComponentData)
throwInternal("Somehow retrieved a non-executable component on a component interaction: $component")
if (component.filters === ComponentFilters.INVALID_FILTERS) {
- return@launch event.reply_(defaultMessagesFactory.get(event).componentNotAllowedErrorMsg, ephemeral = true).queue()
+ return@launch event.reply(messagesFactory.get(event).componentNotAllowed(event)).setEphemeral(true).queue()
}
component.filters.onEach { filter ->
@@ -101,7 +100,7 @@ internal class ComponentsListener(
component: ActionComponentData
): Boolean {
if (!component.constraints.isAllowed(event)) {
- event.reply_(defaultMessagesFactory.get(event).componentNotAllowedErrorMsg, ephemeral = true).queue()
+ event.reply(messagesFactory.get(event).componentNotAllowed(event)).setEphemeral(true).queue()
return false
}
@@ -139,9 +138,9 @@ internal class ComponentsListener(
"Component" to event.component
))
if (e is InsufficientPermissionException) {
- event.replyExceptionMessage(defaultMessagesFactory.get(event).getBotPermErrorMsg(setOf(e.permission)))
+ event.replyExceptionMessage(messagesFactory.get(event).missingBotPermissions(event, setOf(e.permission)))
} else {
- event.replyExceptionMessage(defaultMessagesFactory.get(event).generalErrorMsg)
+ event.replyExceptionMessage(messagesFactory.get(event).uncaughtException(event))
}
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/components/handler/ComponentHandlerExecutor.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/components/handler/ComponentHandlerExecutor.kt
index 61b2ffe05a..682efb3ce6 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/components/handler/ComponentHandlerExecutor.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/components/handler/ComponentHandlerExecutor.kt
@@ -1,15 +1,14 @@
package io.github.freya022.botcommands.internal.components.handler
-import dev.minn.jda.ktx.messages.reply_
import io.github.freya022.botcommands.api.components.annotations.JDAButtonListener
import io.github.freya022.botcommands.api.components.annotations.JDASelectMenuListener
import io.github.freya022.botcommands.api.components.annotations.RequiresComponents
import io.github.freya022.botcommands.api.components.event.EntitySelectEvent
import io.github.freya022.botcommands.api.components.event.StringSelectEvent
import io.github.freya022.botcommands.api.components.serialization.SerializedComponentData
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.annotations.BService
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.internal.components.ComponentType
import io.github.freya022.botcommands.internal.components.data.ActionComponentData
import io.github.freya022.botcommands.internal.components.data.EphemeralComponentData
@@ -33,7 +32,7 @@ private val logger = KotlinLogging.logger { }
@BService
@RequiresComponents
internal class ComponentHandlerExecutor internal constructor(
- private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
private val componentHandlerContainer: ComponentHandlerContainer,
) {
internal suspend fun runHandler(component: ActionComponentData, event: GenericComponentInteractionCreateEvent): Boolean {
@@ -61,7 +60,7 @@ internal class ComponentHandlerExecutor internal constructor(
Component raw data: $userData
""".trimIndent()
}
- event.reply_(defaultMessagesFactory.get(event).componentExpiredErrorMsg, ephemeral = true).queue()
+ event.reply(messagesFactory.get(event).componentExpired(event)).setEphemeral(true).queue()
return false
}
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/messages/BotCommandsMessagesDefaultMessagesAdapter.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/core/messages/BotCommandsMessagesDefaultMessagesAdapter.kt
new file mode 100644
index 0000000000..c5634e18b2
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/core/messages/BotCommandsMessagesDefaultMessagesAdapter.kt
@@ -0,0 +1,93 @@
+@file:Suppress("removal", "DEPRECATION")
+
+package io.github.freya022.botcommands.internal.core.messages
+
+import io.github.freya022.botcommands.api.commands.application.slash.options.SlashCommandOption
+import io.github.freya022.botcommands.api.commands.text.TopLevelTextCommandInfo
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
+import io.github.freya022.botcommands.api.localization.DefaultMessages
+import net.dv8tion.jda.api.Permission
+import net.dv8tion.jda.api.events.GenericEvent
+import net.dv8tion.jda.api.utils.TimeFormat
+import net.dv8tion.jda.api.utils.messages.MessageCreateData
+import java.time.Instant
+
+internal class BotCommandsMessagesDefaultMessagesAdapter internal constructor(
+ private val defaultMessages: DefaultMessages,
+) : BotCommandsMessages {
+
+ override fun uncaughtException(event: GenericEvent?): MessageCreateData {
+ return defaultMessages.generalErrorMsg.toMessage()
+ }
+
+ override fun missingUserPermissions(event: GenericEvent?, permissions: Set): MessageCreateData {
+ return defaultMessages.getUserPermErrorMsg(permissions).toMessage()
+ }
+
+ override fun missingBotPermissions(event: GenericEvent?, permissions: Set): MessageCreateData {
+ return defaultMessages.getBotPermErrorMsg(permissions).toMessage()
+ }
+
+ override fun ownerOnly(event: GenericEvent?): MessageCreateData {
+ return defaultMessages.ownerOnlyErrorMsg.toMessage()
+ }
+
+ override fun userRateLimited(event: GenericEvent?, deadline: Instant): MessageCreateData {
+ return defaultMessages.getUserRateLimitMsg(TimeFormat.RELATIVE.atInstant(deadline)).toMessage()
+ }
+
+ override fun channelRateLimited(event: GenericEvent?, deadline: Instant): MessageCreateData {
+ return defaultMessages.getChannelRateLimitMsg(TimeFormat.RELATIVE.atInstant(deadline)).toMessage()
+ }
+
+ override fun guildRateLimited(event: GenericEvent?, deadline: Instant): MessageCreateData {
+ return defaultMessages.getGuildRateLimitMsg(TimeFormat.RELATIVE.atInstant(deadline)).toMessage()
+ }
+
+ override fun applicationCommandsNotAvailable(event: GenericEvent?): MessageCreateData {
+ return defaultMessages.applicationCommandsNotAvailableMsg.toMessage()
+ }
+
+ override fun commandNotFound(event: GenericEvent?, suggestions: Collection): MessageCreateData {
+ val suggestionsStr = suggestions.joinToString(separator = "**, **", prefix = "**", postfix = "**") { it.name }
+ return defaultMessages.getCommandNotFoundMsg(suggestionsStr).toMessage()
+ }
+
+ override fun resolverChannelNotFound(event: GenericEvent?, channelId: Long): MessageCreateData {
+ return defaultMessages.resolverChannelNotFoundMsg.toMessage()
+ }
+
+ override fun resolverChannelMissingAccess(event: GenericEvent?, channelId: Long): MessageCreateData {
+ return defaultMessages.getResolverChannelMissingAccessMsg("<#$channelId>").toMessage()
+ }
+
+ override fun resolverUserNotFound(event: GenericEvent?, userId: Long): MessageCreateData {
+ return defaultMessages.resolverUserNotFoundMsg.toMessage()
+ }
+
+ override fun slashCommandUnresolvableOption(event: GenericEvent?, option: SlashCommandOption): MessageCreateData {
+ return defaultMessages.getSlashCommandUnresolvableOptionMsg(option.discordName).toMessage()
+ }
+
+ override fun closedDirectMessages(event: GenericEvent?): MessageCreateData {
+ return defaultMessages.closedDMErrorMsg.toMessage()
+ }
+
+ override fun nsfwOnly(event: GenericEvent?): MessageCreateData {
+ return defaultMessages.nsfwOnlyErrorMsg.toMessage()
+ }
+
+ override fun componentNotAllowed(event: GenericEvent?): MessageCreateData {
+ return defaultMessages.componentNotAllowedErrorMsg.toMessage()
+ }
+
+ override fun componentExpired(event: GenericEvent?): MessageCreateData {
+ return defaultMessages.componentExpiredErrorMsg.toMessage()
+ }
+
+ override fun modalExpired(event: GenericEvent?): MessageCreateData {
+ return defaultMessages.modalExpiredErrorMsg.toMessage()
+ }
+
+ private fun String.toMessage(): MessageCreateData = MessageCreateData.fromContent(this)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/messages/BotCommandsMessagesFactoryDefaultMessagesFactoryAdapter.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/core/messages/BotCommandsMessagesFactoryDefaultMessagesFactoryAdapter.kt
new file mode 100644
index 0000000000..9e397b590d
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/core/messages/BotCommandsMessagesFactoryDefaultMessagesFactoryAdapter.kt
@@ -0,0 +1,27 @@
+@file:Suppress("removal", "DEPRECATION")
+
+package io.github.freya022.botcommands.internal.core.messages
+
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
+import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent
+import net.dv8tion.jda.api.interactions.Interaction
+import java.util.*
+
+internal class BotCommandsMessagesFactoryDefaultMessagesFactoryAdapter internal constructor(
+ private val defaultMessagesFactory: DefaultMessagesFactory,
+) : BotCommandsMessagesFactory {
+
+ override fun get(locale: Locale): BotCommandsMessages {
+ return BotCommandsMessagesDefaultMessagesAdapter(defaultMessagesFactory.get(locale))
+ }
+
+ override fun get(event: MessageReceivedEvent): BotCommandsMessages {
+ return BotCommandsMessagesDefaultMessagesAdapter(defaultMessagesFactory.get(event))
+ }
+
+ override fun get(event: Interaction): BotCommandsMessages {
+ return BotCommandsMessagesDefaultMessagesAdapter(defaultMessagesFactory.get(event))
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/core/messages/BotCommandsMessagesFactoryProvider.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/core/messages/BotCommandsMessagesFactoryProvider.kt
new file mode 100644
index 0000000000..0cc86de046
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/core/messages/BotCommandsMessagesFactoryProvider.kt
@@ -0,0 +1,76 @@
+@file:Suppress("DEPRECATION")
+
+package io.github.freya022.botcommands.internal.core.messages
+
+import io.github.classgraph.ClassGraph
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
+import io.github.freya022.botcommands.api.core.messages.DefaultBotCommandsMessagesFactory
+import io.github.freya022.botcommands.api.core.service.ConditionalServiceChecker
+import io.github.freya022.botcommands.api.core.service.ServiceContainer
+import io.github.freya022.botcommands.api.core.service.annotations.BService
+import io.github.freya022.botcommands.api.core.service.annotations.ConditionalService
+import io.github.freya022.botcommands.api.core.service.getInterfacedServiceTypes
+import io.github.freya022.botcommands.api.core.utils.simpleNestedName
+import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
+import io.github.freya022.botcommands.api.localization.LocalizationService
+import io.github.freya022.botcommands.api.localization.PermissionLocalization
+import io.github.freya022.botcommands.api.localization.interaction.UserLocaleProvider
+import io.github.freya022.botcommands.api.localization.text.TextCommandLocaleProvider
+import io.github.freya022.botcommands.internal.localization.FallbackDefaultMessagesFactory
+import io.github.freya022.botcommands.internal.utils.classRef
+import io.github.oshai.kotlinlogging.KotlinLogging
+import org.springframework.boot.autoconfigure.AutoConfiguration
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
+import org.springframework.context.annotation.Bean
+
+private val logger = KotlinLogging.logger { }
+
+@BService
+@AutoConfiguration
+internal open class BotCommandsMessagesFactoryProvider internal constructor() {
+
+ @Bean
+ @ConditionalOnMissingBean(BotCommandsMessagesFactory::class)
+ @BService
+ @ConditionalService(ActivationCondition::class)
+ open fun botCommandsMessagesFactory(
+ defaultMessagesFactory: DefaultMessagesFactory,
+ permissionLocalization: PermissionLocalization,
+ localizationService: LocalizationService,
+ textCommandLocaleProvider: TextCommandLocaleProvider,
+ userLocaleProvider: UserLocaleProvider,
+ ): BotCommandsMessagesFactory {
+ // Check if the user has a custom factory or if the fallback factory has customized files
+ if (defaultMessagesFactory !is FallbackDefaultMessagesFactory || hasCustomDefaultMessages()) {
+ logger.warn { "${classRef()} has been deprecated and will be removed in the full release." }
+ return BotCommandsMessagesFactoryDefaultMessagesFactoryAdapter(defaultMessagesFactory)
+ }
+
+ return DefaultBotCommandsMessagesFactory(permissionLocalization, localizationService, textCommandLocaleProvider, userLocaleProvider)
+ }
+
+ private fun hasCustomDefaultMessages(): Boolean {
+ // The base name is guaranteed to be "DefaultMessages" as it is hardcoded in [[DefaultMessages]]
+ return ClassGraph()
+ .acceptPathsNonRecursive("bc_localization")
+ .scan()
+ .use { scan ->
+ scan.allResources
+ .any {
+ val path = it.path
+ path.startsWith("bc_localization/DefaultMessages") && !path.startsWith("bc_localization/DefaultMessages-default")
+ }
+ }
+ }
+
+ internal object ActivationCondition : ConditionalServiceChecker {
+ override fun checkServiceAvailability(serviceContainer: ServiceContainer, checkedClass: Class<*>): String? {
+ val types = serviceContainer.getInterfacedServiceTypes()
+ if (types.isNotEmpty()) {
+ return "An user supplied ${classRef()} is already active (${types.first().simpleNestedName})"
+ }
+
+ return null
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/DefaultDefaultMessagesFactoryProvider.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/DefaultDefaultMessagesFactoryProvider.kt
index 0572fef420..fe33f4b4e7 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/DefaultDefaultMessagesFactoryProvider.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/DefaultDefaultMessagesFactoryProvider.kt
@@ -1,3 +1,5 @@
+@file:Suppress("removal", "DEPRECATION")
+
package io.github.freya022.botcommands.internal.localization
import io.github.freya022.botcommands.api.core.service.ConditionalServiceChecker
@@ -6,18 +8,14 @@ import io.github.freya022.botcommands.api.core.service.annotations.BService
import io.github.freya022.botcommands.api.core.service.annotations.ConditionalService
import io.github.freya022.botcommands.api.core.service.getInterfacedServiceTypes
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
-import io.github.freya022.botcommands.api.localization.DefaultMessages
import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.api.localization.LocalizationService
import io.github.freya022.botcommands.api.localization.interaction.UserLocaleProvider
import io.github.freya022.botcommands.api.localization.text.TextCommandLocaleProvider
import io.github.freya022.botcommands.internal.utils.classRef
-import net.dv8tion.jda.api.events.message.MessageReceivedEvent
-import net.dv8tion.jda.api.interactions.Interaction
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
-import java.util.*
// I hate those names
@Configuration
@@ -31,27 +29,7 @@ internal open class DefaultDefaultMessagesFactoryProvider {
localizationService: LocalizationService,
textCommandLocaleProvider: TextCommandLocaleProvider,
userLocaleProvider: UserLocaleProvider,
- ): DefaultMessagesFactory = DefaultDefaultMessagesFactory(localizationService, textCommandLocaleProvider, userLocaleProvider)
-
- private class DefaultDefaultMessagesFactory(
- private val localizationService: LocalizationService,
- private val textCommandLocaleProvider: TextCommandLocaleProvider,
- private val userLocaleProvider: UserLocaleProvider,
- ): DefaultMessagesFactory {
- private val localeDefaultMessagesMap: MutableMap = hashMapOf()
-
- override fun get(locale: Locale): DefaultMessages = localeDefaultMessagesMap.computeIfAbsent(locale) {
- DefaultMessages(localizationService, it)
- }
-
- override fun get(event: MessageReceivedEvent): DefaultMessages {
- return get(textCommandLocaleProvider.getLocale(event))
- }
-
- override fun get(event: Interaction): DefaultMessages {
- return get(userLocaleProvider.getLocale(event))
- }
- }
+ ): DefaultMessagesFactory = FallbackDefaultMessagesFactory(localizationService, textCommandLocaleProvider, userLocaleProvider)
internal object ActivationCondition : ConditionalServiceChecker {
override fun checkServiceAvailability(serviceContainer: ServiceContainer, checkedClass: Class<*>): String? {
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/FallbackDefaultMessagesFactory.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/FallbackDefaultMessagesFactory.kt
new file mode 100644
index 0000000000..e637767228
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/FallbackDefaultMessagesFactory.kt
@@ -0,0 +1,32 @@
+@file:Suppress("removal", "DEPRECATION")
+
+package io.github.freya022.botcommands.internal.localization
+
+import io.github.freya022.botcommands.api.localization.DefaultMessages
+import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
+import io.github.freya022.botcommands.api.localization.LocalizationService
+import io.github.freya022.botcommands.api.localization.interaction.UserLocaleProvider
+import io.github.freya022.botcommands.api.localization.text.TextCommandLocaleProvider
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent
+import net.dv8tion.jda.api.interactions.Interaction
+import java.util.*
+
+internal class FallbackDefaultMessagesFactory internal constructor(
+ private val localizationService: LocalizationService,
+ private val textCommandLocaleProvider: TextCommandLocaleProvider,
+ private val userLocaleProvider: UserLocaleProvider,
+): DefaultMessagesFactory {
+ private val cache: MutableMap = hashMapOf()
+
+ override fun get(locale: Locale): DefaultMessages = cache.computeIfAbsent(locale) {
+ DefaultMessages(localizationService, it)
+ }
+
+ override fun get(event: MessageReceivedEvent): DefaultMessages {
+ return get(textCommandLocaleProvider.getLocale(event))
+ }
+
+ override fun get(event: Interaction): DefaultMessages {
+ return get(userLocaleProvider.getLocale(event))
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/PermissionLocalizationProvider.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/PermissionLocalizationProvider.kt
new file mode 100644
index 0000000000..50046655d1
--- /dev/null
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/PermissionLocalizationProvider.kt
@@ -0,0 +1,39 @@
+package io.github.freya022.botcommands.internal.localization
+
+import io.github.freya022.botcommands.api.core.service.ConditionalServiceChecker
+import io.github.freya022.botcommands.api.core.service.ServiceContainer
+import io.github.freya022.botcommands.api.core.service.annotations.BService
+import io.github.freya022.botcommands.api.core.service.annotations.ConditionalService
+import io.github.freya022.botcommands.api.core.service.getInterfacedServiceTypes
+import io.github.freya022.botcommands.api.core.utils.simpleNestedName
+import io.github.freya022.botcommands.api.localization.DefaultPermissionLocalization
+import io.github.freya022.botcommands.api.localization.LocalizationService
+import io.github.freya022.botcommands.api.localization.PermissionLocalization
+import io.github.freya022.botcommands.internal.utils.classRef
+import org.springframework.boot.autoconfigure.AutoConfiguration
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
+import org.springframework.context.annotation.Bean
+
+@BService
+@AutoConfiguration
+internal open class PermissionLocalizationProvider internal constructor() {
+
+ @Bean
+ @ConditionalOnMissingBean(PermissionLocalization::class)
+ @BService
+ @ConditionalService(ActivationCondition::class)
+ open fun permissionLocalization(localizationService: LocalizationService): PermissionLocalization {
+ return DefaultPermissionLocalization(localizationService)
+ }
+
+ internal object ActivationCondition : ConditionalServiceChecker {
+ override fun checkServiceAvailability(serviceContainer: ServiceContainer, checkedClass: Class<*>): String? {
+ val types = serviceContainer.getInterfacedServiceTypes()
+ if (types.isNotEmpty()) {
+ return "An user supplied ${classRef()} is already active (${types.first().simpleNestedName})"
+ }
+
+ return null
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/interaction/LocalizableInteractionFactory.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/interaction/LocalizableInteractionFactory.kt
index 58ac8dd18e..9c51765ce0 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/interaction/LocalizableInteractionFactory.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/interaction/LocalizableInteractionFactory.kt
@@ -1,6 +1,9 @@
+@file:Suppress("DEPRECATION")
+
package io.github.freya022.botcommands.internal.localization.interaction
import io.github.freya022.botcommands.api.core.config.BLocalizationConfig
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.annotations.BService
import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.api.localization.LocalizationService
@@ -16,7 +19,8 @@ internal class LocalizableInteractionFactory internal constructor(
private val userLocaleProvider: UserLocaleProvider,
private val guildLocaleProvider: GuildLocaleProvider,
private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
) {
internal fun create(event: IReplyCallback) =
- LocalizableInteractionImpl(event, localizationService, localizationConfig, userLocaleProvider, guildLocaleProvider, defaultMessagesFactory)
+ LocalizableInteractionImpl(event, localizationService, localizationConfig, userLocaleProvider, guildLocaleProvider, defaultMessagesFactory, messagesFactory)
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/interaction/LocalizableInteractionImpl.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/interaction/LocalizableInteractionImpl.kt
index 594a18438e..287f2740aa 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/interaction/LocalizableInteractionImpl.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/interaction/LocalizableInteractionImpl.kt
@@ -1,6 +1,10 @@
+@file:Suppress("removal", "DEPRECATION")
+
package io.github.freya022.botcommands.internal.localization.interaction
import io.github.freya022.botcommands.api.core.config.BLocalizationConfig
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.localization.DefaultMessages
import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.api.localization.Localization
@@ -22,6 +26,7 @@ internal class LocalizableInteractionImpl internal constructor(
private val userLocaleProvider: UserLocaleProvider,
private val guildLocaleProvider: GuildLocaleProvider,
private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
) : AbstractLocalizableAction(localizationConfig, localizationService),
LocalizableInteraction {
@@ -38,10 +43,16 @@ internal class LocalizableInteractionImpl internal constructor(
)
}
+ @Suppress("DEPRECATION", "removal")
+ @Deprecated("Replaced with getBotCommandsMessages()")
override fun getDefaultMessages(): DefaultMessages {
return defaultMessagesFactory.get(deferrableCallback)
}
+ override fun getBotCommandsMessages(): BotCommandsMessages {
+ return messagesFactory.get(deferrableCallback)
+ }
+
override fun getUserMessage(localizationPath: String, vararg entries: Localization.Entry): String {
return getLocalizedMessage(userLocale, localizationPath, *entries)
}
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/text/LocalizableTextCommandFactory.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/text/LocalizableTextCommandFactory.kt
index 665fda4148..a75a5aadd3 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/text/LocalizableTextCommandFactory.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/text/LocalizableTextCommandFactory.kt
@@ -1,6 +1,9 @@
+@file:Suppress("DEPRECATION")
+
package io.github.freya022.botcommands.internal.localization.text
import io.github.freya022.botcommands.api.core.config.BLocalizationConfig
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.annotations.BService
import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.api.localization.LocalizationService
@@ -14,7 +17,8 @@ internal class LocalizableTextCommandFactory internal constructor(
private val localizationConfig: BLocalizationConfig,
private val localeProvider: TextCommandLocaleProvider,
private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
) {
internal fun create(event: MessageReceivedEvent) =
- LocalizableTextCommandImpl(event, localizationService, localizationConfig, localeProvider, defaultMessagesFactory)
+ LocalizableTextCommandImpl(event, localizationService, localizationConfig, localeProvider, defaultMessagesFactory, messagesFactory)
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/text/LocalizableTextCommandImpl.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/text/LocalizableTextCommandImpl.kt
index 4be783a7f9..70850d5e78 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/localization/text/LocalizableTextCommandImpl.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/localization/text/LocalizableTextCommandImpl.kt
@@ -1,6 +1,10 @@
+@file:Suppress("removal", "DEPRECATION")
+
package io.github.freya022.botcommands.internal.localization.text
import io.github.freya022.botcommands.api.core.config.BLocalizationConfig
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.localization.DefaultMessages
import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.api.localization.Localization
@@ -20,6 +24,7 @@ internal class LocalizableTextCommandImpl internal constructor(
localizationConfig: BLocalizationConfig,
private val localeProvider: TextCommandLocaleProvider,
private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
) : AbstractLocalizableAction(localizationConfig, localizationService), LocalizableTextCommand {
private val locale: Locale by lazy { localeProvider.getLocale(event) }
@@ -32,10 +37,16 @@ internal class LocalizableTextCommandImpl internal constructor(
)
}
+ @Suppress("DEPRECATION", "removal")
+ @Deprecated("Replaced with getBotCommandsMessages()")
override fun getDefaultMessages(): DefaultMessages {
return defaultMessagesFactory.get(locale)
}
+ override fun getBotCommandsMessages(): BotCommandsMessages {
+ return messagesFactory.get(locale)
+ }
+
override fun getGuildMessage(localizationPath: String, vararg entries: Localization.Entry): String {
return getLocalizedMessage(locale, localizationPath, *entries)
}
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/modals/ModalListener.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/modals/ModalListener.kt
index 6e6922637c..58eb24d359 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/modals/ModalListener.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/modals/ModalListener.kt
@@ -1,10 +1,9 @@
package io.github.freya022.botcommands.internal.modals
-import dev.minn.jda.ktx.messages.reply_
import io.github.freya022.botcommands.api.core.annotations.BEventListener
import io.github.freya022.botcommands.api.core.config.BModalsConfig
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.annotations.BService
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.api.modals.ModalEvent
import io.github.freya022.botcommands.api.modals.Modals
import io.github.freya022.botcommands.api.modals.annotations.ModalHandler
@@ -24,7 +23,7 @@ private val logger = KotlinLogging.logger { }
@RequiresModals
internal class ModalListener(
private val context: BContextImpl,
- private val defaultMessagesFactory: DefaultMessagesFactory,
+ private val messagesFactory: BotCommandsMessagesFactory,
private val localizableInteractionFactory: LocalizableInteractionFactory,
private val modalHandlerContainer: ModalHandlerContainer,
private val modalMaps: ModalMaps,
@@ -44,7 +43,7 @@ internal class ModalListener(
val modalData = modalMaps.consumeModal(ModalMaps.parseModalId(jdaEvent.modalId))
if (modalData == null) { //Probably the modal expired
- jdaEvent.reply_(defaultMessagesFactory.get(jdaEvent).modalExpiredErrorMsg, ephemeral = true).queue()
+ jdaEvent.reply(messagesFactory.get(jdaEvent).modalExpired(jdaEvent)).setEphemeral(true).queue()
return@launch
}
@@ -73,9 +72,9 @@ internal class ModalListener(
put("Modal values", event.values.associate { it.id to it.asString })
})
if (e is InsufficientPermissionException) {
- event.replyExceptionMessage(defaultMessagesFactory.get(event).getBotPermErrorMsg(setOf(e.permission)))
+ event.replyExceptionMessage(messagesFactory.get(event).missingBotPermissions(event, setOf(e.permission)))
} else {
- event.replyExceptionMessage(defaultMessagesFactory.get(event).generalErrorMsg)
+ event.replyExceptionMessage(messagesFactory.get(event).uncaughtException(event))
}
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/parameters/resolvers/AbstractUserSnowflakeResolver.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/parameters/resolvers/AbstractUserSnowflakeResolver.kt
index ebc2a4bda5..b2b238b439 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/parameters/resolvers/AbstractUserSnowflakeResolver.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/parameters/resolvers/AbstractUserSnowflakeResolver.kt
@@ -1,6 +1,5 @@
package io.github.freya022.botcommands.internal.parameters.resolvers
-import dev.minn.jda.ktx.messages.reply_
import io.github.freya022.botcommands.api.commands.application.context.user.options.UserContextCommandOption
import io.github.freya022.botcommands.api.commands.application.slash.options.SlashCommandOption
import io.github.freya022.botcommands.api.commands.text.BaseCommandEvent
@@ -8,11 +7,11 @@ import io.github.freya022.botcommands.api.commands.text.options.TextCommandOptio
import io.github.freya022.botcommands.api.components.options.ComponentOption
import io.github.freya022.botcommands.api.components.serialization.SerializedComponentData
import io.github.freya022.botcommands.api.core.BContext
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
import io.github.freya022.botcommands.api.core.service.getService
import io.github.freya022.botcommands.api.core.traceNull
import io.github.freya022.botcommands.api.core.utils.retrieveMemberByIdOrNull
import io.github.freya022.botcommands.api.core.utils.retrieveUserByIdOrNull
-import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
import io.github.freya022.botcommands.api.parameters.ClassParameterResolver
import io.github.freya022.botcommands.api.parameters.resolvers.ComponentParameterResolver
import io.github.freya022.botcommands.api.parameters.resolvers.SlashParameterResolver
@@ -41,8 +40,8 @@ internal sealed class AbstractUserSnowflakeResolver,
ComponentParameterResolver,
UserContextParameterResolver {
-
- private val defaultMessagesFactory: DefaultMessagesFactory = context.getService()
+
+ private val messagesFactory: BotCommandsMessagesFactory = context.getService()
final override val pattern: Pattern get() = userMentionPattern
final override val testExample: String = "<@1234>"
@@ -76,7 +75,7 @@ internal sealed class AbstractUserSnowflakeResolver")).queue()
+ event.message.reply(messagesFactory.get(event).resolverChannelMissingAccess(event, channelId)).queue()
})
private suspend fun retrieveThreadChannel(
- event: IReplyCallback,
+ event: GenericComponentInteractionCreateEvent,
guild: Guild,
channelId: Long
): ThreadChannel? = retrieveThreadChannel(guild, channelId, onMissingAccess = {
- event.reply_(defaultMessagesFactory.get(event).getResolverChannelMissingAccessMsg("<#$channelId>"), ephemeral = true).queue()
+ event.reply(messagesFactory.get(event).resolverChannelMissingAccess(event, channelId)).queue()
})
private suspend fun retrieveThreadChannel(
diff --git a/src/main/kotlin/io/github/freya022/botcommands/internal/utils/Exceptions.kt b/src/main/kotlin/io/github/freya022/botcommands/internal/utils/Exceptions.kt
index 4d16c975d4..3fd7319a49 100644
--- a/src/main/kotlin/io/github/freya022/botcommands/internal/utils/Exceptions.kt
+++ b/src/main/kotlin/io/github/freya022/botcommands/internal/utils/Exceptions.kt
@@ -9,6 +9,7 @@ import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback
import net.dv8tion.jda.api.requests.ErrorResponse
+import net.dv8tion.jda.api.utils.messages.MessageCreateData
import java.lang.reflect.InvocationTargetException
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@@ -138,7 +139,7 @@ internal fun Throwable.unwrap(): Throwable {
}
internal suspend fun IReplyCallback.replyExceptionMessage(
- message: String
+ message: MessageCreateData
) = runIgnoringResponse(ErrorResponse.UNKNOWN_INTERACTION, ErrorResponse.UNKNOWN_WEBHOOK) {
if (isAcknowledged) {
// Give ourselves 5 seconds to delete
diff --git a/src/main/resources/bc_localization/BotCommandsMessages-default.json b/src/main/resources/bc_localization/BotCommandsMessages-default.json
new file mode 100644
index 0000000000..041a624cad
--- /dev/null
+++ b/src/main/resources/bc_localization/BotCommandsMessages-default.json
@@ -0,0 +1,29 @@
+{
+ "uncaught_exception": "An uncaught exception occurred and has been reported to the bot developers, please try again later",
+
+ "missing.permissions.user": "You are not allowed to do this",
+ "missing.permissions.bot": "I am missing these permissions: {permissions}",
+ "owner_only": "Only the owner can use this",
+
+ "ratelimited.user": "You will be able to use this {timestamp}",
+ "ratelimited.channel": "You will be able to use this, in this channel, {timestamp}",
+ "ratelimited.guild": "You will be able to use this, in this guild, {timestamp}",
+
+ "commands.application.not_available": "Application commands are not available yet, please try again later",
+ "commands.text.not_found": "Unknown command, maybe you meant: {suggestions}",
+
+ "resolver.channel.not_found": "The target channel does not exist anymore",
+ "resolver.channel.missing_access": "The bot cannot access {channel_id}",
+ "resolver.user.not_found": "The target user does not exist anymore",
+
+ "commands.slash.option.unresolvable": "The option '{option_name}' could not be resolved.",
+
+ "direct_messages.closed": "Unable to send you a DM, please open your DMs from this server",
+
+ "nsfw_only": "This command can only be used in NSFW channels",
+
+ "components.not_allowed": "You are not allowed to use this",
+ "components.expired": "This component is not usable anymore",
+
+ "modals.expired": "This modal is no longer available"
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/github/freya022/botcommands/framework/BotCommandsMessagesTests.kt b/src/test/kotlin/io/github/freya022/botcommands/framework/BotCommandsMessagesTests.kt
new file mode 100644
index 0000000000..0b38830753
--- /dev/null
+++ b/src/test/kotlin/io/github/freya022/botcommands/framework/BotCommandsMessagesTests.kt
@@ -0,0 +1,161 @@
+@file:Suppress("DEPRECATION", "removal")
+
+package io.github.freya022.botcommands.framework
+
+import io.github.freya022.botcommands.api.core.BotCommands
+import io.github.freya022.botcommands.api.core.config.registerServiceSupplier
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessages
+import io.github.freya022.botcommands.api.core.messages.BotCommandsMessagesFactory
+import io.github.freya022.botcommands.api.core.messages.DefaultBotCommandsMessagesFactory
+import io.github.freya022.botcommands.api.core.messages.exceptions.MissingMessageTemplateException
+import io.github.freya022.botcommands.api.core.service.getService
+import io.github.freya022.botcommands.api.core.utils.joinAsList
+import io.github.freya022.botcommands.api.localization.DefaultMessages
+import io.github.freya022.botcommands.api.localization.DefaultMessagesFactory
+import io.github.freya022.botcommands.framework.utils.createTest
+import io.github.freya022.botcommands.internal.core.messages.BotCommandsMessagesFactoryProvider
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.slot
+import io.mockk.spyk
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent
+import net.dv8tion.jda.api.interactions.Interaction
+import net.dv8tion.jda.api.utils.messages.MessageCreateData
+import org.junit.jupiter.api.assertDoesNotThrow
+import java.time.Instant
+import java.util.*
+import kotlin.reflect.KFunction
+import kotlin.test.Test
+import kotlin.test.assertIsNot
+import kotlin.test.assertSame
+import kotlin.test.fail
+
+class BotCommandsMessagesTests {
+
+ @Test
+ fun `Adapter is used when custom DefaultMessagesFactory type is used`() {
+ val context = BotCommands.createTest {
+ services {
+ registerServiceSupplier {
+ object : DefaultMessagesFactory {
+ override fun get(locale: Locale): DefaultMessages = throw UnsupportedOperationException()
+ override fun get(event: MessageReceivedEvent) = throw UnsupportedOperationException()
+ override fun get(event: Interaction) = throw UnsupportedOperationException()
+ }
+ }
+ }
+ }
+
+ assertIsNot(context.getService())
+ }
+
+ @Test
+ fun `Adapter is used when custom DefaultMessages JSON exists`() {
+ val context = BotCommands.createTest {
+ services {
+ registerServiceSupplier {
+ mockk {
+ every {
+ botCommandsMessagesFactory(any(), any(), any(), any(), any())
+ } answers { callOriginal() }
+
+ every { this@mockk["hasCustomDefaultMessages"]() } returns true
+ }
+ }
+ }
+ }
+
+ assertIsNot(context.getService())
+ }
+
+ @Test
+ fun `Can override autoconfiguration`() {
+ val expected = mockk()
+ val context = BotCommands.createTest {
+ services {
+ registerServiceSupplier { expected }
+ }
+ }
+
+ val actual = assertDoesNotThrow { context.getService() }
+ assertSame(expected, actual)
+ }
+
+ @Test
+ fun `All messages have defaults`() {
+ val context = BotCommands.createTest {
+ services {
+ // Override the autoconfiguration so we don't unexpectedly use a different implementation
+ registerServiceSupplier(
+ additionalTypes = setOf(
+ BotCommandsMessagesFactory::class,
+ )
+ ) { context ->
+ DefaultBotCommandsMessagesFactory(
+ context.getService(),
+ context.getService(),
+ context.getService(),
+ context.getService(),
+ )
+ }
+ }
+ }
+
+ val templatePathSlot = slot()
+ val messages = spyk(context.getService().get(Locale.ROOT)) {
+ every { this@spyk["getLocalizationTemplate"](capture(templatePathSlot)) } answers { callOriginal() }
+ }
+
+ val methodCalls = mapOf(
+ methodCall(messages::uncaughtException) { this(mockk()) },
+ methodCall(messages::missingUserPermissions) { this(mockk(), emptySet()) },
+ methodCall(messages::missingBotPermissions) { this(mockk(), emptySet()) },
+ methodCall(messages::ownerOnly) { this(mockk()) },
+ methodCall(messages::userRateLimited) { this(mockk(), Instant.now()) },
+ methodCall(messages::channelRateLimited) { this(mockk(), Instant.now()) },
+ methodCall(messages::guildRateLimited) { this(mockk(), Instant.now()) },
+ methodCall(messages::applicationCommandsNotAvailable) { this(mockk()) },
+ methodCall(messages::commandNotFound) { this(mockk(), emptySet()) },
+ methodCall(messages::resolverChannelNotFound) { this(mockk(), 0) },
+ methodCall(messages::resolverChannelMissingAccess) { this(mockk(), 0) },
+ methodCall(messages::resolverUserNotFound) { this(mockk(), 0) },
+ methodCall(messages::slashCommandUnresolvableOption) {
+ this(mockk(), mockk {
+ every { discordName } returns "discord_name"
+ })
+ },
+ methodCall(messages::closedDirectMessages) { this(mockk()) },
+ methodCall(messages::nsfwOnly) { this(mockk()) },
+ methodCall(messages::componentNotAllowed) { this(mockk()) },
+ methodCall(messages::componentExpired) { this(mockk()) },
+ methodCall(messages::modalExpired) { this(mockk()) },
+ )
+
+ val missingTests =
+ BotCommandsMessages::class.java.declaredMethods.mapTo(hashSetOf()) { it.name } - methodCalls.keys
+ if (missingTests.isNotEmpty()) {
+ fail("The following methods are missing tests:\n" + missingTests.joinAsList())
+ }
+
+ val methodsMissingTemplate: MutableList = arrayListOf()
+ methodCalls.values.forEach { methodCall ->
+ templatePathSlot.clear()
+ try {
+ methodCall()
+ } catch (_: MissingMessageTemplateException) {
+ methodsMissingTemplate += templatePathSlot.captured
+ }
+ }
+
+ if (methodsMissingTemplate.isNotEmpty()) {
+ fail("The following template keys are missing default translations:\n" + methodsMissingTemplate.joinAsList())
+ }
+ }
+
+ private fun > methodCall(
+ callableRef: F,
+ executor: F.() -> Unit,
+ ): Pair Unit> {
+ return callableRef.name to { executor(callableRef) }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/io/github/freya022/botcommands/framework/PermissionLocalizationTests.kt b/src/test/kotlin/io/github/freya022/botcommands/framework/PermissionLocalizationTests.kt
new file mode 100644
index 0000000000..e48ac674fe
--- /dev/null
+++ b/src/test/kotlin/io/github/freya022/botcommands/framework/PermissionLocalizationTests.kt
@@ -0,0 +1,58 @@
+package io.github.freya022.botcommands.framework
+
+import io.github.freya022.botcommands.api.core.BotCommands
+import io.github.freya022.botcommands.api.core.config.registerServiceSupplier
+import io.github.freya022.botcommands.api.core.service.getService
+import io.github.freya022.botcommands.api.localization.DefaultPermissionLocalization
+import io.github.freya022.botcommands.api.localization.LocalizationService
+import io.github.freya022.botcommands.api.localization.PermissionLocalization
+import io.github.freya022.botcommands.framework.utils.createTest
+import io.mockk.every
+import io.mockk.mockk
+import net.dv8tion.jda.api.Permission
+import org.junit.jupiter.api.assertDoesNotThrow
+import java.util.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertSame
+
+class PermissionLocalizationTests {
+
+ @Test
+ fun `Should use Permission's enum name when there are no bundles`() {
+ val localizationService = mockk {
+ every { getInstance(any(), any()) } returns null
+ }
+
+ val permissionLocalization = DefaultPermissionLocalization(localizationService)
+ val actual = permissionLocalization.localize(Permission.VIEW_CHANNEL, Locale.FRENCH)
+
+ assertEquals("View Channels", actual)
+ }
+
+ @Test
+ fun `Should be able to use the default bundle`() {
+ val expected = "Localized permission name"
+ val localizationService = mockk {
+ every { getInstance("Permissions", Locale.FRENCH)?.get("VIEW_CHANNEL")?.localize() } returns expected
+ }
+
+ val permissionLocalization = DefaultPermissionLocalization(localizationService)
+ val actual = permissionLocalization.localize(Permission.VIEW_CHANNEL, Locale.FRENCH)
+
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun `Can override autoconfiguration`() {
+ val expected = mockk()
+ val context = BotCommands.createTest {
+ services {
+ registerServiceSupplier { expected }
+ }
+ }
+
+ val actual = assertDoesNotThrow { context.getService() }
+ assertSame(expected, actual)
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/bc_localization/DefaultMessages.json b/src/test/resources/bc_localization/DefaultMessages.json
deleted file mode 100644
index 64c785130d..0000000000
--- a/src/test/resources/bc_localization/DefaultMessages.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "null.component.type.error.message": "Component go bruh :skull:"
-}
\ No newline at end of file
diff --git a/src/test/resources/bc_localization/DefaultMessages_fr.json b/src/test/resources/bc_localization/DefaultMessages_fr.json
deleted file mode 100644
index 8f169c4d46..0000000000
--- a/src/test/resources/bc_localization/DefaultMessages_fr.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "null.component.type.error.message": "Ce composant n'est plus utilisable"
-}
\ No newline at end of file