Page MenuHomedesp's stash

Command.java
No OneTemporary

Command.java

package me.despawningbone.discordbot.command;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
//import java.time.OffsetDateTime;
//import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import org.apache.commons.lang3.exception.ExceptionUtils;
import me.despawningbone.discordbot.DiscordBot;
import me.despawningbone.discordbot.command.CommandResult.CommandResultType;
import me.despawningbone.discordbot.utils.MiscUtils;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
public class Command { //no need to be abstract anymore //make it final to ensure thread safety?
protected String name;
protected String desc;
protected String usage;
protected List<String> alias; //fixed-size in this case is negligible, as it will not be edited later on anyways
protected List<String> remarks = null;
protected boolean isDisabled;
protected EnumSet<Permission> perms = EnumSet.noneOf(Permission.class); //so that not configured commands would have a default of no perms needed, instead of null //WILL NOT ALLOW DENY OVERRIDE FOR NOW
protected int botUserLevel; //0 = everyone, 1 = BotMod, 2 = BotOwner; negative value = either BotUser or required perm
protected List<String> examples;
protected ExecuteImpl impl = null;
private Command parent = null;
private String cat; //not used; remove? //actually gets used now
private LinkedHashMap<String, Command> subCmds = new LinkedHashMap<>(); //preserve order
public HashMap<String, Command> subCmdAliases = new HashMap<String, Command>(); //TODO temporary solution
//DONE add examples; what about subcmds?
//for overriding
protected Command() {}
//only used by nested commands, since all explicitly declared commands override execute(); hot creation of subcommands are possible, but permission setting via !desp settings is not supported
public Command(String name, List<String> aliases, Command.ExecuteImpl exeFunc, String usage, List<String> examples, String desc, List<String> remarks, EnumSet<Permission> defaultPerms, int botUserLevel) {
this.name = name;
this.alias = aliases;
this.impl = exeFunc;
this.usage = usage;
this.examples = examples;
this.desc = desc;
this.remarks = remarks;
this.perms = defaultPerms;
this.botUserLevel = botUserLevel;
}
public interface ExecuteImpl { //DONE make this a seperate class to store the ever increasing params? if so, protected constructor since it doesnt need to be instantiated elsewhere other than registerSubCommand here //implemented nested commands instead
public CommandResult execute(TextChannel channel, User author, Message msg, String[] args);
}
private void setParent(Command parent) { //private so only register function can access to avoid accidental use
this.parent = parent;
}
//for anonymous inner classes if needed
public void registerSubCommand(Command subCmd) {
subCmd.setParent(this); //make parent only set on register since subcmds with wrong/no parents can result in finnicky bugs
subCmds.put(subCmd.getName(), subCmd);
if(subCmd.getAliases() != null) subCmd.getAliases().forEach(a -> subCmdAliases.put(a, subCmd));
}
public void registerSubCommand(String name, List<String> aliases, Command.ExecuteImpl exe, String usage, List<String> examples, String desc, List<String> remarks) { //subcmd has its own examples? //DONE subcmd permissions and botuserlevel //probably not needed, as its gonna be binded to guild instead of cmds soon enough
registerSubCommand(name, aliases, exe, usage, examples, desc, remarks, EnumSet.noneOf(Permission.class), 0);
}
public void registerSubCommand(String name, List<String> aliases, Command.ExecuteImpl exe, String usage, List<String> examples, String desc, List<String> remarks, EnumSet<Permission> defaultPerms, int botUserLevel) {
Command subCmd = new Command(name, aliases, exe, usage, examples, desc, remarks, defaultPerms, botUserLevel);
registerSubCommand(subCmd);
}
public boolean hasSubCommand() {
return !subCmds.isEmpty();
}
public Set<String> getSubCommandNames() {
return subCmds.keySet();
}
public Command getSubCommand(String name) {
Command subCmd = subCmds.get(name);
return subCmd == null ? subCmdAliases.get(name) : subCmd;
}
public Command getParent() {
return parent;
}
public enum BotUserLevel { //DONE? implement this DONE test this
DEFAULT,
BOT_MOD,
BOT_OWNER
}
public String getCategory() { //is null if it is a subcommand
return cat;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
public String getUsage() {
return usage;
}
public List<String> getAliases() {
return alias;
}
public List<String> getRemarks() {
return remarks;
}
public EnumSet<Permission> getDefaultPerms() { //DONE make this channel dependent?
return perms;
}
public int getRequiredBotUserLevel() {
return botUserLevel;
}
public List<String> getExamples() {
return examples;
}
public boolean isDisabled() { //FIXED think of what to do with disabledGuild; it makes the command class not thread safe; store in DB instead, and be channel based?
return isDisabled;
}
//even when multiple thread references to one specific command, it should be thread safe because it doesnt modify anything in the execute stage; and even the hashmap is somewhat immutable as it will never be changed after finished loading
//i dont think i need to even volatile that hashmap
//the only thing i might need to do to make it thread safe is to make the hashmap a concurrenthashmap
public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { //if you want a main command to work with sub commands, just super() this and then write the main command stuff
OffsetDateTime timesent = OffsetDateTime.now();
try(Connection con = DiscordBot.db.getConnection(); Statement s = con.createStatement()) { //not the best, but i cant share connection through threads
String prefix = MiscUtils.getPrefix(s, channel.getGuild().getId());
if(hasSubCommand()) {
if(args.length > 0) {
Command subCmd = getSubCommand(args[0].toLowerCase());
if(subCmd != null) {
int botUserLevel = subCmd.getRequiredBotUserLevel(); Command temp = subCmd;
while(temp.getParent() != null && botUserLevel == 0) {
temp = temp.getParent();
botUserLevel = temp.getRequiredBotUserLevel(); //if not set get parent's
}
String perms = subCmd.hasSubCommand() ? null : MiscUtils.getMissingPerms(MiscUtils.getActivePerms(s, channel, subCmd), botUserLevel, channel.getGuild().getMember(author), channel); //yet again pass to handler
if(perms == null) {
OffsetDateTime timeReceived = OffsetDateTime.now();
long ms = Math.abs(timesent.until(timeReceived, ChronoUnit.MILLIS));
System.out.println("subcmd parse Time taken: " + ms + "ms");
return subCmd.execute(channel, author, msg, Arrays.copyOfRange(args, 1, args.length));
} else if(perms.equals("DISABLED") || this.isDisabled) {
msg.addReaction("❎").queue();
return new CommandResult(CommandResultType.DISABLED);
} else {
return new CommandResult(CommandResultType.NOPERMS, perms);
}
} else {
if(impl == null) return new CommandResult(CommandResultType.INVALIDARGS, "Unknown sub command! Check `" + prefix + "help " + this.getName() + "` for more info.");
}
} else { //only run if no execute definition, if not let it override default messages
if(impl == null) return new CommandResult(CommandResultType.INVALIDARGS, "Please specify a subcommand. You can view them with `" + prefix + "help " + this.name + "`.");
}
}
return impl.execute(channel, author, msg, args); //fallback if no subcommand found; useful for defining custom messages or default command for a set of subcommands
} catch (SQLException e) {
return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
}
}
public void executeAsync(TextChannel channel, User author, Message msg, String[] args, Consumer<CommandResult> success) {
OffsetDateTime timesent = OffsetDateTime.now();
CompletableFuture.supplyAsync(() -> execute(channel, author, msg, args))
.exceptionally(ex -> new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(ex.getCause()))) //getCause should always work as ex is a CompletionException
.thenAccept(success).thenAccept(r -> { //DONE thenAccept parses the CommandResult
OffsetDateTime timeReceived = OffsetDateTime.now();
long ms = Math.abs(timesent.until(timeReceived, ChronoUnit.MILLIS));
System.out.println("Time taken: " + ms + "ms");
});
}
}

File Metadata

Mime Type
text/x-java
Expires
Mon, Jul 7, 4:54 AM (1 d, 10 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
23/85/b45dd537935c14ee79c0417e52d0

Event Timeline