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 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
+ msg.getContentDisplay() + (r.getRemarks() == null ? "." : ". (" + r.getRemarks() + ")")); //logging has to be before sendMessage, or else if no permission it will just quit
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
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
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();
"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();
"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();
"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();
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();
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
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
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
+ 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) {
.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