diff --git a/src/me/despawningbone/discordbot/EventListener.java b/src/me/despawningbone/discordbot/EventListener.java index 9f84331..bc182e1 100644 --- a/src/me/despawningbone/discordbot/EventListener.java +++ b/src/me/despawningbone/discordbot/EventListener.java @@ -1,289 +1,291 @@ 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.util.Arrays; import java.util.List; import java.util.Properties; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.exception.ExceptionUtils; 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.utils.messages.MessageCreateBuilder; import net.dv8tion.jda.api.utils.messages.MessageCreateData; 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.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.entities.Message.Attachment; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageUpdateEvent; import net.dv8tion.jda.api.events.user.UserActivityStartEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; public class EventListener extends ListenerAdapter { @Override public void onMessageReceived(MessageReceivedEvent event) { //do not support DMs for now if(!event.isFromGuild()) return; 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 User author = event.getAuthor(); - TextChannel channel = event.getChannel().asTextChannel(); + //TODO support MessageChannel/MessageChannelUnion instead of just text channel since voice channels can also have text now + TextChannel channel = event.getChannel().asTextChannel(); Message msg = event.getMessage(); //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? } //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); 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 //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(Emoji.fromUnicode("❎")).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? } //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() + ")")); } //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) { MessageCreateBuilder smsg = new MessageCreateBuilder(); //main greet String nick = channel.getGuild().getMemberById(author.getId()).getNickname(); if (nick != null) { smsg.addContent("Yo " + nick + "!\n"); } else { smsg.addContent("Yo " + author.getName() + "!\n"); } //easter eggs String easterEgg = greetEasterEggs.getProperty(author.getId()); if(easterEgg != null) { smsg.addContent(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.setEmbeds(eb.build()); MessageCreateData fmsg = smsg.build(); channel.sendMessage(fmsg).queue(); return new CommandResult(CommandResultType.SUCCESS, null); } @Override public void onMessageUpdate(MessageUpdateEvent event) { //log edits //do not support DMs for now if(!event.isFromGuild()) return; Message msg = event.getMessage(); User author = event.getAuthor(); TextChannel channel = event.getChannel().asTextChannel(); 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(); String sGame = (toolTip.lastIndexOf(" (") != -1 ? toolTip.substring(0, toolTip.lastIndexOf(" (")) : toolTip) + "||" + osu.asRichPresence().getDetails(); //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(); } catch (SQLException e) { //FIXED if i make this not osu only, be aware of SQL injections through sGame (probably being paranoid tho) e.printStackTrace(); } } } //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 onGuildVoiceUpdate(GuildVoiceUpdateEvent 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(); String id = event.getGuild().getId(); TextChannel channel = DiscordBot.lastMusicCmd.get(id); + if(vs == null || vs.getChannel() == null) return; //bot is not active in this guild, skip checks if(!event.getMember().getUser().getId().equals(DiscordBot.BotID)) { if(event.getChannelJoined() != null) { if (vs.getChannel().equals(event.getChannelJoined())) { String type = updateActivity(event.getGuild(), event.getChannelJoined().asVoiceChannel()); if(type != null) channel.sendMessage((type.equals("loop") ? "Looping" : "Autoplay") + " mode disabled due to new users joining the music channel.").queue(); } } else if(event.getChannelLeft() != null) { if (vs.getChannel().equals(event.getChannelLeft()) && 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(event.getChannelJoined() != null && event.getChannelLeft() != null) { //got moved //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().asVoiceChannel()); if(type != null) channel.sendMessage((type.equals("loop") ? "Looping" : "Autoplay") + " mode disabled due to new users joining the music channel.").queue(); } } else if(event.getChannelLeft() != null) { //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 } } //we dont care about the bot joining a channel } } } diff --git a/src/me/despawningbone/discordbot/command/anime/Anime.java b/src/me/despawningbone/discordbot/command/anime/Anime.java index 93fccaa..6c26d5c 100644 --- a/src/me/despawningbone/discordbot/command/anime/Anime.java +++ b/src/me/despawningbone/discordbot/command/anime/Anime.java @@ -1,103 +1,103 @@ package me.despawningbone.discordbot.command.anime; import java.awt.Color; import java.io.IOException; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.exception.ExceptionUtils; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; 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 me.despawningbone.discordbot.utils.MiscUtils; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.User; public class Anime extends Command { private static byte[] encodedMalAuth = Base64.encodeBase64((DiscordBot.tokens.getProperty("mal")).getBytes(StandardCharsets.UTF_8)); public static String malAuth = "Basic " + new String(encodedMalAuth); public Anime() { this.desc = "Find information for any anime!"; this.usage = " [| index]"; this.examples = Arrays.asList("clockwork planet", "chuunibyou | 2"); } @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { if(args.length < 1) { return new CommandResult(CommandResultType.INVALIDARGS, "Please enter an anime title."); } channel.sendTyping().queue(); String[] split = String.join(" ", args).split(" \\|"); String search = split[0]; int index = 0; try { if(split.length > 1) { index = Integer.parseInt(split[1].trim()) - 1; if(index < 1 || index > 10) throw new NumberFormatException(); } } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { channel.sendMessage("Invalid index inputted. Defaulting to first result...").queue(); } - JSONObject anime, info; - + JSONObject info; try { - JSONObject main = new JSONObject(new JSONTokener(new URL("https://api.jikan.moe/v3/search/anime?q=" + URLEncoder.encode(search, "UTF-8")).openStream())); - anime = main.getJSONArray("results").getJSONObject(index); - info = new JSONObject(new JSONTokener(new URL("https://api.jikan.moe/v3/anime/" + anime.getInt("mal_id")).openStream())); + JSONObject main = new JSONObject(new JSONTokener(new URL("https://api.jikan.moe/v4/anime?q=" + URLEncoder.encode(search, "UTF-8")).openStream())); + info = main.getJSONArray("data").getJSONObject(index); } catch (IOException e) { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } catch (JSONException e) { e.printStackTrace(); return new CommandResult(CommandResultType.INVALIDARGS, "There are not enough results for your specified index!"); } EmbedBuilder em = new EmbedBuilder(); - em.setTitle("Anime info for " + anime.getString("title") + " (" + anime.getString("type") + ")", anime.getString("url")); - em.setThumbnail(anime.getString("image_url")); + em.setTitle("Anime info for " + info.getString("title") + " (" + info.getString("type") + ")", info.getString("url")); + em.setThumbnail(info.getJSONObject("images").getJSONObject("jpg").getString("image_url")); em.setDescription("JP: " + info.getString("title_japanese") + "\n\n" + (info.isNull("synopsis") ? "No synopsis information has been added to this title." : info.getString("synopsis"))); - em.addField("Episodes", (anime.getInt("episodes") == 0 ? "Unknown" : String.valueOf(anime.getInt("episodes"))), true); - em.addField("Rating", anime.getDouble("score") == 0.0 ? "N/A" : String.valueOf(anime.getDouble("score")), true); + em.addField("Episodes", (info.getInt("episodes") == 0 ? "Unknown" : String.valueOf(info.getInt("episodes"))), true); + em.addField("Rating", info.getDouble("score") == 0.0 ? "N/A" : String.valueOf(info.getDouble("score")), true); em.addField("Airing status", info.getString("status"), true); String[] aired = info.getJSONObject("aired").getString("string").split("to"); em.addField("Premier season", info.isNull("premiered") ? "Unknown" : info.getString("premiered"), true); em.addField("Start date", aired[0], true); em.addField("End date", aired.length > 1 ? aired[1] : aired[0], true); ArrayList studios = new ArrayList(); - for(Object genre : info.getJSONArray("studios")) studios.add("[" + ((JSONObject) genre).getString("name") + "](" + ((JSONObject) genre).getString("url") + ")"); - String source = info.getJSONObject("related").has("Adaptation") ? - "[" + info.getString("source") + "](" + info.getJSONObject("related").getJSONArray("Adaptation").getJSONObject(0).getString("url") + ")" : info.getString("source"); - if(!source.contains("[Light novel]") && !source.contains("anga]")) source = source.replaceFirst("\\[(.*?)\\](.*)", "$1 ([Adaptation]$2)"); //something couldve adapted it if its original instead + for(Object genre : info.getJSONArray("studios")) studios.add("[" + ((JSONObject) genre).getString("name") + "](" + ((JSONObject) genre).getString("url") + ")"); + //can be obtained via /v4/{id}/relations, but seems like too small of an info to call the api again + //String source = info.getJSONObject("related").has("Adaptation") ? + // "[" + info.getString("source") + "](" + info.getJSONObject("related").getJSONArray("Adaptation").getJSONObject(0).getString("url") + ")" : info.getString("source"); + //if(!source.contains("[Light novel]") && !source.contains("anga]")) source = source.replaceFirst("\\[(.*?)\\](.*)", "$1 ([Adaptation]$2)"); //something couldve adapted it if its original instead em.addField("Studios", String.join(", ", studios), true); em.addField("PG rating", info.getString("rating"), true); - em.addField("Source", source, true); + em.addField("Source", info.getString("source"), true); + //em.addField("Source", source, true); ArrayList genres = new ArrayList(); for(Object genre : info.getJSONArray("genres")) genres.add(((JSONObject) genre).getString("name")); em.addField("Genre", String.join(", ", genres), false); em.setFooter(MiscUtils.ordinal(index + 1) + " result - Rank #" + (info.isNull("rank") ? "N/A" : info.getInt("rank")) + ", Popularity #" + (info.isNull("popularity") ? "N/A" : info.getInt("popularity")) + " | MyAnimeList.net", "https://cdn.myanimelist.net/img/sp/icon/apple-touch-icon-256.png"); em.setColor(new Color(46, 81, 162)); channel.sendMessageEmbeds(em.build()).queue(); return new CommandResult(CommandResultType.SUCCESS); } } diff --git a/src/me/despawningbone/discordbot/command/anime/AnimePic.java b/src/me/despawningbone/discordbot/command/anime/AnimePic.java index aa83411..b4abc77 100644 --- a/src/me/despawningbone/discordbot/command/anime/AnimePic.java +++ b/src/me/despawningbone/discordbot/command/anime/AnimePic.java @@ -1,108 +1,104 @@ package me.despawningbone.discordbot.command.anime; import java.io.IOException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom; import org.apache.commons.lang3.exception.ExceptionUtils; import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; 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.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.User; public class AnimePic extends Command { public AnimePic() { this.desc = "Get anime pics!"; this.usage = "[search words] [-f] [| index]"; this.remarks = Arrays.asList("Leave the search words blank for a random pic!", "If you have not specified the index, it will be a random result from the search.", "Include `-f` if you want to load the full picture!", "However, full pictures takes a while to load in."); this.alias = Arrays.asList("apic"); this.examples = Arrays.asList("ryuZU -f", "neptune | 2"); } //TODO migrate to another more popular image board? @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { Connection con = null; channel.sendTyping().queue(); //full res option boolean full = false; ArrayList amend = new ArrayList<>(Arrays.asList(args)); if(amend.contains("-f")) { amend.remove("-f"); full = true; } //random, use most popular last 3 months as order - should return better results if(amend.size() < 1) { con = Jsoup.connect("https://www.zerochan.net/?s=fav&t=2p=" + (ThreadLocalRandom.current().nextInt(1000) + 1)); } String stripped = String.join(" ", amend); String search; int index; if(stripped.contains("|")) { String[] split = stripped.split(" \\|"); search = split[0]; try { index = Integer.parseInt(split[1].trim()) - 1; } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { return new CommandResult(CommandResultType.INVALIDARGS, "Invalid index inputted"); } } else { search = stripped; index = -1; } try { if(con == null) con = Jsoup.connect("https://www.zerochan.net/search?q=" + URLEncoder.encode(search, "UTF-8")).ignoreHttpErrors(true).followRedirects(true); Document document = con.get(); Element href = document.body(); Elements resList = null; try { resList = href.select("ul[id=\"thumbs2\"]").get(0).select("li"); if (resList.size() == 0) throw new IndexOutOfBoundsException(); if (resList.size() < index) throw new IndexOutOfBoundsException(); } catch (IndexOutOfBoundsException e) { if(!href.select("ul[id=\"children\"]").isEmpty()) return new CommandResult(CommandResultType.FAILURE, "You need to specify your search words more!"); else return new CommandResult(CommandResultType.NORESULT); } Element a = resList.get(index == -1 ? ThreadLocalRandom.current().nextInt(resList.size()) : index).select("a").first(); if(a.hasAttr("rel")) { return new CommandResult(CommandResultType.FAILURE, "This picture is not public!"); } - EmbedBuilder eb = new EmbedBuilder(); eb.setTitle((index == -1 ? "Random" : MiscUtils.ordinal(index + 1)) + (search.isEmpty() ? " anime pic" : " pic for " + search), a.absUrl("href")); //fetch img - seems like discord shrinks the full image anyways nowadays, is -f really useful anymore lol - String imgName = a.nextElementSibling().child(0).attr("href").replace("+", "."); //spaces are represented as dots in the cdn for some reason - String imgPrefix = a.attr("href").substring(1); //remove prefix / - String img = full ? - "https://static.zerochan.net" + imgName + ".full." + imgPrefix + ".jpg": - "https://s1.zerochan.net" + imgName + ".600." + imgPrefix + ".jpg"; + String fullImgUrl = a.lastElementSibling().child(0).attr("href"); + String img = full ? fullImgUrl : fullImgUrl.replace("static.zerochan.net", "s1.zerochan.net").replace(".full.", ".600.").replace(".png", ".jpg"); eb.setImage(img); eb.setFooter(full ? "The image might need a while to load in." : "Include -f if you want the full resolution!", null); channel.sendMessageEmbeds(eb.build()).queue(); return new CommandResult(CommandResultType.SUCCESS); } catch (IOException e) { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } } } diff --git a/src/me/despawningbone/discordbot/command/anime/Waifu.java b/src/me/despawningbone/discordbot/command/anime/Waifu.java index 7bbdff6..8c2607c 100644 --- a/src/me/despawningbone/discordbot/command/anime/Waifu.java +++ b/src/me/despawningbone/discordbot/command/anime/Waifu.java @@ -1,186 +1,186 @@ package me.despawningbone.discordbot.command.anime; import java.awt.Color; import java.io.IOException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import org.apache.commons.lang3.exception.ExceptionUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.jsoup.Connection.Response; import org.jsoup.Jsoup; 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.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.User; public class Waifu extends Command { public Waifu() { this.desc = "Find information about your waifu/husbando!"; //, or leave it blank for a random one!"; this.usage = " [| index]"; this.examples = Arrays.asList("neptune", "ryuZU"); this.alias = Arrays.asList("husbando"); } @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { channel.sendTyping().queue(); String[] stripped = String.join(" ", args).split("\\|"); if(args.length < 1) return new CommandResult(CommandResultType.FAILURE, "Please enter something to search for!"); JSONObject main, props; try { //get required cookies //Mozilla/4.0 actually bypasses the need for CSRF, but breaks /api/search/all since it requires X-Requested-With (/browse?name= still works but doesn't handle multiple types at once) Response con = Jsoup.connect("https://mywaifulist.moe/").userAgent("Mozilla/5.0").execute(); String res = Jsoup.connect("https://mywaifulist.moe/api/search/all") //advancedsearch is no longer a thing .userAgent("Mozilla/5.0") .header("X-Requested-With", "XMLHttpRequest") .header("X-XSRF-TOKEN", URLDecoder.decode(con.cookie("XSRF-TOKEN"), "UTF-8")) //base64 might be encoded - if it is, then laravel will return 419 page expired .header("Content-Type", "application/json") //needed or the endpoint can't parse the query json .header("Referer", "https://mywaifulist.moe/") //needed to avoid internal server error for some reason .cookies(con.cookies()) .ignoreContentType(true) .requestBody("{\"query\":\"" + stripped[0].trim() + "\"}").post().text(); //check index int index = 0; JSONArray jarr = new JSONArray(new JSONTokener(res)); try { //System.out.println(arr.length()); if(stripped.length > 1) index = Integer.parseInt(stripped[1].trim()) - 1; if(jarr.length() <= index && index != 0) throw new NumberFormatException(); //if set index is out of bounds } catch (NumberFormatException e) { channel.sendMessage("Invalid index inputted. Defaulting to first result.").queue(); channel.sendTyping().queue(); index = 0; } //sort search results for fetching with specified index Pattern wholeWord = Pattern.compile("(?i).*\\b" + stripped[0].trim() + "\\b.*"); List arr = new ArrayList<>(); for(Object obj : jarr) arr.add((JSONObject) obj); arr = arr.stream().filter(o -> !o.isNull("entity_type") && (o.getString("entity_type").equalsIgnoreCase("waifu") || o.getString("entity_type").equalsIgnoreCase("husbando"))) //filter only characters .sorted((a, b) -> Integer.compare( //combine likes and trash to get popularity - sort by popularity since the search result sucks ass (b.has("likes") ? b.getInt("likes") : 0) + (b.has("trash") ? b.getInt("trash") : 0), (a.has("likes") ? a.getInt("likes") : 0) + (a.has("trash") ? a.getInt("trash") : 0))) .sorted((a, b) -> wholeWord.matcher(a.getString("name")).matches() && !wholeWord.matcher(b.getString("name")).matches() ? -1 : 0) //move whole word matches up only if last one was not matched .collect(Collectors.toList()); //fetch props = new JSONObject(new JSONTokener(Jsoup.connect("https://mywaifulist.moe/waifu/" + arr.get(index).getString("slug")) .userAgent("Mozilla/4.0") .followRedirects(true) .get().selectFirst("#app").attr("data-page"))).getJSONObject("props"); main = props.getJSONObject("waifu"); } catch (IndexOutOfBoundsException | JSONException e) { e.printStackTrace(); return new CommandResult(CommandResultType.NORESULT); } catch (IOException e) { if(e.toString().contains("Status=422")) return new CommandResult(CommandResultType.NORESULT); //search scope too broad(?) return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } EmbedBuilder em = new EmbedBuilder(); em.setColor(new Color(44, 62, 80)); em.setTitle((main.getBoolean("husbando") ? "Husbando" : "Waifu") + " info of " + main.getString("name"), "https://mywaifulist.moe/waifu/" + main.getString("slug")); //series JSONObject series = main.getJSONArray("appearances").getJSONObject(0); em.setAuthor("Series: " + series.getString("name"), "https://mywaifulist.moe/series/" + series.getString("slug")); if(!series.isNull("display_picture") && !series.getString("display_picture").isEmpty()) em.setThumbnail(series.getString("display_picture")); try { em.setDescription(main.getString("description").substring(0, Math.min(main.getString("description").length(), 2040)) + (main.getString("description").length() > 2040 ? "..." : "")); } catch (IllegalArgumentException e) { return new CommandResult(CommandResultType.TOOLONG); } em.setImage(main.getString("display_picture").replaceAll("\\\\", "")); //series appearance + series description ArrayList appearances = new ArrayList<>(); int totalName = 0; for(Object obj : main.getJSONArray("appearances")) { JSONObject jobj = ((JSONObject) obj); appearances.add(jobj); totalName += jobj.getString("name").length() + jobj.getString("slug").length() + 43; //magic number for giving enough leeway } final int avg = (1024 - totalName) / appearances.size(); //get max description average length em.addField("Appearances", appearances.stream().map(jobj -> "[" + jobj.getString("name") + "](https://mywaifulist.moe/series/" + jobj.getString("slug") + (!jobj.isNull("description") ? (" \"" + jobj.getString("description").substring(0, Math.min(jobj.getString("description").length(), avg)).replaceAll("\"", "”")) //trim desc to max length, replacing double quotes since it will interfere with markdown + (jobj.getString("description").length() > avg ? "..." : "") + "\")" : ")")) //append ... if its not finished (only if desc is non null will desc be printed) .collect(Collectors.joining(", ")), false); //aliases if(!main.isNull("original_name") && !main.getString("original_name").isEmpty()) em.addField("Also known as", main.getString("original_name") + (main.isNull("romaji_name") || main.getString("romaji_name").isEmpty() ? "" : ", " + main.getString("romaji_name")), false); em.addBlankField(false); //optionally existing info if (!main.isNull("origin")) em.addField("Origin", main.getString("origin"), true); if (!main.isNull("height")) em.addField("Height", String.valueOf(main.getDouble("height")), true); if (!main.isNull("weight")) em.addField("Weight", String.valueOf(main.getDouble("weight")), true); if (!main.isNull("bust")) em.addField("Bust", String.valueOf(main.getDouble("bust")), true); if (!main.isNull("hip")) em.addField("Hip", String.valueOf(main.getDouble("hip")), true); if (!main.isNull("waist")) em.addField("Waist", String.valueOf(main.getDouble("waist")), true); if (!main.isNull("blood_type")) em.addField("Blood type", main.getString("blood_type"), true); if (!main.isNull("birthday_day") && main.getInt("birthday_day") != 0 && !main.isNull("birthday_month") && !main.getString("birthday_month").isEmpty()) em.addField("Birthday", main.getString("birthday_month") + " " + String.valueOf(main.getInt("birthday_day")) + (main.isNull("birthday_year") || main.getString("birthday_year").isEmpty() ? "" : ", " + main.getString("birthday_year")), true); //only add blank on 5 fields for formatting if (em.getFields().size() == 5) { em.addBlankField(true); } //tags is back! (for some entries at least) String tags = props.getJSONArray("tags").toList().stream().map(o -> o.toString().replaceAll("\\{name=", "").replaceAll(", id=.*\\}", "")).collect(Collectors.joining(", ")); if(!tags.isEmpty()) em.addField("Tags", tags, false); //popularity stats if(em.getFields().size() > 2) em.addBlankField(false); em.addField("Likes", main.getInt("likes") + " (#" + (main.isNull("like_rank") ? "N/A" : main.getInt("like_rank")) + ")", true); em.addField("Popularity rank", "#" + (main.isNull("popularity_rank") ? "N/A" : main.getInt("popularity_rank")), true); em.addField("Trash", main.getInt("trash") + " (#" + (main.isNull("trash_rank") ? "N/A" : main.getInt("trash_rank")) + ")", true); - //creator name no longer exists - em.setFooter("Created at " + DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneId.of("Z")).format(Instant.parse(main.getString("created_at"))) + " | MyWaifuList.moe", null); + //creator name is back, created time is gone however + em.setFooter("Created by " + main.getJSONObject("creator").getString("name") + " | MyWaifuList.moe", null); channel.sendMessageEmbeds(em.build()).queue(); return new CommandResult(CommandResultType.SUCCESS); } } \ No newline at end of file diff --git a/src/me/despawningbone/discordbot/command/info/Calculator.java b/src/me/despawningbone/discordbot/command/info/Calculator.java index d92bb42..a57527d 100644 --- a/src/me/despawningbone/discordbot/command/info/Calculator.java +++ b/src/me/despawningbone/discordbot/command/info/Calculator.java @@ -1,230 +1,242 @@ package me.despawningbone.discordbot.command.info; import java.awt.Color; import java.awt.Graphics; import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import java.util.EmptyStackException; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.math3.special.Gamma; import org.json.JSONObject; import org.json.JSONTokener; import com.neovisionaries.ws.client.WebSocket; import com.neovisionaries.ws.client.WebSocketAdapter; import com.neovisionaries.ws.client.WebSocketException; import com.neovisionaries.ws.client.WebSocketFactory; 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.channel.concrete.TextChannel; import net.dv8tion.jda.api.utils.FileUpload; import net.dv8tion.jda.api.entities.User; import net.objecthunter.exp4j.Expression; import net.objecthunter.exp4j.ExpressionBuilder; import net.objecthunter.exp4j.function.Function; import net.objecthunter.exp4j.operator.Operator; public class Calculator extends Command { public Calculator() { this.alias = Arrays.asList("calc"); this.desc = "Do some calculations!"; this.usage = "[-w] "; this.remarks = Arrays.asList("This calculator is using exp4j.", "You can get the built in operators and functions at:", "http://projects.congrace.de/exp4j/", "`!` - factorials", "`logx(base, num)` - logarithm (base x)", "are supported too.", "\nYou can also query [Wolfram Alpha](https://www.wolframalpha.com/) using the `-w` switch."); this.examples = Arrays.asList("3*4-2", "4 * (sin(3 - 5)) + 5!", "log(e) + logx(10, 100)", " -w laurent series log(x) about x=0"); } static final double planckConstant = 6.62607004 * Math.pow(10, -34), eulerMascheroni = 0.57721566490153286060651209008240243104215933593992; static final Function logb = new Function("logx", 2) { @Override public double apply(double... args) { return Math.log(args[1]) / Math.log(args[0]); } }; static final Function digamma = new Function("digamma", 1) { @Override public double apply(double... args) { return Gamma.digamma(args[0]); } }; static final Operator factorial = new Operator("!", 2, true, Operator.PRECEDENCE_POWER + 1) { @Override public double apply(double... args) { final long arg = (long) args[0]; if ((double) arg != args[0]) { throw new IllegalArgumentException("Operand for factorial has to be an integer"); } if (arg < 0) { throw new IllegalArgumentException("The operand of the factorial can not " + "be " + "less than zero"); } double result = 1; for (int i = 1; i <= arg; i++) { result *= i; } return result; } }; @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { List params = new ArrayList<>(Arrays.asList(args)); if(params.removeAll(Collections.singleton("-w"))) { return queryWolfram(String.join(" ", params), channel); } else { if (args.length < 1) { return new CommandResult(CommandResultType.INVALIDARGS, "Please enter an operation."); } String splitted = String.join(" ", args); //easter eggs if (msg.getContentDisplay().toLowerCase().contains("the meaning of life, the universe, and everything")) { // easter egg channel.sendMessage("The answer is: `42`").queue(); return new CommandResult(CommandResultType.SUCCESS, "Executed easter egg"); } if (splitted.equals("9+10") || splitted.equals("9 + 10")) { // easter egg channel.sendMessage("The answer is: `21`\n\n *i am smart ;)*").queue(); return new CommandResult(CommandResultType.SUCCESS, "Executed easter egg"); } if (splitted.equals("666") || splitted.equals("333")) { // easter egg channel.sendMessage("No you don't").queue(); return new CommandResult(CommandResultType.SUCCESS, "Executed easter egg"); } //patch for factorial formatting String operation = String.join("", args); if (operation.contains("!")) { int index = operation.indexOf("!"); while (index >= 0) { // indexOf returns -1 if no match found operation = new StringBuilder(operation).insert(index + 1, "(1)").toString(); index = operation.indexOf("!", index + 1); } } // DecimalFormat format = new DecimalFormat(); // format.setDecimalSeparatorAlwaysShown(false); // System.out.println(operation); String ans = null; try { Expression e = new ExpressionBuilder(operation).variable("h").variable("γ").function(digamma).function(logb).operator(factorial).build() .setVariable("h", planckConstant).setVariable("γ", eulerMascheroni); ans = Double.toString(e.evaluate()); // String ans = format.format(e.evaluate()); } catch (EmptyStackException e1) { return new CommandResult(CommandResultType.INVALIDARGS, "You have imbalanced parentheses."); } catch (ArithmeticException e1) { return new CommandResult(CommandResultType.INVALIDARGS, "You cannot divide by zero."); } catch (IllegalArgumentException e1) { return new CommandResult(CommandResultType.FAILURE, "An error has occured: " + e1.getMessage()); } if (ans.equals("NaN")) { ans = "Undefined"; } channel.sendMessage("The answer is: `" + ans + "`").queue(); return new CommandResult(CommandResultType.SUCCESS); } } public CommandResult queryWolfram(String operation, TextChannel channel) { channel.sendTyping().queue(); try { CompletableFuture result = new CompletableFuture<>(); - WebSocket socket = new WebSocketFactory().createSocket("wss://www.wolframalpha.com/n/v1/api/fetcher/results"); + WebSocket socket = new WebSocketFactory().createSocket("wss://gateway.wolframalpha.com/gateway"); ArrayList pos = new ArrayList<>(); //prevent duplicate socket.addListener(new WebSocketAdapter() { @Override public void onTextMessage(WebSocket websocket, String message) { try { //System.out.println(message); JSONObject resp = new JSONObject(new JSONTokener(message)); switch(resp.getString("type")) { case "pods": for(Object obj : resp.getJSONArray("pods")) { //pods might have multiple values JSONObject pod = (JSONObject) obj; if(pod.getBoolean("error")) continue; //skip errors if(!pos.contains(pod.getInt("position"))) { //check dupe //build big image for each pods ArrayList images = new ArrayList<>(); int width = 0, height = 0; if(!pod.has("subpods")) continue; //ignore empty pods that doesnt have image, usually fixed somewhere else for(Object subobj : pod.getJSONArray("subpods")) { JSONObject subpodImg = ((JSONObject) subobj).getJSONObject("img"); - images.add(ImageIO.read(new URL(subpodImg.getString("src")))); + + ImageReader reader = ImageIO.getImageReadersByMIMEType(subpodImg.getString("contenttype")).next(); + try (ImageInputStream stream = ImageIO.createImageInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(subpodImg.getString("data"))))) { + reader.setInput(stream); + BufferedImage image = reader.read(0); + images.add(image); + } finally { + reader.dispose(); + } if(subpodImg.getInt("width") > width) width = subpodImg.getInt("width"); //get widest image and use it as final width height += subpodImg.getInt("height"); //add all images } //create final image BufferedImage podImg = new BufferedImage(width + 20, height + 20, BufferedImage.TYPE_INT_RGB); //padding Graphics g = podImg.getGraphics(); g.setColor(new Color(255, 255, 255)); //fill as white first g.fillRect(0, 0, width + 20, height + 20); int y = 10; for(BufferedImage img : images) { g.drawImage(img, 10, y, null); y += img.getHeight(); } //send each pod as an individual message ByteArrayOutputStream os = new ByteArrayOutputStream(); ImageIO.write(podImg, "png", os); channel.sendMessage(pod.getString("title") + ":") .addFiles(FileUpload.fromData(os.toByteArray(), "result.png")).queue(); pos.add(pod.getInt("position")); //update to prevent dupes } } break; case "futureTopic": result.complete(new CommandResult(CommandResultType.FAILURE, "This query was identified under the topic of `" + resp.getJSONObject("futureTopic").getString("topic") + "`; Development of this topic is under investigation...")); break; case "didyoumean": result.complete(new CommandResult(CommandResultType.INVALIDARGS, "No good results found :cry:\nDid you mean `" + resp.getJSONArray("didyoumean").getJSONObject(0).getString("val") + "`?")); break; case "noResult": result.complete(new CommandResult(CommandResultType.NORESULT)); break; case "queryComplete": //might provide no output for certain queries, but theres no practical way to detect with this listener rn websocket.disconnect(); result.complete(new CommandResult(CommandResultType.SUCCESS)); //if its preceded by anything it wouldnt update; thats how complete() works break; } } catch(Exception e) { result.complete(new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e))); } } }); - //System.out.println("{\"type\":\"init\",\"lang\":\"en\",\"exp\":" + System.currentTimeMillis() + ",\"displayDebuggingInfo\":false,\"messages\":[{\"type\":\"newQuery\",\"locationId\":\"hipuj\",\"language\":\"en\",\"displayDebuggingInfo\":false,\"yellowIsError\":false,\"input\":\"" + operation + "\",\"assumption\":[],\"file\":null}],\"input\":\"" + operation + "\",\"assumption\":[],\"file\":null}"); socket.connect(); - socket.sendText("{\"type\":\"init\",\"lang\":\"en\",\"exp\":" + System.currentTimeMillis() + ",\"displayDebuggingInfo\":false,\"messages\":[{\"type\":\"newQuery\",\"locationId\":\"hipuj\",\"language\":\"en\",\"displayDebuggingInfo\":false,\"yellowIsError\":false,\"input\":\"" + operation + "\",\"assumption\":[],\"file\":null}],\"input\":\"" + operation + "\",\"assumption\":[],\"file\":null}"); + socket.sendText("{\"category\":\"results\",\"type\":\"init\",\"lang\":\"en\",\"exp\":" + System.currentTimeMillis() + ",\"displayDebuggingInfo\":false,\"messages\":[]}"); + socket.sendText("{\"type\":\"newQuery\",\"locationId\":\"/input\",\"language\":\"en\",\"displayDebuggingInfo\":false,\"yellowIsError\":false,\"requestSidebarAd\":true,\"category\":\"results\",\"input\":\"" + operation + "\",\"i2d\":false,\"assumption\":[],\"apiParams\":{},\"file\":null,\"theme\":\"light\"}"); return result.get(); } catch (IOException | WebSocketException | InterruptedException | ExecutionException e) { return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); } } } diff --git a/src/me/despawningbone/discordbot/command/info/CityInfo.java b/src/me/despawningbone/discordbot/command/info/CityInfo.java index 3f66324..7de5d37 100644 --- a/src/me/despawningbone/discordbot/command/info/CityInfo.java +++ b/src/me/despawningbone/discordbot/command/info/CityInfo.java @@ -1,149 +1,147 @@ 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.Duration; +import java.time.Instant; +import java.time.LocalDateTime; import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; 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.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.jsoup.Connection.Response; import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; 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.channel.concrete.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 = "
"; this.examples = Arrays.asList("hong kong", "tokyo"); //"HK", "akihabara"); + + String[] countryCodes = Locale.getISOCountries(); + for (String cc : countryCodes) { + countries.put(new Locale("", cc).getDisplayCountry(), cc.toUpperCase()); + } } + private HashMap countries = new HashMap(); + NumberFormat formatter = new DecimalFormat("#0.00"); @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 { - boolean hasWeather = true; - JSONObject info = null; - String wQualifiedName = "", woeid = "", lng = "", lat = "", region = "", countryShort = ""; TimeZone timezone = null; - try { - //www.yahoo.com changed its endpoint - there's no longer a AJAX API for weather info, and the search autocomplete is basically just a prefix search which is way inferior; so we use ca.news.yahoo.com instead - URLConnection sCon = new URL("https://ca.news.yahoo.com/_td/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 = wQualifiedName.substring(wQualifiedName.lastIndexOf(",") + 1); //the display name from yahoo is not always consistent with Java's Locale display name, so we extract from qualified name instead - 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 - - //get cookies and crumb for WeatherService - Response mainCon = Jsoup.connect("https://ca.news.yahoo.com/weather").execute(); - String mainPage = mainCon.body(); - int mainData = mainPage.indexOf("root.App.main = "); - String crumb = new JSONObject(mainPage.substring(mainData + 16, mainPage.indexOf(";\n", mainData))).getJSONObject("context").getJSONObject("dispatcher").getJSONObject("stores").getJSONObject("WeatherStore").getString("crumb"); - - info = new JSONObject(new JSONTokener( - Jsoup.connect("https://ca.news.yahoo.com/_td/api/resource/WeatherService;crumb=" + URLEncoder.encode(crumb, "UTF-8") + ";woeids=[" + woeid + "]?lang=en-US&returnMeta=true") - .cookies(mainCon.cookies()) - .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0") - .ignoreContentType(true) - .execute().bodyStream() - )).getJSONObject("data").getJSONArray("weathers").getJSONObject(0); - - hasWeather = info.getJSONObject("observation").getJSONObject("temperature").length() != 0; - } catch(IOException e) { - return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e)); - } catch(JSONException e) { - e.printStackTrace(); - return new CommandResult(CommandResultType.NORESULT); - //hasWeather = false; - } - - Date date = new Date(); - //System.out.println(info); + //www.yahoo.com changed its endpoint - there's no longer a AJAX API for weather info, and the search autocomplete is basically just a prefix search which is way inferior; so we use ca.news.yahoo.com instead + URLConnection sCon = new URL("https://ca.news.yahoo.com/tdv2_fp/api/resource/WeatherLocationService.autocomplete?text=" + URLEncoder.encode(sword, "UTF-8")).openConnection(); + sCon.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0"); + + JSONArray res = new JSONArray(new JSONTokener(sCon.getInputStream())); + + if(res.length() < 1) return new CommandResult(CommandResultType.NORESULT); + + JSONObject wsearch = res.getJSONObject(0); + String woeid = String.valueOf(wsearch.getInt("woeid")); + String lat = String.valueOf(wsearch.getDouble("lat")); + String lng = String.valueOf(wsearch.getDouble("lon")); + String wQualifiedName = wsearch.getString("qualifiedName"); + String countryShort = wsearch.getString("country"); + String region = wsearch.getString("state").isEmpty() ? countryShort : wsearch.getString("state"); + + //original API no longer returns results, seems like all of the current info is generated server side so do some parsing instead + Document mainCon = Jsoup.connect("https://ca.news.yahoo.com/weather/" + wsearch.getString("country") + "/" + (wsearch.getString("state").isEmpty() ? wsearch.getString("country") : wsearch.getString("state")) + "/" + wsearch.getString("city") + "-" + woeid).get(); + int timeOffset = (int) Math.round(Duration.between(Instant.now(), Instant.from(DateTimeFormatter.ofPattern("yyyy M-d, h:m a X").parse(OffsetDateTime.now().getYear() + " " + mainCon.selectFirst("#module-location-heading time").text().replace(".", "").toUpperCase() + " Z"))).getSeconds() / 60.0 / 60.0); + String tempNow = mainCon.selectFirst(".temperature-forecast .celsius").text(); + //not sure why ~= doesnt work in jsoup but works in normal browsers + String tempHigh = mainCon.selectFirst("#module-location-heading .arrowUp ~ span[class*=\"celsius_D(b)\"]").text(); + String tempLow = mainCon.selectFirst("#module-location-heading .arrowDown ~ span[class*=\"celsius_D(b)\"]").text(); + + String humidity = mainCon.selectFirst("#module-weather-details dt:contains(Humidity)").nextElementSibling().text(); + String precipitationProb = mainCon.select(".hourlyForecast .precipitation dt").get(1).text(); + String visibility = mainCon.selectFirst("#module-weather-details dt:contains(Visibility) ~ dd[class*=\"kilometers_D(b)\"]").text(); + String conditionDesc = mainCon.selectFirst("#module-location-heading p").text(); + String feelsLike = mainCon.selectFirst("#module-weather-details dt:contains(RealFeel®) ~ dd[class*=\"celsius_D(b)\"]").text(); + String uv = mainCon.selectFirst("#module-weather-details dt:contains(UV Index)").nextElementSibling().text(); + + String pressure = mainCon.selectFirst("#module-weather-wind-pressure dt:contains(Barometer) ~ dd[class*=\"celsius_D(b)\"]").text(); + String[] wind = mainCon.selectFirst("#module-weather-wind-pressure dt:contains(Wind) ~ dd[class*=\"kilometers_D(b)\"]").text().split(" "); + String windSpeed = wind[0] + " " + wind[1]; + String windDir = wind[2]; + Elements sun = mainCon.select("#module-weather-sun-moon time"); + String sunrise = sun.get(0).text(); + String sunset = sun.get(1).text(); + + String imgUrl = mainCon.selectFirst("picture img").attr("data-wf-src"); + + EmbedBuilder embedmsg = new EmbedBuilder(); embedmsg.setAuthor("Info for " + wQualifiedName, null, null); embedmsg.setColor(new Color(100, 0, 255)); - embedmsg.addField("Country", MiscUtils.countryNameToUnicode(countryShort), true); + embedmsg.addField("Country", countries.containsKey(countryShort) ? MiscUtils.countryNameToUnicode(countries.get(countryShort)) : 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("Current time", OffsetDateTime.now(ZoneOffset.ofHours(timeOffset)).format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ssa").withLocale(Locale.ENGLISH)).trim() + " (UTC" + (Math.signum(timeOffset) == 1 || Math.signum(timeOffset) == 0 ? "+" + timeOffset : timeOffset) + ")", true); + + embedmsg.addField("Temperature", tempNow + "°C (↑" + tempHigh + "C | ↓" + tempLow + "C)", true); + embedmsg.addField("Feels Like", feelsLike + "C", true); + embedmsg.addField("Humidity", humidity + " (Chance of rain: " + precipitationProb + ")", true); + embedmsg.addField("Atmospheric pressure", pressure, true); + embedmsg.addField("Wind speed", windSpeed, true); + embedmsg.addField("Wind direction", windDir, true); + embedmsg.addField("UV index", uv, true); + embedmsg.addField("Sunrise", sunrise, true); + embedmsg.addField("Sunset", sunset, true); + embedmsg.addField("Visibility", visibility + " (" + conditionDesc + ")", true); + embedmsg.setThumbnail(imgUrl); + embedmsg.addField("Latitude", lat, true); embedmsg.addField("Longitude", lng, true); - embedmsg.setFooter(footer, null); + //no weather info update time anymore, just credit accuweather ig + embedmsg.setFooter("Powered by Accuweather / Yahoo", null); try { MessageEmbed fmsg = embedmsg.build(); channel.sendMessageEmbeds(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/Translate.java b/src/me/despawningbone/discordbot/command/info/Translate.java index 99ef812..73dff54 100644 --- a/src/me/despawningbone/discordbot/command/info/Translate.java +++ b/src/me/despawningbone/discordbot/command/info/Translate.java @@ -1,117 +1,101 @@ package me.despawningbone.discordbot.command.info; import java.io.IOException; import java.time.OffsetDateTime; import java.util.Arrays; +import java.util.List; 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.channel.concrete.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.remarks = Arrays.asList("Supported Languages: `" + String.join(", ", this.supportedLangs) + "`"); this.examples = Arrays.asList("-ja あなたも文化人だと思います", "-es despacito"); this.alias = Arrays.asList("tl"); - - executor.scheduleAtFixedRate(() -> refresh(), 0, 300, TimeUnit.SECONDS); //5 mins refresh } + //TODO could make this dynamically fetched, but need to parse from net + private List supportedLangs = Arrays.asList("ar", "bg", "zh", "cs", "da", "nl", "en", "et", "fi", "fr", "de", "el", "hu", "id", "it", "ja", "ko", "lv", "lt", "nb", "pl", "pt", "ro", "ru", "sk", "sl", "es", "sv", "tr", "uk"); 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())) { + if(supportedLangs.contains(args[0].substring(1).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())) { + if(supportedLangs.contains(args[args.length - 1].substring(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?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.sendMessageEmbeds(eb.build()).queue(); return new CommandResult(CommandResultType.SUCCESS); } catch(IOException e) { + e.printStackTrace(); 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&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"); - } catch (IOException e) { - e.printStackTrace(); - id--; //set it back down one - } - } } diff --git a/src/me/despawningbone/discordbot/command/info/UrbanDictionary.java b/src/me/despawningbone/discordbot/command/info/UrbanDictionary.java index 0bb336d..c42c6a1 100644 --- a/src/me/despawningbone/discordbot/command/info/UrbanDictionary.java +++ b/src/me/despawningbone/discordbot/command/info/UrbanDictionary.java @@ -1,182 +1,182 @@ package me.despawningbone.discordbot.command.info; import java.awt.Color; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.apache.commons.lang3.StringEscapeUtils; import org.json.JSONArray; 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.utils.messages.MessageCreateBuilder; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.exceptions.PermissionException; @SuppressWarnings("deprecation") public class UrbanDictionary extends Command { public UrbanDictionary() { this.desc = "Ask the best dictionary in the world!"; this.alias = Arrays.asList("urban", "ud", "u"); this.usage = " [|index]"; this.examples = Arrays.asList("life", "life | 2"); } @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { if (args.length < 1) { return new CommandResult(CommandResultType.INVALIDARGS, "You can't search nothing you dumbo :stuck_out_tongue:"); } String search; String content = String.join(" ", args); JSONTokener result = null; // System.out.println(content); //debug boolean nonum = false; boolean error = false; int num = 0; try { try { num = Integer.valueOf(content.split(" \\| ")[1]); } catch (NumberFormatException e) { error = true; } if (num < 1 || error) { channel.sendMessage("Invalid number entered. The bot will default to the top definition.").queue(); num = 0; } else { num = num - 1; } } catch (ArrayIndexOutOfBoundsException e) { nonum = true; } String ss = null; if (nonum) { ss = content; } else { ss = content.split(" \\| ", 2)[0]; } try { search = URLEncoder.encode(ss, "UTF-8"); try { - InputStream input = new URL("http://api.urbandictionary.com/v0/define?term=" + search).openStream(); + InputStream input = new URL("https://api.urbandictionary.com/v0/define?term=" + search).openStream(); result = new JSONTokener(new InputStreamReader(input, "UTF-8")); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } if(result == null) return new CommandResult(CommandResultType.FAILURE, "The service is not available right now!"); JSONObject mainobj = new JSONObject(result); // System.out.println(mainobj.toString()); //debug JSONArray choice = mainobj.getJSONArray("list"); if (choice.length() < 1) { return new CommandResult(CommandResultType.NORESULT); } // System.out.println(choice.toString()); //debug List list = new ArrayList(); for (int i = 0; i < choice.length(); i++) { list.add(choice.getJSONObject(i)); } Collections.sort(list, new Comparator() { public int compare(JSONObject a, JSONObject b) { int valA = 0; int valB = 0; valA = a.getInt("thumbs_up") - a.getInt("thumbs_down"); valB = b.getInt("thumbs_up") - b.getInt("thumbs_down"); return Integer.compare(valB, valA); } }); JSONArray sortedchoice = new JSONArray(list); JSONObject r; try { r = (JSONObject) sortedchoice.get(num); } catch (JSONException e) { channel.sendMessage("The number you've entered is too large. The bot will default to the top definition.") .queue(); num = 0; r = (JSONObject) sortedchoice.get(num); } // JSONObject r = (JSONObject) choice.get(num); String def = StringEscapeUtils.unescapeJava(r.getString("definition")); String tlink = StringEscapeUtils.unescapeJava(r.getString("permalink")); String eg = StringEscapeUtils.unescapeJava(r.getString("example")); String word = StringEscapeUtils.unescapeJava(r.getString("word")); String a = StringEscapeUtils.unescapeJava(r.getString("author")); BigInteger tu = r.getBigInteger("thumbs_up"); BigInteger td = r.getBigInteger("thumbs_down"); // String link = tlink.split("/", 4)[0] + "/" + tlink.split("/", 4)[1] + // "/" + tlink.split("/", 4)[2] + "/"; EmbedBuilder e = new EmbedBuilder(); if (num == 0) { e.setAuthor(word + " (Top definition)\n\n", tlink, null); } else { e.setAuthor(word + " (" + MiscUtils.ordinal(num + 1) + " definition)\n", tlink, null); } // e.setThumbnail("https://is4-ssl.mzstatic.com/image/thumb/Purple71/v4/71/5a/45/715a450b-b0af-0912-c0a6-208fd92b559b/source/256x256bb.jpg"); e.setColor(new Color(0, 0, 255)); try { e.setDescription(def); //e.addField("\nDefinition", def, false); if (!eg.isEmpty()) { e.addField("Example:", "```" + eg + "```", false); } e.addField("👍 ", String.valueOf(tu), true); e.addField("👎", String.valueOf(td), true); e.setFooter("Author: " + a, null); MessageEmbed em = e.build(); channel.sendMessageEmbeds(em).queue(); } catch (PermissionException | IllegalArgumentException e1) { //this shouldnt happen anymore, since description limit is the same as a normal message MessageCreateBuilder smsg = new MessageCreateBuilder(); if (num == 0) { smsg.addContent("**" + word + "** (Top definition)\n\n"); } else { smsg.addContent("**" + word + "** (" + MiscUtils.ordinal(num + 1) + " definition)\n\n"); } smsg.addContent(def); if (!eg.isEmpty()) { smsg.addContent("```\n" + eg + "```\n"); } else { smsg.addContent("\n\n"); } smsg.addContent("<" + tlink + ">\n\n"); smsg.addContent("Author: `" + a + "`\n"); smsg.addContent("👍 " + tu + " 👎 " + td); MessageCreateData fmsg = null; try { fmsg = smsg.build(); } catch (IllegalStateException e2) { return new CommandResult(CommandResultType.TOOLONG); } channel.sendMessage(fmsg).queue(); } return new CommandResult(CommandResultType.SUCCESS); } } diff --git a/src/me/despawningbone/discordbot/command/misc/Roll.java b/src/me/despawningbone/discordbot/command/misc/Roll.java index 1005633..cb205cf 100644 --- a/src/me/despawningbone/discordbot/command/misc/Roll.java +++ b/src/me/despawningbone/discordbot/command/misc/Roll.java @@ -1,100 +1,98 @@ package me.despawningbone.discordbot.command.misc; import java.util.Arrays; import java.util.Random; 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.utils.messages.MessageCreateBuilder; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.User; public class Roll extends Command { public Roll() { this.desc = "Roll a dice!"; this.usage = "[int][-int]"; this.remarks = Arrays.asList("Possible parameter combinations are as follows:\n" + DiscordBot.prefix + "roll - this will return a value within normal dice range.\n" + DiscordBot.prefix + "roll [intbound]\n - this will return a value from 0 to the bound.\n" + DiscordBot.prefix + "roll [intmin]-[intmax] - this will return a value within the range.\n"); this.examples = Arrays.asList("", "100", "100-1000"); } @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { Random randomno = new Random(); MessageCreateBuilder smsg = new MessageCreateBuilder(); int num = 6; int range = 0; boolean no = false; if (args.length > 0) { String p1 = args[0]; int n = 0; try { n = Integer.parseInt(p1); } catch (NumberFormatException e) { if (p1.equals("yourself")) { // easter egg smsg.addContent("*rolls*"); no = true; n = 1; } else if (p1.contains("-")) { try { range = Integer.parseInt(p1.split("-")[0]); n = Integer.parseInt(p1.split("-")[1]); } catch (NumberFormatException e1) { smsg.addContent("Please enter a correct range.\n"); no = true; } } else { smsg.addContent("Please enter a correct number.\n"); no = true; n = 1; } } if (n > 100000000) { smsg.addContent("The number entered is too large.\n"); no = true; } if (n < 1 && !no) { smsg.addContent("No number smaller than 1, please.\n"); no = true; } if (!no) { num = n; if (num < range) { int buffer = num; num = range; range = buffer; } int dice = randomno.nextInt((num - range) + 1) + range; String ID = author.getId(); smsg.addContent("<@!" + ID + ">" + " ,Your roll is: "); smsg.addContent(String.valueOf("`" + dice + "`" + "!")); MessageCreateData out = smsg.build(); channel.sendMessage(out).queue(); return new CommandResult(CommandResultType.SUCCESS); } else { - MessageCreateData out = smsg.build(); - channel.sendMessage(out).queue(); - return new CommandResult(CommandResultType.FAILURE, smsg.toString()); + return new CommandResult(CommandResultType.FAILURE, smsg.getContent()); } } else { int dice = 0; if (range == 0) { dice = randomno.nextInt(num) + 1; } else { dice = randomno.nextInt((num - range) + 1) + range; } String ID = author.getId(); smsg.addContent("<@!" + ID + ">" + " ,Your roll is: "); smsg.addContent(String.valueOf("`" + dice + "`" + "!")); MessageCreateData out = smsg.build(); channel.sendMessage(out).queue(); return new CommandResult(CommandResultType.SUCCESS); } } }