Skip to content

Added Cooldown-Types #84

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
375cd2a
added cooldown types
DenuxPlays Nov 15, 2022
c042e11
first implementations of Cooldown types
DenuxPlays Nov 15, 2022
966f8ec
Merge branch 'main' into denux/cooldown_types
DenuxPlays Nov 15, 2022
c494a51
fixes bug
DenuxPlays Nov 15, 2022
78f8686
more bug fixing
DenuxPlays Nov 15, 2022
d51d9bd
even more bug fixing
DenuxPlays Nov 16, 2022
3f8203b
added java docs in `Pair.java`
DenuxPlays Nov 16, 2022
e95c62d
added more java docs
DenuxPlays Nov 16, 2022
be0fd94
removed debug prints
DenuxPlays Nov 16, 2022
06e1b08
javadocs + optimized imports
DenuxPlays Nov 16, 2022
05e6231
updated cooldown example
DenuxPlays Nov 16, 2022
e975659
Merge branch 'main' into denux/cooldown_types
DenuxPlays Nov 28, 2022
6b07d6d
Merge remote-tracking branch 'DIH4JDA/main' into denux/cooldown_types
DenuxPlays Dec 5, 2022
0f0dbdc
resolved merge conflicts
DenuxPlays Feb 11, 2023
ca519c3
Merge branch 'main' into denux/cooldown_types
DenuxPlays Feb 14, 2023
8beadf6
first implementation of cooldown types (v2)
DenuxPlays Feb 14, 2023
a8731ad
added missing java docs
DenuxPlays Feb 14, 2023
af228c5
fixed some bugs
DenuxPlays Feb 14, 2023
e402518
fixed leftover bugs
DenuxPlays Feb 14, 2023
8ff1082
optimized imports
DenuxPlays Feb 14, 2023
494bd5f
improved java docs
DenuxPlays Feb 14, 2023
bd16409
change names
DenuxPlays Feb 14, 2023
d0c12a9
Update InteractionHandler.java
DenuxPlays Feb 20, 2023
7820040
Merge branch 'main' into denux/cooldown_types
DenuxPlays Apr 27, 2023
3704cd3
Merge branch 'main' into denux/cooldown_types
DenuxPlays Sep 19, 2023
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
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.0")
testImplementation("ch.qos.logback:logback-classic:1.4.4")
//needed for reasons...
testCompileOnly("com.google.code.findbugs:jsr305:3.0.2")

implementation("com.github.DV8FromTheWorld:JDA:3e37938a3a")
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.CooldownType;
import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand;
import xyz.dynxsty.dih4jda.interactions.components.ButtonHandler;
import xyz.dynxsty.dih4jda.util.ComponentIdBuilder;
Expand All @@ -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), CooldownType.USER_GLOBAL); // Add cooldown to prevent spam by users
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import xyz.dynxsty.dih4jda.events.DIH4JDAEventListener;

import javax.annotation.Nonnull;
import java.time.ZoneId;

public class DIH4JDAListener implements DIH4JDAEventListener {

Expand All @@ -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: <t:%s:R>", event.getCooldown().getNextUse().toEpochMilli()).queue();
event.getInteraction().replyFormat("You are on cooldown. Next use <t:%s:R>",
event.getCooldown().getNextUse().atZone(ZoneId.systemDefault()).toEpochSecond()).queue();
}

// add more events if you need to
Expand Down
17 changes: 13 additions & 4 deletions src/main/java/xyz/dynxsty/dih4jda/InteractionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.CooldownType;
import xyz.dynxsty.dih4jda.interactions.commands.application.RegistrationType;
import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand;
import xyz.dynxsty.dih4jda.interactions.components.ButtonHandler;
Expand Down Expand Up @@ -591,12 +592,20 @@ private boolean passesRequirements(@Nonnull CommandInteraction interaction, @Non
}
}
// check if the command has enabled some sort of cooldown
if (command.getCommandCooldown() != Duration.ZERO) {
if (command.hasCooldown(userId)) {
DIH4JDAEvent.fire(new CommandCooldownEvent(dih4jda, interaction, command.retrieveCooldown(userId)));
Pair<Duration, CooldownType> cooldownPair = command.getCommandCooldown();
if (cooldownPair.getFirst() != Duration.ZERO) {
long guildId = interaction.getGuild().getIdLong();
RestrictedCommand.Cooldown cooldown = command.retrieveCooldown(userId, guildId);
if (command.hasCooldown(userId, guildId)) {
DIH4JDAEvent.fire(new CommandCooldownEvent(dih4jda, interaction, cooldown));
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 USER_GUILD: command.applyCooldown(interaction.getUser(), interaction.getGuild(), nextUse); break;
case GUILD: command.applyCooldown(interaction.getGuild(), nextUse); break;
}
}
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,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.CooldownType;

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, CooldownType)} Command Cooldown}
*
* <b>Command Cooldowns DO NOT persist between sessions!</b>
*
* @see RestrictedCommand#setCommandCooldown(Duration)
* @see RestrictedCommand#setCommandCooldown(Duration, CooldownType)
*/
public class CommandCooldownEvent extends DIH4JDAEvent<CommandInteraction> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.CooldownType;
import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand;

