Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@
* <p>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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
Expand All @@ -59,8 +62,8 @@ class DefaultRateLimitHandler(
event.guildChannel.canTalk() -> event.channel
else -> event.author.openPrivateChannel().await()
}
val messages = context.getService<DefaultMessagesFactory>().get(event)
val content = getRateLimitMessage(messages, probe)
val messages = context.getService<BotCommandsMessagesFactory>().get(event)
val content = getRateLimitMessage(event, messages, probe)

runIgnoringResponse(ErrorResponse.CANNOT_SEND_TO_USER) {
val messageId = channel.sendMessage(content).await().idLong
Expand All @@ -80,20 +83,25 @@ class DefaultRateLimitHandler(
commandInfo: ApplicationCommandInfo,
probe: ConsumptionProbe
) where T : GenericCommandInteractionEvent, T : IReplyCallback {
onRateLimit(context, event, probe)
onRateLimit0(context, event, probe)
}

override suspend fun <T> onRateLimit(
context: BContext,
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<DefaultMessagesFactory>().get(event)
val content = getRateLimitMessage(messages, probe)
private suspend fun <T> onRateLimit0(
context: BContext,
event: T,
probe: ConsumptionProbe
) where T : GenericInteractionCreateEvent,
T : IReplyCallback {
val messages = context.getService<BotCommandsMessagesFactory>().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
Expand All @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Permission>): 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<Permission>): 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<TopLevelTextCommandInfo>): 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
}
Original file line number Diff line number Diff line change
@@ -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
}
Loading