Page MenuHomedesp's stash

No OneTemporary

diff --git a/src/me/despawningbone/discordbot/EventListener.java b/src/me/despawningbone/discordbot/EventListener.java
index 96b5091..657e613 100644
--- a/src/me/despawningbone/discordbot/EventListener.java
+++ b/src/me/despawningbone/discordbot/EventListener.java
@@ -1,416 +1,419 @@
package me.despawningbone.discordbot;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import me.despawningbone.discordbot.command.Command;
import me.despawningbone.discordbot.command.CommandResult;
import me.despawningbone.discordbot.command.CommandResult.CommandResultType;
import me.despawningbone.discordbot.command.music.AudioTrackHandler;
import me.despawningbone.discordbot.command.music.GuildMusicManager;
import me.despawningbone.discordbot.command.music.Music;
import me.despawningbone.discordbot.utils.MiscUtils;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.MessageBuilder;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.GuildVoiceState;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.entities.Message.Attachment;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceJoinEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceMoveEvent;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.events.message.guild.GuildMessageUpdateEvent;
import net.dv8tion.jda.api.events.user.UserActivityStartEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
public class EventListener extends ListenerAdapter {
public static Multimap<String, String> testAnimePicCache = ArrayListMultimap.create();
@Override
public void onGuildMessageReceived(GuildMessageReceivedEvent event) {
DiscordBot.mainJDA = event.getJDA(); // refresh it? //no need, everything's getJDA() is the same jda basis but audioplayer's still using it; need to migrate to store in guildmusicmanager
//String[] words = event.getMessage().getContentDisplay().split(" ");
User author = event.getAuthor();
TextChannel channel = event.getChannel();
Message msg = event.getMessage();
//checkAnimePics(event);
//DONE use log4j
//logging
if (!DiscordBot.logExcemptID.contains(author.getId())) {
String guildinfo = "[" + event.getGuild().getName() + " #" + channel.getName() + "]";
String payload = "[INFO] " + guildinfo + System.lineSeparator() + " " + msg + System.lineSeparator() + " Full msg: " + msg.getContentDisplay();
List<Attachment> att = event.getMessage().getAttachments();
if (!att.isEmpty()) {
payload += System.lineSeparator() + " Attachments:";
for (int i = 0; i < att.size(); i++) {
payload += System.lineSeparator() + " " + att.get(i).getUrl();
}
}
DiscordBot.logger.trace(payload); //TODO log embeds and add user id too?
}
/*if (String.join("", words).equalsIgnoreCase("!despacito")) {
channel.sendMessage("https://www.youtube.com/watch?v=W3GrSMYbkBE").queue();
return;
}*/
CommandResult result = null;
try (Connection con = DiscordBot.db.getConnection(); Statement s = con.createStatement()){
String prefix = MiscUtils.getPrefix(s, event.getGuild().getId());
if(msg.getContentDisplay().toLowerCase().startsWith(prefix.toLowerCase())) {
String msgStripped = msg.getContentDisplay().substring(prefix.length()).replaceAll("\\s\\s+", " "); //merges space
String[] args = msgStripped.split(" "); // base on command length?
Command cmd = DiscordBot.commands.get(args[0].toLowerCase());
cmd = cmd == null ? DiscordBot.aliases.get(args[0].toLowerCase()) : cmd;
if (cmd != null) {
ResultSet uRs = s.executeQuery("SELECT reports FROM users WHERE id = " + author.getId() + ";");
if(uRs.next()) { //checks null at the same time
if(uRs.getString(1).split("\n").length >= 5) {
channel.sendMessage("You are banned from using the bot.").queue();
DiscordBot.logger.info("[WARN] " + author.getName() + " (" + author.getId() + ") tried to execute " + msg.getContentDisplay() + " but was banned.");
return;
}
}
uRs.close();
long perm = MiscUtils.getActivePerms(s, channel, cmd);
//result = cmd.execute(channel, author, msg, args);
String perms = cmd.hasSubCommand() ? null : MiscUtils.getMissingPerms(perm, cmd.getRequiredBotUserLevel(), event.getMember(), channel); //pass it to the subcommand handler to handle instead
+ if(cmd.isDisabled()) perms = "DISABLED"; //override if disabled by code
if(perms == null || event.getAuthor().getId().equals(DiscordBot.OwnerID)) { //owner overrides perms for convenience
/*if(cmd.isDisabled(channel.getGuild().getId())) {
channel.sendMessage("This command is disabled!").queue();
result = new CommandResult(CommandResultType.FAILURE, "Disabled command");
} else {*/
cmd.executeAsync(channel, author, msg, Arrays.copyOfRange(args, 1, args.length), r -> { //catch all exceptions? //should have actually
DiscordBot.logger.info("[" + r.getResultType() + "] " + author.getName() + " (" + author.getId() + ") executed "
+ msg.getContentDisplay() + (r.getRemarks() == null ? "." : ". (" + r.getRemarks() + ")")); //logging has to be before sendMessage, or else if no permission it will just quit
if(r.getMessage() != null) channel.sendMessage(r.getMessage()).queue();
}); //dont know if async will screw anything up //wont, TODO log date and which server executed the command also?
return;
//}
- } else if(perms.equals("DISABLED") || cmd.isDisabled()) {
+ } else if(perms.equals("DISABLED")) {
+ author.openPrivateChannel().queue(c -> //notify user whats wrong if possible
+ c.sendMessage("Sorry, but the command `" + msg.getContentDisplay() + "` is disabled in the channel `#" + channel.getName() + "`.").queue());
msg.addReaction("❎").queue();
result = new CommandResult(CommandResultType.DISABLED);
} else {
result = new CommandResult(CommandResultType.NOPERMS, perms);
channel.sendMessage(result.getMessage()).queue();
}
} else {
result = new CommandResult(CommandResultType.FAILURE, "Invalid command");
//do more stuff?
}
} else if(msg.getContentRaw().matches("<@!?" + DiscordBot.BotID + ">")) result = greet(channel, author, prefix);
} catch (SQLException e) {
result = new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
}
if(result != null) DiscordBot.logger.info("[" + result.getResultType() + "] " + author.getName() + " (" + author.getId() + ") executed "
+ msg.getContentDisplay() + (result.getRemarks() == null ? "." : ". (" + result.getRemarks() + ")"));
}
@SuppressWarnings("unused")
private void checkAnimePics(GuildMessageReceivedEvent event) {
//TESTING
CompletableFuture.runAsync(() -> {
User author = event.getAuthor();
TextChannel channel = event.getChannel();
Message msg = event.getMessage();
List<String> pics = new ArrayList<>();
if(channel.getId().equals("615396635383562272") && !DiscordBot.BotID.equals(author.getId())) {
for(Attachment att : msg.getAttachments()) {
if(att.isImage()) pics.add(att.getUrl());
}
/*if(msg.getContentDisplay().contains("http")) {
for(String s : msg.getContentDisplay().split("http")) {
try {
new URL("http" + s);
pics.add("http" + s);
} catch(MalformedURLException e) {
;
}
}
}*/
try {
Thread.sleep(1500);
msg = channel.retrieveMessageById(msg.getId()).complete();
} catch (InterruptedException e2) {
e2.printStackTrace();
}
for(MessageEmbed em : msg.getEmbeds()) {
//System.out.println(em.toJSONObject());
if(em.getThumbnail() != null) {
System.out.println("thumb");
if(em.getSiteProvider() != null && em.getSiteProvider().getName().equals("pixiv")) { //pixiv doesnt show whole pic in embed
pics.add(em.getUrl());
} else {
pics.add(em.getThumbnail().getUrl());
}
} else if(em.getImage() != null && !author.isBot()) {
System.out.println("img");
pics.add(em.getImage().getUrl());
} else {
System.out.println("url");
pics.add(em.getUrl());
}
}
//System.out.println(pics);
//System.out.println(testAnimePicCache);
for(String pic : pics) { //handle first since these mustnt be the same urls, therefore cache saucenao sauce instead
String origPic = pic;
if(!testAnimePicCache.containsValue(pic)) {
Element saucenao;
try {
saucenao = Jsoup.connect("https://saucenao.com/search.php?url=" + pic).get().body();
Element result = saucenao.selectFirst(".resulttable");
try { //normal pixiv/deviantart handling
if(result.parent().attr("class").equals("result hidden")) throw new NullPointerException();
Element source = result.selectFirst("strong:contains(ID:)").nextElementSibling();
pic = source.attr("href"); //will not run if the cache has the value || if the link specified aint a pic
} catch (NullPointerException e1) { //weird saucenao card formatting (eg episode info), or no result; we dont handle these
;
}
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("next" + testAnimePicCache);
if(testAnimePicCache.containsValue(pic)) {
try {
Multimap<String, String> temp = ArrayListMultimap.create();
System.out.println("temp");
Message m = channel.retrieveMessageById(Multimaps.invertFrom(testAnimePicCache, temp).get(pic).toArray(new String[1])[0]).complete();
EmbedBuilder eb = new EmbedBuilder();
eb.setTitle("Repost detected!");
eb.setDescription("[This image](" + origPic + ")" + (origPic.equals(pic) ? "" : "([source](" + pic + "))") + " is a repost of [this message](" + m.getJumpUrl() + ") by `" + m.getAuthor().getName() + "#" + m.getAuthor().getDiscriminator() + "` at **" + m.getTimeCreated().format(DateTimeFormatter.RFC_1123_DATE_TIME) + "**.\n");
eb.setThumbnail(origPic);
channel.sendMessage(eb.build()).queue();
continue;
} catch(NullPointerException e) { //if the message is deleted
e.printStackTrace();
}
} else {
System.out.println("put");
testAnimePicCache.put(msg.getId(), pic);
}
}
}
}).whenComplete((r, t) -> {if(t != null) t.printStackTrace();});
}
private Properties greetEasterEggs = new Properties();
{
try(FileInputStream in = new FileInputStream(new File(System.getProperty("user.dir") + File.separator + "eastereggs.properties"))){
greetEasterEggs.load(in);
} catch (IOException e) {
e.printStackTrace();
}
}
private CommandResult greet(TextChannel channel, User author, String prefix) {
MessageBuilder smsg = new MessageBuilder();
String nick = channel.getGuild().getMemberById(author.getId()).getNickname();
if (nick != null) {
smsg.append("Yo " + nick + "!\n");
} else {
smsg.append("Yo " + author.getName() + "!\n");
}
String easterEgg = greetEasterEggs.getProperty(author.getId());
if(easterEgg != null) {
smsg.append(easterEgg.replace("\\n", "\n") + "\n");
}
EmbedBuilder eb = new EmbedBuilder();
eb.setColor(0x051153); //TODO version info based on git commits
eb.appendDescription("This bot is running **despbot v1.5.0**, Shard `" + DiscordBot.mainJDA.getShardInfo().getShardString() + "`. ([invite me!](https://discordapp.com/oauth2/authorize?&client_id=" + DiscordBot.BotID + "&scope=bot&permissions=0))\n");
eb.appendDescription("Connected guilds: `" + DiscordBot.mainJDA.getGuildCache().size() + (DiscordBot.mainJDA.getShardManager() == null ? "" : "/" + DiscordBot.mainJDA.getShardManager().getShards().stream().mapToLong(jda -> jda.getGuildCache().size()).sum()) + "`; ");
eb.appendDescription("Total members (cached): `" + DiscordBot.mainJDA.getUserCache().size() + (DiscordBot.mainJDA.getShardManager() == null ? "" : "/" + DiscordBot.mainJDA.getShardManager().getShards().stream().mapToLong(jda -> jda.getUserCache().size()).sum()) + "`\n");
eb.appendDescription("DM `" + DiscordBot.mainJDA.getUserById(DiscordBot.OwnerID).getAsTag() + "` if you have any questions!\n");
eb.appendDescription("To get a list of commands, do `" + prefix + "help`.");
smsg.setEmbed(eb.build());
Message fmsg = smsg.build();
channel.sendMessage(fmsg).queue();
return new CommandResult(CommandResultType.SUCCESS, null);
}
@Override
public void onGuildMessageUpdate(GuildMessageUpdateEvent event) {
//System.out.println("edit"); //debug
Message msg = event.getMessage();
User author = event.getAuthor();
TextChannel channel = event.getChannel();
if (!DiscordBot.logExcemptID.contains(author.getId())) {
String guildinfo = "[" + event.getGuild().getName() + " #" + channel.getName() + "]";
DiscordBot.logger.trace("[EDIT] " + guildinfo + System.lineSeparator() + " " + msg + System.lineSeparator() + " Full edited msg: " + msg.getContentDisplay());
}
}
@Override
public void onUserActivityStart(UserActivityStartEvent event) { //store presence for checking osu pp
Activity osu = event.getNewActivity();
if (osu != null && osu.getName().equals("osu!") && osu.isRich() //if need to include other games and details, just remove this if clause and change sGame below
&& osu.asRichPresence().getDetails() != null) {
String toolTip = osu.asRichPresence().getLargeImage().getText();
//DiscordBot.guildMemberPresence.put(event.getUser().getId(), game); //so that game update to nothing wont be logged
/*System.out.println(event.getGuild().getName());
System.out.println(event.getUser().getName());
System.out.println(game.getName());
System.out.println(game.getUrl());
System.out.println(game.getType());
System.out.println(game.isRich() ? game.asRichPresence().getDetails() : "no rich");
System.out.println(game.isRich() ? game.asRichPresence().getState() : "no rich");*/
//OffsetDateTime timesent = OffsetDateTime.now();
//System.out.println(toolTip);
String sGame = (toolTip.lastIndexOf(" (") != -1 ? toolTip.substring(0, toolTip.lastIndexOf(" (")) : toolTip) + "||" + osu.asRichPresence().getDetails();
//sGame = sGame.replaceAll("'", "''");
/*OffsetDateTime timeReceived = OffsetDateTime.now();
long ms = Math.abs(timesent.until(timeReceived, ChronoUnit.MILLIS));
System.out.println("Time taken: " + ms + "ms");*/
try (Connection con = DiscordBot.db.getConnection()) { //DONE do i need to close the statement?
PreparedStatement s = con.prepareStatement("INSERT INTO users(id, game) VALUES (" + event.getUser().getId() + ", ?) ON CONFLICT(id) DO UPDATE SET game = ? WHERE game <> ?;" ); //prevent blank updates
s.setString(1, sGame); s.setString(2, sGame); s.setString(3, sGame);
s.execute();
//con.createStatement().execute("INSERT INTO users(id, game) VALUES (" + event.getUser().getId() + ", '" + sGame + "') ON CONFLICT(id) DO UPDATE SET game = '" + sGame + "' WHERE game <> '" + sGame + "';" ); //prevent blank updates
} catch (SQLException e) { //FIXED if i make this not osu only, be aware of SQL injections through sGame (probably being paranoid tho)
e.printStackTrace();
}
/*timeReceived = OffsetDateTime.now();
ms = Math.abs(timesent.until(timeReceived, ChronoUnit.MILLIS));
System.out.println("Time taken: " + ms + "ms");*/
}
}
//TODO only update when not paused?
//TODO update on member deafen?
private void waitActivity(Guild guild) {
AudioTrackHandler ap = ((Music) DiscordBot.commands.get("music")).getAudioTrackHandler();
ap.getGuildMusicManager(guild).player.setPaused(true);
ap.getGuildMusicManager(guild).clearQueueCleanup = ap.ex.schedule(() -> {
ap.stopAndClearQueue(guild);
DiscordBot.lastMusicCmd.get(guild.getId()).sendMessage("The queue has been cleared.").queue();
ap.getGuildMusicManager(guild).clearQueueCleanup = null;
}, 1, TimeUnit.MINUTES);
}
private String updateActivity(Guild guild, VoiceChannel vc) {
AudioTrackHandler ap = ((Music) DiscordBot.commands.get("music")).getAudioTrackHandler();
GuildMusicManager mm = ap.getGuildMusicManager(guild);
if(mm.clearQueueCleanup != null) {
mm.player.setPaused(false);
mm.clearQueueCleanup.cancel(true);
mm.clearQueueCleanup = null;
}
String type = mm.scheduler.loop; //mm cannot be null
if (type != null
&& vc.getMembers().size() > 2
&& vc.getMembers().contains(guild.getMemberById(DiscordBot.OwnerID))) {
ap.toggleLoopQueue(guild, type);
return type;
}
return null;
}
@Override
public void onGuildVoiceLeave(GuildVoiceLeaveEvent event) { // theoretically i dont need to check if lastMusicCmd has the entry or not, as it must have one to trigger this
GuildVoiceState vs = event.getGuild().getMemberById(DiscordBot.BotID).getVoiceState();
if(!event.getMember().getUser().getId().equals(DiscordBot.BotID)) {
if (vs.inVoiceChannel()) {
if (vs.getChannel().equals(event.getChannelLeft())
&& event.getChannelLeft().getMembers().size() < 2) {
TextChannel channel = DiscordBot.lastMusicCmd.get(event.getGuild().getId());
channel.sendMessage(
"All users have left the music channel, the player is now paused.\nThe queue will be cleared in 1 minute if there is no activity.")
.queue();
waitActivity(event.getGuild());
}
}
} else { //got kicked
AudioTrackHandler ap = ((Music) DiscordBot.commands.get("music")).getAudioTrackHandler();
if(ap != null) { //if not destroyed
ap.stopAndClearQueue(event.getGuild()); //can double fire on normal end; shouldnt be too big of a problem
}
}
}
@Override
public void onGuildVoiceMove(GuildVoiceMoveEvent event) { // theoretically i dont need to check if lastMusicCmd has the entry or not, as it must have one to trigger this
GuildVoiceState vs = event.getGuild().getMemberById(DiscordBot.BotID).getVoiceState();
if (vs.inVoiceChannel()) {
String id = event.getGuild().getId();
TextChannel channel = DiscordBot.lastMusicCmd.get(id);
if (!event.getMember().getUser().getId().equals(DiscordBot.BotID)) {
if (vs.getChannel().equals(event.getChannelLeft())) {
if (!event.getMember().getUser().getId().equals(DiscordBot.BotID)
&& event.getChannelLeft().getMembers().size() < 2) {
channel.sendMessage(
"All users have left the music channel, the player is now paused.\nThe queue will be cleared in 1 minute if there is no activity.")
.queue();
waitActivity(event.getGuild());
}
} else if (vs.getChannel().equals(event.getChannelJoined())) {
String type = updateActivity(event.getGuild(), event.getChannelJoined());
if(type != null) channel.sendMessage((type.equals("loop") ? "Looping" : "Autoplay") + " mode disabled due to new users joining the music channel.").queue();
}
} else { //moved bot to empty channel
if(event.getChannelJoined().getMembers().size() < 2) {
channel.sendMessage(
"The bot has been moved to an empty channel, the player is now paused.\nThe queue will be cleared in 1 minute if there is no activity.")
.queue();
waitActivity(event.getGuild());
} else { //moved bot to channel with ppl
event.getGuild().getAudioManager().openAudioConnection(event.getChannelJoined()); //seems to need explicit reconnect on bot move, it gets stuck on attempting to reconnect otherwise; probably would be fixed soon but hot patch for now
String type = updateActivity(event.getGuild(), event.getChannelJoined());
if(type != null) channel.sendMessage((type.equals("loop") ? "Looping" : "Autoplay") + " mode disabled due to new users joining the music channel.").queue();
}
}
}
}
@Override
public void onGuildVoiceJoin(GuildVoiceJoinEvent event) {
GuildVoiceState vs = event.getGuild().getMemberById(DiscordBot.BotID).getVoiceState();
String id = event.getGuild().getId();
TextChannel channel = DiscordBot.lastMusicCmd.get(id);
if(channel != null) {
if (vs.inVoiceChannel() && !event.getMember().getUser().getId().equals(DiscordBot.BotID)) {
if (vs.getChannel().equals(event.getChannelJoined())) {
String type = updateActivity(event.getGuild(), event.getChannelJoined());
if(type != null) channel.sendMessage((type.equals("loop") ? "Looping" : "Autoplay") + " mode disabled due to new users joining the music channel.").queue();
}
}
}
}
}
diff --git a/src/me/despawningbone/discordbot/command/Command.java b/src/me/despawningbone/discordbot/command/Command.java
index e2ba553..ec581fd 100644
--- a/src/me/despawningbone/discordbot/command/Command.java
+++ b/src/me/despawningbone/discordbot/command/Command.java
@@ -1,208 +1,212 @@
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
+ public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { //if you want a main command to work with sub commands, just define impl along with subcmds instead of overriding this method
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) {
+ if(subCmd.isDisabled()) perms = "DISABLED"; //override if disabled by code
+
+ if(perms == null || author.getId().equals(DiscordBot.OwnerID)) { //owner overrides perms for convenience
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) {
+ } else if(perms.equals("DISABLED")) {
+ author.openPrivateChannel().queue(c -> //notify user whats wrong if possible
+ c.sendMessage("Sorry, but the command `" + msg.getContentDisplay() + "` is disabled in the channel `#" + channel.getName() + "`.").queue());
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-diff
Expires
Tue, Apr 15, 10:18 PM (8 h, 34 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
4e/23/9a7c533f73c12a6a24ba386ee68e

Event Timeline