diff --git a/build.gradle b/build.gradle index 997eb99b..f46c4b63 100644 --- a/build.gradle +++ b/build.gradle @@ -46,7 +46,7 @@ shadowJar { archiveName = 'DiSky ' + version + '.jar' } -def targetJavaVersion = 8 +def targetJavaVersion = 11 java { def javaVersion = JavaVersion.toVersion(targetJavaVersion) sourceCompatibility = javaVersion diff --git a/src/main/java/info/itsthesky/disky/elements/structures/slash/SlashManager.java b/src/main/java/info/itsthesky/disky/elements/structures/slash/SlashManager.java index 1de18b64..2ed18895 100644 --- a/src/main/java/info/itsthesky/disky/elements/structures/slash/SlashManager.java +++ b/src/main/java/info/itsthesky/disky/elements/structures/slash/SlashManager.java @@ -1,16 +1,16 @@ package info.itsthesky.disky.elements.structures.slash; import ch.njol.skript.lang.Trigger; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; import info.itsthesky.disky.DiSky; import info.itsthesky.disky.core.Bot; -import info.itsthesky.disky.elements.effects.SendTyping; import info.itsthesky.disky.elements.events.interactions.SlashCommandReceiveEvent; import info.itsthesky.disky.elements.events.interactions.SlashCompletionEvent; +import info.itsthesky.disky.elements.structures.slash.elements.OnCooldownEvent; import info.itsthesky.disky.elements.structures.slash.models.ParsedArgument; import info.itsthesky.disky.elements.structures.slash.models.ParsedCommand; import info.itsthesky.disky.elements.structures.slash.models.RegisteredCommand; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.events.guild.GuildReadyEvent; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; @@ -22,6 +22,7 @@ import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData; import net.dv8tion.jda.api.requests.RestAction; +import org.jetbrains.annotations.Nullable; import java.util.*; @@ -82,8 +83,13 @@ public void registerCommand(ParsedCommand command) { if (readyGlobal) { runnable.run(); } else { - waitingGlobalCommands.add(runnable); - DiSky.debug("Bot " + bot.getName() + " is not ready yet, waiting for ready event to register command (now " + waitingGlobalCommands.size() + " waiting)"); + if (!bot.getInstance().getStatus().equals(JDA.Status.CONNECTED)) { + waitingGlobalCommands.add(runnable); + DiSky.debug("Bot " + bot.getName() + " is not ready yet, waiting for ready event to register command (now " + waitingGlobalCommands.size() + " waiting)"); + return; + } + + runnable.run(); } } else { @@ -110,8 +116,14 @@ public void registerCommand(ParsedCommand command) { if (readyGuilds.contains(guildId)) { runnable.run(); } else { - waitingGuildCommands.computeIfAbsent(guildId, k -> new ArrayList<>()).add(runnable); - DiSky.debug("Guild " + guildId + " is not ready yet, waiting for ready event to register command (now " + waitingGuildCommands.get(guildId).size() + " waiting)"); + final @Nullable Guild guild = bot.getInstance().getGuildById(guildId); + if (guild == null) { + waitingGuildCommands.computeIfAbsent(guildId, k -> new ArrayList<>()).add(runnable); + DiSky.debug("Guild " + guildId + " is not ready yet, waiting for ready event to register command (now " + waitingGuildCommands.get(guildId).size() + " waiting)"); + return; + } + + runnable.run(); } } } @@ -173,6 +185,8 @@ public void updateCommand(ParsedCommand command, registeredCommand.setTrigger(command.getTrigger()); // we update the trigger anyway & the args registeredCommand.setArguments(command.getArguments()); + registeredCommand.setOnCooldown(command.getOnCooldown()); + registeredCommand.setCooldown(command.getCooldown()); if (!registeredCommand.shouldUpdate(command)) { @@ -223,6 +237,8 @@ public void updateGlobalCommand(ParsedCommand command, registeredCommand.setTrigger(command.getTrigger()); // we update the trigger anyway & the args registeredCommand.setArguments(command.getArguments()); + registeredCommand.setOnCooldown(command.getOnCooldown()); + registeredCommand.setCooldown(command.getCooldown()); if (!registeredCommand.shouldUpdate(command)) { @@ -264,9 +280,6 @@ public void shutdown() { } - - - private SlashCommandData buildCommand(ParsedCommand parsedCommand) { final SlashCommandData slashCommandData = Commands.slash(parsedCommand.getName(), parsedCommand.getDescription()); @@ -306,6 +319,8 @@ else if (value instanceof Number) return slashCommandData; } + // --------------------------------------------------------------------------------------------- + @Override public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { if (!event.isGlobalCommand()) { @@ -315,12 +330,7 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { return; } - registeredCommand.prepareArguments(event.getOptions()); - final Trigger trigger = registeredCommand.getTrigger(); - final SlashCommandReceiveEvent.BukkitSlashCommandReceiveEvent bukkitEvent = new SlashCommandReceiveEvent.BukkitSlashCommandReceiveEvent(new SlashCommandReceiveEvent()); - bukkitEvent.setJDAEvent(event); - - trigger.execute(bukkitEvent); + tryExecute(registeredCommand, event); } else { final RegisteredCommand registeredCommand = findCommand(event.getName()); if (registeredCommand == null) { @@ -328,13 +338,38 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { return; } - registeredCommand.prepareArguments(event.getOptions()); - final Trigger trigger = registeredCommand.getTrigger(); - final SlashCommandReceiveEvent.BukkitSlashCommandReceiveEvent bukkitEvent = new SlashCommandReceiveEvent.BukkitSlashCommandReceiveEvent(new SlashCommandReceiveEvent()); - bukkitEvent.setJDAEvent(event); + tryExecute(registeredCommand, event); + } + } - trigger.execute(bukkitEvent); + private void tryExecute(RegisteredCommand command, SlashCommandInteractionEvent event) { + + // cooldown + if (command.hasCooldown()) { + if (command.isInCooldown(event.getUser())) { + if (command.getOnCooldown() != null) { + final OnCooldownEvent.BukkitCooldownEvent bukkitEvent = + new OnCooldownEvent.BukkitCooldownEvent(new OnCooldownEvent(), + command.getCooldown(event.getUser()) - System.currentTimeMillis()); + bukkitEvent.setJDAEvent(event); + command.prepareArguments(event.getOptions()); + command.getOnCooldown().execute(bukkitEvent); + + if (!bukkitEvent.isCancelled()) + return; + } + } + + command.setCooldown(event.getUser()); } + + // "real" execution + command.prepareArguments(event.getOptions()); + final Trigger trigger = command.getTrigger(); + final SlashCommandReceiveEvent.BukkitSlashCommandReceiveEvent bukkitEvent = new SlashCommandReceiveEvent.BukkitSlashCommandReceiveEvent(new SlashCommandReceiveEvent()); + bukkitEvent.setJDAEvent(event); + + trigger.execute(bukkitEvent); } @Override diff --git a/src/main/java/info/itsthesky/disky/elements/structures/slash/StructSlashCommand.java b/src/main/java/info/itsthesky/disky/elements/structures/slash/StructSlashCommand.java index 31fc4493..dbdd2b51 100644 --- a/src/main/java/info/itsthesky/disky/elements/structures/slash/StructSlashCommand.java +++ b/src/main/java/info/itsthesky/disky/elements/structures/slash/StructSlashCommand.java @@ -8,6 +8,7 @@ import ch.njol.skript.lang.Trigger; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleEvent; +import ch.njol.skript.util.Timespan; import info.itsthesky.disky.DiSky; import info.itsthesky.disky.api.events.SimpleDiSkyEvent; import info.itsthesky.disky.api.skript.entries.MutexEntryData; @@ -17,6 +18,7 @@ import info.itsthesky.disky.elements.events.bots.ReadyEvent; import info.itsthesky.disky.elements.events.interactions.SlashCommandReceiveEvent; import info.itsthesky.disky.elements.events.interactions.SlashCompletionEvent; +import info.itsthesky.disky.elements.structures.slash.elements.OnCooldownEvent; import info.itsthesky.disky.elements.structures.slash.models.ParsedArgument; import info.itsthesky.disky.elements.structures.slash.models.ParsedCommand; import net.dv8tion.jda.api.Permission; @@ -27,6 +29,7 @@ import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryContainer; import org.skriptlang.skript.lang.entry.EntryValidator; +import org.skriptlang.skript.lang.entry.KeyValueEntryData; import org.skriptlang.skript.lang.structure.Structure; import java.util.*; @@ -59,6 +62,14 @@ public class StructSlashCommand extends Structure { .addSection("arguments", true) .addSection("name", true) + .addEntryData(new KeyValueEntryData("cooldown", null, true) { + @Override + protected @Nullable Timespan getValue(@NotNull String value) { + return Timespan.parse(value); + } + }) + .addSection("on cooldown", true) + .addSection("trigger", false) .build(); @@ -137,6 +148,12 @@ public boolean load() { if (!trigger) return false; + + // Cooldown + final boolean cooldown = parseCooldown(parsedCommand); + if (!cooldown) + return false; + //region Debug DiSky.debug("------------------- Name -------------------"); DiSky.debug("Default: " + parsedCommand.getName()); @@ -181,6 +198,19 @@ public boolean load() { else DiSky.debug("No trigger found."); + DiSky.debug("------------------- Cooldown -------------------"); + if (parsedCommand.hasCooldown()) { + DiSky.debug("Cooldown: " + parsedCommand.getCooldown() + "ms (" + parsedCommand.getCooldown() / 1000 + "s)"); + if (parsedCommand.getOnCooldown() != null) { + DiSky.debug(" - On Cooldown trigger found."); + DiSky.debug(" - Label: " + parsedCommand.getOnCooldown().getDebugLabel()); + } + else + DiSky.debug(" - No on cooldown trigger found."); + } + else + DiSky.debug("No cooldown found."); + DiSky.debug("------------------- End -------------------"); //endregion @@ -260,69 +290,70 @@ private List parseArguments() { if (argNode == null) continue; - if (!(argNode instanceof SectionNode)) { - Skript.error("Invalid argument node (Not a section! Check the wiki for information's): " + argNode); - return null; - } - - final SectionNode argSection = (SectionNode) argNode; - final EntryContainer container = ARGUMENT_VALIDATOR.validate(argSection); - if (container == null) - return null; - - final String description = container.get("description", String.class, true); - - // Choices, if applicable - final SectionNode choiceNode = container.getOptional("choices", SectionNode.class, true); - if (choiceNode != null) { - final OptionType type = argument.getType(); - if (!type.canSupportChoices()) { - Skript.error("Choices are not supported for the argument type: " + type); + if (argNode instanceof SectionNode) { + final SectionNode argSection = (SectionNode) argNode; + final EntryContainer container = ARGUMENT_VALIDATOR.validate(argSection); + if (container == null) return null; - } - choiceNode.convertToEntries(0); - for (Node choice : choiceNode) { - final String name = choice.getKey(); - final String value = choiceNode.get(name, ""); - if (value.isEmpty()) { - Skript.error("Empty value for choice: " + name); + final String description = container.get("description", String.class, true); + + // Choices, if applicable + final SectionNode choiceNode = container.getOptional("choices", SectionNode.class, true); + if (choiceNode != null) { + final OptionType type = argument.getType(); + if (!type.canSupportChoices()) { + Skript.error("Choices are not supported for the argument type: " + type); return null; } - final Object arg; - if (type.equals(OptionType.NUMBER) || type.equals(OptionType.INTEGER)) { - try { - arg = Integer.parseInt(value); - } catch (NumberFormatException ex) { - Skript.error("Invalid number value for choice: " + name); + choiceNode.convertToEntries(0); + for (Node choice : choiceNode) { + final String name = choice.getKey(); + final String value = choiceNode.get(name, ""); + if (value.isEmpty()) { + Skript.error("Empty value for choice: " + name); return null; } - } else if (type.equals(OptionType.STRING)) { - arg = value; - } else { - Skript.error("Invalid choice type: " + type); - return null; + + final Object arg; + if (type.equals(OptionType.NUMBER) || type.equals(OptionType.INTEGER)) { + try { + arg = Integer.parseInt(value); + } catch (NumberFormatException ex) { + Skript.error("Invalid number value for choice: " + name); + return null; + } + } else if (type.equals(OptionType.STRING)) { + arg = value; + } else { + Skript.error("Invalid choice type: " + type); + return null; + } + + argument.addChoice(name, arg); } + } - argument.addChoice(name, arg); + // auto completion + final SectionNode completionNode = container.getOptional("on completion request", SectionNode.class, true); + if (completionNode != null) { + final Trigger trigger = new Trigger(getParser().getCurrentScript(), "completion for argument " + argument.getName(), new SimpleEvent(), + SkriptUtils.loadCode(completionNode, SlashCompletionEvent.BukkitSlashCompletionEvent.class)); + argument.setOnCompletionRequest(trigger); } - } - // auto completion - final SectionNode completionNode = container.getOptional("on completion request", SectionNode.class, true); - if (completionNode != null) { - final Trigger trigger = new Trigger(getParser().getCurrentScript(), "completion for argument " + argument.getName(), new SimpleEvent(), - SkriptUtils.loadCode(completionNode, SlashCompletionEvent.BukkitSlashCompletionEvent.class)); - argument.setOnCompletionRequest(trigger); - } + if (argument.hasChoices() && argument.isAutoCompletion()) { + Skript.error("You can't have both auto completion and choices for the same argument."); + return null; + } - if (argument.hasChoices() && argument.isAutoCompletion()) { - Skript.error("You can't have both auto completion and choices for the same argument."); - return null; + argument.setDescription(description); + } else { + final String description = node.getValue(argument.getName()); + argument.setDescription(description); } - argument.setDescription(description); } return arguments; @@ -472,6 +503,29 @@ public boolean parseTrigger(ParsedCommand parsedCommand) { //endregion + //region Cooldown + + public boolean parseCooldown(ParsedCommand parsedCommand) { + final Timespan cooldown = entryContainer.getOptional("cooldown", Timespan.class, true); + if (cooldown == null) + return true; + + final SectionNode sectionNode = entryContainer.getOptional("on cooldown", SectionNode.class, true); + if (sectionNode == null) { + Skript.error("You must specify a section for the cooldown. ('on cooldown' section, to be ran when the command is on cooldown)"); + return false; + } + + + final Trigger trigger = new Trigger(getParser().getCurrentScript(), "on cooldown for " + parsedCommand.getName(), + new OnCooldownEvent(), + SkriptUtils.loadCode(sectionNode, OnCooldownEvent.BukkitCooldownEvent.class)); + + parsedCommand.setCooldown(cooldown.getMilliSeconds()); + parsedCommand.setOnCooldown(trigger); + return true; + } + @Override public @NotNull Priority getPriority() { return PRIORITY; diff --git a/src/main/java/info/itsthesky/disky/elements/structures/slash/elements/ExprRemainingTime.java b/src/main/java/info/itsthesky/disky/elements/structures/slash/elements/ExprRemainingTime.java new file mode 100644 index 00000000..e3f9332b --- /dev/null +++ b/src/main/java/info/itsthesky/disky/elements/structures/slash/elements/ExprRemainingTime.java @@ -0,0 +1,40 @@ +package info.itsthesky.disky.elements.structures.slash.elements; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.util.Timespan; +import info.itsthesky.disky.api.skript.SimpleGetterExpression; +import org.jetbrains.annotations.NotNull; + +public class ExprRemainingTime extends SimpleGetterExpression { + + static { + Skript.registerExpression( + ExprRemainingTime.class, + Timespan.class, + ExpressionType.COMBINED, + "remaining time" + ); + } + + @Override + protected String getValue() { + return "remaining time of the cooldown"; + } + + @Override + protected Class getEvent() { + return OnCooldownEvent.BukkitCooldownEvent.class; + } + + @Override + protected Timespan convert(OnCooldownEvent.BukkitCooldownEvent event) { + final long remaining = event.getRemainingTime(); + return new Timespan(remaining); + } + + @Override + public @NotNull Class getReturnType() { + return Timespan.class; + } +} diff --git a/src/main/java/info/itsthesky/disky/elements/structures/slash/ExprSlashArgument.java b/src/main/java/info/itsthesky/disky/elements/structures/slash/elements/ExprSlashArgument.java similarity index 93% rename from src/main/java/info/itsthesky/disky/elements/structures/slash/ExprSlashArgument.java rename to src/main/java/info/itsthesky/disky/elements/structures/slash/elements/ExprSlashArgument.java index db707176..69999c97 100644 --- a/src/main/java/info/itsthesky/disky/elements/structures/slash/ExprSlashArgument.java +++ b/src/main/java/info/itsthesky/disky/elements/structures/slash/elements/ExprSlashArgument.java @@ -1,10 +1,7 @@ -package info.itsthesky.disky.elements.structures.slash; +package info.itsthesky.disky.elements.structures.slash.elements; -import ch.njol.skript.ScriptLoader; import ch.njol.skript.Skript; import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Name; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; import ch.njol.skript.lang.Literal; @@ -15,15 +12,12 @@ import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; -import info.itsthesky.disky.elements.commands.Argument; -import info.itsthesky.disky.elements.commands.CommandEvent; -import info.itsthesky.disky.elements.commands.CommandFactory; +import info.itsthesky.disky.elements.structures.slash.StructSlashCommand; import info.itsthesky.disky.elements.structures.slash.models.ParsedArgument; import org.bukkit.event.Event; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.List; public class ExprSlashArgument extends SimpleExpression { diff --git a/src/main/java/info/itsthesky/disky/elements/structures/slash/elements/OnCooldownEvent.java b/src/main/java/info/itsthesky/disky/elements/structures/slash/elements/OnCooldownEvent.java new file mode 100644 index 00000000..5dca574a --- /dev/null +++ b/src/main/java/info/itsthesky/disky/elements/structures/slash/elements/OnCooldownEvent.java @@ -0,0 +1,36 @@ +package info.itsthesky.disky.elements.structures.slash.elements; + +import info.itsthesky.disky.api.events.specific.InteractionEvent; +import info.itsthesky.disky.api.events.specific.ModalEvent; +import info.itsthesky.disky.elements.events.interactions.SlashCommandReceiveEvent; +import org.bukkit.event.Cancellable; + +public class OnCooldownEvent extends SlashCommandReceiveEvent { + + public static class BukkitCooldownEvent extends BukkitSlashCommandReceiveEvent implements Cancellable, + ModalEvent, InteractionEvent { + + public BukkitCooldownEvent(SlashCommandReceiveEvent event, + long remainingTime) { + super(event); + this.remainingTime = remainingTime; + } + + private long remainingTime; + private boolean cancelled = false; + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + public long getRemainingTime() { + return remainingTime; + } + } + +} diff --git a/src/main/java/info/itsthesky/disky/elements/structures/slash/models/ParsedCommand.java b/src/main/java/info/itsthesky/disky/elements/structures/slash/models/ParsedCommand.java index 30f4a260..6fc414b9 100644 --- a/src/main/java/info/itsthesky/disky/elements/structures/slash/models/ParsedCommand.java +++ b/src/main/java/info/itsthesky/disky/elements/structures/slash/models/ParsedCommand.java @@ -29,6 +29,9 @@ public class ParsedCommand { private Bot bot; private List guilds = new ArrayList<>(); + private long cooldown; // in ms + private Trigger onCooldown; + private Trigger trigger; // ------------------------------------------------------------ @@ -113,6 +116,26 @@ public void setTrigger(Trigger trigger) { this.trigger = trigger; } + public long getCooldown() { + return cooldown; + } + + public void setCooldown(long cooldown) { + this.cooldown = cooldown; + } + + public Trigger getOnCooldown() { + return onCooldown; + } + + public void setOnCooldown(Trigger onCooldown) { + this.onCooldown = onCooldown; + } + + public boolean hasCooldown() { + return cooldown > 0 || onCooldown != null; + } + public boolean shouldUpdate(ParsedCommand command) { return !this.equals(command) || !this.getArguments().equals(command.getArguments()) diff --git a/src/main/java/info/itsthesky/disky/elements/structures/slash/models/RegisteredCommand.java b/src/main/java/info/itsthesky/disky/elements/structures/slash/models/RegisteredCommand.java index ca434261..71cd44e4 100644 --- a/src/main/java/info/itsthesky/disky/elements/structures/slash/models/RegisteredCommand.java +++ b/src/main/java/info/itsthesky/disky/elements/structures/slash/models/RegisteredCommand.java @@ -1,11 +1,18 @@ package info.itsthesky.disky.elements.structures.slash.models; +import net.dv8tion.jda.api.entities.User; + +import java.util.HashMap; +import java.util.Map; + public class RegisteredCommand extends ParsedCommand { private final long commandId; private final String botName; private final String guildId; + private final Map cooldowns; + public RegisteredCommand(ParsedCommand parsedCommand, long commandId, String botName, @@ -19,10 +26,13 @@ public RegisteredCommand(ParsedCommand parsedCommand, setDisabledByDefault(parsedCommand.isDisabledByDefault()); setTrigger(parsedCommand.getTrigger()); setBot(parsedCommand.getBot()); + setOnCooldown(parsedCommand.getOnCooldown()); + setCooldown(parsedCommand.getCooldown()); this.commandId = commandId; this.botName = botName; this.guildId = guildId; + this.cooldowns = new HashMap<>(); } public long getCommandId() { @@ -36,4 +46,16 @@ public String getBotName() { public String getGuildId() { return guildId; } + + public boolean isInCooldown(User user) { + return cooldowns.containsKey(user) && cooldowns.get(user) > System.currentTimeMillis(); + } + + public long getCooldown(User user) { + return cooldowns.getOrDefault(user, 0L); + } + + public void setCooldown(User user) { + cooldowns.put(user, System.currentTimeMillis() + getCooldown()); + } }