Skip to content

Commit

Permalink
Add CooldownPool
Browse files Browse the repository at this point in the history
  • Loading branch information
MineKing9534 committed Dec 17, 2023
1 parent 9a47f16 commit 91da977
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 33 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

group = 'de.mineking'
version = '3.2.4'
version = '3.3.0'

var commit = getCommit()
var release = System.getenv("RELEASE") == "true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import de.mineking.discordutils.commands.condition.IRegistrationCondition;
import de.mineking.discordutils.commands.condition.cooldown.Cooldown;
import de.mineking.discordutils.commands.condition.cooldown.CooldownImpl;
import de.mineking.discordutils.commands.condition.cooldown.CooldownPool;
import de.mineking.discordutils.commands.context.IAutocompleteContext;
import de.mineking.discordutils.commands.context.ICommandContext;
import de.mineking.discordutils.commands.option.Autocomplete;
Expand All @@ -19,6 +20,7 @@
import java.time.Duration;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

Expand All @@ -32,6 +34,8 @@
* @see CommandManager#registerCommand(Class)
*/
public class AnnotatedCommand<T, C extends ICommandContext, A extends IAutocompleteContext> extends Command<C> {
private final static Map<String, CooldownImpl<?>> cooldowns = new HashMap<>();

@NotNull
public final Class<T> clazz;
@NotNull
Expand Down Expand Up @@ -108,30 +112,39 @@ private AnnotatedCommand(CommandManager<C, ?> manager, ApplicationCommand info,
CommandManager.logger.error("Failed to read condition field", e);
}
}
}

for(var m : clazz.getMethods()) {
var cooldown = m.getAnnotation(Cooldown.class);

if(cooldown != null) {
//TODO use Cooldown#uses()
condition = getCondition().and(new CooldownImpl<>(Duration.ofSeconds(cooldown.interval()), (man, context) ->
instance.apply(context).ifPresent(i -> {
try {
manager.getManager().invokeMethod(m, i, p -> {
if(p.getType().isAssignableFrom(context.getClass())) return context;
else return null;
});
} catch(InvocationTargetException | IllegalAccessException e) {
CommandManager.logger.error("Failed to execute cooldown error method", e);
}
})
));

break;
}
for(var m : clazz.getMethods()) {
var cooldown = m.getAnnotation(Cooldown.class);

if(cooldown != null) {
var impl = new CooldownImpl<C>(Duration.ofMillis(cooldown.unit().toMillis(cooldown.interval())), cooldown.uses(), (man, context) ->
instance.apply(context).ifPresent(i -> {
try {
manager.getManager().invokeMethod(m, i, p -> {
if(p.getType().isAssignableFrom(context.getClass())) return context;
else return null;
});
} catch(InvocationTargetException | IllegalAccessException e) {
CommandManager.logger.error("Failed to execute cooldown error method", e);
}
})
);

if(!cooldown.identifier().isEmpty()) cooldowns.put(cooldown.identifier(), impl);

condition = getCondition().and(impl);

break;
}
}

if(clazz.isAnnotationPresent(CooldownPool.class)) {
var impl = cooldowns.get(clazz.getAnnotation(CooldownPool.class).value());
if(impl == null) CommandManager.logger.warn("Cooldown-Pool referenced by " + getPath(".") + " not found - Ignoring...");
else condition = getCondition().and((CooldownImpl<C>) impl);
}

if(info.type() == net.dv8tion.jda.api.interactions.commands.Command.Type.SLASH) {
for(var t : clazz.getClasses()) {
var i = t.getAnnotation(ApplicationCommand.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
Expand All @@ -13,9 +14,21 @@
*/
int interval();

/**
* The unit of {@link #interval()}
*/
TimeUnit unit() default TimeUnit.SECONDS;

/**
* The number of allowed uses in the specified time interval.
* NOTE: THIS IS NOT SUPPORTED YET!
*/
int uses() default 1;

/**
* An identifier of this cooldown. Can be used to use the same cooldown scope for multiple commands
*
* @see CooldownPool
*/
String identifier() default "";
}
Original file line number Diff line number Diff line change
@@ -1,45 +1,50 @@
package de.mineking.discordutils.commands.condition.cooldown;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import de.mineking.discordutils.commands.CommandManager;
import de.mineking.discordutils.commands.condition.IExecutionCondition;
import de.mineking.discordutils.commands.context.ICommandContext;
import net.dv8tion.jda.api.interactions.DiscordLocale;
import org.jetbrains.annotations.NotNull;

import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;

public class CooldownImpl<C extends ICommandContext> implements IExecutionCondition<C> {
private final Map<Long, Instant> cooldown = new HashMap<>();
private final Cache<Long, AtomicInteger> cooldown;

private final Duration duration;
private final int uses;

private final BiConsumer<CommandManager<C, ?>, C> handler;

/**
* @param duration The cooldown duration
* @param uses The allowed number of uses in the provided interval
* @param handler A handler that is executed if an execution is blocked due to the user being on cooldown. You should send an error message here.
*/
public CooldownImpl(Duration duration, BiConsumer<CommandManager<C, ?>, C> handler) {
public CooldownImpl(@NotNull Duration duration, int uses, @NotNull BiConsumer<CommandManager<C, ?>, C> handler) {
cooldown = Caffeine.newBuilder()
.expireAfterWrite(duration)
.build();

this.duration = duration;
this.uses = uses;
this.handler = handler;
}

@Override
public boolean isAllowed(@NotNull CommandManager<C, ?> manager, @NotNull C context) {
long user = context.getEvent().getUser().getIdLong();

if(!cooldown.containsKey(user)) return true;
if(cooldown.get(user).isAfter(Instant.now())) {
if(handler != null) handler.accept(manager, context);
return false;
}
var current = cooldown.getIfPresent(user);

cooldown.put(user, Instant.now().plus(duration));
if(current == null) cooldown.put(user, current = new AtomicInteger());
else current.incrementAndGet();

return true;
return current.get() < uses;
}

@NotNull
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package de.mineking.discordutils.commands.condition.cooldown;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CooldownPool {
/**
* The identifier of the cooldown
*/
String value();
}

0 comments on commit 91da977

Please sign in to comment.