diff --git a/src/me/despawningbone/discordbot/command/anime/Sauce.java b/src/me/despawningbone/discordbot/command/anime/Sauce.java index 3ce28ff..95035f4 100644 --- a/src/me/despawningbone/discordbot/command/anime/Sauce.java +++ b/src/me/despawningbone/discordbot/command/anime/Sauce.java @@ -1,168 +1,169 @@ package me.despawningbone.discordbot.command.anime; import java.awt.Color; import java.io.IOException; +import java.net.ConnectException; import java.net.MalformedURLException; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.exception.ExceptionUtils; import org.jsoup.Jsoup; 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 net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.User; public class Sauce extends Command{ public Sauce() { this.desc = "Get the source of an anime pic!"; this.usage = "[-d] [imgurl]"; this.alias = Arrays.asList("source", "saucenao", "iqdb"); this.remarks = Arrays.asList("The URL should be a direct link to an image.", "You can also upload an image as an attachment while calling this command instead of using a url.", " * Specify the `-d` parameter to do a depth search!", " * It is useful for cropped images and edited images, but makes the search much longer."); } @Override public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) { //allow people to not input url to check sauce of the most recent image like u/2dgt3d? List amend = new ArrayList(Arrays.asList(args)); int temp = amend.indexOf("-d"); if(temp != -1) amend.subList(temp, temp + 1).clear(); String url = null; try { if(amend.size() < 1) { if(msg.getAttachments().size() > 0) url = msg.getAttachments().get(0).getUrl(); else throw new MalformedURLException(); } else { url = amend.get(0).trim(); new URL(url); } } catch(MalformedURLException e) { return new CommandResult(CommandResultType.FAILURE, "Please enter a valid URL!"); } System.out.println(url); try { channel.sendTyping().queueAfter(20, TimeUnit.MILLISECONDS); if(temp != -1) { channel.sendMessage("Performing depth search for the picture... (this can take up to 20 seconds)").queue(); String[] urls = yandexSearch(url); System.out.println(Arrays.asList(urls)); url = urls[2] == null ? urls[0] : urls[1] + ";" + urls[2]; //sync //List collect = Arrays.asList(tempSearchSauce(urls[0]),tempSearchSauce(urls[1])); //async CompletableFuture first = CompletableFuture.supplyAsync(() -> {try {return tempSearchSauce(urls[0]);} catch (IOException e){e.printStackTrace(); return null;}}); CompletableFuture ratio = CompletableFuture.supplyAsync(() -> {try {return tempSearchSauce(urls[1]);} catch (IOException e){e.printStackTrace(); return null;}}); List collect = CompletableFuture.allOf(first, ratio) .thenApply(future -> Arrays.asList(first.join(), ratio.join())) .whenComplete((s, t) -> {if(!(ratio.join() != null && first.join() != null) && t != null) throw new CompletionException(t.getCause());}).get(); channel.sendMessage(collect.stream().filter(e -> e != null).sorted((a, b) -> urls[2] == null ? -1 : Double.compare(Double.parseDouble(b.build().getFooter().getText().replaceAll(".*?([.0-9]*%).*", "$1").replace("%", "")), Double.parseDouble(a.build().getFooter().getText().replaceAll(".*?([.0-9]*%).*", "$1").replace("%", "")))).findFirst().get().build()).queue(); } else { EmbedBuilder eb = tempSearchSauce(url); channel.sendMessage(eb.build()).queue(); } } catch (IOException | InterruptedException | ExecutionException e) { Throwable t = e instanceof ExecutionException ? e.getCause() : e; return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(t)); } catch (NullPointerException | NoSuchElementException e) { if(e instanceof NoSuchElementException) { EmbedBuilder eb = new EmbedBuilder(); String[] urls = url.split(";"); eb.setThumbnail(urls[0]); eb.setTitle("Possibly related image", urls[0]); eb.setDescription("Maybe [this](" + urls[urls.length > 1 ? 1 : 0] + ") will help in your search for the delicious marinara?"); channel.sendMessage(eb.build()).queueAfter(20, TimeUnit.MILLISECONDS); } return new CommandResult(CommandResultType.NORESULT); } return new CommandResult(CommandResultType.SUCCESS); } private String[] yandexSearch(String url) throws IOException { Element yandex = Jsoup.connect("https://yandex.com/images/search?url=" + URLEncoder.encode(url, "UTF-8") + "&rpt=imageview").get().body(); String[] size = new String[]{yandex.selectFirst(".CbirPreview-Placeholder").attr("width"), yandex.selectFirst(".CbirPreview-Placeholder").attr("height")}; //double ratio = Double.parseDouble(size[0]) / Double.parseDouble(size[1]) >= 1 ? 16.0/9 : 9/16.0; double ratio = Double.parseDouble(size[0]) / Double.parseDouble(size[1]); - System.out.println(yandex.select(".other-sites__thumb")); - System.out.println(size[0] + " " + size[1] + " " + ratio); + //System.out.println(yandex.select(".other-sites__thumb")); + //System.out.println(size[0] + " " + size[1] + " " + ratio); String other, site = null, similar = URLDecoder.decode(yandex.select(".cbir-similar__thumb .cbir-similar__image").get(yandex.select(".cbir-other-sizes__item").size() > 0 ? 1 : 0).parent().attr("href").split("img_url=")[1].split("&")[0], "UTF-8"); if(yandex.select(".cbir-other-sizes__list").size() > 0) { //TODO merge the sort algorithms? /*other = yandex.select(".cbir-other-sizes__list").first().select("a").stream().sorted((a, b) -> { //selects largest possible section of the other sizes String[] aA = a.select(".cbir-other-sizes__resolution").first().text().split("×"), bA = b.select(".cbir-other-sizes__resolution").first().text().split("×"); return Double.compare(Math.abs(Double.parseDouble(aA[0]) / Double.parseDouble(aA[1]) - ratio), Math.abs(Double.parseDouble(bA[0]) / Double.parseDouble(bA[1]) - ratio)); }).findFirst().get().attr("href");*/ other = yandex.select(".cbir-other-sizes__list a").first().attr("href"); //sorting with ratio seems to perform bad for most crops so dont sort anymore } else { yandex.select(".other-sites__item").stream().limit(12).sorted((a, b) -> { String[] aA = a.select(".other-sites__meta").first().text().split("×"), bA = b.select(".other-sites__meta").first().text().split("×"); System.out.println(Math.abs(Double.parseDouble(aA[0]) / Double.parseDouble(aA[1]) - ratio) + ", " + Math.abs(Double.parseDouble(bA[0]) / Double.parseDouble(bA[1]) - ratio)); return Double.compare(Math.abs(Double.parseDouble(aA[0]) / Double.parseDouble(aA[1]) - ratio), Math.abs(Double.parseDouble(bA[0]) / Double.parseDouble(bA[1]) - ratio));}).forEach(s -> System.out.println(s)); Element eSite = yandex.select(".other-sites__item").stream().limit(12).sorted((a, b) -> { String[] aA = a.select(".other-sites__meta").first().text().split("×"), bA = b.select(".other-sites__meta").first().text().split("×"); return Double.compare(Math.abs(Double.parseDouble(aA[0]) / Double.parseDouble(aA[1]) - ratio), Math.abs(Double.parseDouble(bA[0]) / Double.parseDouble(bA[1]) - ratio));}).findFirst().get(); other = eSite.select(".other-sites__preview-link").first().attr("href"); - site = Jsoup.connect(eSite.select(".other-sites__snippet-site-link").first().attr("href")).get().html().split(";URL='")[1].split("'")[0]; + site = eSite.select(".other-sites__snippet-site-link").first().attr("href"); } return new String[]{similar, other, site}; //gets second image coz usually the first one is just a sharper identical image, then get the largest image that has the image inside or select an image closest to 16:9 for anime checking //closest to given ratio now, anime checking usually cant be done with depth search anyways since not every scene is screen capped online } private EmbedBuilder tempSearchSauce(String url) throws IOException { EmbedBuilder eb = new EmbedBuilder(); eb.setColor(new Color(29, 29, 29)); try { //search iqdb first for the tags; results usually more organized and formatted Element iqdb = Jsoup.connect("https://iqdb.org/?url=" + url + "&service[]=1&service[]=2&service[]=3&service[]=4&service[]=5&service[]=11&service[]=13").get().body(); //services excluding eshuushuu since it has no tags and will fallback to saucenao anyways if(iqdb.select(".err").size() > 0 && iqdb.select(".err").html().contains("HTTP")) throw new IOException(iqdb.selectFirst(".err").ownText().split("\\.")[0]); Elements tb = iqdb.select("th:contains(Best match)").get(0).parent().siblingElements(); Element img = tb.get(0).selectFirst("img"); eb.setThumbnail("https://iqdb.org/" + img.attr("src")); eb.setTitle("Source: " + tb.get(1).selectFirst("td").ownText(), (img.parent().attr("href").contains("http") ? "" : "https:") + img.parent().attr("href")); String tags = img.attr("alt").split("Tags:")[1]; eb.setDescription(tags.contains(",") ? tags.replaceAll(",", "\n") : tags.replaceAll(" ", "\n").replaceAll("\\_", " ")); eb.setFooter(tb.get(2).select("td").eachText().get(0) + " | " + tb.get(3).select("td").text(), null); - } catch (IndexOutOfBoundsException | SocketTimeoutException e) { //fallback to saucenao, usually pixiv source instead of image boards + } catch (IndexOutOfBoundsException | SocketTimeoutException | ConnectException e) { //fallback to saucenao, usually pixiv source instead of image boards try { Element saucenao = Jsoup.connect("https://saucenao.com/search.php?url=" + url).get().body(); Element result = saucenao.selectFirst(".resulttable"); if(result == null) return null; if(result.parent().attr("class").equals("result hidden")) return null; eb.setThumbnail((result.selectFirst("img").attr("src").contains("http") ? "" : "https:") + result.selectFirst("img").attr("src").replaceAll(" ", "%20")); try { //normal pixiv/deviantart handling Element source = result.selectFirst("strong:contains(ID:)").nextElementSibling(); eb.setAuthor(result.select(".resulttitle strong").text()); eb.setTitle("Source: " + source.previousElementSibling().text().replaceAll("ID:", "#") + source.text(), source.attr("href")); Element member = result.select(".linkify").get(2); eb.setDescription("Author: [" + member.text() + "](" + member.attr("href") + ")"); } catch (NullPointerException e1) { //weird saucenao card formatting (eg episode info) result.select("br").after("\\n"); String[] title = result.selectFirst(".resulttitle") == null ? new String[]{"No title"} : result.selectFirst(".resulttitle").wholeText().replaceAll("\\\\n", "\n").split("\n", 2); //there can be no titles, like 4chan sources eb.setTitle(title[0], result.selectFirst(".resultmiscinfo a") == null ? null : result.selectFirst(".resultmiscinfo a").attr("href")); if(title.length > 1) eb.appendDescription(title[1] + "\n"); eb.appendDescription(result.selectFirst(".resultcontentcolumn").wholeText().replaceAll("\\\\n", "\n")); } eb.setFooter(result.select(".resultsimilarityinfo").text() + " Similarity", null); } catch (IndexOutOfBoundsException e1) { return null; } } return eb; } }