diff --git a/build.gradle.kts b/build.gradle.kts index 42a8be00..2339baac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -56,6 +56,8 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0") testImplementation("ch.qos.logback:logback-classic:1.4.11") + //needed for reasons... + testCompileOnly("com.google.code.findbugs:jsr305:3.0.2") api("net.dv8tion:JDA:5.0.0-beta.13") { exclude(module = "opus-java") diff --git a/src/examples/java/xyz/dynxsty/examples/commands/PollCommand.java b/src/examples/java/xyz/dynxsty/examples/commands/PollCommand.java index d12f3493..68a397bb 100644 --- a/src/examples/java/xyz/dynxsty/examples/commands/PollCommand.java +++ b/src/examples/java/xyz/dynxsty/examples/commands/PollCommand.java @@ -6,6 +6,7 @@ import net.dv8tion.jda.api.interactions.commands.build.Commands; import net.dv8tion.jda.api.interactions.components.buttons.Button; import org.jetbrains.annotations.NotNull; +import xyz.dynxsty.dih4jda.interactions.commands.application.CooldownScope; import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; import xyz.dynxsty.dih4jda.interactions.components.ButtonHandler; import xyz.dynxsty.dih4jda.util.ComponentIdBuilder; @@ -20,7 +21,7 @@ public class PollCommand extends SlashCommand implements ButtonHandler { public PollCommand() { setCommandData(Commands.slash("poll", "Creates a poll with 2 options.")); setRequiredPermissions(Permission.MESSAGE_MANAGE); - setCommandCooldown(Duration.of(1, ChronoUnit.MINUTES)); // Add cooldown to prevent spam by users + setCommandCooldown(Duration.of(1, ChronoUnit.MINUTES), CooldownScope.USER_GLOBAL); // Add cooldown to prevent spam by users } @Override diff --git a/src/examples/java/xyz/dynxsty/examples/listeners/DIH4JDAListener.java b/src/examples/java/xyz/dynxsty/examples/listeners/DIH4JDAListener.java index ed9c374c..a64b274b 100644 --- a/src/examples/java/xyz/dynxsty/examples/listeners/DIH4JDAListener.java +++ b/src/examples/java/xyz/dynxsty/examples/listeners/DIH4JDAListener.java @@ -5,6 +5,7 @@ import xyz.dynxsty.dih4jda.events.DIH4JDAEventListener; import javax.annotation.Nonnull; +import java.time.ZoneId; public class DIH4JDAListener implements DIH4JDAEventListener { @@ -18,8 +19,8 @@ public void onCommandException(@Nonnull CommandExceptionEvent event) { @Override public void onCommandCooldown(@Nonnull CommandCooldownEvent event) { - event.getInteraction().getMessageChannel().sendMessageFormat("Seems like you have to wait before you use the " + - "command again.\n You can try again in: ", event.getCooldown().getNextUse().toEpochMilli()).queue(); + event.getInteraction().replyFormat("You are on cooldown. Next use ", + event.getCooldown().getNextUse().atZone(ZoneId.systemDefault()).toEpochSecond()).queue(); } // add more events if you need to diff --git a/src/main/java/xyz/dynxsty/dih4jda/InteractionHandler.java b/src/main/java/xyz/dynxsty/dih4jda/InteractionHandler.java index 9aa27b68..f6b2c414 100644 --- a/src/main/java/xyz/dynxsty/dih4jda/InteractionHandler.java +++ b/src/main/java/xyz/dynxsty/dih4jda/InteractionHandler.java @@ -37,6 +37,7 @@ import xyz.dynxsty.dih4jda.interactions.commands.RestrictedCommand; import xyz.dynxsty.dih4jda.interactions.commands.application.BaseApplicationCommand; import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; +import xyz.dynxsty.dih4jda.interactions.commands.application.CooldownScope; import xyz.dynxsty.dih4jda.interactions.commands.application.RegistrationType; import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; import xyz.dynxsty.dih4jda.interactions.components.ButtonHandler; @@ -578,57 +579,83 @@ private void handleMessageContextCommand(@Nonnull MessageContextInteractionEvent } } + /** + * Checks if the given {@link CommandInteraction} passes the + * {@link RestrictedCommand} requirements. + * If not, this will then fire the corresponding event using {@link DIH4JDAEvent#fire(DIH4JDAEvent)} + * + * @param interaction The {@link CommandInteraction}. + * @param command The {@link RestrictedCommand} which contains the (possible) restrictions. + * @param type The {@link RegistrationType} of the {@link BaseApplicationCommand}. + * @return Whether the event was fired. + * @since v1.5 + */ + private boolean passesRequirements(@Nonnull CommandInteraction interaction, @Nonnull RestrictedCommand command, + @Nonnull RegistrationType type) { + long userId = interaction.getUser().getIdLong(); + Long[] guildIds = command.getRequiredGuilds(); + Permission[] permissions = command.getRequiredPermissions(); + Long[] userIds = command.getRequiredUsers(); + Long[] roleIds = command.getRequiredRoles(); + if (type == RegistrationType.GUILD && guildIds.length != 0 && interaction.isFromGuild() && + !Arrays.asList(guildIds).contains(interaction.getGuild().getIdLong()) + ) { + DIH4JDAEvent.fire(new InvalidGuildEvent(dih4jda, interaction, Set.of(guildIds))); + return false; + } + if (permissions.length != 0 && interaction.isFromGuild() && + interaction.getMember() != null && !interaction.getMember().hasPermission(permissions)) { + DIH4JDAEvent.fire(new InsufficientPermissionsEvent(dih4jda, interaction, Set.of(permissions))); + return false; + } + if (userIds.length != 0 && !Arrays.asList(userIds).contains(userId)) { + DIH4JDAEvent.fire(new InvalidUserEvent(dih4jda, interaction, Set.of(userIds))); + return false; + } + if (interaction.isFromGuild() && interaction.getMember() != null) { + Member member = interaction.getMember(); + if (roleIds.length != 0 && !member.getRoles().isEmpty() && + member.getRoles().stream().noneMatch(r -> Arrays.asList(roleIds).contains(r.getIdLong()))) { + DIH4JDAEvent.fire(new InvalidRoleEvent(dih4jda, interaction, Set.of(roleIds))); + return false; + } + } + return !hasCooldown(interaction, command); + } + /** - * Checks if the given {@link CommandInteraction} passes the - * {@link RestrictedCommand} requirements. - * If not, this will then fire the corresponding event using {@link DIH4JDAEvent#fire(DIH4JDAEvent)} - * + * Checks if the given {@link CommandInteraction} and {@link net.dv8tion.jda.api.entities.User} has a cooldown. * @param interaction The {@link CommandInteraction}. - * @param command The {@link RestrictedCommand} which contains the (possible) restrictions. - * @param type The {@link RegistrationType} of the {@link BaseApplicationCommand}. - * @return Whether the event was fired. - * @since v1.5 - */ - private boolean passesRequirements(@Nonnull CommandInteraction interaction, @Nonnull RestrictedCommand command, - @Nonnull RegistrationType type) { - long userId = interaction.getUser().getIdLong(); - Long[] guildIds = command.getRequiredGuilds(); - Permission[] permissions = command.getRequiredPermissions(); - Long[] userIds = command.getRequiredUsers(); - Long[] roleIds = command.getRequiredRoles(); - if (type == RegistrationType.GUILD && guildIds.length != 0 && interaction.isFromGuild() && - interaction.isFromGuild() && !List.of(guildIds).contains(interaction.getGuild().getIdLong()) - ) { - DIH4JDAEvent.fire(new InvalidGuildEvent(dih4jda, interaction, Set.of(guildIds))); - return false; - } - if (permissions.length != 0 && interaction.isFromGuild() && - interaction.getMember() != null && !interaction.getMember().hasPermission(permissions)) { - DIH4JDAEvent.fire(new InsufficientPermissionsEvent(dih4jda, interaction, Set.of(permissions))); - return false; - } - if (userIds.length != 0 && !List.of(userIds).contains(userId)) { - DIH4JDAEvent.fire(new InvalidUserEvent(dih4jda, interaction, Set.of(userIds))); + * @param command The {@link RestrictedCommand} which contains the cooldown. + * @return true if the command and user has a cooldown, false otherwise. + */ + private boolean hasCooldown(@Nonnull CommandInteraction interaction, @Nonnull RestrictedCommand command) { + // check if the command has enabled some sort of cooldown + Pair cooldownPair = command.getCooldownConfiguration(); + if (cooldownPair.getFirst().equals(Duration.ZERO) || cooldownPair.getSecond().equals(CooldownScope.NONE)) { return false; } - if (interaction.isFromGuild() && interaction.getMember() != null) { - Member member = interaction.getMember(); - if (roleIds.length != 0 && !member.getRoles().isEmpty() && - member.getRoles().stream().noneMatch(r -> List.of(roleIds).contains(r.getIdLong()))) { - DIH4JDAEvent.fire(new InvalidRoleEvent(dih4jda, interaction, Set.of(roleIds))); - return false; + RestrictedCommand.Cooldown cooldown = command.getCooldown(interaction.getUser(), interaction.getGuild()); + if (interaction.isFromGuild()) { + if (command.hasCooldown(interaction.getMember())) { + DIH4JDAEvent.fire(new CommandCooldownEvent(dih4jda, interaction, cooldown)); + return true; } - } - // check if the command has enabled some sort of cooldown - if (!command.getCommandCooldown().equals(Duration.ZERO)) { - if (command.hasCooldown(userId)) { - DIH4JDAEvent.fire(new CommandCooldownEvent(dih4jda, interaction, command.retrieveCooldown(userId))); - return false; - } else { - command.applyCooldown(userId, Instant.now().plus(command.getCommandCooldown())); + Instant nextUse = Instant.now().plus(cooldownPair.getFirst()); + switch (cooldownPair.getSecond()) { + case USER_GLOBAL: command.applyCooldown(interaction.getUser(), nextUse); break; + case MEMBER_GUILD: command.applyCooldown(interaction.getUser(), interaction.getGuild(), nextUse); break; + case GUILD: command.applyCooldown(interaction.getGuild(), nextUse); break; + } + } else { + if (command.hasCooldown(interaction.getUser())) { + DIH4JDAEvent.fire(new CommandCooldownEvent(dih4jda, interaction, cooldown)); + return true; } + Instant nextUse = Instant.now().plus(cooldownPair.getFirst()); + command.applyCooldown(interaction.getUser(), nextUse); } - return true; + return false; } /** diff --git a/src/main/java/xyz/dynxsty/dih4jda/events/CommandCooldownEvent.java b/src/main/java/xyz/dynxsty/dih4jda/events/CommandCooldownEvent.java index b75b33a3..835f0835 100644 --- a/src/main/java/xyz/dynxsty/dih4jda/events/CommandCooldownEvent.java +++ b/src/main/java/xyz/dynxsty/dih4jda/events/CommandCooldownEvent.java @@ -4,17 +4,18 @@ import net.dv8tion.jda.api.interactions.commands.CommandInteraction; import xyz.dynxsty.dih4jda.DIH4JDA; import xyz.dynxsty.dih4jda.interactions.commands.RestrictedCommand; +import xyz.dynxsty.dih4jda.interactions.commands.application.CooldownScope; import javax.annotation.Nonnull; import java.time.Duration; /** * An event that gets fired when the user, which invoked the command, is not yet able to use this command due to - * a specified {@link RestrictedCommand#setCommandCooldown(Duration) Command Cooldown} + * a specified {@link RestrictedCommand#setCommandCooldown(Duration, CooldownScope)} Command Cooldown} * * Command Cooldowns DO NOT persist between sessions! * - * @see RestrictedCommand#setCommandCooldown(Duration) + * @see RestrictedCommand#setCommandCooldown(Duration, CooldownScope) */ public class CommandCooldownEvent extends DIH4JDAEvent { diff --git a/src/main/java/xyz/dynxsty/dih4jda/events/DIH4JDAEventListener.java b/src/main/java/xyz/dynxsty/dih4jda/events/DIH4JDAEventListener.java index 7505f703..7b7725e1 100644 --- a/src/main/java/xyz/dynxsty/dih4jda/events/DIH4JDAEventListener.java +++ b/src/main/java/xyz/dynxsty/dih4jda/events/DIH4JDAEventListener.java @@ -7,6 +7,7 @@ import xyz.dynxsty.dih4jda.interactions.AutoCompletable; import xyz.dynxsty.dih4jda.interactions.commands.RestrictedCommand; import xyz.dynxsty.dih4jda.interactions.commands.application.ContextCommand; +import xyz.dynxsty.dih4jda.interactions.commands.application.CooldownScope; import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand; import javax.annotation.Nonnull; @@ -84,12 +85,12 @@ default void onInvalidGuild(@Nonnull InvalidGuildEvent event) {} /** * An event that gets fired when the user, which invoked the command, is not yet able to use this command due to - * a specified {@link RestrictedCommand#setCommandCooldown(Duration) Command Cooldown}
+ * a specified {@link RestrictedCommand#setCommandCooldown(Duration, CooldownScope) Command Cooldown}
* * Command Cooldowns DO NOT persist between sessions!
* * @param event The {@link CommandCooldownEvent} that was fired. - * @see RestrictedCommand#setCommandCooldown(Duration) + * @see RestrictedCommand#setCommandCooldown(Duration, CooldownScope) */ default void onCommandCooldown(@Nonnull CommandCooldownEvent event) {} } diff --git a/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/RestrictedCommand.java b/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/RestrictedCommand.java index d3addbab..fd712b82 100644 --- a/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/RestrictedCommand.java +++ b/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/RestrictedCommand.java @@ -2,8 +2,13 @@ import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.User; +import xyz.dynxsty.dih4jda.interactions.commands.application.CooldownScope; +import xyz.dynxsty.dih4jda.util.Pair; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.time.Duration; import java.time.Instant; import java.util.HashMap; @@ -15,13 +20,17 @@ * @since v1.6 */ public abstract class RestrictedCommand { - private final Map COOLDOWN_CACHE = new HashMap<>(); + + private final Map COOLDOWN_USER_GLOBAL = new HashMap<>(); + private final Map, Cooldown> COOLDOWN_USER_GUILD = new HashMap<>(); + private final Map COOLDOWN_GUILD = new HashMap<>(); private Long[] requiredGuilds = new Long[]{}; private Permission[] requiredPermissions = new Permission[]{}; private Long[] requiredUsers = new Long[]{}; private Long[] requiredRoles = new Long[]{}; - private Duration commandCooldown = Duration.ZERO; + private Duration cooldownDuration = Duration.ZERO; + private CooldownScope cooldownScope = CooldownScope.NONE; /** * Creates a default instance. @@ -112,80 +121,244 @@ public final void setRequiredRoles(@Nonnull Long... roles) { * * Command Cooldowns DO NOT persist between sessions!
* - * @param commandCooldown The {@link Duration} the user has to wait between command executions. + * @param duration The {@link Duration} the user has to wait between command executions. + * @param scope The {@link CooldownScope} you want to use. + * @since v1.6 */ - public void setCommandCooldown(@Nonnull Duration commandCooldown) { - this.commandCooldown = commandCooldown; + public void setCommandCooldown(@Nonnull Duration duration, @Nonnull CooldownScope scope) { + this.cooldownDuration = duration; + this.cooldownScope = scope; } /** - * Returns the {@link Duration} the user has to wait between command executions. + * Returns the {@link Duration} and {@link CooldownScope} the user has to wait between command executions. * - * @return The {@link Duration}. - * @see RestrictedCommand#setCommandCooldown(Duration) + * @return A {@link Pair} that contains the {@link Duration} and the {@link CooldownScope}. + * @see RestrictedCommand#setCommandCooldown(Duration, CooldownScope) + * @since v1.6 */ @Nonnull - public Duration getCommandCooldown() { - return commandCooldown; + public Pair getCooldownConfiguration() { + return new Pair<>(cooldownDuration, cooldownScope); } /** - * Manually applies a cooldown for the specified user id.
- * - * Command Cooldowns DO NOT persist between sessions!
+ * Manually applies a cooldown with the type {@link CooldownScope#USER_GLOBAL} to the provided {@link User}. * - * @param userId The targets' user id. - * @param nextUse The {@link Instant} that marks the time the command can be used again. + * @param user The {@link User} who the {@link Cooldown} should apply to. + * @param nextUse The time as an {@link Instant} where the user can execute the command the next time. + * @since v1.7 */ - public void applyCooldown(long userId, @Nonnull Instant nextUse) { - COOLDOWN_CACHE.put(userId, new Cooldown(Instant.now(), nextUse)); + public void applyCooldown(@Nonnull User user, @Nonnull Instant nextUse) { + COOLDOWN_USER_GLOBAL.put(user.getIdLong(), Cooldown.forNextUse(nextUse)); } /** - * Gets the {@link Cooldown time} the specified user can execute this command again. - * If the user has not executed the command yet, this will return a {@link Cooldown} with - * both the nextUse and the lastUse of {@link Instant#EPOCH} instead. + * Manually applies a cooldown with the type {@link CooldownScope#GUILD} to the provided {@link Guild}. * - * @param userId The targets' user id. - * @return The {@link Instant} that marks the time the command can be used again. + * @param guild The {@link Guild} where the {@link Cooldown} should apply to. + * @param nextUse The time as an {@link Instant} where the user can execute the command the next time. + * @since v1.7 */ - @Nonnull - public Cooldown retrieveCooldown(long userId) { - Cooldown cooldown = COOLDOWN_CACHE.get(userId); - if (cooldown == null) return new Cooldown(Instant.EPOCH, Instant.EPOCH); - return cooldown; + public void applyCooldown(@Nonnull Guild guild, @Nonnull Instant nextUse) { + COOLDOWN_GUILD.put(guild.getIdLong(), Cooldown.forNextUse(nextUse)); } /** - * Returns whether the command can be used by the specified user.
+ * Manually applies a cooldown with the type {@link CooldownScope#MEMBER_GUILD} to the provided {@link User} and {@link Guild}. + * @param user The {@link User} who the {@link Cooldown} should apply to. + * @param guild The {@link Guild} where the {@link Cooldown} should apply to. + * @param nextUse The time as an {@link Instant} where the user can execute the command the next time. + * @since v1.7 + */ + public void applyCooldown(@Nonnull User user, @Nonnull Guild guild, @Nonnull Instant nextUse) { + COOLDOWN_USER_GUILD.put(new Pair<>(user.getIdLong(), guild.getIdLong()), Cooldown.forNextUse(nextUse)); + } + + /** + * Checks if the provided {@link User} is on cooldown.
+ * Checks only for the {@link CooldownScope#USER_GLOBAL} type. * - * Command Cooldowns DO NOT persist between sessions!
+ * @see RestrictedCommand#hasCooldown(Member) + * @param user The {@link User} to check. + * @return true if the user is on cooldown, false otherwise. + * @since v1.7 + */ + public boolean hasCooldown(@Nonnull User user) { + Cooldown cooldown = COOLDOWN_USER_GLOBAL.get(user.getIdLong()); + if (cooldown == null) return false; + cleanUpCooldown(user,cooldown); + return cooldown.isInCooldown(); + } + + /** + * Checks if the provided {@link User} is on cooldown. * - * @param userId The targets' user id. - * @return Whether the command can be executed. + * @see RestrictedCommand#hasCooldown(Member) + * @param user The {@link User} to check. + * @param guild The {@link Guild} to check. + * @return true if the user is on cooldown, false otherwise. + * @since v1.7 */ - public boolean hasCooldown(long userId) { - return retrieveCooldown(userId).getNextUse().isAfter(Instant.now()); + private boolean hasCooldown(@Nonnull User user, @Nonnull Guild guild) { + if (hasCooldown(user)) return true; + Cooldown cooldown = COOLDOWN_GUILD.get(guild.getIdLong()); + if (cooldown != null) return cooldown.isInCooldown(); + cooldown = COOLDOWN_USER_GUILD.get(new Pair<>(user.getIdLong(), guild.getIdLong())); + if (cooldown == null) return false; + + cleanUpCooldown(user, guild, cooldown); + return cooldown.isInCooldown(); } /** - * Model class which represents a single command cooldown. + * Checks if the provided {@link Member} is on cooldown. * - *

Command Cooldowns DO NOT persist between sessions!

+ * @param member The {@link Member} to check. + * @return true if the user is on cooldown, false otherwise. + * @since v1.7 + */ + public boolean hasCooldown(@Nonnull Member member) { + CooldownScope type = retrieveCooldownType(member.getUser(), member.getGuild()); + switch (type) { + case USER_GLOBAL: + return hasCooldown(member.getUser()); + case GUILD: + case MEMBER_GUILD: + return hasCooldown(member.getUser(), member.getGuild()); + case NONE: + return false; + } + return false; + } + + /** + * Retrieves the {@link CooldownScope} for the provided {@link User} and {@link Guild}. + * @param user The {@link User} to check. + * @param guild The {@link Guild} to check. + * @return The {@link CooldownScope}. + * @since v1.7 + */ + private CooldownScope retrieveCooldownType(@Nonnull User user, @Nonnull Guild guild) { + if (COOLDOWN_USER_GLOBAL.get(user.getIdLong()) != null) { + return CooldownScope.USER_GLOBAL; + } else if (COOLDOWN_GUILD.get(guild.getIdLong()) != null) { + return CooldownScope.GUILD; + } else if (COOLDOWN_USER_GUILD.get(Pair.of(user.getIdLong(), guild.getIdLong())) != null) { + return CooldownScope.MEMBER_GUILD; + } + return CooldownScope.NONE; + } + + /** + * Removes the {@link RestrictedCommand#COOLDOWN_GUILD} and {@link RestrictedCommand#COOLDOWN_USER_GUILD} map entries + * linked to the given inputs. + * @param user The {@link User} to clean up. + * @param guild The {@link Guild} to clean up. + * @since v1.7 + */ + private void cleanUpCooldown(@Nonnull User user, @Nonnull Guild guild, @Nonnull Cooldown cooldown) { + if (!cooldown.isInCooldown()) { + COOLDOWN_USER_GUILD.remove(Pair.of(user.getIdLong(), guild.getIdLong())); + COOLDOWN_GUILD.remove(guild.getIdLong()); + } + } + + /** + * Removes the {@link RestrictedCommand#COOLDOWN_USER_GLOBAL} map entries linked to the given input. + * @param user The {@link User} to clean up. + * @since v1.7 + */ + private void cleanUpCooldown(@Nonnull User user, @Nonnull Cooldown cooldown) { + if (!cooldown.isInCooldown()) { + COOLDOWN_USER_GLOBAL.remove(user.getIdLong()); + } + } + + /** + * Retrieves the {@link Cooldown} for the provided {@link User} and {@link Guild}. + * @param user The {@link User} to get the cooldown for. + * @param guild The {@link Guild} to get the cooldown for + * @return The {@link Cooldown}. + * @since v1.7 + */ + @Nonnull + public Cooldown getCooldown(@Nonnull User user, @Nullable Guild guild) { + if (guild == null) return getCooldown(user); + CooldownScope type = retrieveCooldownType(user, guild); + switch (type) { + case USER_GLOBAL: + return COOLDOWN_USER_GLOBAL.get(user.getIdLong()); + case GUILD: + return COOLDOWN_GUILD.get(guild.getIdLong()); + case MEMBER_GUILD: + return COOLDOWN_USER_GUILD.get(Pair.of(user.getIdLong(), guild.getIdLong())); + case NONE: + return Cooldown.forNextUse(Instant.EPOCH); + } + return Cooldown.forNextUse(Instant.EPOCH); + } + + /** + * Retrieves the {@link Cooldown} for the provided {@link User}. + * @param user The {@link User} to get the cooldown for. + * @return The {@link Cooldown}. + * @since v1.7 + */ + @Nonnull + public Cooldown getCooldown(@Nonnull User user) { + return COOLDOWN_USER_GLOBAL.get(user.getIdLong()) == null ? Cooldown.forNextUse(Instant.EPOCH) : COOLDOWN_USER_GLOBAL.get(user.getIdLong()); + } + + + /** + * Model class which represents a single command cooldown.
+ * Command Cooldowns DO NOT persist between sessions! + * @since v1.7 */ public static class Cooldown { + + /** + * The {@link Instant} the user has used the {@link RestrictedCommand} the last time. + * @since v1.7 + */ private final Instant lastUse; + /** + * The {@link Instant} of when a user can use the {@link RestrictedCommand} the next time. + * @since v1.7 + */ private final Instant nextUse; - protected Cooldown(@Nonnull Instant lastUse, @Nonnull Instant nextUse) { + /** + * Creates a new {@link Cooldown} with the provided {@link Instant}s. + * + * @param lastUse The {@link Instant} the user has used the {@link RestrictedCommand} the last time. + * @param nextUse The {@link Instant} of when a user can use the {@link RestrictedCommand} the next time. + * @since v1.7 + */ + private Cooldown(@Nonnull Instant lastUse, @Nonnull Instant nextUse) { this.lastUse = lastUse; this.nextUse = nextUse; } + /** + * Creates a new {@link Cooldown} where the last use is set to the current {@link Instant} and + * the next use is set to the provided {@link Instant}. + * + * @param nextUse The {@link Instant} of when a user can use the {@link RestrictedCommand} the next time. + * @return The new {@link Cooldown}. + * @since v1.7 + */ + @Nonnull + public static Cooldown forNextUse(@Nonnull Instant nextUse) { + return new Cooldown(Instant.now(), nextUse); + } + /** * Gets you the {@link Instant} of when a user can use the {@link RestrictedCommand} the next time. * * @return The next {@link Instant time} the command may be used again. + * @since v1.7 */ @Nonnull public Instant getNextUse() { @@ -196,10 +369,35 @@ public Instant getNextUse() { * Gets you the {@link Instant} the user has used the {@link RestrictedCommand} the last time. * * @return The last {@link Instant time} the command was used. + * @since v1.7 */ @Nonnull public Instant getLastUse() { return lastUse; } + + /** + * Checks if the user is currently on cooldown. + * + * @return true if the user is on cooldown, false otherwise. + * @since v1.7 + */ + public boolean isInCooldown() { + return Instant.now().isBefore(nextUse); + } + + /** + * Returns a string representation of the object. + * + * @return The representation as a {@link String}. + * @since v1.7 + */ + @Override + public String toString() { + return "Cooldown{" + + "lastUse=" + lastUse + + ", nextUse=" + nextUse + + '}'; + } } } diff --git a/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/application/CooldownScope.java b/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/application/CooldownScope.java new file mode 100644 index 00000000..d0e5a10d --- /dev/null +++ b/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/application/CooldownScope.java @@ -0,0 +1,27 @@ +package xyz.dynxsty.dih4jda.interactions.commands.application; + +/** + * The supported {@link xyz.dynxsty.dih4jda.interactions.commands.RestrictedCommand.Cooldown} types.
+ * Cooldowns does not work between different shards or DIH4JDA instances. + */ +public enum CooldownScope { + /** + * Limits the amount how often a user can execute a command.
+ * User / Global + */ + USER_GLOBAL, + /** + * Limits the amount how often everyone can execute a command on a guild.
+ * everyone / Guild + */ + GUILD, + /** + * Limits the amount how often a user can execute a command on a guild.
+ * Member / Guild
+ */ + MEMBER_GUILD, + /** + * No cooldown is applied. + */ + NONE +} diff --git a/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/application/RegistrationType.java b/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/application/RegistrationType.java index 40ad55d4..016ae795 100644 --- a/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/application/RegistrationType.java +++ b/src/main/java/xyz/dynxsty/dih4jda/interactions/commands/application/RegistrationType.java @@ -3,7 +3,6 @@ /** * Whether the command should be queued as a global- or as a guild command. * (Read more) - * */ public enum RegistrationType { /** diff --git a/src/main/java/xyz/dynxsty/dih4jda/util/Pair.java b/src/main/java/xyz/dynxsty/dih4jda/util/Pair.java index f5cc7a95..cdaef8ef 100644 --- a/src/main/java/xyz/dynxsty/dih4jda/util/Pair.java +++ b/src/main/java/xyz/dynxsty/dih4jda/util/Pair.java @@ -1,8 +1,10 @@ package xyz.dynxsty.dih4jda.util; +import org.jetbrains.annotations.Contract; import lombok.Getter; import javax.annotation.Nonnull; +import java.util.Objects; /** * A Pair of two elements. @@ -18,7 +20,7 @@ public class Pair { private final S second; /** - * Creates a new {@link Pair} of to {@link Object}s. + * Creates a new {@link Pair} of two {@link Object}s. * * @param first the first {@link Object}. * @param second the second {@link Object}. @@ -27,4 +29,80 @@ public Pair(@Nonnull F first, @Nonnull S second) { this.first = first; this.second = second; } + + /** + * Creates a new {@link Pair} of two {@link Object}s. + * + * @param first The first value. + * @param second The second value. + * @return The new {@link Pair}. + * @param The first {@link Object} + * @param The second {@link Object}. + */ + @Nonnull + @Contract(value = "_, _ -> new", pure = true) + public static Pair of(@Nonnull F first, @Nonnull S second) { + return new Pair<>(first, second); + } + + /** + * Gets you the {@link Object} that was defined as first. + * + * @return the first {@link Object}. + */ + @Nonnull + public F getFirst() { + return first; + } + + /** + * Gets you the {@link Object} that was defined as second. + * + * @return the second {@link Object}. + */ + @Nonnull + public S getSecond() { + return second; + } + + /** + * Returns a string representation of the object. + * + * @return The representation as a {@link String}. + */ + @Override + public String toString() { + return "Pair{" + + "first=" + first + + ", second=" + second + + '}'; + } + + /** + * Checks if the objects are qual. + * + * @param o The {@link Object} you want to compare. + * @return True if they contain the same values. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Pair pair = (Pair) o; + return Objects.equals(first, pair.first) && Objects.equals(second, pair.second); + } + + /** + * Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap. + * + * @return A hash code value for this object. + */ + @Override + public int hashCode() { + return Objects.hash(first, second); + } }