Page MenuHomedesp's stash

No OneTemporary

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<Attachment> att = event.getMessage().getAttachments();
if (!att.isEmpty()) {
payload += System.lineSeparator() + " Attachments:";
for (int i = 0; i < att.size(); i++) {
payload += System.lineSeparator() + " " + att.get(i).getUrl();
}
}
DiscordBot.logger.trace(payload); //TODO log embeds and add user id too?
}
//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 = "<name> [| 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<String> studios = new ArrayList<String>();
- 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<String> genres = new ArrayList<String>();
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<String> 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 = "<name> [| 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<JSONObject> 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<JSONObject> 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] <operation>";
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<String> 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<CommandResult> 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<Integer> 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<BufferedImage> 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 = "<address/search words>";
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<String, String> countries = new HashMap<String, String>();
+
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] <sentence> [-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<String> 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 = "<words> [|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<JSONObject> list = new ArrayList<JSONObject>();
for (int i = 0; i < choice.length(); i++) {
list.add(choice.getJSONObject(i));
}
Collections.sort(list, new Comparator<JSONObject>() {
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);
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 14, 4:58 PM (11 h, 58 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
82/36/f32318b84b12e22a6e4d1011e1f5

Event Timeline