import javax.annotation.Nonnull;
Expand Down Expand Up @@ -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}<br>
* a specified {@link RestrictedCommand#setCommandCooldown(Duration, CooldownType) Command Cooldown}<br>
*
* <b>Command Cooldowns DO NOT persist between sessions!</b><br>
*
* @param event The {@link CommandCooldownEvent} that was fired.
* @see RestrictedCommand#setCommandCooldown(Duration)
* @see RestrictedCommand#setCommandCooldown(Duration, CooldownType)
*/
default void onCommandCooldown(@Nonnull CommandCooldownEvent event) {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import xyz.dynxsty.dih4jda.interactions.commands.application.CooldownType;
import xyz.dynxsty.dih4jda.util.Pair;

import javax.annotation.Nonnull;
import java.time.Duration;
Expand All @@ -15,13 +18,14 @@
* @since v1.6
*/
public abstract class RestrictedCommand {
private final Map<Long, Cooldown> COOLDOWN_CACHE = new HashMap<>();
private final Map<Pair<Long, Long>, Cooldown> COOLDOWN_CACHE = 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 CooldownType cooldownType = CooldownType.USER_GLOBAL;

/**
* Creates a default instance.
Expand Down Expand Up @@ -113,32 +117,75 @@ public final void setRequiredRoles(@Nonnull Long... roles) {
* <b>Command Cooldowns DO NOT persist between sessions!</b><br>
*
* @param commandCooldown The {@link Duration} the user has to wait between command executions.
* @param type The {@link CooldownType} you want to use.
*/
public void setCommandCooldown(@Nonnull Duration commandCooldown) {
public void setCommandCooldown(@Nonnull Duration commandCooldown, @Nonnull CooldownType type) {
this.commandCooldown = commandCooldown;
this.cooldownType = type;
}

/**
* Returns the {@link Duration} the user has to wait between command executions.
*
* @return The {@link Duration}.
* @see RestrictedCommand#setCommandCooldown(Duration)
* @see RestrictedCommand#setCommandCooldown(Duration, CooldownType)
*/
@Nonnull
public Duration getCommandCooldown() {
return commandCooldown;
public Pair<Duration, CooldownType> getCommandCooldown() {
return new Pair<>(commandCooldown, cooldownType);
}

/**
* Manually applies a cooldown for the specified user id.<br>
* Manually applies a cooldown for the specified user and guild id.<br>
* <b>Command Cooldowns DO NOT persist between sessions!</b><br>
* <b>For internal use only!</b>
*
* @param userId The id of the user you want to apply the cooldown on.
* @param guildId The id of the guild you want to apply the cooldown on.
* @param nextUse The time as an {@link Instant} where the user can execute the command the next time.
*/
private void applyCooldown(long userId, long guildId, @Nonnull Instant nextUse) {
COOLDOWN_CACHE.put(new Pair<>(userId, guildId), new Cooldown(Instant.now(), nextUse));
}

/**
* Manually applies a cooldown for the specified user.<br>
* Represents the {@link CooldownType#USER_GLOBAL}.<br>
* <b>Command Cooldowns DO NOT persist between sessions!</b><br>
* <b>For internal use only!</b>
*
* @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} you want to apply the cooldown on.
* @param nextUse The time as an {@link Instant} where the user can execute the command the next time.
*/
public void applyCooldown(@Nonnull User user, @Nonnull Instant nextUse) {
applyCooldown(user.getIdLong(), 0, nextUse);
}

/**
* Manually applies a cooldown for the specified user and guild.<br>
* Represents the {@link CooldownType#USER_GUILD}.<br>
* <b>Command Cooldowns DO NOT persist between sessions!</b><br>
* <b>For internal use only!</b>
*
* @param user The {@link User} you want to apply the cooldown on.
* @param guild The {@link Guild} you want to apply the cooldown on.
* @param nextUse The time as an {@link Instant} where the user can execute the command the next time.
*/
public void applyCooldown(@Nonnull User user, @Nonnull Guild guild, @Nonnull Instant nextUse) {
applyCooldown(user.getIdLong(), guild.getIdLong(), nextUse);
}

/**
* Manually applies a cooldown for the specified guild.<br>
* Represents the {@link CooldownType#GUILD}.<br>
* <b>Command Cooldowns DO NOT persist between sessions!</b><br>
* <b>For internal use only!</b>
*
* @param guild The {@link Guild} you want to apply the cooldown on.
* @param nextUse The time as an {@link Instant} where the user can execute the command the next time.
*/
public void applyCooldown(long userId, @Nonnull Instant nextUse) {
COOLDOWN_CACHE.put(userId, new Cooldown(Instant.now(), nextUse));
public void applyCooldown(@Nonnull Guild guild, @Nonnull Instant nextUse) {
applyCooldown(0, guild.getIdLong(), nextUse);
}

/**
Expand All @@ -147,12 +194,20 @@ public void applyCooldown(long userId, @Nonnull Instant nextUse) {
* both the nextUse and the lastUse of {@link Instant#EPOCH} instead.
*
* @param userId The targets' user id.
* @param guildId The targets' guild id.
* @return The {@link Instant} that marks the time the command can be used again.
*/
@Nonnull
public Cooldown retrieveCooldown(long userId) {
Cooldown cooldown = COOLDOWN_CACHE.get(userId);
if (cooldown == null) return new Cooldown(Instant.EPOCH, Instant.EPOCH);
public Cooldown retrieveCooldown(long userId, long guildId) {
Cooldown cooldown = null;
switch (cooldownType) {
case USER_GLOBAL: cooldown = COOLDOWN_CACHE.get(new Pair<>(userId, 0L)); break;
case USER_GUILD: cooldown = COOLDOWN_CACHE.get(new Pair<>(userId, guildId)); break;
case GUILD: cooldown = COOLDOWN_CACHE.get(new Pair<>(0L, guildId)); break;
}
if (cooldown == null) {
return new Cooldown(Instant.EPOCH, Instant.EPOCH);
}
return cooldown;
}

Expand All @@ -162,18 +217,30 @@ public Cooldown retrieveCooldown(long userId) {
* <b>Command Cooldowns DO NOT persist between sessions!</b><br>
*
* @param userId The targets' user id.
* @param guildId The targets' guild id.
* @return Whether the command can be executed.
*/
public boolean hasCooldown(long userId) {
return retrieveCooldown(userId).getNextUse().isAfter(Instant.now());
public boolean hasCooldown(long userId, long guildId) {
Cooldown cooldown;
cooldown = retrieveCooldown(userId, guildId);
boolean hasCooldown = cooldown.getNextUse().isAfter(Instant.now());
if (!hasCooldown) {
switch (cooldownType) {
case USER_GLOBAL: COOLDOWN_CACHE.remove(new Pair<>(userId, 0L)); break;
case USER_GUILD: COOLDOWN_CACHE.remove(new Pair<>(userId, guildId)); break;
case GUILD: COOLDOWN_CACHE.remove(new Pair<>(0L, guildId)); break;
}
}
return hasCooldown;
}

/**
* Model class which represents a single command cooldown.
*
* <h2>Command Cooldowns DO NOT persist between sessions!</h2>
* <p>
* <b>Command Cooldowns DO NOT persist between sessions!</b>
*/
public static class Cooldown {

private final Instant lastUse;
private final Instant nextUse;

Expand Down Expand Up @@ -201,5 +268,18 @@ public Instant getNextUse() {
public Instant getLastUse() {
return lastUse;
}

/**
* Returns a string representation of the object.
*
* @return The representation as a {@link String}.
*/
@Override
public String toString() {
return "Cooldown{" +
"lastUse=" + lastUse +
", nextUse=" + nextUse +
'}';
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package xyz.dynxsty.dih4jda.interactions.commands.application;

/**
* The supported {@link xyz.dynxsty.dih4jda.interactions.commands.RestrictedCommand.Cooldown} types.<br>
* <b>Cooldowns does not work between different shards or DIH4JDA instances.</b>
*/
public enum CooldownType {
/**
* Limits the amount how often a user can execute a command.<br>
* <b>User / Global</b>
*/
USER_GLOBAL,
/**
* Limits the amount how often a user can execute a command on a guild.<br>
* <b>User / Guild</b><br>
*/
USER_GUILD,
/**
* Limits the amount how often everyone can execute a command on a guild.<br>
* <b>everyone / Guild</b>
*/
GUILD
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
/**
* Whether the command should be queued as a global- or as a guild command.
* <a href="https://discord.com/developers/docs/interactions/application-commands">(Read more)</a>
*
*/
public enum RegistrationType {
/**
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/xyz/dynxsty/dih4jda/util/Pair.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package xyz.dynxsty.dih4jda.util;

import javax.annotation.Nonnull;
import java.util.Objects;

/**
* A Pair of two elements.
Expand Down Expand Up @@ -43,4 +44,45 @@ public F getFirst() {
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);
}
}