diff --git a/src/me/despawningbone/discordbot/DiscordBot.java b/src/me/despawningbone/discordbot/DiscordBot.java index a37ec66..c77a14a 100644 --- a/src/me/despawningbone/discordbot/DiscordBot.java +++ b/src/me/despawningbone/discordbot/DiscordBot.java @@ -1,290 +1,269 @@ package me.despawningbone.discordbot; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.Stack; import java.util.StringJoiner; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import javax.security.auth.login.LoginException; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.PropertyConfigurator; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; //import com.sedmelluq.discord.lavaplayer.jdaudp.NativeAudioSendFactory; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import me.despawningbone.discordbot.command.Command; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.requests.GatewayIntent; public class DiscordBot { - // TODO move to appropriate locations - - //public static List BannedID = new ArrayList(); //DONE store in DB + //TODO move to appropriate locations + public static List ModID = new ArrayList(); public static List logExcemptID = new ArrayList(); //TODO synchronize below or change to concurrent //but why? its thread safe since its read only public static HashMap commands = new HashMap(); public static HashMap aliases = new HashMap(); //TODO temporary solution public static TreeMap> catCmds = new TreeMap>(); //order base on categories? public static ConcurrentHashMap lastMusicCmd = new ConcurrentHashMap(); //TODO store in guild configs //nah its fine - //public static HashMap guildMemberPresence = new HashMap(); - public static final String prefix = "!desp "; //DONE allow guild change prefix? public static JDA mainJDA = null; public static String BotID; public static String OwnerID; public static Properties tokens = new Properties(); static final Logger logger = LoggerFactory.getLogger(DiscordBot.class); //package private public static HikariDataSource db; - /*public static FirefoxDriver driver;*/ - - // DONE SQLite integration; check if program termination will screw up the connection - // TODO SHARDING - // DONE on shutdown alert those playing music or await? //alerted + //DONE SQLite integration; check if program termination will screw up the connection + //TODO SHARDING + //DONE on shutdown alert those playing music or await? //alerted public static void main(String[] args) { - // initiate Selenium - /*System.setProperty("webdriver.gecko.driver", - System.getProperty("user.dir") + File.separator + "geckodriver.exe"); - FirefoxOptions options = new FirefoxOptions(); - options.setHeadless(true); - driver = new FirefoxDriver(options);*/ PropertyConfigurator.configure(System.getProperty("user.dir") + File.separator + "log4j.properties"); + //init tokens file try(FileInputStream in = new FileInputStream(new File(System.getProperty("user.dir") + File.separator + "tokens.properties"))){ tokens.load(in); } catch (IOException e) { e.printStackTrace(); return; } initCmds(); initDB(); - // login + //login try { JDA jda = JDABuilder.create(tokens.getProperty("bot"), GatewayIntent.getIntents(GatewayIntent.ALL_INTENTS)) //.setAudioSendFactory(new NativeAudioSendFactory()) //segfaults frequently somehow, disabling .addEventListeners(new me.despawningbone.discordbot.EventListener()).build(); jda.getPresence().setActivity(Activity.watching("Ping me for info!")); // for user bots, see // https://discordapp.com/developers/docs/topics/gateway#update-status // and // https://gist.github.com/MrPowerGamerBR/b8acccb9951b0d78a5115024e3ee0d03 //find owner id from bot owner BotID = jda.getSelfUser().getId(); OwnerID = jda.retrieveApplicationInfo().complete().getOwner().getId(); } catch (LoginException e) { e.printStackTrace(); return; } - // initiate logs //handled by log4j? + //initiate logs //handled by log4j? File directory = new File(System.getProperty("user.dir") + File.separator + "logs" + File.separator); if (!directory.exists()) { directory.mkdir(); } - - //initiate test anime pics cache for repost check - /*try { - FileInputStream in = new FileInputStream(System.getProperty("user.dir") + File.separator + "testAnimePicCache.bin"); - ObjectInputStream ois = new ObjectInputStream(in); - EventListener.testAnimePicCache = (Multimap) ois.readObject(); - ois.close(); - } catch (IOException | ClassNotFoundException e) { - e.printStackTrace(); - } - - Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> { //no need to be a global variable since i dont need to access it - try { - FileOutputStream out = new FileOutputStream(System.getProperty("user.dir") + File.separator + "testAnimePicCache.bin"); - ObjectOutputStream oos = new ObjectOutputStream(out); - oos.writeObject(EventListener.testAnimePicCache); - oos.flush(); - oos.close(); - } catch (IOException e) { - e.printStackTrace(); - } - }, 1, 5, TimeUnit.MINUTES);*/ - // DEPRECATED //or is it? make unmodifiable instead + //DEPRECATED //or is it? make unmodifiable instead for(String id : tokens.getProperty("botmod").split(",")) { ModID.add(id); } ModID.add(OwnerID); + + //TODO put into sql and make guild setting to opt out logExcemptID.add(BotID); } private static void initDB() { //init guild settings when do !desp settings for the first time? //dont really need coz im doing upsert for all values anyways HikariConfig dbConf = new HikariConfig(); dbConf.setJdbcUrl("jdbc:sqlite:data.db"); dbConf.setIdleTimeout(45000); dbConf.setMaxLifetime(60000); dbConf.setMaximumPoolSize(10); //dbConf.setMaximumPoolSize(25); //for deployment in server db = new HikariDataSource(dbConf); try (Connection con = db.getConnection()){ Statement s = con.createStatement(); s.execute("CREATE TABLE IF NOT EXISTS settings" + "(id INTEGER PRIMARY KEY," //performance problem using text; integer can handle long anyways + "prefix TEXT DEFAULT '!desp '," + "premium INTEGER DEFAULT 0," + "mchannel TEXT," //allow people to set this manually? By default, use last music cmd place + "locale TEXT DEFAULT 'EN'," //or int? //user specific or guild specific, or user override guild? + "shortcuts TEXT," //use another method? + "votepct INTEGER DEFAULT 50," + "looplimit INTEGER DEFAULT 1," + "volume INTEGER DEFAULT 100," //premium?, is default actually 100? + "helpdm INTEGER DEFAULT 0);"); //send help to dm or not, excluding cmd help(?) //add logexempt? + + //init perms table PreparedStatement pragma = con.prepareStatement("SELECT name FROM pragma_table_info('perms_' || ?);"); //NOTE: sqlite does not support changing default values, beware when adding new commands for(Entry> entry : catCmds.entrySet()) { pragma.setString(1, entry.getKey().toLowerCase()); ResultSet rs = pragma.executeQuery(); + + //traverse cmd tree to obtain node names boolean isEmpty = true; HashMap> nodes = new HashMap<>(); Stack tree = new Stack<>(); for(Command cmd : entry.getValue()) tree.push(cmd); //populate stack coz there is no root node while(!tree.empty()) { Command cmd = tree.pop(); String name = cmd.getName(); for(Command iterate = cmd.getParent(); iterate != null; iterate = iterate.getParent()) { name = iterate.getName() + "." + name; } nodes.put(name, cmd.getDefaultPerms()); for(String sub : cmd.getSubCommandNames()) { tree.push(cmd.getSubCommand(sub)); } } while(rs.next()) { isEmpty = false; nodes.remove(rs.getString("name")); //remove already added node names } if(isEmpty) { String create = "CREATE TABLE perms_" + entry.getKey().toLowerCase() + " (id INTEGER PRIMARY KEY, _GLOBAL_ TEXT DEFAULT '0\n', "; //perms for a category are always default no restraints StringJoiner join = new StringJoiner(", "); for(Entry> node : nodes.entrySet()) { join.add("\"" + node.getKey() + "\" TEXT DEFAULT '" + ((0L << 32) | (Permission.getRaw(node.getValue()) & 0xffffffffL)) + "\n'"); //initialize with no permission deny override, permissions allow //needs to be text so that channel overrides delimiter can be stored } create += join.toString() + ");"; s.execute(create); //DONE? create table } else { if(!nodes.isEmpty()) { //which means there is new subCmd/cmd for(Entry> node : nodes.entrySet()) { s.execute("ALTER TABLE perms_" + entry.getKey().toLowerCase() + " ADD COLUMN \"" + node.getKey() + "\" TEXT DEFAULT '" + ((0L << 32) | (Permission.getRaw(node.getValue()) & 0xffffffffL)) + "\n';"); //not able to prep statement coz its column name s.close(); } } } } pragma.close(); + + //init users table + //DONE TEST generate the nodes, retrieve default from fields? //what about additional commands added, use ALTER TABLE ADD COLUMN DEFAULT? I dont need to sort it according to category at all, i am not and will not be bulk printing permissions anywhere //actually i might be coz of list edited perms //fit subnodes into sub tables, split by category so its more balanced (category as table name)? //fields are gonna be sth like :| etc? //DONE store the permissions with long merged; <32bit = deny, >32bit = allow s.execute("CREATE TABLE IF NOT EXISTS users" + "(id INTEGER PRIMARY KEY," + "reports TEXT DEFAULT ''," + "game TEXT);"); //disposable, i can change it anytime //add botmod field? if so, i can deprecate the hard coded list, but it would require querying to this table on perm checks //i need to query it to check if the user is banned anyways //subcmd need to query again, so i didnt do it; besides the modlist is gonna be too small to be significant anyways //add user specific locale? might be confusing s.close(); } catch (SQLException e1) { e1.printStackTrace(); } } private static void initCmds() { Reflections reflections = new Reflections("me.despawningbone.discordbot.command"); Set> classes = reflections.getSubTypesOf(Command.class); for (Class s : classes) { try { + //skip abstract classes if (Modifier.isAbstract(s.getModifiers())) { continue; } + String pkName = s.getPackage().getName(); String cat = StringUtils.capitalize(pkName.substring(pkName.lastIndexOf(".") + 1)); - //if(cat.equals("Osu")) cat = "osu!"; Command c = s.getConstructor().newInstance(); //DONE use constructor to put name and cat instead + //dont skip disabled commands - they should still show up but disabled - if (c.isDisabled()) { - continue; - } - try { //init name and cat + //init name and cat + try { Field nameF = Command.class.getDeclaredField("name"); nameF.setAccessible(true); if(nameF.get(c) == null) nameF.set(c, s.getSimpleName().toLowerCase()); //allow overrides Field catF = Command.class.getDeclaredField("cat"); catF.setAccessible(true); catF.set(c, cat); } catch (NoSuchFieldException e) { //should never throw e.printStackTrace(); } - // check disable whole command category? + + //check disable whole command category? //no non-perm-wise category wide disabling support List cmds = catCmds.get(cat); if(cmds == null) { cmds = new ArrayList(Arrays.asList(c)); } else { cmds.add(c); } catCmds.put(cat, cmds); - // set command category? commands.put(c.getName(), c); List l = c.getAliases(); if(l != null) { for(int i = 0; i < l.size(); i++) { aliases.put(l.get(i), c); } } } catch (ReflectiveOperationException e) { e.printStackTrace(); } } + + //sort each category alphabetically for(Entry> entry : catCmds.entrySet()) { Collections.sort(entry.getValue(), (a, b) -> a.getName().compareTo(b.getName())); } } } diff --git a/src/me/despawningbone/discordbot/EventListener.java b/src/me/despawningbone/discordbot/EventListener.java index 657e613..1fa64eb 100644 --- a/src/me/despawningbone/discordbot/EventListener.java +++ b/src/me/despawningbone/discordbot/EventListener.java @@ -1,419 +1,314 @@ 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 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 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; - }*/ + //parse cmd CommandResult result = null; - try (Connection con = DiscordBot.db.getConnection(); Statement s = con.createStatement()){ + //check prefix String prefix = MiscUtils.getPrefix(s, event.getGuild().getId()); if(msg.getContentDisplay().toLowerCase().startsWith(prefix.toLowerCase())) { + //preprocess args String msgStripped = msg.getContentDisplay().substring(prefix.length()).replaceAll("\\s\\s+", " "); //merges space String[] args = msgStripped.split(" "); // base on command length? + + //get cmd from args Command cmd = DiscordBot.commands.get(args[0].toLowerCase()); cmd = cmd == null ? DiscordBot.aliases.get(args[0].toLowerCase()) : cmd; if (cmd != null) { + + //check if banned 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(); + + //check perms 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; - //} + + //execute async + 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")) { 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(); + msg.addReaction("❎").queue(); //instead of sending messages, react instead to avoid clutter (which is what most ppl disable a bot in a channel for) 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); + + //non prefix "command" - greetings with tagging + } else if(msg.getContentRaw().matches("<@!?" + DiscordBot.BotID + ">")) { + result = greet(channel, author, prefix); + } } catch (SQLException e) { result = new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } + + //log non async results 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 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 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();}); - } + //init easter eggs file 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(); + + //main greet String nick = channel.getGuild().getMemberById(author.getId()).getNickname(); if (nick != null) { smsg.append("Yo " + nick + "!\n"); } else { smsg.append("Yo " + author.getName() + "!\n"); } + + //easter eggs String easterEgg = greetEasterEggs.getProperty(author.getId()); if(easterEgg != null) { smsg.append(easterEgg.replace("\\n", "\n") + "\n"); } + + //bot info 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 + public void onGuildMessageUpdate(GuildMessageUpdateEvent event) { //log edits 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");*/ + + //update db 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 ec581fd..5c3ba94 100644 --- a/src/me/despawningbone/discordbot/command/Command.java +++ b/src/me/despawningbone/discordbot/command/Command.java @@ -1,212 +1,214 @@ 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 alias; //fixed-size in this case is negligible, as it will not be edited later on anyways protected List remarks = null; protected boolean isDisabled; protected EnumSet 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 examples; protected ExecuteImpl impl = null; private Command parent = null; private String cat; //not used; remove? //actually gets used now private LinkedHashMap subCmds = new LinkedHashMap<>(); //preserve order public HashMap subCmdAliases = new HashMap(); //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 aliases, Command.ExecuteImpl exeFunc, String usage, List examples, String desc, List remarks, EnumSet 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 aliases, Command.ExecuteImpl exe, String usage, List examples, String desc, List 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 aliases, Command.ExecuteImpl exe, String usage, List examples, String desc, List remarks, EnumSet 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 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 getAliases() { return alias; } public List getRemarks() { return remarks; } public EnumSet getDefaultPerms() { //DONE make this channel dependent? return perms; } public int getRequiredBotUserLevel() { return botUserLevel; } public List 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; } + //TODO deprecate use of @Override execute, use Command.ExecuteImpl instead - this allows merging of perm handlers in EventListener and here + //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 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(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")) { 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 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"); }); } } diff --git a/src/me/despawningbone/discordbot/command/admin/BotBan.java b/src/me/despawningbone/discordbot/command/admin/BotBan.java index c9349f4..d9f29a8 100644 --- a/src/me/despawningbone/discordbot/command/admin/BotBan.java +++ b/src/me/despawningbone/discordbot/command/admin/BotBan.java @@ -1,118 +1,76 @@ package me.despawningbone.discordbot.command.admin; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import org.apache.commons.lang3.exception.ExceptionUtils; import me.despawningbone.discordbot.DiscordBot; import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; public class BotBan extends Command { public BotBan() { this.alias = Arrays.asList("bban"); this.desc = "Ban a user from the bot"; this.usage = ""; this.examples = Arrays.asList(DiscordBot.OwnerID); this.botUserLevel = BotUserLevel.BOT_MOD.ordinal(); } @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { if (args.length < 1) { return new CommandResult(CommandResultType.INVALIDARGS, "Please enter a user ID."); } else { - //boolean alrBanned = false; String SID = args[0]; Member b; try { b = channel.getGuild().getMemberById(SID); } catch (NumberFormatException e) { return new CommandResult(CommandResultType.INVALIDARGS, "Invalid user ID."); } if (b != null) { if (DiscordBot.ModID.contains(SID) || SID.equals(DiscordBot.BotID)) { return new CommandResult(CommandResultType.FAILURE, "That user cannot be banned."); } else { try (Connection con = DiscordBot.db.getConnection(); Statement s = con.createStatement()) { String reports = ""; ResultSet uRs = s.executeQuery("SELECT reports FROM users WHERE id = " + SID + ";"); + if(uRs.next()) { reports = uRs.getString(1); if(reports.split("\n").length >= 5) { return new CommandResult(CommandResultType.FAILURE, "The user is already banned from the bot."); } } + PreparedStatement set = con.prepareStatement("INSERT INTO users(id, reports) VALUES (?, \"0\n0\n0\n0\n0\n\") ON CONFLICT(id) DO UPDATE SET reports = \"0\n0\n0\n0\n0\n\""); set.setString(1, SID); + if(set.executeUpdate() != 0) { channel.sendMessage("You have successfully banned <@!" + SID + "> from the bot.").queue(); return new CommandResult(CommandResultType.SUCCESS); } else { - throw new IllegalArgumentException("This should never happen"); + throw new IllegalStateException("This should never happen"); } } catch (SQLException e) { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } - - /*for (int i = 0; i < DiscordBot.BannedID.size(); i++) { - if (DiscordBot.BannedID.get(i).equals(SID)) { - alrBanned = true; - break; - } - } - if (!alrBanned) { - boolean inList = false; - for (int i = 0; i < DiscordBot.playerreportlist.size(); i++) { - if (DiscordBot.playerreportlist.get(i).equals(SID)) { - inList = true; - DiscordBot.playerreportlist.set(i, SID + " " + "5"); - break; - } - } - if (!inList) { - DiscordBot.playerreportlist.add(SID + " " + "5"); - } - DiscordBot.BannedID.add(SID); - Path fp = Paths.get(System.getProperty("user.dir"), "PlayerReports.txt"); - try { - List fileContent = new ArrayList<>( - Files.readAllLines(fp, StandardCharsets.UTF_8)); - - for (int f = 0; f < fileContent.size(); f++) { - if (fileContent.get(f).startsWith(SID)) { - fileContent.set(f, SID + " " + "5"); - inList = true; - break; - } - } - if (!inList) { - fileContent.add(SID + " " + "5"); - } - Files.write(fp, fileContent, StandardCharsets.UTF_8); - } catch (IOException e) { - e.printStackTrace(); - } - channel.sendMessage("You have successfully banned <@!" + SID + "> from the bot.").queue(); - return new CommandResult(CommandResultType.SUCCESS); - } else { - return new CommandResult(CommandResultType.FAILURE, "The user is already banned from the bot."); - }*/ } } else { return new CommandResult(CommandResultType.FAILURE, "This user ID is invalid."); } } } } diff --git a/src/me/despawningbone/discordbot/command/admin/BotUnban.java b/src/me/despawningbone/discordbot/command/admin/BotUnban.java index 091b99c..4786ed9 100644 --- a/src/me/despawningbone/discordbot/command/admin/BotUnban.java +++ b/src/me/despawningbone/discordbot/command/admin/BotUnban.java @@ -1,97 +1,64 @@ package me.despawningbone.discordbot.command.admin; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import org.apache.commons.lang3.exception.ExceptionUtils; import me.despawningbone.discordbot.DiscordBot; import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; public class BotUnban extends Command { public BotUnban() { this.alias = Arrays.asList("bunban"); this.desc = "Unban a user from the bot"; this.usage = ""; this.botUserLevel = BotUserLevel.BOT_MOD.ordinal(); this.examples = Arrays.asList(DiscordBot.OwnerID); } @Override //DONE rewrite BotBan/BotUnban to use SQLite public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { if (args.length < 1) { return new CommandResult(CommandResultType.INVALIDARGS, "Please enter a user ID."); } else { - //boolean unbanned = false; String SID = args[0]; Member t = channel.getGuild().getMemberById(SID); if (t != null) { try (Connection con = DiscordBot.db.getConnection(); Statement s = con.createStatement()) { String reports = ""; ResultSet uRs = s.executeQuery("SELECT reports FROM users WHERE id = " + SID + ";"); if(uRs.next()) { reports = uRs.getString(1); if(reports.split("\n").length >= 5) { //can actually just UPDATE since it must have an entry PreparedStatement set = con.prepareStatement("INSERT INTO users(id, reports) VALUES (?, \"\") ON CONFLICT(id) DO UPDATE SET reports = \"\""); set.setString(1, SID); if(set.executeUpdate() != 0) { channel.sendMessage("You unbanned <@!" + SID + ">.").queue(); return new CommandResult(CommandResultType.SUCCESS); + } else { + throw new IllegalStateException("This should never happen"); } } } return new CommandResult(CommandResultType.FAILURE, "This user is not banned."); } catch (SQLException e) { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } - /*for (int i = 0; i < DiscordBot.BannedID.size(); i++) { - if (DiscordBot.BannedID.get(i).equals(SID)) { - unbanned = true; - DiscordBot.BannedID.remove(i); - break; - } - } - if (unbanned == true) { - for (int i = 0; i < DiscordBot.playerreportlist.size(); i++) { - if (DiscordBot.playerreportlist.get(i).startsWith(SID)) { - DiscordBot.playerreportlist.remove(i); - break; - } - } - Path fp = Paths.get(System.getProperty("user.dir"), "PlayerReports.txt"); - try { - List fileContent = new ArrayList<>(Files.readAllLines(fp, StandardCharsets.UTF_8)); - for (int f = 0; f < fileContent.size(); f++) { - if (fileContent.get(f).startsWith(SID)) { - fileContent.remove(f); - break; - } - } - Files.write(fp, fileContent, StandardCharsets.UTF_8); - } catch (IOException e) { - e.printStackTrace(); - } - } - if (unbanned == true) { - channel.sendMessage("You unbanned <@!" + SID + ">.").queue(); - String name = channel.getGuild().getMemberById(SID).getEffectiveName(); - return new CommandResult(CommandResultType.SUCCESS, "User: " + name); - } - return new CommandResult(CommandResultType.FAILURE, "This user is not banned.");*/ } else { return new CommandResult(CommandResultType.FAILURE, "This user ID is invalid."); } } } } diff --git a/src/me/despawningbone/discordbot/command/admin/Purge.java b/src/me/despawningbone/discordbot/command/admin/Purge.java index 51ed140..60ea209 100644 --- a/src/me/despawningbone/discordbot/command/admin/Purge.java +++ b/src/me/despawningbone/discordbot/command/admin/Purge.java @@ -1,51 +1,53 @@ package me.despawningbone.discordbot.command.admin; import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.stream.Collectors; import me.despawningbone.discordbot.DiscordBot; import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; 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 Purge extends Command { public Purge() { this.desc = "Clear the bot's message"; this.usage = ""; this.remarks = Arrays.asList("Note: Count must be in between 1 to 100, and it includes the messages in between."); this.botUserLevel = -BotUserLevel.BOT_MOD.ordinal(); this.examples = Arrays.asList("20"); this.perms = EnumSet.of(Permission.MESSAGE_MANAGE); } @Override //TODO rewrite purge to purge until message deleted meet the count instead public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { int n = 0; try { n = Integer.parseInt(args[0]); } catch (NumberFormatException e) { return new CommandResult(CommandResultType.INVALIDARGS, "Please enter a correct number."); } catch (ArrayIndexOutOfBoundsException e) { return new CommandResult(CommandResultType.INVALIDARGS, "Please enter a number."); } if (n > 100) { return new CommandResult(CommandResultType.INVALIDARGS, "The number entered is too large."); } + channel.getHistory().retrievePast(n).queue(history -> { List msgs = history.stream() .filter(hmsg -> hmsg.getAuthor().getId().equals(DiscordBot.BotID)).collect(Collectors.toList()); if(msgs.size() > 1 && channel.getGuild().getSelfMember().hasPermission(Permission.MESSAGE_MANAGE)) channel.deleteMessages(msgs).queue(); else msgs.forEach(m -> channel.deleteMessageById(m.getId()).queue()); //if no result ignore }); + return new CommandResult(CommandResultType.SUCCESS); } } diff --git a/src/me/despawningbone/discordbot/command/admin/UserReport.java b/src/me/despawningbone/discordbot/command/admin/UserReport.java index f9f846c..e6f0d39 100644 --- a/src/me/despawningbone/discordbot/command/admin/UserReport.java +++ b/src/me/despawningbone/discordbot/command/admin/UserReport.java @@ -1,183 +1,99 @@ package me.despawningbone.discordbot.command.admin; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import java.util.List; import org.apache.commons.lang3.exception.ExceptionUtils; import me.despawningbone.discordbot.DiscordBot; import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; public class UserReport extends Command { public UserReport() { this.alias = Arrays.asList("ureport"); this.desc = "Report a user if they are abusing the bot!"; this.usage = ""; this.remarks = Arrays.asList("Note: 5 global reports will result in a ban from the bot."); this.examples = Arrays.asList("311086271642599424", "despawningbone"); } @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { if (args.length < 1) { return new CommandResult(CommandResultType.INVALIDARGS, "Please specify which user you want to report."); } else { String SID = null; boolean success = true; String pname = String.join(" ", args); long ID = 0; try { ID = Long.parseLong(pname); } catch (NumberFormatException e) { success = false; // System.out.println(pname); //debug List pm = channel.getGuild().getMembersByEffectiveName(pname, true); if (pm.size() <= 0) { return new CommandResult(CommandResultType.FAILURE, "There is no such user."); } else if (pm.size() > 1) { return new CommandResult(CommandResultType.FAILURE, "Theres more than 1 user with the same name. Please use !desp ID to get the ID of the user you want to report."); } else { SID = pm.get(0).getUser().getId(); ID = Long.parseLong(SID); } } if (success == true) { SID = Long.toString(ID); } if (author.getId().equals(SID)) { return new CommandResult(CommandResultType.FAILURE, "You cannot report yourself you dumbo :stuck_out_tongue:"); } else if (DiscordBot.ModID.contains(SID) || SID.equals(DiscordBot.BotID)) { return new CommandResult(CommandResultType.FAILURE, "That user cannot be reported."); } try (Connection con = DiscordBot.db.getConnection(); Statement s = con.createStatement()) { String reports = ""; ResultSet uRs = s.executeQuery("SELECT reports FROM users WHERE id = " + SID + ";"); if(uRs.next()) { reports = uRs.getString(1); if(reports.contains(author.getId())) { return new CommandResult(CommandResultType.FAILURE, "You cannot report a user more than once."); //make it so that its toggling the reports instead? } else if(reports.split("\n").length < 5){ reports += author.getId() + "\n"; } else { return new CommandResult(CommandResultType.FAILURE, "The user has already been banned!"); } } else { reports = author.getId() + "\n"; } PreparedStatement set = con.prepareStatement("INSERT INTO users(id, reports) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET reports = ?" ); set.setString(1, SID); set.setString(2, reports); set.setString(3, reports); int update = set.executeUpdate(); if(update != 0) { int times = reports.split("\n").length; channel.sendMessage("You have successfully reported <@!" + ID + ">.\nThe user has been reported for " + times + "/5 times.\n").queue(); if(times >= 5) { channel.sendMessage("The user <@!" + ID + "> is now banned from using the bot.").queue(); } return new CommandResult(CommandResultType.SUCCESS); } else { throw new IllegalArgumentException("This should never happen"); } } catch (SQLException e) { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } - - /*for (int i = 0; i < DiscordBot.reportedlist.size(); i++) { - String[] randrp = DiscordBot.reportedlist.get(i).split(" "); - if (randrp[0].contains(author.getId()) && randrp[1].contains(SID)) { - return new CommandResult(CommandResultType.FAILURE, "You cannot report a user more than once."); - } - } - boolean banned = false; - boolean contains = false; - for (int i = 0; i < DiscordBot.playerreportlist.size(); i++) { - if (DiscordBot.playerreportlist.get(i).contains(SID)) { - contains = true; - } - } - if (contains == false) { - DiscordBot.playerreportlist.add(SID + " " + "1"); - times = 1; - FileWriter fileWriter = null; - BufferedWriter bufferedWriter; - try { - fileWriter = new FileWriter(DiscordBot.pfile, true); - } catch (IOException e1) { - e1.printStackTrace(); - } - try { - bufferedWriter = new BufferedWriter(fileWriter); - bufferedWriter.write(SID + " " + "1" + "\r\n"); - bufferedWriter.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } else { - // System.out.println("Got record");//debug - for (int i = 0; i < DiscordBot.playerreportlist.size(); i++) { - String[] pandt = DiscordBot.playerreportlist.get(i).split(" "); - if (pandt[0].contains(SID)) { - times = Long.parseLong(pandt[1]); - // System.out.println(times); //debug - if (times < 5) { - times = times + (long) 1; - // System.out.println(times); //debug - if (times == 5) { - DiscordBot.BannedID.add(Long.toString(ID)); - banned = true; - } - DiscordBot.playerreportlist.set(i, SID + " " + Long.toString(times)); - Path fp = Paths.get(System.getProperty("user.dir"), "PlayerReports.txt"); - try { - List fileContent = new ArrayList<>( - Files.readAllLines(fp, StandardCharsets.UTF_8)); - - for (int f = 0; f < fileContent.size(); f++) { - if (fileContent.get(f).startsWith(SID)) { - fileContent.set(f, SID + " " + Long.toString(times)); - break; - } - } - - Files.write(fp, fileContent, StandardCharsets.UTF_8); - } catch (IOException e) { - e.printStackTrace(); - } - - break; - } else { - if (!DiscordBot.BannedID.contains(String.valueOf(ID))) { - DiscordBot.BannedID.add(Long.toString(ID)); - } - return new CommandResult(CommandResultType.FAILURE, "The user <@!" + ID - + "> had enough reports therefore he is banned from using this bot."); - } - } - } - } - DiscordBot.reportedlist.add(author.getId() + " " + SID); - channel.sendMessage("You have successfully reported <@!" + ID + ">.\nThe user has been reported for " - + times + "/5 times.\n").queue(); - String addinfo = null; - if (banned == true) { - channel.sendMessage("The user <@!" + ID + "> is now banned from using the bot.").queue(); - addinfo = channel.getGuild().getMemberById(SID).getEffectiveName() + " (" + SID - + ") is banned from the bot."; - } - return new CommandResult(CommandResultType.SUCCESS, addinfo);*/ } } } diff --git a/src/me/despawningbone/discordbot/command/info/CityInfo.java b/src/me/despawningbone/discordbot/command/info/CityInfo.java index 8a831d8..2826901 100644 --- a/src/me/despawningbone/discordbot/command/info/CityInfo.java +++ b/src/me/despawningbone/discordbot/command/info/CityInfo.java @@ -1,272 +1,140 @@ package me.despawningbone.discordbot.command.info; import java.awt.Color; import java.io.IOException; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.text.DecimalFormat; import java.text.NumberFormat; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.TimeZone; import org.apache.commons.lang3.exception.ExceptionUtils; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; import me.despawningbone.discordbot.utils.MiscUtils; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.exceptions.InsufficientPermissionException; public class CityInfo extends Command { public CityInfo() { this.alias = Arrays.asList("ci", "weather"); this.desc = "Search for info about a city!"; //"Search for info about the city the address is in!"; this.usage = "
"; for (String country : Locale.getISOCountries()) { Locale locale = new Locale("en", country); countryCodes.put(locale.getDisplayCountry(Locale.ENGLISH), locale.getCountry()); } this.examples = Arrays.asList("hong kong", "tokyo"); //"HK", "akihabara"); } HashMap countryCodes = new HashMap<>(); NumberFormat formatter = new DecimalFormat("#0.00"); - //private final String flickrAPI = DiscordBot.tokens.getProperty("flickr"); - @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { if(args.length < 1) { return new CommandResult(CommandResultType.INVALIDARGS, "Please input a city name."); //or a address."); } else { channel.sendTyping().queue(); String sword = String.join(" ", args); try { - /*JSONTokener georesult = null; - InputStream geostream = null; - String search = URLEncoder.encode(sword, "UTF-8"); - URL geocode = null; - try { - geocode = new URL("https://maps.googleapis.com/maps/api/geocode/json?address=" + search + "&key=" + AudioPlayer.GAPI + "&language=en"); - geostream = geocode.openStream(); - } catch (IOException e) { - e.printStackTrace(); - } - georesult = new JSONTokener(geostream); - JSONObject geomain = new JSONObject(georesult); - JSONArray resultList = geomain.getJSONArray("results"); - if(resultList.isNull(0)) { - channel.sendMessage("Unfortunately there is no results :cry:").queue(); - return new CommandResult(CommandResultType.NORESULT); - } - JSONObject firstResult = resultList.getJSONObject(0); - JSONObject loc = firstResult.getJSONObject("geometry").getJSONObject("location"); - //String formattedAddr = firstResult.getString("formatted_address"); - JSONArray addrComponents = firstResult.getJSONArray("address_components"); - String formattedAddr = "", addr = firstResult.getString("formatted_address"); - String countryShort = null; String region = null; String locality = null; String country = null; String colarea = null; - boolean stop = false; - for(int i = 0; i < addrComponents.length(); i++) { - JSONObject component = addrComponents.getJSONObject(i); - String compname = component.getString("long_name"); - if(!stop) { - if(i == addrComponents.length() - 1) { - formattedAddr += compname; - } else { - formattedAddr += compname + ", "; - } - } - List types = component.getJSONArray("types").toList(); - if(types.contains("country")) { - countryShort = component.getString("short_name"); - country = compname; - if(i == 0) { - channel.sendMessage("You need to specify which part of the country you want to get the info from.").queue(); - return new CommandResult(CommandResultType.FAILURE, "Address is a country"); - } - } else if(types.contains("continent")) { - if(i == 0) { - channel.sendMessage("You need to specify which part of the continent you want to get the info from.").queue(); - return new CommandResult(CommandResultType.FAILURE, "Address is a continent"); - } - } else if(types.contains("postal_code")) { - if(i == 0) { - formattedAddr = addr; - stop = true; - } - } else if(types.contains("administrative_area_level_1")) { - region = compname; - } else if(types.contains("locality")) { - locality = compname; - } else if(types.contains("colloquial_area")) { - colarea = compname; - } else if(types.contains("natural_feature") && addrComponents.length() == 1) { - channel.sendMessage("Search civilized locations please :joy:").queue(); - return new CommandResult(CommandResultType.FAILURE, "Address is natural"); - } - } - if(region == null) { - if(stop) { - region = country; - } else { - if(locality.equals("Singapore")) { - formattedAddr = addr; - } - region = colarea; - if(locality != null) region = locality; - } - } - double lat = loc.getDouble("lat"); - double lng = loc.getDouble("lng"); - JSONTokener timeresult = null; - InputStream timestream = null; - URL timezone = null; - Timestamp timestamp = new Timestamp(System.currentTimeMillis()); - long sec = Math.round(timestamp.getTime() / 1000.0); - try { //can deprecate this since the new weather scrape has local time, but there wont be a name for the timezone anymore - timezone = new URL("https://maps.googleapis.com/maps/api/timezone/json?location=" + lat + "," + lng + "×tamp=" + sec + "&key=" + AudioPlayer.GAPI + "&language=en"); - timestream = timezone.openStream(); - } catch (IOException e) { - e.printStackTrace(); - } - timeresult = new JSONTokener(timestream); - JSONObject timemain = new JSONObject(timeresult); - String timeZoneName = timemain.getString("timeZoneName"); - int rawOffset = timemain.getInt("rawOffset"); - int dstOffset = timemain.getInt("dstOffset"); - ZonedDateTime zone = ZonedDateTime.now(ZoneOffset.ofTotalSeconds(rawOffset + dstOffset)); - int hours = (rawOffset + dstOffset) / 60 / 60;*/ - boolean hasWeather = true; JSONObject info = null; String wQualifiedName = "", woeid = "", lng = "", lat = "", region = "", countryShort = ""; TimeZone timezone = null; - /*try { - URLConnection con = new URL("https://api.flickr.com/services/rest/?method=flickr.places.find&api_key=" + flickrAPI + "&query=" + URLEncoder.encode(sword, "UTF-8") + "&format=json&nojsoncallback=1").openConnection(); - con.setRequestProperty("Accept-Language", "en"); - JSONArray warray = new JSONObject(new JSONTokener(con.getInputStream())).getJSONObject("places").getJSONArray("place"); - int index; - for(index = 0; index < warray.length(); index++) { - if(warray.getJSONObject(index).has("timezone")) { - break; - } - } - JSONObject wsearch = warray.getJSONObject(index); - woeid = wsearch.getString("woeid"); //flickr api, using generated api key (will it expire?) //highest accuracy so far - //wQualifiedName = wsearch.getString("_content"); //too short - ArrayList pSplit = new ArrayList<>(Arrays.asList(URLDecoder.decode(wsearch.getString("place_url"), "UTF-8").substring(1).split("/"))); - if(!pSplit.get(pSplit.size() - 1).equals(wsearch.getString("woe_name"))) { - pSplit.add(wsearch.getString("woe_name")); - } - Collections.reverse(pSplit); - wQualifiedName = String.join(", ", pSplit); - timezone = TimeZone.getTimeZone(wsearch.getString("timezone")); - lat = wsearch.getString("latitude"); - lng = wsearch.getString("longitude"); - String[] rSplit = wQualifiedName.split(", "); - region = rSplit.length > 1 ? rSplit[rSplit.length - 2] : rSplit[0]; - countryShort = countryCodes.get(rSplit[rSplit.length - 1].trim()); - } catch(IOException e) { - return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); - } catch (JSONException e) { - //e.printStackTrace(); - return new CommandResult(CommandResultType.NORESULT); - }*/ //FLICKR DED try { URLConnection sCon = new URL("https://www.yahoo.com/news/_tdnews/api/resource/WeatherSearch;text=" + URLEncoder.encode(sword, "UTF-8") + "?returnMeta=true").openConnection(); sCon.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0"); JSONObject wsearch = new JSONObject(new JSONTokener(sCon.getInputStream())).getJSONArray("data").getJSONObject(0); woeid = String.valueOf(wsearch.getInt("woeid")); //yahoo scrape lat = String.valueOf(wsearch.getDouble("lat")); lng = String.valueOf(wsearch.getDouble("lon")); wQualifiedName = wsearch.getString("qualifiedName"); countryShort = countryCodes.get(wsearch.getString("country")); - region = wsearch.getString("city"); + region = wQualifiedName.split(",")[wQualifiedName.split(",").length - 2]; //get second highest level, highest should always be country code timezone = TimeZone.getTimeZone(new JSONObject(new JSONTokener(new URL("https://api.internal.teleport.org/api/locations/" + lat + "," + lng + "/?embed=location:nearest-cities/location:nearest-city/city:timezone").openStream())).getJSONObject("_embedded").getJSONArray("location:nearest-cities").getJSONObject(0).getJSONObject("_embedded").getJSONObject("location:nearest-city").getJSONObject("_embedded").getJSONObject("city:timezone").getString("iana_name")); //can use metaweather, but not accurate enough //can also broaden the scope for yahoo scrape for it to work better //JSONObject wsearch = new JSONObject(new JSONTokener(new URL("https://api.flickr.com/services/rest/?method=flickr.places.findByLatLon&api_key=bdaafeafab62267931d920dda27a4f90&lat=" + lat + "&lon=" + lng + "&format=json&nojsoncallback=1").openStream())).getJSONObject("places").getJSONArray("place").getJSONObject(0); //gonna use flickr find instead URLConnection iCon = new URL("https://www.yahoo.com/news/_tdnews/api/resource/WeatherService;woeids=[" + woeid + "]?lang=en-US&returnMeta=true").openConnection(); iCon.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0"); info = new JSONObject(new JSONTokener(iCon.getInputStream())).getJSONObject("data").getJSONArray("weathers").getJSONObject(0); } catch(IOException e) { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } catch(JSONException e) { e.printStackTrace(); return new CommandResult(CommandResultType.NORESULT); //hasWeather = false; } - /*String ftimezone = timeZoneName + " (UTC" + (Math.signum(hours) == 1 || Math.signum(hours) == 0 ? "+" + hours : hours) + ")"; - if(ftimezone.length() < 34) { - ftimezone += String.join("", Collections.nCopies(34 - ftimezone.length(), " ")); - }*/ + Date date = new Date(); //System.out.println(info); EmbedBuilder embedmsg = new EmbedBuilder(); embedmsg.setAuthor("Info for " + wQualifiedName, null, null); embedmsg.setColor(new Color(100, 0, 255)); - //embedmsg.setFooter("Weather info last updated: " + info.getString("lastBuildDate") , null); embedmsg.addField("Country", MiscUtils.countryNameToUnicode(countryShort), true); embedmsg.addField("Region", region, true); embedmsg.addField("Current time", OffsetDateTime.now(timezone.toZoneId()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ssa").withLocale(Locale.ENGLISH)).trim(), true); long hours = (timezone.getOffset(date.getTime())/1000/60/60); embedmsg.addField("Timezone" , timezone.getDisplayName(timezone.inDaylightTime(date), TimeZone.LONG, Locale.ENGLISH) + " (UTC" + (Math.signum(hours) == 1 || Math.signum(hours) == 0 ? "+" + hours : hours) + ")" + "\u1160", true); //FLICKR DED String footer = "Weather info not available"; if (hasWeather) { //use another api if weather info not available? JSONObject obs = info.getJSONObject("observation"); JSONObject temp = obs.getJSONObject("temperature"); JSONObject forecast = info.getJSONObject("forecasts").getJSONArray("daily").getJSONObject(0); if(temp.has("now")) { embedmsg.addField("Temperature", fToC(temp.getInt("now")) + "°C (↑" + fToC(temp.getInt("high")) + "°C | ↓" + fToC(temp.getInt("low")) + "°C)", true); embedmsg.addField("Humidity", obs.getInt("humidity") + "% (Chance of rain: " + forecast.getInt("precipitationProbability") + "%)", true); embedmsg.addField("Visibility", miToKm(obs.getDouble("visibility")) + "km (" + obs.getString("conditionDescription") + ")", true); embedmsg.addField("Atmospheric pressure", formatter.format(obs.getDouble("barometricPressure") / 0.029530) + "millibars", true); embedmsg.addField("Wind speed", miToKm(obs.getDouble("windSpeed")) + "km/h", true); embedmsg.addField("Wind direction", obs.getInt("windDirection") + "° (" + obs.getString("windDirectionCode") + ")", true); embedmsg.addField("Feels Like", fToC(temp.getInt("feelsLike")) + "°C", true); embedmsg.addField("UV index", obs.getInt("uvIndex") + " (" + obs.getString("uvDescription") + ")", true); embedmsg.addField("Sunrise", MiscUtils.convertMillis(info.getJSONObject("sunAndMoon").getLong("sunrise") * 1000).substring(0, 5), true); embedmsg.addField("Sunset", MiscUtils.convertMillis(info.getJSONObject("sunAndMoon").getLong("sunset") * 1000).substring(0, 5), true); String imgUrl = info.getJSONArray("photos").getJSONObject(0).getJSONArray("resolutions").getJSONObject(0).getString("url"); //seems to have dead urls, how fix embedmsg.setThumbnail(imgUrl.split(":\\/\\/").length > 2 ? "https://" + imgUrl.split(":\\/\\/")[2] : imgUrl); footer = "Weather info last updated: " + OffsetDateTime.parse(obs.getJSONObject("observationTime").getString("timestamp")).format(DateTimeFormatter.RFC_1123_DATE_TIME) .replace("GMT", timezone.getDisplayName(timezone.inDaylightTime(date), TimeZone.SHORT, Locale.ENGLISH)); //+ " | " + wQualifiedName; //add weather provider to footer? } } embedmsg.addField("Latitude", lat, true); embedmsg.addField("Longitude", lng, true); embedmsg.setFooter(footer, null); try { MessageEmbed fmsg = embedmsg.build(); channel.sendMessage(fmsg).queue(); } catch(InsufficientPermissionException e2) { return new CommandResult(CommandResultType.FAILURE, "Unfortunately, the bot is missing the permission `MESSAGE_EMBED_LINKS` which is required for this command to work."); } return new CommandResult(CommandResultType.SUCCESS); } catch (Exception e) { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } } } private String fToC(int f) { //fucking no metric ree return formatter.format((f-32)*5.0/9); } private String miToKm(double mile) { return formatter.format(mile*1.609344); } } diff --git a/src/me/despawningbone/discordbot/command/info/CurrencyConvert.java b/src/me/despawningbone/discordbot/command/info/CurrencyConvert.java index 60c3686..b2a6e52 100644 --- a/src/me/despawningbone/discordbot/command/info/CurrencyConvert.java +++ b/src/me/despawningbone/discordbot/command/info/CurrencyConvert.java @@ -1,121 +1,82 @@ package me.despawningbone.discordbot.command.info; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.text.DecimalFormat; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.apache.commons.lang3.exception.ExceptionUtils; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; /*import org.jsoup.Jsoup; import org.jsoup.nodes.Document;*/ import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; public class CurrencyConvert extends Command { public CurrencyConvert() { this.alias = Arrays.asList("currency", "cc"); this.desc = "convert a currency to another!"; this.usage = " [amount]"; this.remarks = Arrays.asList("The currency units use the format of ISO 4217. For the list, see below:", "https://en.wikipedia.org/wiki/ISO_4217"); this.examples = Arrays.asList("HKD USD", "EUR SGD 100"); } @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { String fromc = null; String toc = null; String num = null; try { fromc = args[0].toUpperCase(); toc = args[1].toUpperCase(); } catch (ArrayIndexOutOfBoundsException e) { return new CommandResult(CommandResultType.INVALIDARGS, "Please enter 2 currencies."); } try { //there is a lot more info, use them too? InputStream stream = new URL("https://query1.finance.yahoo.com/v8/finance/chart/"+ fromc + toc + "=X?range=6h&includePrePost=false&interval=1m").openStream(); JSONObject main = new JSONObject(new JSONTokener(stream)).getJSONObject("chart").getJSONArray("result").getJSONObject(0); double rate = 0; try { List close = main.getJSONObject("indicators").getJSONArray("quote").getJSONObject(0).getJSONArray("close").toList(); close.removeAll(Collections.singleton(null)); rate = (double) close.get(close.size() - 1); } catch (JSONException e) { rate = main.getJSONObject("meta").getDouble("previousClose"); //rate += new JSONObject(new JSONTokener(new URL("https://query1.finance.yahoo.com/v1/finance/lookup?formatted=true&lang=en-US®ion=US&query=" + fromc + toc + "=x&type=all&count=25&start=0&corsDomain=finance.yahoo.com").openStream())) //.getJSONObject("finance").getJSONArray("result").getJSONObject(0).getJSONArray("documents").getJSONObject(0).getJSONObject("regularMarketChange").getDouble("raw"); } if(rate == 1) { - //throw new NullPointerException("Invalid currency conversion!"); return new CommandResult(CommandResultType.INVALIDARGS, "Invalid currency conversion!"); } try { num = args[2]; } catch (ArrayIndexOutOfBoundsException e) { num = "1"; } double f = rate * Double.parseDouble(num); channel.sendMessage(num + " " + fromc.toUpperCase() + " = " + new DecimalFormat("#.####").format(f) + " " + toc.toUpperCase()).queue(); return new CommandResult(CommandResultType.SUCCESS); } catch (IOException e) { if(e instanceof FileNotFoundException) { return new CommandResult(CommandResultType.INVALIDARGS, "Invalid currency. See the help for the list of available currencies."); } else { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } } catch (NumberFormatException e) { return new CommandResult(CommandResultType.INVALIDARGS, "Invalid amount."); } - - /*SCRAPE DEPRECATED*/ - - /*try { // again, not pretty good to scrape but thats the only way to get the ever updating yahoo finance data - Document doc = Jsoup.connect("https://hk.finance.yahoo.com/quote/" + fromc + toc + "=X").get(); - // System.out.println(doc); - try { - String rate = doc.select("span[data-reactid=\"35\"]").first().text().replace(",", ""); - // String cc = doc.select("div[data-reactid=\"6\"] h1").first().text().split(" ")[0].replace("/", ""); - // System.out.println(cc); - if (Double.parseDouble(rate) == 1 //!cc.equals(fromc + toc) ) { - throw new NullPointerException(); - } - boolean nonum = false; - try { - num = args[2]; - } catch (ArrayIndexOutOfBoundsException e) { - nonum = true; - num = "1"; - } - String finalrate; - if (!nonum) { - double tempn = Double.parseDouble(num); - double tempr = Double.parseDouble(rate); - finalrate = String.valueOf(tempn * tempr); - } else { - finalrate = rate; - } - channel.sendMessage(num + " " + fromc.toUpperCase() + " = " + finalrate + " " + toc.toUpperCase()) - .queue(); - return new CommandResult(CommandResultType.SUCCESS, null); - } catch (NullPointerException | NumberFormatException e) { - channel.sendMessage("Invalid currency.").queue(); - return new CommandResult(CommandResultType.INVALIDARGS, null); - } - } catch (IOException e) { - return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); - }*/ } } diff --git a/src/me/despawningbone/discordbot/command/info/Fandom.java b/src/me/despawningbone/discordbot/command/info/Fandom.java index 930173d..d9b7e72 100644 --- a/src/me/despawningbone/discordbot/command/info/Fandom.java +++ b/src/me/despawningbone/discordbot/command/info/Fandom.java @@ -1,106 +1,106 @@ package me.despawningbone.discordbot.command.info; import java.awt.Color; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.time.Instant; import java.util.Arrays; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; public class Fandom extends Command { public Fandom() { this.desc = "Search across all the fan-made wikis!"; this.alias = Arrays.asList("wikia", "gamepedia"); //gamepedia is now fandom basically this.usage = ": [| index]"; this.examples = Arrays.asList("zelda: gate of time", "clockwork planet: ryuZU", "angel beats: kanade"); } - @Override //query for details using future too? since i already have to make 2 queries, making 3 in parallel wont make it much slower; the only concern is rate limit //already doing sequential 3 queries, aint too slow so its fine + @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { channel.sendTyping().queue(); if (args.length < 1) { return new CommandResult(CommandResultType.INVALIDARGS, "Invalid input, check help for more info."); } try { String[] init = String.join(" ", args).split(":", 2); if(init.length < 2) { return new CommandResult(CommandResultType.INVALIDARGS, "Please use a colon to seperate the wiki and the search query!"); } //get index String[] split = init[1].split(" \\| "); String sword = split[0]; int num = 0; if (split.length > 1) { try { num = Integer.parseInt(split[1]) - 1; if(num < 0) throw new NumberFormatException(); } catch (NumberFormatException e) { channel.sendMessage("Invalid index inputted. Defaulting to the first result...").queue(); num = 0; } } //bruh the gamepedia merge killed a ton of the actual good UCP/nirvana API controller endpointss //now i gotta use wack substitutions //search - since SearchApiController is now gone i gotta use the other ones String search = URLEncoder.encode(sword.trim(), "UTF-8"); HttpURLConnection url = null; String wiki = init[0].replaceAll("[^\\p{L}\\p{N} ]+", "").replaceAll(" ", "-").toLowerCase(); //the wikia domain is still in use; no need to swap to fandom.com for now //alternative search endpoint (more of an autocomplete only but much faster): "https://" + wiki + ".wikia.com/wikia.php?controller=LinkSuggest&method=getLinkSuggestions&format=json&query=" + search url = (HttpURLConnection) new URL("https://" + wiki + ".wikia.com/api.php?action=query&format=json&list=search&srsearch=" + search).openConnection(); if(url.getResponseCode() == 404) { return new CommandResult(CommandResultType.FAILURE, "Unknown wiki name!"); //404 means unknown wiki now } //get result int id; try { JSONObject result = new JSONObject(new JSONTokener(url.getInputStream())); id = result.getJSONObject("query").getJSONArray("search").getJSONObject(num).getInt("pageid"); } catch(JSONException e) { return new CommandResult(CommandResultType.NORESULT, "it in the " + init[0] + " wiki"); } //fetch details about page; way worse formatting than AsSimpleJson but hey its gone what can i do JSONObject details = new JSONObject(new JSONTokener(new URL("https://" + wiki + ".wikia.com/api/v1/Articles/Details?abstract=500&ids=" + id).openStream())); JSONObject info = details.getJSONObject("items").getJSONObject(String.valueOf(id)); //TODO make async EmbedBuilder eb = new EmbedBuilder(); eb.setTitle(info.getString("title"), details.getString("basepath") + info.getString("url")); eb.setAuthor(StringUtils.capitalize(init[0]) + " wiki", details.getString("basepath")); //only use until the last full stop before table of content or end for slightly better formatting //there might be false positives for table of content detection since its just checking 1 after full stop, but honestly rarely less details > commonly being ugly af String desc = info.getString("abstract").replaceAll("^(?:(.*?\\.) ?1 .*|(.*\\.) .*?)$", "$1$2"); //greedy if table of content is present, else lazy to get the last eb.setDescription(desc.matches(".*\\.$") ? desc : (desc + "...")); //if everything fails (aka last char aint a full stop) give it the good ol ... treatment if(info.has("comments")) eb.addField("Comments", String.valueOf(info.getInt("comments")), false); if(!info.isNull("thumbnail")) eb.setThumbnail(info.getString("thumbnail").substring(0, info.getString("thumbnail").indexOf("/revision/"))); //get full img by trimming revision path eb.setFooter("Last edited by " + info.getJSONObject("revision").getString("user"), null); eb.setTimestamp(Instant.ofEpochSecond(Long.parseLong(info.getJSONObject("revision").getString("timestamp")))); eb.setColor(new Color(0, 42, 50)); channel.sendMessage(eb.build()).queue(); return new CommandResult(CommandResultType.SUCCESS); //add searched result name? } catch (Exception e) { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } } } diff --git a/src/me/despawningbone/discordbot/command/info/Translate.java b/src/me/despawningbone/discordbot/command/info/Translate.java index 763628d..e7c44ea 100644 --- a/src/me/despawningbone/discordbot/command/info/Translate.java +++ b/src/me/despawningbone/discordbot/command/info/Translate.java @@ -1,116 +1,117 @@ package me.despawningbone.discordbot.command.info; import java.io.IOException; import java.time.OffsetDateTime; import java.util.Arrays; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import org.jsoup.Jsoup; import org.apache.commons.lang3.exception.ExceptionUtils; import org.json.JSONObject; import org.json.JSONTokener; import org.jsoup.Connection; import org.jsoup.Connection.Method; import org.jsoup.Connection.Response; import org.jsoup.HttpStatusException; import com.google.common.util.concurrent.ThreadFactoryBuilder; import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; public class Translate extends Command { public Translate() { this.desc = "Translate some sentences!"; this.usage = "[-fromlang] [-tolang]"; this.remarks = Arrays.asList("Supported Languages: `en, ja, zh, de, es, fr, it, ru, pl, pt, nl`"); this.examples = Arrays.asList("-ja あなたも文化人だと思います", "-es despacito"); this.alias = Arrays.asList("tl"); executor.scheduleAtFixedRate(() -> refresh(), 0, 300, TimeUnit.SECONDS); //5 mins refresh } private int id = (ThreadLocalRandom.current().nextInt(1000, 10000) + 1) * 10000 + 1; private String cookie = null, agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0"; private JSONObject cVars = null; final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("tl-scheduler-%d").build()); @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { if(args.length < 0) return new CommandResult(CommandResultType.INVALIDARGS, "Please enter something to translate!"); try { String langF = "auto", langT = "EN", query = String.join(" ", args).replaceAll("\n", " ").replace("-", ""); //since it hates new lines and hyphens apparently if(Arrays.asList("-en", "-ja", "-zh", "-de", "-es", "-fr", "-it", "-ru", "-pl", "-pt", "-nl").contains(args[0].toLowerCase())) { langF = args[0].substring(1).toUpperCase(); query = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); } if(Arrays.asList("-en", "-ja", "-zh", "-de", "-es", "-fr", "-it", "-ru", "-pl", "-pt", "-nl").contains(args[args.length - 1].toLowerCase())) { langT = args[args.length - 1].substring(1).toUpperCase(); query = query.substring(0, query.length() - 3); } if((args.length < 5 ? query.length() > 40 : args.length > 20)) return new CommandResult(CommandResultType.INVALIDARGS, "Please type a shorter phrase to translate!"); //need OPTION call before calling this? //req - Connection con = Jsoup.connect("https://www2.deepl.com/jsonrpc").userAgent(agent) + Connection con = Jsoup.connect("https://www2.deepl.com/jsonrpc?method=LMT_handle_jobs").userAgent(agent) .header("Content-type", "application/json").ignoreContentType(true) .requestBody("{\"jsonrpc\":\"2.0\",\"method\": \"LMT_handle_jobs\",\"params\":{\"jobs\":[{\"kind\":\"default\",\"raw_en_sentence\":\"" + query + "\",\"raw_en_context_before\":[],\"raw_en_context_after\":[],\"preferred_num_beams\":4" + ((args.length < 2 ? query.length() > 5 : args.length > 5) ? "" : ",\"quality\":\"fast\"") + "}],\"commonJobParams\":{}" + ",\"lang\":{\"user_preferred_langs\":[\"EN\"],\"source_lang_user_selected\":\"" + langF + "\",\"target_lang\":\"" + langT + "\"},\"priority\":1,\"timestamp\":" + OffsetDateTime.now().toEpochSecond() + "000},\"id\":" + id++ + "}"); if(cookie != null) con.cookie("LMTBID", cookie); Response resp = con.method(Method.POST).execute(); if(resp.hasCookie("LMTBID")) cookie = resp.cookie("LMTBID"); //set cookies; only LMTBID is useful, and its set on first call to jsonrpc //formatting JSONObject main = new JSONObject(new JSONTokener(resp.body())).getJSONObject("result"); EmbedBuilder eb = new EmbedBuilder(); eb.setColor(0x0f2b46); eb.setTitle("Translation: " + (langF.equals("auto") ? main.getString("source_lang") + "(detected)" : langF) + " -> " + main.getString("target_lang")); eb.addField("Original", query, true); String tl = ""; for(Object obj : main.getJSONArray("translations").getJSONObject(0).getJSONArray("beams")) { String p = ((JSONObject) obj).getString("postprocessed_sentence"); if(tl.isEmpty()) tl = p + "\n"; else tl += "*" + p + "*\n"; } eb.addField("Translated", tl, true); eb.setFooter("Translated by DeepL", "https://egress.storeden.net/gallery/5a2577c9ffe48e7e4b418464"); eb.setTimestamp(OffsetDateTime.now()); channel.sendMessage(eb.build()).queue(); return new CommandResult(CommandResultType.SUCCESS); } catch(IOException e) { if(e instanceof HttpStatusException) //aka pending code fix return new CommandResult(CommandResultType.FAILURE, "The translator is currently temporarily unavailable, please try again later."); - + return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } } private void refresh() { try { - Connection con = Jsoup.connect("https://www.deepl.com/PHP/backend/clientState.php?request_type=jsonrpc&il=EN").userAgent(agent) - .requestBody("{\"jsonrpc\":\"2.0\",\"method\":\"getClientState\",\"params\":{\"v\":\"20180814\"" + (cVars == null ? "" : ",\"clientVars\":" + cVars.toString()) + "},\"id\":" + id++ + "}"); + Connection con = Jsoup.connect("https://www.deepl.com/PHP/backend/clientState.php?request_type=jsonrpc&il=EN&method=getClientState").userAgent(agent) + .requestBody("{\"jsonrpc\":\"2.0\",\"method\":\"getClientState\",\"params\":{\"v\":\"20180814\"" + ",\"clientVars\":" + (cVars == null ? "{}" : cVars.toString()) + "},\"id\":" + id++ + "}"); if(cookie != null) con.cookie("LMTBID", cookie); - + JSONObject json = new JSONObject(new JSONTokener(con.ignoreContentType(true).post().text())); //System.out.println(json); - if(cVars == null) cVars = json.getJSONObject("result").getJSONObject("clientVars"); + if(cVars == null) cVars = json.getJSONObject("result").getJSONObject("clientVars"); } catch (IOException e) { e.printStackTrace(); + id--; //set it back down one } } } diff --git a/src/me/despawningbone/discordbot/command/misc/BaseConvert.java b/src/me/despawningbone/discordbot/command/misc/BaseConvert.java index 800f6e0..8fd0329 100644 --- a/src/me/despawningbone/discordbot/command/misc/BaseConvert.java +++ b/src/me/despawningbone/discordbot/command/misc/BaseConvert.java @@ -1,113 +1,56 @@ package me.despawningbone.discordbot.command.misc; import java.util.Arrays; import me.despawningbone.discordbot.command.Command; import me.despawningbone.discordbot.command.CommandResult; import me.despawningbone.discordbot.command.CommandResult.CommandResultType; import me.despawningbone.discordbot.utils.MiscUtils; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; public class BaseConvert extends Command { public BaseConvert() { this.alias = Arrays.asList("bc"); this.desc = "convert integers of a base to another base!"; this.usage = "[frombase] "; this.examples = Arrays.asList("2 100", "16 8 FF"); } @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { //base 1 is weird, fix? String x = "0"; long base = 10; long frombase = 10; try { - if (args.length < 3) { - try { - base = Long.parseLong(args[0]); - x = args[1]; - } catch (ArrayIndexOutOfBoundsException e) { - return new CommandResult(CommandResultType.INVALIDARGS, "Please enter" + (args.length < 1 ? "something :joy:" : "a value to convert.")); - } catch (NumberFormatException e) { - base = MiscUtils.nameToBase(args[0]); - } - } else { + if(args.length < 2) + return new CommandResult(CommandResultType.INVALIDARGS, "Please enter " + (args.length < 1 ? "something :joy:" : "a value to convert.")); + + if (args.length < 3) { //only specify to base + try { base = Long.parseLong(args[0]); } catch (NumberFormatException e) { base = MiscUtils.nameToBase(args[0]); } + x = args[1]; + } else { //specify both bases try { frombase = Long.parseLong(args[0]); } catch (NumberFormatException e) { frombase = MiscUtils.nameToBase(args[0]); } try { base = Long.parseLong(args[1]); } catch (NumberFormatException e) { base = MiscUtils.nameToBase(args[1]); } x = args[2]; } - if (base > 30 || frombase > 30) { + + if (base > 30 || frombase > 30) return new CommandResult(CommandResultType.INVALIDARGS, "Please enter a base smaller than 30."); - } + try { channel.sendMessage("Base" + MiscUtils.longToSubscript(frombase) + ": `" + x.toUpperCase() + "`\n" + "Base" + MiscUtils.longToSubscript(base) + ": `" + Long.toString(Long.parseLong(x, (int) frombase), (int) base).toUpperCase() + "`").queue(); return new CommandResult(CommandResultType.SUCCESS); } catch (NumberFormatException e) { return new CommandResult(CommandResultType.INVALIDARGS, "Please enter a valid value within the base range specified."); } } catch (IllegalArgumentException e) { return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage()); } - - /*DEPRECATED OLD CODE*/ - - /*try { - if (args.length < 2) { - try { - base = Long.parseLong(msg.getContentDisplay().split(" ")[2]); - } catch (ArrayIndexOutOfBoundsException e) { - channel.sendMessage("Please enter something :joy:").queue(); - return new CommandResult(CommandResultType.INVALIDARGS, null); - } - } else { - frombase = Long.parseLong(msg.getContentDisplay().split(" ")[2]); - base = Long.parseLong(msg.getContentDisplay().split(" ")[3]); - } - if (base > 30 || frombase > 30) { - channel.sendMessage("Please enter a base smaller than 30.").queue(); - return new CommandResult(CommandResultType.INVALIDARGS, null); - } - } catch (NumberFormatException e) { - String sbase = msg.getContentDisplay().split(" ")[2]; - try { - if (msg.getContentDisplay().split(" ").length > 4) { - frombase = MiscUtils.nameToBase(sbase); - base = MiscUtils.nameToBase(msg.getContentDisplay().split(" ")[3]); - } else { - base = MiscUtils.nameToBase(sbase); - } - } catch (NullPointerException | IllegalArgumentException e1) { - if (e1 instanceof IllegalArgumentException) { - channel.sendMessage(e1.getMessage()).queue(); - return new CommandResult(CommandResultType.INVALIDARGS, null); - } - return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e1)); - } - } - if (msg.getContentDisplay().split(" ").length < 5) { - try { - x = msg.getContentDisplay().split(" ", 4)[3]; - } catch (ArrayIndexOutOfBoundsException e) { - channel.sendMessage("Please enter a value to convert.").queue(); - return new CommandResult(CommandResultType.INVALIDARGS, null); - } - } else { - x = msg.getContentDisplay().split(" ", 5)[4]; - } - try { - channel.sendMessage("Base" + MiscUtils.longToSubscript(frombase) + ": `" + x + "`\n" + "Base" - + MiscUtils.longToSubscript(base) + ": `" - + Long.toString(Long.parseLong(x, (int) frombase), (int) base).toUpperCase() + "`").queue(); - return new CommandResult(CommandResultType.SUCCESS, null); - } catch (NumberFormatException e) { - channel.sendMessage("Please enter a valid positive integer within the base range specified.").queue(); - return new CommandResult(CommandResultType.INVALIDARGS, null); - }*/ } }