From 913da39535c0f5f2870fb4e6a02d98d539cc6e2d Mon Sep 17 00:00:00 2001 From: java-coding-prodigy Date: Thu, 24 Feb 2022 18:47:51 +0530 Subject: [PATCH 1/5] Added WolframAlpha command Stuff Created a class for the expression types tag. `deferReply` at the start now. Added Tips and Tip classes for the error POJOs Some changes to POJO classes Forgot to include this in the commit used the Tips POJO committing gitignore Changed as per @Zabuzard 's review Performed the changes requested a small change. Using Optionals instead of a try-catch @NotNull added and spotless applied Some changes Added unit test and utility class Some changes tested a failing case which failed successfully and wrote the pixel by pixel check to compare the images. Another test Fixed `combineImages` A different approach to send the images. removed a single `.` Logged stuff for debugging. Some changes Fixed some issues and removed some obsolete logs. Fixed almost everything but might change entire design Embeds Working embeds Alternative method using `WebhookMessageUpdateAction` New Package Did you means Future topics, Language messages and Related Examples Example Page This did not get committed properly Updating QueryResult Sending the actual URI to the users now Query result fix and displaying an error if it occurs @JsonProperty @JacksonXmlElementWrapper POJOs fix Added some examples POJO fix0 POJO fix1 POJO fix2 POJO fix3 POJO fix4 POJO fix5 POJO fix6 POJO fix7 POJO fix8 Handling the unsuccessful result case Added Error POJOS Some refactoring The result of some git forkery Ready for review :thumbs_up ENUM!!!!!! Some more refactoring Fixed overlapping "error" attribute conflict Error unrapping Refactoring... Some more refactoring remove import Remove that method I am back Fix config issue Put constants in seperate enum Put constants in seperate enum Some minor changes Some more minor changes even less major changes Better UX Fix mistake Fix rebasing issues. fix that issue remove comment --- .gitignore | 1 + application/build.gradle | 3 + .../togetherjava/tjbot/commands/Features.java | 1 + .../mathcommands/wolframalpha/Constants.java | 65 +++++ .../mathcommands/wolframalpha/DidYouMean.java | 21 ++ .../wolframalpha/DidYouMeans.java | 53 ++++ .../mathcommands/wolframalpha/Error.java | 33 +++ .../wolframalpha/ExamplePage.java | 35 +++ .../wolframalpha/FutureTopic.java | 42 +++ .../wolframalpha/LanguageMessage.java | 44 ++++ .../mathcommands/wolframalpha/PlainText.java | 20 ++ .../mathcommands/wolframalpha/Pod.java | 98 +++++++ .../wolframalpha/QueryResult.java | 241 ++++++++++++++++++ .../wolframalpha/RelatedExample.java | 71 ++++++ .../wolframalpha/RelatedExamples.java | 42 +++ .../wolframalpha/ResultStatus.java | 17 ++ .../mathcommands/wolframalpha/SubPod.java | 45 ++++ .../mathcommands/wolframalpha/Tip.java | 20 ++ .../mathcommands/wolframalpha/Tips.java | 52 ++++ .../wolframalpha/WolframAlphaCommand.java | 112 ++++++++ .../WolframAlphaCommandUtils.java | 226 ++++++++++++++++ .../wolframalpha/WolframAlphaImage.java | 62 +++++ .../org/togetherjava/tjbot/config/Config.java | 14 +- .../wolframalpha/WolframAlphaCommandTest.java | 75 ++++++ 24 files changed, 1392 insertions(+), 1 deletion(-) create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMean.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMeans.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Error.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ExamplePage.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/FutureTopic.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/LanguageMessage.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/PlainText.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Pod.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/QueryResult.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExample.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExamples.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/SubPod.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tip.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tips.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImage.java create mode 100644 application/src/test/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandTest.java diff --git a/.gitignore b/.gitignore index f24a747936..42983f369c 100644 --- a/.gitignore +++ b/.gitignore @@ -145,6 +145,7 @@ gradle-app.setting # End of https://www.toptal.com/developers/gitignore/api/netbeans,intellij,java,gradle,eclipse application/db/ config.json +application/config.json *.db *.db-shm *.db-wal diff --git a/application/build.gradle b/application/build.gradle index a86b8a66a2..2ce85aab62 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -48,12 +48,15 @@ dependencies { implementation 'org.jooq:jooq:3.15.3' + implementation 'io.mikael:urlbuilder:2.0.9' + implementation 'org.scilab.forge:jlatexmath:1.0.7' implementation 'org.scilab.forge:jlatexmath-font-greek:1.0.7' implementation 'org.scilab.forge:jlatexmath-font-cyrillic:1.0.7' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.13.0' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.0' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.13.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0' implementation 'com.github.freva:ascii-table:1.2.0' diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java index e8528391c0..3fccd30b54 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -105,6 +105,7 @@ public enum Features { features.add(new QuarantineCommand(actionsStore, config)); features.add(new UnquarantineCommand(actionsStore, config)); features.add(new WhoIsCommand()); + features.add(new WolframAlphaCommand(config)); // Mixtures features.add(new FreeCommand(config, freeChannelMonitor)); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java new file mode 100644 index 0000000000..f2bfbc6090 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java @@ -0,0 +1,65 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import net.dv8tion.jda.api.requests.restaction.WebhookMessageUpdateAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.*; +import java.awt.font.TextAttribute; +import java.awt.image.ImageObserver; +import java.net.http.HttpClient; +import java.util.Map; + +public enum Constants { + ; + + /** + * Starting part of a regular wolframalpha query link. + */ + public static final String USER_ENDPOINT = "https://www.wolframalpha.com/input"; + static final Logger logger = LoggerFactory.getLogger(WolframAlphaCommand.class); + /** + * Maximum Embeds that can be sent in a {@link WebhookMessageUpdateAction} + */ + static final int MAX_EMBEDS = 10; + /** + * The image observer used for getting data from images. + */ + static final ImageObserver IMAGE_OBSERVER = null; + /** + * The width of the margins of the images generated + */ + static final int IMAGE_MARGIN_WIDTH = 10; + /** + * The height of the margins of the images generated + */ + public static final int IMAGE_MARGIN_HEIGHT = 15; + + static final XmlMapper XML = new XmlMapper(); + static final int MAX_IMAGE_HEIGHT_PX = 400; + /** + * WolframAlpha text Color + */ + static final Color WOLFRAM_ALPHA_TEXT_COLOR = Color.decode("#3C3C3C"); + /** + * WolframAlpha Font + */ + static final Font WOLFRAM_ALPHA_FONT = new Font("Times", Font.PLAIN, 15) + .deriveFont(Map.of(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON)); + /** + * Height of the unscaled text displayed in Font {@link #WOLFRAM_ALPHA_FONT} + */ + static final int TEXT_HEIGHT = 20; + static final int HTTP_STATUS_CODE_OK = 200; + static final String QUERY_OPTION = "query"; + /** + * WolframAlpha API endpoint to connect to. + * + * @see WolframAlpha API + * Reference. + */ + static final String API_ENDPOINT = "http://api.wolframalpha.com/v2/query"; + static final HttpClient client = HttpClient.newHttpClient(); +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMean.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMean.java new file mode 100644 index 0000000000..e4c35d33c9 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMean.java @@ -0,0 +1,21 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; + +@JsonRootName("didyoumean") +@JsonIgnoreProperties(ignoreUnknown = true) +final class DidYouMean { + + @JacksonXmlText + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMeans.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMeans.java new file mode 100644 index 0000000000..5fda806809 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMeans.java @@ -0,0 +1,53 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + *

+ * Example Query: btuyghe
+ * Result: {@code + * tighe + * } + *

+ */ +@JsonRootName("didyoumeans") +@JsonIgnoreProperties(ignoreUnknown = true) +final class DidYouMeans { + + @JacksonXmlProperty(isAttribute = true) + private int count; + + @JsonProperty("didyoumean") + @JacksonXmlElementWrapper(useWrapping = false) + private List didYouMeans; + + @SuppressWarnings("unused") + public int getCount() { + return count; + } + + @SuppressWarnings("unused") + public void setCount(int count) { + this.count = count; + } + + public List getDidYouMeans() { + return Collections.unmodifiableList(didYouMeans); + } + + @SuppressWarnings("unused") + public void setDidYouMeans(List didYouMeans) { + this.didYouMeans = new ArrayList<>(didYouMeans); + } + + + +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Error.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Error.java new file mode 100644 index 0000000000..be6b8bd402 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Error.java @@ -0,0 +1,33 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +@JsonRootName("error") +@JsonIgnoreProperties(ignoreUnknown = true) +final class Error { + private int code; + @JsonProperty("msg") + private String message; + + public int getCode() { + return code; + } + + @SuppressWarnings("unused") + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + @SuppressWarnings("unused") + public void setMessage(String message) { + this.message = message; + } + + +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ExamplePage.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ExamplePage.java new file mode 100644 index 0000000000..794376595c --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ExamplePage.java @@ -0,0 +1,35 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +@JsonRootName("examplepage") +@JsonIgnoreProperties(ignoreUnknown = true) +final class ExamplePage { + + @JacksonXmlProperty(isAttribute = true) + private String category; + @JacksonXmlProperty(isAttribute = true) + private String url; + + @SuppressWarnings("unused") + public String getCategory() { + return category; + } + + @SuppressWarnings("unused") + public void setCategory(String category) { + this.category = category; + } + + @SuppressWarnings("unused") + public String getUrl() { + return url; + } + + @SuppressWarnings("unused") + public void setUrl(String url) { + this.url = url; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/FutureTopic.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/FutureTopic.java new file mode 100644 index 0000000000..0e476c1027 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/FutureTopic.java @@ -0,0 +1,42 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +/** + *

+ * Example Query: Operating Systems
+ * Result: {@code } + *

+ */ +@JsonRootName("futuretopic") +@JsonIgnoreProperties(ignoreUnknown = true) +final class FutureTopic { + + @JacksonXmlProperty(isAttribute = true) + private String topic; + + @JacksonXmlProperty(isAttribute = true) + private String msg; + + public String getTopic() { + return topic; + } + + @SuppressWarnings("unused") + public void setTopic(String topic) { + this.topic = topic; + } + + @SuppressWarnings("unused") + public String getMsg() { + return msg; + } + + @SuppressWarnings("unused") + public void setMsg(String msg) { + this.msg = msg; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/LanguageMessage.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/LanguageMessage.java new file mode 100644 index 0000000000..c207d24aa4 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/LanguageMessage.java @@ -0,0 +1,44 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +/** + * + *

+ * Example Query: ¿donde soy tu?
+ * Result: {@code } + *

+ */ +@JsonRootName("langugemsg") +@JsonIgnoreProperties(ignoreUnknown = true) +final class LanguageMessage { + + @JacksonXmlProperty(isAttribute = true) + private String english; + + @JacksonXmlProperty(isAttribute = true) + private String other; + + public String getEnglish() { + return english; + } + + @SuppressWarnings("unused") + public void setEnglish(String english) { + this.english = english; + } + + public String getOther() { + return other; + } + + @SuppressWarnings("unused") + public void setOther(String other) { + this.other = other; + } + + +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/PlainText.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/PlainText.java new file mode 100644 index 0000000000..f07ba095f3 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/PlainText.java @@ -0,0 +1,20 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; + +@JsonRootName("plaintext") +@JsonIgnoreProperties(ignoreUnknown = true) +final class PlainText { + @JacksonXmlText + private String text; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Pod.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Pod.java new file mode 100644 index 0000000000..3e7d0cfa72 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Pod.java @@ -0,0 +1,98 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@JsonRootName("pod") +@JsonIgnoreProperties(ignoreUnknown = true) +final class Pod { + @JacksonXmlProperty(isAttribute = true) + private String title; + @JacksonXmlProperty(isAttribute = true) + private boolean error; + @JacksonXmlProperty(isAttribute = true) + private int position; + @JacksonXmlProperty(isAttribute = true) + private String scanner; + @JacksonXmlProperty(isAttribute = true) + private String id; + @JacksonXmlProperty(isAttribute = true, localName = "numsubpods") + private int numberOfSubPods; + + @JsonProperty("subpod") + @JacksonXmlElementWrapper(useWrapping = false) + private List subPods; + + public String getTitle() { + return title; + } + + @SuppressWarnings("unused") + public void setTitle(String title) { + this.title = title; + } + + public boolean isError() { + return error; + } + + public void setError(boolean error) { + this.error = error; + } + + @SuppressWarnings("unused") + public int getPosition() { + return position; + } + + @SuppressWarnings("unused") + public void setPosition(int position) { + this.position = position; + } + + @SuppressWarnings("unused") + public String getScanner() { + return scanner; + } + + @SuppressWarnings("unused") + public void setScanner(String scanner) { + this.scanner = scanner; + } + + @SuppressWarnings("unused") + public String getId() { + return id; + } + + @SuppressWarnings("unused") + public void setId(String id) { + this.id = id; + } + + @SuppressWarnings("unused") + public int getNumberOfSubPods() { + return numberOfSubPods; + } + + @SuppressWarnings("unused") + public void setNumberOfSubPods(int numberOfSubPods) { + this.numberOfSubPods = numberOfSubPods; + } + + public List getSubPods() { + return Collections.unmodifiableList(subPods); + } + + @SuppressWarnings("unused") + public void setSubPods(List subPods) { + this.subPods = new ArrayList<>(subPods); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/QueryResult.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/QueryResult.java new file mode 100644 index 0000000000..0120b5deee --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/QueryResult.java @@ -0,0 +1,241 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@JsonRootName("queryresult") +@JsonIgnoreProperties(ignoreUnknown = true) +final class QueryResult { + private static final XmlMapper XML = new XmlMapper(); + + @JacksonXmlProperty(isAttribute = true) + private boolean success; + @JsonIgnore + private boolean errorAttribute; + @JacksonXmlProperty(isAttribute = true, localName = "numpods") + private int numberOfPods; + @JacksonXmlProperty(isAttribute = true) + private String version; + @JacksonXmlProperty(isAttribute = true, localName = "datatypes") + private String dataTypes; + @JacksonXmlProperty(isAttribute = true) + private double timing; + @JacksonXmlProperty(isAttribute = true, localName = "timedout") + private String timedOutPods; + @JacksonXmlProperty(isAttribute = true, localName = "parsetiming") + private double parseTiming; + @JacksonXmlProperty(isAttribute = true, localName = "parsetimedout") + private boolean parseTimedOut; + @JacksonXmlProperty(isAttribute = true, localName = "recalculate") + private String recalculateUrl; + + private Tips tips; + @JsonProperty("didyoumeans") + private DidYouMeans didYouMeans; + @JsonProperty("languagemsg") + private LanguageMessage languageMessage; + @JsonProperty("examplepage") + private ExamplePage examplePage; + @JsonProperty("futuretopic") + private FutureTopic futureTopic; + @JsonProperty("relatedexamples") + private RelatedExamples relatedExamples; + @JsonProperty("pod") + @JacksonXmlElementWrapper(useWrapping = false) + private List pods; + @JsonIgnore + private Error errorTag; + + public boolean isSuccess() { + return success; + } + + @SuppressWarnings("unused") + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean isError() { + return errorAttribute; + } + + @SuppressWarnings("unused") + public int getNumberOfPods() { + return numberOfPods; + } + + @SuppressWarnings("unused") + public void setNumberOfPods(int numberOfPods) { + this.numberOfPods = numberOfPods; + } + + @SuppressWarnings("unused") + public String getVersion() { + return version; + } + + @SuppressWarnings("unused") + public void setVersion(String version) { + this.version = version; + } + + @SuppressWarnings("unused") + public String getDataTypes() { + return dataTypes; + } + + @SuppressWarnings("unused") + public void setDataTypes(String dataTypes) { + this.dataTypes = dataTypes; + } + + @SuppressWarnings("unused") + public double getTiming() { + return timing; + } + + @SuppressWarnings("unused") + public void setTiming(double timing) { + this.timing = timing; + } + + @SuppressWarnings("unused") + public String getTimedOutPods() { + return timedOutPods; + } + + @SuppressWarnings("unused") + public void setTimedOutPods(String timedOutPods) { + this.timedOutPods = timedOutPods; + } + + @SuppressWarnings("unused") + public double getParseTiming() { + return parseTiming; + } + + @SuppressWarnings("unused") + public void setParseTiming(double parseTiming) { + this.parseTiming = parseTiming; + } + + @SuppressWarnings("unused") + public boolean isParseTimedOut() { + return parseTimedOut; + } + + @SuppressWarnings("unused") + public void setParseTimedOut(boolean parseTimedOut) { + this.parseTimedOut = parseTimedOut; + } + + @SuppressWarnings("unused") + public String getRecalculateUrl() { + return recalculateUrl; + } + + @SuppressWarnings("unused") + public void setRecalculateUrl(String recalculateUrl) { + this.recalculateUrl = recalculateUrl; + } + + public List getPods() { + return Collections.unmodifiableList(pods); + } + + @SuppressWarnings("unused") + public void setPods(List pods) { + this.pods = new ArrayList<>(pods); + } + + public Tips getTips() { + return tips; + } + + @SuppressWarnings("unused") + public void setTips(Tips tips) { + this.tips = tips; + } + + public DidYouMeans getDidYouMeans() { + return didYouMeans; + } + + @SuppressWarnings("unused") + public void setDidYouMeans(DidYouMeans didYouMeans) { + this.didYouMeans = didYouMeans; + } + + public LanguageMessage getLanguageMessage() { + return languageMessage; + } + + @SuppressWarnings("unused") + public void setLanguageMessage(LanguageMessage languageMessage) { + this.languageMessage = languageMessage; + } + + @SuppressWarnings("unused") + public ExamplePage getExamplePage() { + return examplePage; + } + + @SuppressWarnings("unused") + public void setExamplePage(ExamplePage examplePage) { + this.examplePage = examplePage; + } + + @SuppressWarnings("unused") + public FutureTopic getFutureTopic() { + return futureTopic; + } + + @SuppressWarnings("unused") + public void setFutureTopic(FutureTopic futureTopic) { + this.futureTopic = futureTopic; + } + + @SuppressWarnings("unused") + public RelatedExamples getRelatedExamples() { + return relatedExamples; + } + + @SuppressWarnings("unused") + public void setRelatedExamples(RelatedExamples relatedExamples) { + this.relatedExamples = relatedExamples; + } + + @SuppressWarnings("unused") + public Error getErrorTag() { + return errorTag; + } + + @JsonAnySetter + @SuppressWarnings("ChainOfInstanceofChecks") + public void setError(String name, Object value) { + if (!"error".equals(name)) { + return; + } + + // NOTE Unfortunately the WA API returns "error" as attribute and tag at the same time. + // There is no elegant fix to differentiate them other than doing it manually, + // see https://github.com/FasterXML/jackson-dataformat-xml/issues/65 + // and https://github.com/FasterXML/jackson-dataformat-xml/issues/383 + if (value instanceof String) { + errorAttribute = XML.convertValue(value, boolean.class); + return; + } + if (value instanceof Map) { + errorTag = XML.convertValue(value, Error.class); + return; + } + throw new IllegalArgumentException("Unsupported error format"); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExample.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExample.java new file mode 100644 index 0000000000..0c6507730f --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExample.java @@ -0,0 +1,71 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +@JsonRootName("relatedexample") +@JsonIgnoreProperties(ignoreUnknown = true) +final class RelatedExample { + + @JacksonXmlProperty(isAttribute = true) + private String input; + @JacksonXmlProperty(isAttribute = true, localName = "desc") + private String description; + @JacksonXmlProperty(isAttribute = true) + private String category; + @JacksonXmlProperty(isAttribute = true, localName = "categorythumb") + private String categoryThumb; + @JacksonXmlProperty(isAttribute = true, localName = "categorypage") + private String categoryPage; + + @SuppressWarnings("unused") + public String getInput() { + return input; + } + + @SuppressWarnings("unused") + public void setInput(String input) { + this.input = input; + } + + @SuppressWarnings("unused") + public String getDescription() { + return description; + } + + @SuppressWarnings("unused") + public void setDescription(String description) { + this.description = description; + } + + @SuppressWarnings("unused") + public String getCategory() { + return category; + } + + @SuppressWarnings("unused") + public void setCategory(String category) { + this.category = category; + } + + @SuppressWarnings("unused") + public String getCategoryThumb() { + return categoryThumb; + } + + @SuppressWarnings("unused") + public void setCategoryThumb(String categoryThumb) { + this.categoryThumb = categoryThumb; + } + + @SuppressWarnings("unused") + public String getCategoryPage() { + return categoryPage; + } + + @SuppressWarnings("unused") + public void setCategoryPage(String categoryPage) { + this.categoryPage = categoryPage; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExamples.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExamples.java new file mode 100644 index 0000000000..014ee0a8c9 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExamples.java @@ -0,0 +1,42 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + +@JsonRootName("relatedexamples") +@JsonIgnoreProperties(ignoreUnknown = true) +final class RelatedExamples { + + @JacksonXmlProperty(isAttribute = true) + private int count; + + @JsonProperty("relatedexample") + @JacksonXmlElementWrapper(useWrapping = false) + private List relatedExamples; + + @SuppressWarnings("unused") + public int getCount() { + return count; + } + + @SuppressWarnings("unused") + public void setCount(int count) { + this.count = count; + } + + public List getRelatedExamples() { + return Collections.unmodifiableList(relatedExamples); + } + + @SuppressWarnings("unused") + public void setRelatedExamples(List relatedExamples) { + this.relatedExamples = new ArrayList<>(relatedExamples); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java new file mode 100644 index 0000000000..f2665d231f --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java @@ -0,0 +1,17 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +public enum ResultStatus { + SUCCESS, + NOT_SUCCESS, + ERROR; + + static ResultStatus getResultStatus(QueryResult result) { + if (result.isSuccess()) { + return SUCCESS; + } else if (!result.isError()) { + return NOT_SUCCESS; + } else { + return ERROR; + } + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/SubPod.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/SubPod.java new file mode 100644 index 0000000000..cc5367f051 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/SubPod.java @@ -0,0 +1,45 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +@JsonRootName("subpod") +@JsonIgnoreProperties(ignoreUnknown = true) + +final class SubPod { + @JacksonXmlProperty(isAttribute = true) + private String title; + + @JsonProperty("img") + private WolframAlphaImage image; + @JsonProperty("plaintext") + private PlainText plainText; + + @SuppressWarnings("unused") + public String getTitle() { + return title; + } + + @SuppressWarnings("unused") + public void setTitle(String title) { + this.title = title; + } + + public WolframAlphaImage getImage() { + return image; + } + + public void setImage(WolframAlphaImage image) { + this.image = image; + } + + public PlainText getPlainText() { + return plainText; + } + + public void setPlainText(PlainText plainText) { + this.plainText = plainText; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tip.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tip.java new file mode 100644 index 0000000000..5ab05d90e5 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tip.java @@ -0,0 +1,20 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +@JsonRootName("tip") +@JsonIgnoreProperties(ignoreUnknown = true) +final class Tip { + @JacksonXmlProperty(isAttribute = true) + private String text; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tips.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tips.java new file mode 100644 index 0000000000..a0a31ce1a3 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tips.java @@ -0,0 +1,52 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + *

+ * Example Query: bhefjuhkynmbtg
+ * Result:
+ * {@code + * + * } + *

+ */ +@JsonRootName("tips") +@JsonIgnoreProperties(ignoreUnknown = true) +final class Tips { + @JacksonXmlProperty(isAttribute = true) + private int count; + + @JsonProperty("tip") + @JacksonXmlElementWrapper(useWrapping = false) + private List tips; + + @SuppressWarnings("unused") + public int getCount() { + return count; + } + + @SuppressWarnings("unused") + public void setCount(int count) { + this.count = count; + } + + @SuppressWarnings("unused") + public List getTips() { + return Collections.unmodifiableList(tips); + } + + @SuppressWarnings("unused") + public void setTips(List tips) { + this.tips = new ArrayList<>(tips); + } + +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java new file mode 100644 index 0000000000..7b87baba0d --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java @@ -0,0 +1,112 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import io.mikael.urlbuilder.UrlBuilder; +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.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.requests.restaction.WebhookMessageUpdateAction; +import org.jetbrains.annotations.NotNull; +import org.togetherjava.tjbot.commands.SlashCommandAdapter; +import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.config.Config; + +import java.io.IOException; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Objects; +import java.util.Optional; + +public final class WolframAlphaCommand extends SlashCommandAdapter { + + private final String wolframAlphaAppId; + + public WolframAlphaCommand(Config config) { + super("wolfram-alpha", "Renders mathematical queries using WolframAlpha", + SlashCommandVisibility.GUILD); + getData().addOption(OptionType.STRING, Constants.QUERY_OPTION, + "the query to send to WolframAlpha", true); + wolframAlphaAppId = config.getWolframAlphaAppId(); + } + + @Override + public void onSlashCommand(@NotNull SlashCommandEvent event) { + + // The processing takes some time + event.deferReply().queue(); + + String query = + Objects.requireNonNull(event.getOption(Constants.QUERY_OPTION)).getAsString(); + + MessageEmbed uriEmbed = new EmbedBuilder() + .setTitle(query + "- Wolfram|Alpha", + UrlBuilder.fromString(Constants.USER_ENDPOINT) + .addParameter("i", query) + .toUri() + .toString()) + .setDescription( + "Wolfram|Alpha brings expert-level knowledge and capabilities to the broadest possible range of people-spanning all professions and education levels.") + .build(); + + WebhookMessageUpdateAction action = + event.getHook().editOriginal("").setEmbeds(uriEmbed); + + HttpRequest request = HttpRequest + .newBuilder(UrlBuilder.fromString(Constants.API_ENDPOINT) + .addParameter("appid", wolframAlphaAppId) + .addParameter("format", "image,plaintext") + .addParameter("input", query) + .toUri()) + .GET() + .build(); + + Optional> optResponse = getResponse(request, action); + if (optResponse.isEmpty()) + return; + HttpResponse response = optResponse.get(); + Optional optResult = WolframAlphaCommandUtils.parseQuery(response, action); + if (optResult.isEmpty()) + return; + QueryResult result = optResult.get(); + action = action.setContent("Computed in:" + result.getTiming()); + action.setContent(switch (ResultStatus.getResultStatus(result)) { + + case ERROR -> WolframAlphaCommandUtils.handleError(result); + + case NOT_SUCCESS -> WolframAlphaCommandUtils.handleMisunderstoodQuery(result); + + case SUCCESS -> "Here are the results of your query, Check the link for the complete results\n" + + (result.getTimedOutPods().isEmpty() ? "" + : "Some pods have timed out. Visit the URI") + + "\n" + + WolframAlphaCommandUtils.handleSuccessfulResult(result, action, uriEmbed); + }).queue(); + } + + private @NotNull Optional> getResponse(@NotNull HttpRequest request, + @NotNull WebhookMessageUpdateAction action) { + HttpResponse response; + try { + response = Constants.client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + action.setContent("Unable to get a response from WolframAlpha API").queue(); + Constants.logger.warn("Could not get the response from the server", e); + return Optional.empty(); + } catch (InterruptedException e) { + action.setContent("Connection to WolframAlpha was interrupted").queue(); + Constants.logger.warn("Connection to WolframAlpha was interrupted", e); + Thread.currentThread().interrupt(); + return Optional.empty(); + } + + if (response.statusCode() != Constants.HTTP_STATUS_CODE_OK) { + action.setContent("The response' status code was incorrect").queue(); + Constants.logger.warn("Unexpected status code: Expected: {} Actual: {}", + Constants.HTTP_STATUS_CODE_OK, response.statusCode()); + return Optional.empty(); + } + return Optional.of(response); + } + +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java new file mode 100644 index 0000000000..31c038afc1 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java @@ -0,0 +1,226 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +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.requests.restaction.WebhookMessageUpdateAction; +import org.jetbrains.annotations.NotNull; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.font.FontRenderContext; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URL; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +enum WolframAlphaCommandUtils { + ; + + static byte[] imageToBytes(BufferedImage img) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(img, "png", baos); + return baos.toByteArray(); + } + + static BufferedImage combineImages(List images, int height) { + int width = + images.stream().mapToInt(BufferedImage::getWidth).max().orElse(Integer.MAX_VALUE); + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); + Graphics imgGraphics = image.getGraphics(); + imgGraphics.setColor(Color.WHITE); + imgGraphics.fillRect(0, 0, width, height); + int resultHeight = 0; + for (BufferedImage img : images) { + imgGraphics.drawImage(img, 0, resultHeight, Constants.IMAGE_OBSERVER); + resultHeight += img.getHeight(Constants.IMAGE_OBSERVER); + } + return image; + } + + static boolean compareImages(BufferedImage img1, BufferedImage img2) { + int width1 = img1.getWidth(); + int height1 = img1.getHeight(); + return width1 == img2.getWidth() && height1 == img2.getHeight() + && IntStream.range(0, width1) + .mapToObj(x -> IntStream.range(0, height1) + .anyMatch(y -> img1.getRGB(x, y) != img2.getRGB(x, y))) + .noneMatch(x -> x); + } + + static int getWidth(String header) { + return (int) Constants.WOLFRAM_ALPHA_FONT + .getStringBounds(header, new FontRenderContext(new AffineTransform(), true, true)) + .getWidth(); + } + + static String handleMisunderstoodQuery(QueryResult result) { + List output = new ArrayList<>(); + output + .add("The Wolfram|Alpha API was unable to produce a successful result. Visit the URI"); + Tips tips = result.getTips(); + if (tips != null && tips.getCount() != 0) { + if (tips.getCount() == 1) { + output.add("Here is a tip: " + tips.getTips().get(0).getText()); + } else { + output.add("Here are some tips: \n" + tips.getTips() + .stream() + .map(Tip::getText) + .map(text -> "• " + text) + .collect(Collectors.joining("\n"))); + } + } + FutureTopic futureTopic = result.getFutureTopic(); + if (futureTopic != null) { + output.add( + "Your query is regarding The topic \"%s\" which might be supported by Wolfram Alpha in the future" + .formatted(futureTopic.getTopic())); + } + LanguageMessage languageMsg = result.getLanguageMessage(); + if (languageMsg != null) { + output.add(languageMsg.getEnglish() + "\n" + languageMsg.getOther()); + } + DidYouMeans didYouMeans = result.getDidYouMeans(); + if (didYouMeans != null && didYouMeans.getCount() != 0) { + if (didYouMeans.getCount() == 1) + output.add("Did you mean: " + didYouMeans.getDidYouMeans().get(0).getMessage()); + else { + output.add("Did you mean \n" + didYouMeans.getDidYouMeans() + .stream() + .map(DidYouMean::getMessage) + .map(text -> "• " + text) + .collect(Collectors.joining("\n"))); + } + } + RelatedExamples relatedExamples = result.getRelatedExamples(); + if (relatedExamples != null && relatedExamples.getCount() != 0) { + if (relatedExamples.getCount() == 1) { + output.add("Here is a related example: " + + relatedExamples.getRelatedExamples().get(0).getCategoryThumb()); + } else { + output + .add("Here are some related examples \n" + relatedExamples.getRelatedExamples() + .stream() + .map(RelatedExample::getCategoryThumb) + .map(text -> "• " + text) + .collect(Collectors.joining("\n"))); + } + } + return String.join("\n", output); + } + + static String handleError(QueryResult result) { + Error error = result.getErrorTag(); + Constants.logger.error( + "Error getting response from Wolfram Alpha API: Error Code: {} Error Message: {}", + error.getCode(), error.getMessage()); + return "An error occurred while getting response from the Wolfram|Alpha API. Check the URI"; + } + + static @NotNull Optional parseQuery(@NotNull HttpResponse response, + @NotNull WebhookMessageUpdateAction action) { + QueryResult result; + try { + Files.writeString(Path + .of("C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\main\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\responsebody.xml"), + response.body()); + result = Constants.XML.readValue(response.body(), QueryResult.class); + + } catch (IOException e) { + action.setContent("Unexpected response from WolframAlpha API").queue(); + Constants.logger.error("Unable to deserialize the class ", e); + return Optional.empty(); + } + return Optional.of(result); + } + + static @NotNull String handleSuccessfulResult(@NotNull QueryResult result, + WebhookMessageUpdateAction action, MessageEmbed embed) { + + int filesAttached = 0; + int resultHeight = 0; + EmbedBuilder embedBuilder = new EmbedBuilder(); + List embeds = new ArrayList<>(); + embeds.add(embed); + List images = new ArrayList<>(); + List pods = result.getPods(); + for (Pod pod : pods) { + List subPods = pod.getSubPods(); + boolean firstSubPod = true; + for (SubPod subPod : subPods) { + WolframAlphaImage image = subPod.getImage(); + try { + + String source = image.getSource(); + String header = pod.getTitle(); + int width = (firstSubPod ? Math.max(getWidth(header), image.getWidth()) + : image.getWidth()) + Constants.IMAGE_MARGIN_WIDTH; + int height = image.getHeight(); + if (firstSubPod) + height += Constants.TEXT_HEIGHT; + BufferedImage readImage = + new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); + Graphics graphics = readImage.getGraphics(); + if (firstSubPod) { + graphics.setFont(Constants.WOLFRAM_ALPHA_FONT); + graphics.setColor(Color.WHITE); + graphics.setColor(Constants.WOLFRAM_ALPHA_TEXT_COLOR); + graphics.drawString(header, Constants.IMAGE_MARGIN_WIDTH, + Constants.IMAGE_MARGIN_HEIGHT); + } + graphics.drawImage(ImageIO.read(new URL(source)), Constants.IMAGE_MARGIN_WIDTH, + firstSubPod ? Constants.TEXT_HEIGHT : 0, Constants.IMAGE_OBSERVER); + + if (filesAttached == Constants.MAX_EMBEDS) { + // noinspection ResultOfMethodCallIgnored + action.setEmbeds(embeds); + return "Too many images. Visit the URI"; + } + + + if (resultHeight + image.getHeight() > Constants.MAX_IMAGE_HEIGHT_PX) { + BufferedImage combinedImage = + WolframAlphaCommandUtils.combineImages(images, resultHeight); + images.clear(); + action = action.addFile( + WolframAlphaCommandUtils.imageToBytes(combinedImage), + "result%d.png".formatted(++filesAttached)); + resultHeight = 0; + embeds.add(embedBuilder + .setImage("attachment://result%d.png".formatted(filesAttached)) + .build()); + } else if (pod == pods.get(pods.size() - 1) + && subPod == subPods.get(subPods.size() - 1) && !images.isEmpty()) { + action = action.addFile( + WolframAlphaCommandUtils.imageToBytes(WolframAlphaCommandUtils + .combineImages(images, resultHeight + image.getHeight())), + "result%d.png".formatted(++filesAttached)); + images.clear(); + embeds.add(embedBuilder + .setImage("attachment://result%d.png".formatted(filesAttached)) + .build()); + } + resultHeight += readImage.getHeight(); + images.add(readImage); + firstSubPod = false; + } catch (IOException e) { + Constants.logger.error("Failed to read image {} from the WolframAlpha response", + image, e); + return "Unable to generate message based on the WolframAlpha response"; + } + } + } + // noinspection ResultOfMethodCallIgnored + action.setEmbeds(embeds); + return ""; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImage.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImage.java new file mode 100644 index 0000000000..5666de9547 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImage.java @@ -0,0 +1,62 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + + +@JsonRootName("img") +@JsonIgnoreProperties(ignoreUnknown = true) +final class WolframAlphaImage { + @JacksonXmlProperty(isAttribute = true, localName = "src") + private String source; + @JacksonXmlProperty(isAttribute = true) + private int width; + @JacksonXmlProperty(isAttribute = true) + private int height; + @JacksonXmlProperty(isAttribute = true) + private String title; + + public String getTitle() { + return title; + } + + @SuppressWarnings("unused") + public void setTitle(String title) { + this.title = title; + } + + public String getSource() { + return source; + } + + @SuppressWarnings("unused") + public void setSource(String source) { + this.source = source; + } + + @SuppressWarnings("unused") + public int getWidth() { + return width; + } + + @SuppressWarnings("unused") + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + @SuppressWarnings("unused") + public void setHeight(int height) { + this.height = height; + } + + @Override + public String toString() { + return "WolframAlphaImage{" + "source='" + source + '\'' + ", width=" + width + ", height=" + + height + ", title='" + title + '\'' + '}'; + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/config/Config.java b/application/src/main/java/org/togetherjava/tjbot/config/Config.java index a6deae76f7..e2ea866b08 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -30,6 +30,7 @@ public final class Config { private final SuggestionsConfig suggestions; private final String quarantinedRolePattern; private final ScamBlockerConfig scamBlocker; + private final String wolframAlphaAppId; @SuppressWarnings("ConstructorWithTooManyParameters") @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) @@ -46,7 +47,8 @@ private Config(@JsonProperty("token") String token, @JsonProperty("helpChannelPattern") String helpChannelPattern, @JsonProperty("suggestions") SuggestionsConfig suggestions, @JsonProperty("quarantinedRolePattern") String quarantinedRolePattern, - @JsonProperty("scamBlocker") ScamBlockerConfig scamBlocker) { + @JsonProperty("scamBlocker") ScamBlockerConfig scamBlocker, + @JsonProperty("wolframAlphaAppId") String wolframAlphaAppId) { this.token = token; this.databasePath = databasePath; this.projectWebsite = projectWebsite; @@ -61,6 +63,7 @@ private Config(@JsonProperty("token") String token, this.suggestions = suggestions; this.quarantinedRolePattern = quarantinedRolePattern; this.scamBlocker = scamBlocker; + this.wolframAlphaAppId = wolframAlphaAppId; } /** @@ -207,4 +210,13 @@ public String getQuarantinedRolePattern() { public @NotNull ScamBlockerConfig getScamBlocker() { return scamBlocker; } + + /** + * Gets the application ID used to connect to the WolframAlpha API. + * + * @return the application ID for the WolframAlpha API + */ + public @NotNull String getWolframAlphaAppId() { + return wolframAlphaAppId; + } } diff --git a/application/src/test/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandTest.java new file mode 100644 index 0000000000..fe3bb728bd --- /dev/null +++ b/application/src/test/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandTest.java @@ -0,0 +1,75 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +final class WolframAlphaCommandTest { + private static final Logger logger = LoggerFactory.getLogger( + org.togetherjava.tjbot.commands.mathcommands.wolframalpha.WolframAlphaCommandTest.class); + + @Test + void compareImagesTest() { + BufferedImage image1 = new BufferedImage(100, 100, 6); + BufferedImage image2 = new BufferedImage(100, 100, 6); + image1.getGraphics().setColor(Color.RED); + image2.getGraphics().setColor(Color.YELLOW); + assertFalse(WolframAlphaCommandUtils.compareImages(image1, image2)); + } + + @Test + void combineImagesTest() throws IOException { + BufferedImage image1 = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); + BufferedImage image2 = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); + BufferedImage image3 = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); + BufferedImage mergedImage = new BufferedImage(100, 300, BufferedImage.TYPE_INT_ARGB_PRE); + Graphics g1 = image1.getGraphics(); + g1.setColor(Color.RED); + g1.fillRect(0, 0, 100, 100); + File img1 = Path + .of("C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\test\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\img1.png") + .toFile(); + ImageIO.write(image1, "png", img1); + logger.info("image 1 successfully written"); + Graphics g2 = image2.getGraphics(); + g2.setColor(Color.BLUE); + g2.fillRect(0, 0, 100, 100); + File img2 = Path + .of("C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\test\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\img2.png") + .toFile(); + ImageIO.write(image2, "png", img2); + logger.info("image 2 successfully written"); + Graphics g3 = image3.getGraphics(); + g3.setColor(Color.YELLOW); + g3.fillRect(0, 0, 100, 100); + ImageIO.write(image3, "png", new File( + "C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\test\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\img3.png")); + logger.info("image 3 successfully written"); + Graphics g4 = mergedImage.getGraphics(); + g4.setColor(Color.RED); + g4.fillRect(0, 0, 100, 100); + g4.setColor(Color.BLUE); + g4.fillRect(0, 100, 100, 100); + g4.setColor(Color.YELLOW); + g4.fillRect(0, 200, 100, 100); + ImageIO.write(mergedImage, "png", new File( + "C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\test\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\manuallyMergedImg.png")); + BufferedImage mergedByMethod = + WolframAlphaCommandUtils.combineImages(List.of(image1, image2, image3), 300); + ImageIO.write(mergedByMethod, "png", new File( + "C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\test\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\methodMergedimage.png")); + assertTrue(WolframAlphaCommandUtils.compareImages(mergedImage, mergedByMethod)); + } +} + From 9ed826f543d59e5230d2e45eb748214788bb6a95 Mon Sep 17 00:00:00 2001 From: Zabuzard Date: Thu, 5 May 2022 11:41:46 +0200 Subject: [PATCH 2/5] Fixed most issues after rebase, code style, package refactoring --- application/config.json.template | 3 +- .../togetherjava/tjbot/commands/Features.java | 1 + .../mathcommands/wolframalpha/Constants.java | 31 +++---- .../wolframalpha/ResultStatus.java | 4 +- .../wolframalpha/WolframAlphaCommand.java | 49 ++++++---- .../WolframAlphaCommandUtils.java | 90 ++++++++++--------- .../wolframalpha/{ => api}/DidYouMean.java | 5 +- .../wolframalpha/{ => api}/DidYouMeans.java | 20 +++-- .../wolframalpha/{ => api}/Error.java | 4 +- .../wolframalpha/{ => api}/ExamplePage.java | 5 +- .../wolframalpha/{ => api}/FutureTopic.java | 20 +++-- .../{ => api}/LanguageMessage.java | 19 ++-- .../wolframalpha/{ => api}/PlainText.java | 4 +- .../wolframalpha/{ => api}/Pod.java | 4 +- .../wolframalpha/{ => api}/QueryResult.java | 11 +-- .../{ => api}/RelatedExample.java | 5 +- .../{ => api}/RelatedExamples.java | 5 +- .../wolframalpha/{ => api}/SubPod.java | 5 +- .../wolframalpha/{ => api}/Tip.java | 4 +- .../wolframalpha/{ => api}/Tips.java | 20 +++-- .../{ => api}/WolframAlphaImage.java | 5 +- .../org/togetherjava/tjbot/config/Config.java | 2 +- .../wolframalpha/WolframAlphaCommandTest.java | 75 ---------------- 23 files changed, 167 insertions(+), 224 deletions(-) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/DidYouMean.java (93%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/DidYouMeans.java (88%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/Error.java (95%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/ExamplePage.java (95%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/FutureTopic.java (83%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/LanguageMessage.java (76%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/PlainText.java (92%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/Pod.java (98%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/QueryResult.java (94%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/RelatedExample.java (97%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/RelatedExamples.java (96%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/SubPod.java (96%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/Tip.java (94%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/Tips.java (88%) rename application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/{ => api}/WolframAlphaImage.java (97%) delete mode 100644 application/src/test/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandTest.java diff --git a/application/config.json.template b/application/config.json.template index e0f9271047..38136da3c3 100644 --- a/application/config.json.template +++ b/application/config.json.template @@ -32,5 +32,6 @@ "hostBlacklist": ["bit.ly"], "suspiciousHostKeywords": ["discord", "nitro", "premium"], "isHostSimilarToKeywordDistanceThreshold": 2 - } + }, + "wolframAlphaAppId": "79J52T-6239TVXHR7" } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java index 3fccd30b54..3d9c7aa88d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -10,6 +10,7 @@ import org.togetherjava.tjbot.commands.free.FreeChannelMonitor; import org.togetherjava.tjbot.commands.free.FreeCommand; import org.togetherjava.tjbot.commands.mathcommands.TeXCommand; +import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.WolframAlphaCommand; import org.togetherjava.tjbot.commands.moderation.*; import org.togetherjava.tjbot.commands.moderation.scam.ScamBlocker; import org.togetherjava.tjbot.commands.moderation.scam.ScamHistoryPurgeRoutine; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java index f2bfbc6090..452b86ba2d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java @@ -2,10 +2,9 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; import net.dv8tion.jda.api.requests.restaction.WebhookMessageUpdateAction; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.awt.*; +import java.awt.Color; +import java.awt.Font; import java.awt.font.TextAttribute; import java.awt.image.ImageObserver; import java.net.http.HttpClient; @@ -14,11 +13,6 @@ public enum Constants { ; - /** - * Starting part of a regular wolframalpha query link. - */ - public static final String USER_ENDPOINT = "https://www.wolframalpha.com/input"; - static final Logger logger = LoggerFactory.getLogger(WolframAlphaCommand.class); /** * Maximum Embeds that can be sent in a {@link WebhookMessageUpdateAction} */ @@ -38,19 +32,10 @@ public enum Constants { static final XmlMapper XML = new XmlMapper(); static final int MAX_IMAGE_HEIGHT_PX = 400; - /** - * WolframAlpha text Color - */ - static final Color WOLFRAM_ALPHA_TEXT_COLOR = Color.decode("#3C3C3C"); - /** - * WolframAlpha Font - */ - static final Font WOLFRAM_ALPHA_FONT = new Font("Times", Font.PLAIN, 15) + static final Color AMBIENT_COLOR = Color.decode("#3C3C3C"); + static final Font AMBIENT_FONT = new Font("Times", Font.PLAIN, 15) .deriveFont(Map.of(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON)); - /** - * Height of the unscaled text displayed in Font {@link #WOLFRAM_ALPHA_FONT} - */ - static final int TEXT_HEIGHT = 20; + static final int AMBIENT_TEXT_HEIGHT = 20; static final int HTTP_STATUS_CODE_OK = 200; static final String QUERY_OPTION = "query"; /** @@ -61,5 +46,9 @@ public enum Constants { * Reference. */ static final String API_ENDPOINT = "http://api.wolframalpha.com/v2/query"; - static final HttpClient client = HttpClient.newHttpClient(); + /** + * WolframAlpha API endpoint for regular users (web frontend). + */ + public static final String USER_API_ENDPOINT = "https://www.wolframalpha.com/input"; + static final HttpClient CLIENT = HttpClient.newHttpClient(); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java index f2665d231f..196db02d08 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java @@ -1,6 +1,8 @@ package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; -public enum ResultStatus { +import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.QueryResult; + +enum ResultStatus { SUCCESS, NOT_SUCCESS, ERROR; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java index 7b87baba0d..880a00ddd3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java @@ -4,12 +4,15 @@ 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.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.requests.restaction.WebhookMessageUpdateAction; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.QueryResult; import org.togetherjava.tjbot.config.Config; import java.io.IOException; @@ -18,22 +21,31 @@ import java.util.Objects; import java.util.Optional; +/** + * Command to send a query to the Wolfram Alpha API. + * Renders its response as images. + */ public final class WolframAlphaCommand extends SlashCommandAdapter { + private static final Logger LOGGER = LoggerFactory.getLogger(WolframAlphaCommand.class); - private final String wolframAlphaAppId; + private final String appId; - public WolframAlphaCommand(Config config) { + /** + * Creates a new instance. + * + * @param config the config to use + */ + public WolframAlphaCommand(@NotNull Config config) { super("wolfram-alpha", "Renders mathematical queries using WolframAlpha", SlashCommandVisibility.GUILD); getData().addOption(OptionType.STRING, Constants.QUERY_OPTION, "the query to send to WolframAlpha", true); - wolframAlphaAppId = config.getWolframAlphaAppId(); + appId = config.getWolframAlphaAppId(); } @Override - public void onSlashCommand(@NotNull SlashCommandEvent event) { - - // The processing takes some time + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + // The API calls take a bit event.deferReply().queue(); String query = @@ -41,7 +53,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { MessageEmbed uriEmbed = new EmbedBuilder() .setTitle(query + "- Wolfram|Alpha", - UrlBuilder.fromString(Constants.USER_ENDPOINT) + UrlBuilder.fromString(Constants.USER_API_ENDPOINT) .addParameter("i", query) .toUri() .toString()) @@ -54,7 +66,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { HttpRequest request = HttpRequest .newBuilder(UrlBuilder.fromString(Constants.API_ENDPOINT) - .addParameter("appid", wolframAlphaAppId) + .addParameter("appid", appId) .addParameter("format", "image,plaintext") .addParameter("input", query) .toUri()) @@ -62,20 +74,19 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { .build(); Optional> optResponse = getResponse(request, action); - if (optResponse.isEmpty()) + if (optResponse.isEmpty()) { return; + } HttpResponse response = optResponse.get(); Optional optResult = WolframAlphaCommandUtils.parseQuery(response, action); - if (optResult.isEmpty()) + if (optResult.isEmpty()) { return; + } QueryResult result = optResult.get(); action = action.setContent("Computed in:" + result.getTiming()); action.setContent(switch (ResultStatus.getResultStatus(result)) { - case ERROR -> WolframAlphaCommandUtils.handleError(result); - case NOT_SUCCESS -> WolframAlphaCommandUtils.handleMisunderstoodQuery(result); - case SUCCESS -> "Here are the results of your query, Check the link for the complete results\n" + (result.getTimedOutPods().isEmpty() ? "" : "Some pods have timed out. Visit the URI") @@ -84,25 +95,25 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { }).queue(); } - private @NotNull Optional> getResponse(@NotNull HttpRequest request, + private static @NotNull Optional> getResponse(@NotNull HttpRequest request, @NotNull WebhookMessageUpdateAction action) { HttpResponse response; try { - response = Constants.client.send(request, HttpResponse.BodyHandlers.ofString()); + response = Constants.CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); } catch (IOException e) { action.setContent("Unable to get a response from WolframAlpha API").queue(); - Constants.logger.warn("Could not get the response from the server", e); + LOGGER.warn("Could not get the response from the server", e); return Optional.empty(); } catch (InterruptedException e) { action.setContent("Connection to WolframAlpha was interrupted").queue(); - Constants.logger.warn("Connection to WolframAlpha was interrupted", e); + LOGGER.warn("Connection to WolframAlpha was interrupted", e); Thread.currentThread().interrupt(); return Optional.empty(); } if (response.statusCode() != Constants.HTTP_STATUS_CODE_OK) { action.setContent("The response' status code was incorrect").queue(); - Constants.logger.warn("Unexpected status code: Expected: {} Actual: {}", + LOGGER.warn("Unexpected status code: Expected: {} Actual: {}", Constants.HTTP_STATUS_CODE_OK, response.statusCode()); return Optional.empty(); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java index 31c038afc1..e14dc15a5f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java @@ -5,19 +5,23 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.requests.restaction.WebhookMessageUpdateAction; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.*; import javax.imageio.ImageIO; -import java.awt.*; +import java.awt.Color; +import java.awt.Graphics; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URL; import java.net.http.HttpResponse; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -26,13 +30,16 @@ enum WolframAlphaCommandUtils { ; - static byte[] imageToBytes(BufferedImage img) throws IOException { + private static final Logger LOGGER = LoggerFactory.getLogger(WolframAlphaCommandUtils.class); + + static byte @NotNull [] imageToBytes(@NotNull RenderedImage img) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(img, "png", baos); return baos.toByteArray(); } - static BufferedImage combineImages(List images, int height) { + static @NotNull BufferedImage combineImages(@NotNull Collection images, + int height) { int width = images.stream().mapToInt(BufferedImage::getWidth).max().orElse(Integer.MAX_VALUE); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); @@ -47,24 +54,24 @@ static BufferedImage combineImages(List images, int height) { return image; } - static boolean compareImages(BufferedImage img1, BufferedImage img2) { - int width1 = img1.getWidth(); - int height1 = img1.getHeight(); - return width1 == img2.getWidth() && height1 == img2.getHeight() - && IntStream.range(0, width1) - .mapToObj(x -> IntStream.range(0, height1) - .anyMatch(y -> img1.getRGB(x, y) != img2.getRGB(x, y))) + static boolean compareImages(@NotNull BufferedImage first, @NotNull BufferedImage second) { + int firstWidth = first.getWidth(); + int firstHeight = first.getHeight(); + return firstWidth == second.getWidth() && firstHeight == second.getHeight() + && IntStream.range(0, firstWidth) + .mapToObj(x -> IntStream.range(0, firstHeight) + .anyMatch(y -> first.getRGB(x, y) != second.getRGB(x, y))) .noneMatch(x -> x); } - static int getWidth(String header) { - return (int) Constants.WOLFRAM_ALPHA_FONT + static int getWidth(@NotNull String header) { + return (int) Constants.AMBIENT_FONT .getStringBounds(header, new FontRenderContext(new AffineTransform(), true, true)) .getWidth(); } - static String handleMisunderstoodQuery(QueryResult result) { - List output = new ArrayList<>(); + static @NotNull String handleMisunderstoodQuery(@NotNull QueryResult result) { + Collection output = new ArrayList<>(); output .add("The Wolfram|Alpha API was unable to produce a successful result. Visit the URI"); Tips tips = result.getTips(); @@ -91,9 +98,9 @@ static String handleMisunderstoodQuery(QueryResult result) { } DidYouMeans didYouMeans = result.getDidYouMeans(); if (didYouMeans != null && didYouMeans.getCount() != 0) { - if (didYouMeans.getCount() == 1) + if (didYouMeans.getCount() == 1) { output.add("Did you mean: " + didYouMeans.getDidYouMeans().get(0).getMessage()); - else { + } else { output.add("Did you mean \n" + didYouMeans.getDidYouMeans() .stream() .map(DidYouMean::getMessage) @@ -118,9 +125,10 @@ static String handleMisunderstoodQuery(QueryResult result) { return String.join("\n", output); } - static String handleError(QueryResult result) { - Error error = result.getErrorTag(); - Constants.logger.error( + static @NotNull String handleError(@NotNull QueryResult result) { + org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error error = + result.getErrorTag(); + LOGGER.error( "Error getting response from Wolfram Alpha API: Error Code: {} Error Message: {}", error.getCode(), error.getMessage()); return "An error occurred while getting response from the Wolfram|Alpha API. Check the URI"; @@ -128,30 +136,23 @@ static String handleError(QueryResult result) { static @NotNull Optional parseQuery(@NotNull HttpResponse response, @NotNull WebhookMessageUpdateAction action) { - QueryResult result; try { - Files.writeString(Path - .of("C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\main\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\responsebody.xml"), - response.body()); - result = Constants.XML.readValue(response.body(), QueryResult.class); - + return Optional.of(Constants.XML.readValue(response.body(), QueryResult.class)); } catch (IOException e) { action.setContent("Unexpected response from WolframAlpha API").queue(); - Constants.logger.error("Unable to deserialize the class ", e); + LOGGER.error("Unable to deserialize the class ", e); return Optional.empty(); } - return Optional.of(result); } static @NotNull String handleSuccessfulResult(@NotNull QueryResult result, WebhookMessageUpdateAction action, MessageEmbed embed) { - int filesAttached = 0; int resultHeight = 0; EmbedBuilder embedBuilder = new EmbedBuilder(); - List embeds = new ArrayList<>(); + Collection embeds = new ArrayList<>(); embeds.add(embed); - List images = new ArrayList<>(); + Collection images = new ArrayList<>(); List pods = result.getPods(); for (Pod pod : pods) { List subPods = pod.getSubPods(); @@ -159,26 +160,27 @@ static String handleError(QueryResult result) { for (SubPod subPod : subPods) { WolframAlphaImage image = subPod.getImage(); try { - String source = image.getSource(); String header = pod.getTitle(); int width = (firstSubPod ? Math.max(getWidth(header), image.getWidth()) : image.getWidth()) + Constants.IMAGE_MARGIN_WIDTH; int height = image.getHeight(); - if (firstSubPod) - height += Constants.TEXT_HEIGHT; + if (firstSubPod) { + height += Constants.AMBIENT_TEXT_HEIGHT; + } BufferedImage readImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); Graphics graphics = readImage.getGraphics(); if (firstSubPod) { - graphics.setFont(Constants.WOLFRAM_ALPHA_FONT); + graphics.setFont(Constants.AMBIENT_FONT); graphics.setColor(Color.WHITE); - graphics.setColor(Constants.WOLFRAM_ALPHA_TEXT_COLOR); + graphics.setColor(Constants.AMBIENT_COLOR); graphics.drawString(header, Constants.IMAGE_MARGIN_WIDTH, Constants.IMAGE_MARGIN_HEIGHT); } graphics.drawImage(ImageIO.read(new URL(source)), Constants.IMAGE_MARGIN_WIDTH, - firstSubPod ? Constants.TEXT_HEIGHT : 0, Constants.IMAGE_OBSERVER); + firstSubPod ? Constants.AMBIENT_TEXT_HEIGHT : 0, + Constants.IMAGE_OBSERVER); if (filesAttached == Constants.MAX_EMBEDS) { // noinspection ResultOfMethodCallIgnored @@ -186,24 +188,25 @@ static String handleError(QueryResult result) { return "Too many images. Visit the URI"; } - if (resultHeight + image.getHeight() > Constants.MAX_IMAGE_HEIGHT_PX) { BufferedImage combinedImage = WolframAlphaCommandUtils.combineImages(images, resultHeight); images.clear(); + filesAttached++; action = action.addFile( WolframAlphaCommandUtils.imageToBytes(combinedImage), - "result%d.png".formatted(++filesAttached)); + "result%d.png".formatted(filesAttached)); resultHeight = 0; embeds.add(embedBuilder .setImage("attachment://result%d.png".formatted(filesAttached)) .build()); } else if (pod == pods.get(pods.size() - 1) && subPod == subPods.get(subPods.size() - 1) && !images.isEmpty()) { + filesAttached++; action = action.addFile( WolframAlphaCommandUtils.imageToBytes(WolframAlphaCommandUtils .combineImages(images, resultHeight + image.getHeight())), - "result%d.png".formatted(++filesAttached)); + "result%d.png".formatted(filesAttached)); images.clear(); embeds.add(embedBuilder .setImage("attachment://result%d.png".formatted(filesAttached)) @@ -213,13 +216,14 @@ static String handleError(QueryResult result) { images.add(readImage); firstSubPod = false; } catch (IOException e) { - Constants.logger.error("Failed to read image {} from the WolframAlpha response", - image, e); + LOGGER.error("Failed to read image {} from the WolframAlpha response", image, + e); return "Unable to generate message based on the WolframAlpha response"; } } } // noinspection ResultOfMethodCallIgnored + // FIXME This wont work at all if it got reassigned or similar, has to be returned or sth action.setEmbeds(embeds); return ""; } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMean.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMean.java similarity index 93% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMean.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMean.java index e4c35d33c9..9dca4c86e3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMean.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMean.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonRootName; @@ -6,8 +6,7 @@ @JsonRootName("didyoumean") @JsonIgnoreProperties(ignoreUnknown = true) -final class DidYouMean { - +public final class DidYouMean { @JacksonXmlText private String message; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMeans.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMeans.java similarity index 88% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMeans.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMeans.java index 5fda806809..a8482e005f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/DidYouMeans.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMeans.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -11,17 +11,21 @@ import java.util.List; /** - *

- * Example Query: btuyghe
- * Result: {@code + * Example Query: btuyghe + * + * Response: + * + *

+ * {@code
+ * 
  *   tighe
- *   }
- * 

+ * + * } + *
*/ @JsonRootName("didyoumeans") @JsonIgnoreProperties(ignoreUnknown = true) -final class DidYouMeans { - +public final class DidYouMeans { @JacksonXmlProperty(isAttribute = true) private int count; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Error.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Error.java similarity index 95% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Error.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Error.java index be6b8bd402..00d8c53765 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Error.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Error.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -6,7 +6,7 @@ @JsonRootName("error") @JsonIgnoreProperties(ignoreUnknown = true) -final class Error { +public final class Error { private int code; @JsonProperty("msg") private String message; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ExamplePage.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/ExamplePage.java similarity index 95% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ExamplePage.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/ExamplePage.java index 794376595c..a2382ec403 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ExamplePage.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/ExamplePage.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonRootName; @@ -6,8 +6,7 @@ @JsonRootName("examplepage") @JsonIgnoreProperties(ignoreUnknown = true) -final class ExamplePage { - +public final class ExamplePage { @JacksonXmlProperty(isAttribute = true) private String category; @JacksonXmlProperty(isAttribute = true) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/FutureTopic.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/FutureTopic.java similarity index 83% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/FutureTopic.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/FutureTopic.java index 0e476c1027..0e6e23f457 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/FutureTopic.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/FutureTopic.java @@ -1,20 +1,24 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; /** - *

- * Example Query: Operating Systems
- * Result: {@code } - *

+ * Example Query: Operating Systems + * + * Response: + * + *
+ * {@code
+ * 
+ * }
+ * 
*/ @JsonRootName("futuretopic") @JsonIgnoreProperties(ignoreUnknown = true) -final class FutureTopic { - +public final class FutureTopic { @JacksonXmlProperty(isAttribute = true) private String topic; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/LanguageMessage.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/LanguageMessage.java similarity index 76% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/LanguageMessage.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/LanguageMessage.java index c207d24aa4..5b43c51416 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/LanguageMessage.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/LanguageMessage.java @@ -1,21 +1,24 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; /** + * Example Query: ¿donde soy tu? * - *

- * Example Query: ¿donde soy tu?
- * Result: {@code } - *

+ * Response: + * + *
+ * {@code
+ * 
+ * }
+ * 
*/ @JsonRootName("langugemsg") @JsonIgnoreProperties(ignoreUnknown = true) -final class LanguageMessage { - +public final class LanguageMessage { @JacksonXmlProperty(isAttribute = true) private String english; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/PlainText.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/PlainText.java similarity index 92% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/PlainText.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/PlainText.java index f07ba095f3..686f7b71fe 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/PlainText.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/PlainText.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonRootName; @@ -6,7 +6,7 @@ @JsonRootName("plaintext") @JsonIgnoreProperties(ignoreUnknown = true) -final class PlainText { +public final class PlainText { @JacksonXmlText private String text; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Pod.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Pod.java similarity index 98% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Pod.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Pod.java index 3e7d0cfa72..93ca256bfe 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Pod.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Pod.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -12,7 +12,7 @@ @JsonRootName("pod") @JsonIgnoreProperties(ignoreUnknown = true) -final class Pod { +public final class Pod { @JacksonXmlProperty(isAttribute = true) private String title; @JacksonXmlProperty(isAttribute = true) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/QueryResult.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java similarity index 94% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/QueryResult.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java index 0120b5deee..41547f389c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/QueryResult.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.dataformat.xml.XmlMapper; @@ -12,7 +12,7 @@ @JsonRootName("queryresult") @JsonIgnoreProperties(ignoreUnknown = true) -final class QueryResult { +public final class QueryResult { private static final XmlMapper XML = new XmlMapper(); @JacksonXmlProperty(isAttribute = true) @@ -51,7 +51,7 @@ final class QueryResult { @JacksonXmlElementWrapper(useWrapping = false) private List pods; @JsonIgnore - private Error errorTag; + private org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error errorTag; public boolean isSuccess() { return success; @@ -213,7 +213,7 @@ public void setRelatedExamples(RelatedExamples relatedExamples) { } @SuppressWarnings("unused") - public Error getErrorTag() { + public org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error getErrorTag() { return errorTag; } @@ -233,7 +233,8 @@ public void setError(String name, Object value) { return; } if (value instanceof Map) { - errorTag = XML.convertValue(value, Error.class); + errorTag = XML.convertValue(value, + org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error.class); return; } throw new IllegalArgumentException("Unsupported error format"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExample.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExample.java similarity index 97% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExample.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExample.java index 0c6507730f..62bfe13475 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExample.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExample.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonRootName; @@ -6,8 +6,7 @@ @JsonRootName("relatedexample") @JsonIgnoreProperties(ignoreUnknown = true) -final class RelatedExample { - +public final class RelatedExample { @JacksonXmlProperty(isAttribute = true) private String input; @JacksonXmlProperty(isAttribute = true, localName = "desc") diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExamples.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExamples.java similarity index 96% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExamples.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExamples.java index 014ee0a8c9..df14a99e25 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/RelatedExamples.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExamples.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -12,8 +12,7 @@ @JsonRootName("relatedexamples") @JsonIgnoreProperties(ignoreUnknown = true) -final class RelatedExamples { - +public final class RelatedExamples { @JacksonXmlProperty(isAttribute = true) private int count; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/SubPod.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/SubPod.java similarity index 96% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/SubPod.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/SubPod.java index cc5367f051..39fcc4970a 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/SubPod.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/SubPod.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -7,8 +7,7 @@ @JsonRootName("subpod") @JsonIgnoreProperties(ignoreUnknown = true) - -final class SubPod { +public final class SubPod { @JacksonXmlProperty(isAttribute = true) private String title; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tip.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tip.java similarity index 94% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tip.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tip.java index 5ab05d90e5..0ec29d94e6 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tip.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tip.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonRootName; @@ -6,7 +6,7 @@ @JsonRootName("tip") @JsonIgnoreProperties(ignoreUnknown = true) -final class Tip { +public final class Tip { @JacksonXmlProperty(isAttribute = true) private String text; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tips.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tips.java similarity index 88% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tips.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tips.java index a0a31ce1a3..04cfef5a5d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Tips.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tips.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -11,17 +11,21 @@ import java.util.List; /** - *

- * Example Query: bhefjuhkynmbtg
- * Result:
- * {@code + * Example Query: bhefjuhkynmbtg + * + * Response: + * + *

+ * {@code
+ * 
  *   
- *  }
- * 

+ * + * } + *
*/ @JsonRootName("tips") @JsonIgnoreProperties(ignoreUnknown = true) -final class Tips { +public final class Tips { @JacksonXmlProperty(isAttribute = true) private int count; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImage.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/WolframAlphaImage.java similarity index 97% rename from application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImage.java rename to application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/WolframAlphaImage.java index 5666de9547..0573b37e2f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImage.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/WolframAlphaImage.java @@ -1,13 +1,12 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; - @JsonRootName("img") @JsonIgnoreProperties(ignoreUnknown = true) -final class WolframAlphaImage { +public final class WolframAlphaImage { @JacksonXmlProperty(isAttribute = true, localName = "src") private String source; @JacksonXmlProperty(isAttribute = true) diff --git a/application/src/main/java/org/togetherjava/tjbot/config/Config.java b/application/src/main/java/org/togetherjava/tjbot/config/Config.java index e2ea866b08..f12e80a543 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/Config.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/Config.java @@ -30,7 +30,7 @@ public final class Config { private final SuggestionsConfig suggestions; private final String quarantinedRolePattern; private final ScamBlockerConfig scamBlocker; - private final String wolframAlphaAppId; + private final String wolframAlphaAppId; @SuppressWarnings("ConstructorWithTooManyParameters") @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) diff --git a/application/src/test/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandTest.java deleted file mode 100644 index fe3bb728bd..0000000000 --- a/application/src/test/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; - -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.imageio.ImageIO; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -final class WolframAlphaCommandTest { - private static final Logger logger = LoggerFactory.getLogger( - org.togetherjava.tjbot.commands.mathcommands.wolframalpha.WolframAlphaCommandTest.class); - - @Test - void compareImagesTest() { - BufferedImage image1 = new BufferedImage(100, 100, 6); - BufferedImage image2 = new BufferedImage(100, 100, 6); - image1.getGraphics().setColor(Color.RED); - image2.getGraphics().setColor(Color.YELLOW); - assertFalse(WolframAlphaCommandUtils.compareImages(image1, image2)); - } - - @Test - void combineImagesTest() throws IOException { - BufferedImage image1 = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); - BufferedImage image2 = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); - BufferedImage image3 = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); - BufferedImage mergedImage = new BufferedImage(100, 300, BufferedImage.TYPE_INT_ARGB_PRE); - Graphics g1 = image1.getGraphics(); - g1.setColor(Color.RED); - g1.fillRect(0, 0, 100, 100); - File img1 = Path - .of("C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\test\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\img1.png") - .toFile(); - ImageIO.write(image1, "png", img1); - logger.info("image 1 successfully written"); - Graphics g2 = image2.getGraphics(); - g2.setColor(Color.BLUE); - g2.fillRect(0, 0, 100, 100); - File img2 = Path - .of("C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\test\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\img2.png") - .toFile(); - ImageIO.write(image2, "png", img2); - logger.info("image 2 successfully written"); - Graphics g3 = image3.getGraphics(); - g3.setColor(Color.YELLOW); - g3.fillRect(0, 0, 100, 100); - ImageIO.write(image3, "png", new File( - "C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\test\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\img3.png")); - logger.info("image 3 successfully written"); - Graphics g4 = mergedImage.getGraphics(); - g4.setColor(Color.RED); - g4.fillRect(0, 0, 100, 100); - g4.setColor(Color.BLUE); - g4.fillRect(0, 100, 100, 100); - g4.setColor(Color.YELLOW); - g4.fillRect(0, 200, 100, 100); - ImageIO.write(mergedImage, "png", new File( - "C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\test\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\manuallyMergedImg.png")); - BufferedImage mergedByMethod = - WolframAlphaCommandUtils.combineImages(List.of(image1, image2, image3), 300); - ImageIO.write(mergedByMethod, "png", new File( - "C:\\Users\\Abc\\IdeaProjects\\TJ-Bot-baseRepo\\application\\src\\test\\java\\org\\togetherjava\\tjbot\\commands\\mathcommands\\wolframalpha\\methodMergedimage.png")); - assertTrue(WolframAlphaCommandUtils.compareImages(mergedImage, mergedByMethod)); - } -} - From 486f8d216de2f10a86b4dba25605a283ebabbde8 Mon Sep 17 00:00:00 2001 From: Zabuzard Date: Thu, 5 May 2022 12:15:43 +0200 Subject: [PATCH 3/5] Some adjustments, inlined "Constants", removed utility --- .../mathcommands/wolframalpha/Constants.java | 54 ------------------- .../wolframalpha/WolframAlphaCommand.java | 53 ++++++++++-------- .../WolframAlphaCommandUtils.java | 52 +++++++++++------- 3 files changed, 64 insertions(+), 95 deletions(-) delete mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java deleted file mode 100644 index 452b86ba2d..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/Constants.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; - -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import net.dv8tion.jda.api.requests.restaction.WebhookMessageUpdateAction; - -import java.awt.Color; -import java.awt.Font; -import java.awt.font.TextAttribute; -import java.awt.image.ImageObserver; -import java.net.http.HttpClient; -import java.util.Map; - -public enum Constants { - ; - - /** - * Maximum Embeds that can be sent in a {@link WebhookMessageUpdateAction} - */ - static final int MAX_EMBEDS = 10; - /** - * The image observer used for getting data from images. - */ - static final ImageObserver IMAGE_OBSERVER = null; - /** - * The width of the margins of the images generated - */ - static final int IMAGE_MARGIN_WIDTH = 10; - /** - * The height of the margins of the images generated - */ - public static final int IMAGE_MARGIN_HEIGHT = 15; - - static final XmlMapper XML = new XmlMapper(); - static final int MAX_IMAGE_HEIGHT_PX = 400; - static final Color AMBIENT_COLOR = Color.decode("#3C3C3C"); - static final Font AMBIENT_FONT = new Font("Times", Font.PLAIN, 15) - .deriveFont(Map.of(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON)); - static final int AMBIENT_TEXT_HEIGHT = 20; - static final int HTTP_STATUS_CODE_OK = 200; - static final String QUERY_OPTION = "query"; - /** - * WolframAlpha API endpoint to connect to. - * - * @see WolframAlpha API - * Reference. - */ - static final String API_ENDPOINT = "http://api.wolframalpha.com/v2/query"; - /** - * WolframAlpha API endpoint for regular users (web frontend). - */ - public static final String USER_API_ENDPOINT = "https://www.wolframalpha.com/input"; - static final HttpClient CLIENT = HttpClient.newHttpClient(); -} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java index 880a00ddd3..397e987f2b 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java @@ -16,9 +16,10 @@ import org.togetherjava.tjbot.config.Config; import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.util.Objects; import java.util.Optional; /** @@ -27,6 +28,20 @@ */ public final class WolframAlphaCommand extends SlashCommandAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(WolframAlphaCommand.class); + private static final String QUERY_OPTION = "query"; + /** + * WolframAlpha API endpoint to connect to. + * + * @see WolframAlpha API + * Reference. + */ + private static final String API_ENDPOINT = "http://api.wolframalpha.com/v2/query"; + /** + * WolframAlpha API endpoint for regular users (web frontend). + */ + private static final String USER_API_ENDPOINT = "https://www.wolframalpha.com/input"; + private static final HttpClient CLIENT = HttpClient.newHttpClient(); private final String appId; @@ -38,8 +53,8 @@ public final class WolframAlphaCommand extends SlashCommandAdapter { public WolframAlphaCommand(@NotNull Config config) { super("wolfram-alpha", "Renders mathematical queries using WolframAlpha", SlashCommandVisibility.GUILD); - getData().addOption(OptionType.STRING, Constants.QUERY_OPTION, - "the query to send to WolframAlpha", true); + getData().addOption(OptionType.STRING, QUERY_OPTION, "the query to send to WolframAlpha", + true); appId = config.getWolframAlphaAppId(); } @@ -48,24 +63,20 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { // The API calls take a bit event.deferReply().queue(); - String query = - Objects.requireNonNull(event.getOption(Constants.QUERY_OPTION)).getAsString(); + String query = event.getOption(QUERY_OPTION).getAsString(); MessageEmbed uriEmbed = new EmbedBuilder() .setTitle(query + "- Wolfram|Alpha", - UrlBuilder.fromString(Constants.USER_API_ENDPOINT) + UrlBuilder.fromString(USER_API_ENDPOINT) .addParameter("i", query) .toUri() .toString()) - .setDescription( - "Wolfram|Alpha brings expert-level knowledge and capabilities to the broadest possible range of people-spanning all professions and education levels.") .build(); - WebhookMessageUpdateAction action = - event.getHook().editOriginal("").setEmbeds(uriEmbed); + WebhookMessageUpdateAction action = event.getHook().editOriginalEmbeds(uriEmbed); HttpRequest request = HttpRequest - .newBuilder(UrlBuilder.fromString(Constants.API_ENDPOINT) + .newBuilder(UrlBuilder.fromString(API_ENDPOINT) .addParameter("appid", appId) .addParameter("format", "image,plaintext") .addParameter("input", query) @@ -73,21 +84,21 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { .GET() .build(); - Optional> optResponse = getResponse(request, action); - if (optResponse.isEmpty()) { + Optional> maybeResponse = getResponse(request, action); + if (maybeResponse.isEmpty()) { return; } - HttpResponse response = optResponse.get(); - Optional optResult = WolframAlphaCommandUtils.parseQuery(response, action); - if (optResult.isEmpty()) { + HttpResponse response = maybeResponse.orElseThrow(); + Optional maybeResult = WolframAlphaCommandUtils.parseQuery(response, action); + if (maybeResult.isEmpty()) { return; } - QueryResult result = optResult.get(); + QueryResult result = maybeResult.orElseThrow(); action = action.setContent("Computed in:" + result.getTiming()); action.setContent(switch (ResultStatus.getResultStatus(result)) { case ERROR -> WolframAlphaCommandUtils.handleError(result); case NOT_SUCCESS -> WolframAlphaCommandUtils.handleMisunderstoodQuery(result); - case SUCCESS -> "Here are the results of your query, Check the link for the complete results\n" + case SUCCESS -> "Check the above link for the complete results\n" + (result.getTimedOutPods().isEmpty() ? "" : "Some pods have timed out. Visit the URI") + "\n" @@ -99,7 +110,7 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { @NotNull WebhookMessageUpdateAction action) { HttpResponse response; try { - response = Constants.CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); + response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); } catch (IOException e) { action.setContent("Unable to get a response from WolframAlpha API").queue(); LOGGER.warn("Could not get the response from the server", e); @@ -111,10 +122,10 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { return Optional.empty(); } - if (response.statusCode() != Constants.HTTP_STATUS_CODE_OK) { + if (response.statusCode() != HttpURLConnection.HTTP_OK) { action.setContent("The response' status code was incorrect").queue(); LOGGER.warn("Unexpected status code: Expected: {} Actual: {}", - Constants.HTTP_STATUS_CODE_OK, response.statusCode()); + HttpURLConnection.HTTP_OK, response.statusCode()); return Optional.empty(); } return Optional.of(response); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java index e14dc15a5f..4ad009ac66 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java @@ -1,5 +1,6 @@ package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; @@ -11,8 +12,10 @@ import javax.imageio.ImageIO; import java.awt.Color; +import java.awt.Font; import java.awt.Graphics; import java.awt.font.FontRenderContext; +import java.awt.font.TextAttribute; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; @@ -20,18 +23,29 @@ import java.io.IOException; import java.net.URL; import java.net.http.HttpResponse; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; enum WolframAlphaCommandUtils { ; - private static final Logger LOGGER = LoggerFactory.getLogger(WolframAlphaCommandUtils.class); + /** + * Maximum Embeds that can be sent in a {@link WebhookMessageUpdateAction} + */ + private static final int MAX_EMBEDS = 10; + private static final int IMAGE_MARGIN_WIDTH_PX = 10; + private static final int IMAGE_MARGIN_HEIGHT_PX = 15; + private static final int MAX_IMAGE_HEIGHT_PX = 400; + + private static final Color AMBIENT_COLOR = Color.decode("#3C3C3C"); + private static final Font AMBIENT_FONT = new Font("Times", Font.PLAIN, 15) + .deriveFont(Map.of(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON)); + private static final int AMBIENT_TEXT_HEIGHT = 20; + + private static final XmlMapper XML = new XmlMapper(); + static byte @NotNull [] imageToBytes(@NotNull RenderedImage img) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(img, "png", baos); @@ -48,8 +62,8 @@ enum WolframAlphaCommandUtils { imgGraphics.fillRect(0, 0, width, height); int resultHeight = 0; for (BufferedImage img : images) { - imgGraphics.drawImage(img, 0, resultHeight, Constants.IMAGE_OBSERVER); - resultHeight += img.getHeight(Constants.IMAGE_OBSERVER); + imgGraphics.drawImage(img, 0, resultHeight, null); + resultHeight += img.getHeight(null); } return image; } @@ -65,7 +79,7 @@ static boolean compareImages(@NotNull BufferedImage first, @NotNull BufferedImag } static int getWidth(@NotNull String header) { - return (int) Constants.AMBIENT_FONT + return (int) AMBIENT_FONT .getStringBounds(header, new FontRenderContext(new AffineTransform(), true, true)) .getWidth(); } @@ -137,7 +151,7 @@ static int getWidth(@NotNull String header) { static @NotNull Optional parseQuery(@NotNull HttpResponse response, @NotNull WebhookMessageUpdateAction action) { try { - return Optional.of(Constants.XML.readValue(response.body(), QueryResult.class)); + return Optional.of(XML.readValue(response.body(), QueryResult.class)); } catch (IOException e) { action.setContent("Unexpected response from WolframAlpha API").queue(); LOGGER.error("Unable to deserialize the class ", e); @@ -163,32 +177,30 @@ static int getWidth(@NotNull String header) { String source = image.getSource(); String header = pod.getTitle(); int width = (firstSubPod ? Math.max(getWidth(header), image.getWidth()) - : image.getWidth()) + Constants.IMAGE_MARGIN_WIDTH; + : image.getWidth()) + IMAGE_MARGIN_WIDTH_PX; int height = image.getHeight(); if (firstSubPod) { - height += Constants.AMBIENT_TEXT_HEIGHT; + height += AMBIENT_TEXT_HEIGHT; } BufferedImage readImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); Graphics graphics = readImage.getGraphics(); if (firstSubPod) { - graphics.setFont(Constants.AMBIENT_FONT); + graphics.setFont(AMBIENT_FONT); graphics.setColor(Color.WHITE); - graphics.setColor(Constants.AMBIENT_COLOR); - graphics.drawString(header, Constants.IMAGE_MARGIN_WIDTH, - Constants.IMAGE_MARGIN_HEIGHT); + graphics.setColor(AMBIENT_COLOR); + graphics.drawString(header, IMAGE_MARGIN_WIDTH_PX, IMAGE_MARGIN_HEIGHT_PX); } - graphics.drawImage(ImageIO.read(new URL(source)), Constants.IMAGE_MARGIN_WIDTH, - firstSubPod ? Constants.AMBIENT_TEXT_HEIGHT : 0, - Constants.IMAGE_OBSERVER); + graphics.drawImage(ImageIO.read(new URL(source)), IMAGE_MARGIN_WIDTH_PX, + firstSubPod ? AMBIENT_TEXT_HEIGHT : 0, null); - if (filesAttached == Constants.MAX_EMBEDS) { + if (filesAttached == MAX_EMBEDS) { // noinspection ResultOfMethodCallIgnored action.setEmbeds(embeds); return "Too many images. Visit the URI"; } - if (resultHeight + image.getHeight() > Constants.MAX_IMAGE_HEIGHT_PX) { + if (resultHeight + image.getHeight() > MAX_IMAGE_HEIGHT_PX) { BufferedImage combinedImage = WolframAlphaCommandUtils.combineImages(images, resultHeight); images.clear(); From c1da29bfd37d9b9e82d96710865cc2a66d18bccd Mon Sep 17 00:00:00 2001 From: Zabuzard Date: Wed, 11 May 2022 16:38:28 +0200 Subject: [PATCH 4/5] Reworked /wolfram-alpha and its code --- .../wolframalpha/ResultStatus.java | 19 -- .../wolframalpha/WolframAlphaCommand.java | 87 ++---- .../WolframAlphaCommandUtils.java | 242 ---------------- .../wolframalpha/WolframAlphaHandler.java | 272 ++++++++++++++++++ .../wolframalpha/WolframAlphaImages.java | 155 ++++++++++ .../wolframalpha/api/DidYouMean.java | 3 + .../wolframalpha/api/DidYouMeans.java | 10 +- .../mathcommands/wolframalpha/api/Error.java | 3 + .../wolframalpha/api/ExamplePage.java | 3 + .../wolframalpha/api/PlainText.java | 3 + .../mathcommands/wolframalpha/api/Pod.java | 3 + .../wolframalpha/api/QueryResult.java | 9 + .../wolframalpha/api/RelatedExample.java | 3 + .../wolframalpha/api/RelatedExamples.java | 13 +- .../mathcommands/wolframalpha/api/SubPod.java | 3 + .../mathcommands/wolframalpha/api/Tip.java | 3 + .../mathcommands/wolframalpha/api/Tips.java | 10 +- .../wolframalpha/api/WolframAlphaImage.java | 3 + .../wolframalpha/api/package-info.java | 6 + .../wolframalpha/package-info.java | 5 + 20 files changed, 512 insertions(+), 343 deletions(-) delete mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java delete mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaHandler.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/package-info.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/package-info.java diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java deleted file mode 100644 index 196db02d08..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/ResultStatus.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; - -import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.QueryResult; - -enum ResultStatus { - SUCCESS, - NOT_SUCCESS, - ERROR; - - static ResultStatus getResultStatus(QueryResult result) { - if (result.isSuccess()) { - return SUCCESS; - } else if (!result.isError()) { - return NOT_SUCCESS; - } else { - return ERROR; - } - } -} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java index 397e987f2b..90109410c2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java @@ -1,33 +1,26 @@ package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; import io.mikael.urlbuilder.UrlBuilder; -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.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.callbacks.IDeferrableCallback; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.requests.restaction.WebhookMessageUpdateAction; import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; -import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.QueryResult; import org.togetherjava.tjbot.config.Config; -import java.io.IOException; -import java.net.HttpURLConnection; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.util.Optional; +import java.util.concurrent.CompletableFuture; /** * Command to send a query to the Wolfram Alpha API. * Renders its response as images. */ public final class WolframAlphaCommand extends SlashCommandAdapter { - private static final Logger LOGGER = LoggerFactory.getLogger(WolframAlphaCommand.class); private static final String QUERY_OPTION = "query"; /** * WolframAlpha API endpoint to connect to. @@ -37,10 +30,6 @@ public final class WolframAlphaCommand extends SlashCommandAdapter { * Reference. */ private static final String API_ENDPOINT = "http://api.wolframalpha.com/v2/query"; - /** - * WolframAlpha API endpoint for regular users (web frontend). - */ - private static final String USER_API_ENDPOINT = "https://www.wolframalpha.com/input"; private static final HttpClient CLIENT = HttpClient.newHttpClient(); private final String appId; @@ -60,21 +49,13 @@ public WolframAlphaCommand(@NotNull Config config) { @Override public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { - // The API calls take a bit - event.deferReply().queue(); - String query = event.getOption(QUERY_OPTION).getAsString(); + WolframAlphaHandler handler = new WolframAlphaHandler(query); - MessageEmbed uriEmbed = new EmbedBuilder() - .setTitle(query + "- Wolfram|Alpha", - UrlBuilder.fromString(USER_API_ENDPOINT) - .addParameter("i", query) - .toUri() - .toString()) - .build(); - - WebhookMessageUpdateAction action = event.getHook().editOriginalEmbeds(uriEmbed); + // The API call takes a bit + event.deferReply().queue(); + // Send query HttpRequest request = HttpRequest .newBuilder(UrlBuilder.fromString(API_ENDPOINT) .addParameter("appid", appId) @@ -84,51 +65,23 @@ public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { .GET() .build(); - Optional> maybeResponse = getResponse(request, action); - if (maybeResponse.isEmpty()) { - return; - } - HttpResponse response = maybeResponse.orElseThrow(); - Optional maybeResult = WolframAlphaCommandUtils.parseQuery(response, action); - if (maybeResult.isEmpty()) { - return; - } - QueryResult result = maybeResult.orElseThrow(); - action = action.setContent("Computed in:" + result.getTiming()); - action.setContent(switch (ResultStatus.getResultStatus(result)) { - case ERROR -> WolframAlphaCommandUtils.handleError(result); - case NOT_SUCCESS -> WolframAlphaCommandUtils.handleMisunderstoodQuery(result); - case SUCCESS -> "Check the above link for the complete results\n" - + (result.getTimedOutPods().isEmpty() ? "" - : "Some pods have timed out. Visit the URI") - + "\n" - + WolframAlphaCommandUtils.handleSuccessfulResult(result, action, uriEmbed); - }).queue(); + CompletableFuture> apiResponse = + CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()); + + // Parse and respond + apiResponse.thenApply(handler::handleApiResponse) + .thenAccept(response -> sendResponse(response, event)); } - private static @NotNull Optional> getResponse(@NotNull HttpRequest request, - @NotNull WebhookMessageUpdateAction action) { - HttpResponse response; - try { - response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString()); - } catch (IOException e) { - action.setContent("Unable to get a response from WolframAlpha API").queue(); - LOGGER.warn("Could not get the response from the server", e); - return Optional.empty(); - } catch (InterruptedException e) { - action.setContent("Connection to WolframAlpha was interrupted").queue(); - LOGGER.warn("Connection to WolframAlpha was interrupted", e); - Thread.currentThread().interrupt(); - return Optional.empty(); - } + private static void sendResponse(@NotNull WolframAlphaHandler.HandlerResponse response, + @NotNull IDeferrableCallback event) { + WebhookMessageUpdateAction action = + event.getHook().editOriginalEmbeds(response.embeds()); - if (response.statusCode() != HttpURLConnection.HTTP_OK) { - action.setContent("The response' status code was incorrect").queue(); - LOGGER.warn("Unexpected status code: Expected: {} Actual: {}", - HttpURLConnection.HTTP_OK, response.statusCode()); - return Optional.empty(); + for (WolframAlphaHandler.Attachment attachment : response.attachments()) { + action = action.addFile(attachment.data(), attachment.name()); } - return Optional.of(response); - } + action.queue(); + } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java deleted file mode 100644 index 4ad009ac66..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommandUtils.java +++ /dev/null @@ -1,242 +0,0 @@ -package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; - -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -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.requests.restaction.WebhookMessageUpdateAction; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.*; - -import javax.imageio.ImageIO; -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.font.FontRenderContext; -import java.awt.font.TextAttribute; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.awt.image.RenderedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URL; -import java.net.http.HttpResponse; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -enum WolframAlphaCommandUtils { - ; - private static final Logger LOGGER = LoggerFactory.getLogger(WolframAlphaCommandUtils.class); - - /** - * Maximum Embeds that can be sent in a {@link WebhookMessageUpdateAction} - */ - private static final int MAX_EMBEDS = 10; - private static final int IMAGE_MARGIN_WIDTH_PX = 10; - private static final int IMAGE_MARGIN_HEIGHT_PX = 15; - private static final int MAX_IMAGE_HEIGHT_PX = 400; - - private static final Color AMBIENT_COLOR = Color.decode("#3C3C3C"); - private static final Font AMBIENT_FONT = new Font("Times", Font.PLAIN, 15) - .deriveFont(Map.of(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON)); - private static final int AMBIENT_TEXT_HEIGHT = 20; - - private static final XmlMapper XML = new XmlMapper(); - - static byte @NotNull [] imageToBytes(@NotNull RenderedImage img) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ImageIO.write(img, "png", baos); - return baos.toByteArray(); - } - - static @NotNull BufferedImage combineImages(@NotNull Collection images, - int height) { - int width = - images.stream().mapToInt(BufferedImage::getWidth).max().orElse(Integer.MAX_VALUE); - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); - Graphics imgGraphics = image.getGraphics(); - imgGraphics.setColor(Color.WHITE); - imgGraphics.fillRect(0, 0, width, height); - int resultHeight = 0; - for (BufferedImage img : images) { - imgGraphics.drawImage(img, 0, resultHeight, null); - resultHeight += img.getHeight(null); - } - return image; - } - - static boolean compareImages(@NotNull BufferedImage first, @NotNull BufferedImage second) { - int firstWidth = first.getWidth(); - int firstHeight = first.getHeight(); - return firstWidth == second.getWidth() && firstHeight == second.getHeight() - && IntStream.range(0, firstWidth) - .mapToObj(x -> IntStream.range(0, firstHeight) - .anyMatch(y -> first.getRGB(x, y) != second.getRGB(x, y))) - .noneMatch(x -> x); - } - - static int getWidth(@NotNull String header) { - return (int) AMBIENT_FONT - .getStringBounds(header, new FontRenderContext(new AffineTransform(), true, true)) - .getWidth(); - } - - static @NotNull String handleMisunderstoodQuery(@NotNull QueryResult result) { - Collection output = new ArrayList<>(); - output - .add("The Wolfram|Alpha API was unable to produce a successful result. Visit the URI"); - Tips tips = result.getTips(); - if (tips != null && tips.getCount() != 0) { - if (tips.getCount() == 1) { - output.add("Here is a tip: " + tips.getTips().get(0).getText()); - } else { - output.add("Here are some tips: \n" + tips.getTips() - .stream() - .map(Tip::getText) - .map(text -> "• " + text) - .collect(Collectors.joining("\n"))); - } - } - FutureTopic futureTopic = result.getFutureTopic(); - if (futureTopic != null) { - output.add( - "Your query is regarding The topic \"%s\" which might be supported by Wolfram Alpha in the future" - .formatted(futureTopic.getTopic())); - } - LanguageMessage languageMsg = result.getLanguageMessage(); - if (languageMsg != null) { - output.add(languageMsg.getEnglish() + "\n" + languageMsg.getOther()); - } - DidYouMeans didYouMeans = result.getDidYouMeans(); - if (didYouMeans != null && didYouMeans.getCount() != 0) { - if (didYouMeans.getCount() == 1) { - output.add("Did you mean: " + didYouMeans.getDidYouMeans().get(0).getMessage()); - } else { - output.add("Did you mean \n" + didYouMeans.getDidYouMeans() - .stream() - .map(DidYouMean::getMessage) - .map(text -> "• " + text) - .collect(Collectors.joining("\n"))); - } - } - RelatedExamples relatedExamples = result.getRelatedExamples(); - if (relatedExamples != null && relatedExamples.getCount() != 0) { - if (relatedExamples.getCount() == 1) { - output.add("Here is a related example: " - + relatedExamples.getRelatedExamples().get(0).getCategoryThumb()); - } else { - output - .add("Here are some related examples \n" + relatedExamples.getRelatedExamples() - .stream() - .map(RelatedExample::getCategoryThumb) - .map(text -> "• " + text) - .collect(Collectors.joining("\n"))); - } - } - return String.join("\n", output); - } - - static @NotNull String handleError(@NotNull QueryResult result) { - org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error error = - result.getErrorTag(); - LOGGER.error( - "Error getting response from Wolfram Alpha API: Error Code: {} Error Message: {}", - error.getCode(), error.getMessage()); - return "An error occurred while getting response from the Wolfram|Alpha API. Check the URI"; - } - - static @NotNull Optional parseQuery(@NotNull HttpResponse response, - @NotNull WebhookMessageUpdateAction action) { - try { - return Optional.of(XML.readValue(response.body(), QueryResult.class)); - } catch (IOException e) { - action.setContent("Unexpected response from WolframAlpha API").queue(); - LOGGER.error("Unable to deserialize the class ", e); - return Optional.empty(); - } - } - - static @NotNull String handleSuccessfulResult(@NotNull QueryResult result, - WebhookMessageUpdateAction action, MessageEmbed embed) { - int filesAttached = 0; - int resultHeight = 0; - EmbedBuilder embedBuilder = new EmbedBuilder(); - Collection embeds = new ArrayList<>(); - embeds.add(embed); - Collection images = new ArrayList<>(); - List pods = result.getPods(); - for (Pod pod : pods) { - List subPods = pod.getSubPods(); - boolean firstSubPod = true; - for (SubPod subPod : subPods) { - WolframAlphaImage image = subPod.getImage(); - try { - String source = image.getSource(); - String header = pod.getTitle(); - int width = (firstSubPod ? Math.max(getWidth(header), image.getWidth()) - : image.getWidth()) + IMAGE_MARGIN_WIDTH_PX; - int height = image.getHeight(); - if (firstSubPod) { - height += AMBIENT_TEXT_HEIGHT; - } - BufferedImage readImage = - new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); - Graphics graphics = readImage.getGraphics(); - if (firstSubPod) { - graphics.setFont(AMBIENT_FONT); - graphics.setColor(Color.WHITE); - graphics.setColor(AMBIENT_COLOR); - graphics.drawString(header, IMAGE_MARGIN_WIDTH_PX, IMAGE_MARGIN_HEIGHT_PX); - } - graphics.drawImage(ImageIO.read(new URL(source)), IMAGE_MARGIN_WIDTH_PX, - firstSubPod ? AMBIENT_TEXT_HEIGHT : 0, null); - - if (filesAttached == MAX_EMBEDS) { - // noinspection ResultOfMethodCallIgnored - action.setEmbeds(embeds); - return "Too many images. Visit the URI"; - } - - if (resultHeight + image.getHeight() > MAX_IMAGE_HEIGHT_PX) { - BufferedImage combinedImage = - WolframAlphaCommandUtils.combineImages(images, resultHeight); - images.clear(); - filesAttached++; - action = action.addFile( - WolframAlphaCommandUtils.imageToBytes(combinedImage), - "result%d.png".formatted(filesAttached)); - resultHeight = 0; - embeds.add(embedBuilder - .setImage("attachment://result%d.png".formatted(filesAttached)) - .build()); - } else if (pod == pods.get(pods.size() - 1) - && subPod == subPods.get(subPods.size() - 1) && !images.isEmpty()) { - filesAttached++; - action = action.addFile( - WolframAlphaCommandUtils.imageToBytes(WolframAlphaCommandUtils - .combineImages(images, resultHeight + image.getHeight())), - "result%d.png".formatted(filesAttached)); - images.clear(); - embeds.add(embedBuilder - .setImage("attachment://result%d.png".formatted(filesAttached)) - .build()); - } - resultHeight += readImage.getHeight(); - images.add(readImage); - firstSubPod = false; - } catch (IOException e) { - LOGGER.error("Failed to read image {} from the WolframAlpha response", image, - e); - return "Unable to generate message based on the WolframAlpha response"; - } - } - } - // noinspection ResultOfMethodCallIgnored - // FIXME This wont work at all if it got reassigned or similar, has to be returned or sth - action.setEmbeds(embeds); - return ""; - } -} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaHandler.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaHandler.java new file mode 100644 index 0000000000..7362501427 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaHandler.java @@ -0,0 +1,272 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import io.mikael.urlbuilder.UrlBuilder; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error; +import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.*; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.HttpURLConnection; +import java.net.http.HttpResponse; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Handles Wolfram Alpha API query responses. + */ +final class WolframAlphaHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(WolframAlphaHandler.class); + private static final XmlMapper XML = new XmlMapper(); + private static final Color AMBIENT_COLOR = Color.decode("#4290F5"); + private static final String SERVICE_NAME = "Wolfram|Alpha"; + /** + * WolframAlpha API endpoint for regular users (web frontend). + */ + private static final String USER_API_ENDPOINT = "https://www.wolframalpha.com/input"; + /** + * The max height to allow for images, in pixel. Images larger than this are downscaled by + * Discord and do not provide a nice user experience anymore. + */ + private static final int MAX_IMAGE_HEIGHT_PX = 400; + /** + * Maximum amount of embeds Discord supports. + *

+ * This should be replaced with a constant provided by JDA, once it does offer one. + */ + private static final int MAX_EMBEDS = 10; + /** + * Maximum amount of tiles to send. + *

+ * One embed is used as initial description and summary. + */ + private static final int MAX_TILES = MAX_EMBEDS - 1; + + private final String query; + private final String userApiQuery; + + /** + * Creates a new instance + * + * @param query the original query send to the API + */ + WolframAlphaHandler(@NotNull String query) { + this.query = query; + + userApiQuery = UrlBuilder.fromString(USER_API_ENDPOINT) + .addParameter("i", query) + .toUri() + .toString(); + } + + /** + * Handles the given response and returns a user-friendly message that can be displayed. + * + * @param apiResponse response of the Wolfram Alpha API query + * @return user-friendly message for display, as list of embeds + */ + @NotNull + HandlerResponse handleApiResponse(@NotNull HttpResponse apiResponse) { + // Check status code + int statusCode = apiResponse.statusCode(); + if (statusCode != HttpURLConnection.HTTP_OK) { + LOGGER.warn("Wolfram Alpha API returned an unexpected status code: {}", statusCode); + return responseOf("Sorry, the Wolfram Alpha API failed for an unknown reason."); + } + + // Parse XML response + String queryResultXml = apiResponse.body(); + QueryResult queryResult; + try { + queryResult = XML.readValue(queryResultXml, QueryResult.class); + } catch (IOException e) { + LOGGER.warn( + "Wolfram Alpha API returned a response (for query: '{}') that can not be parsed into a QueryResult: {}", + query, queryResultXml, e); + return responseOf( + "Sorry, the Wolfram Alpha API responded with something I do not understand."); + } + + // Handle unsuccessful + if (!queryResult.isSuccess()) { + if (queryResult.isError()) { + Error error = queryResult.getErrorTag(); + LOGGER.error( + "Received an error from the Wolfram Alpha API (for query: '{}'). Code: {}, message: {}", + query, error.getCode(), error.getMessage()); + return responseOf("Sorry, the Wolfram Alpha API responded with an error."); + } + + return handleMisunderstoodQuery(queryResult); + } + + return handleSuccessfulResponse(queryResult); + } + + private @NotNull HandlerResponse handleMisunderstoodQuery(@NotNull QueryResult result) { + StringJoiner output = new StringJoiner("\n"); + output.add("Sorry, I did not understand your query."); + + Tips tips = result.getTips(); + if (tips != null && tips.getCount() != 0) { + output.add("\nHere are some tips:\n" + + createBulletPointList(tips.getTipList(), Tip::getText)); + } + + FutureTopic futureTopic = result.getFutureTopic(); + if (futureTopic != null) { + output + .add("\n" + "The topic '%s' is not supported yet, but will be added in the future." + .formatted(futureTopic.getTopic())); + } + + LanguageMessage languageMessage = result.getLanguageMessage(); + if (languageMessage != null) { + // "Wolfram|Alpha does not yet support German." + // "Wolfram|Alpha versteht noch kein Deutsch." + output.add("\n" + languageMessage.getEnglish()); + output.add(languageMessage.getOther()); + } + + DidYouMeans didYouMeans = result.getDidYouMeans(); + if (didYouMeans != null && didYouMeans.getCount() != 0) { + output.add("\nDid you perhaps mean:\n" + createBulletPointList( + didYouMeans.getDidYouMeanTips(), DidYouMean::getMessage)); + } + + RelatedExamples relatedExamples = result.getRelatedExamples(); + if (relatedExamples != null && relatedExamples.getCount() != 0) { + output.add("\nHere are some related examples:\n" + createBulletPointList( + relatedExamples.getRelatedExampleTips(), RelatedExample::getCategoryThumb)); + } + + return responseOf(output.toString()); + } + + private static @NotNull String createBulletPointList(Collection elements, + Function elementToText) { + return elements.stream() + .map(elementToText) + .map(text -> "• " + text) + .collect(Collectors.joining("\n")); + } + + private @NotNull HandlerResponse handleSuccessfulResponse(@NotNull QueryResult queryResult) { + StringJoiner messages = new StringJoiner("\n\n"); + messages.add("Click the link to see full results."); + + if (!queryResult.getTimedOutPods().isEmpty()) { + messages.add("Some of my calculation took very long, so I cancelled them."); + } + + // Render all the pods and sub-pods + Collection images = new ArrayList<>(); + for (Pod pod : queryResult.getPods()) { + images.add(WolframAlphaImages.renderTitle(pod.getTitle() + ":")); + + for (SubPod subPod : pod.getSubPods()) { + try { + images.add(WolframAlphaImages.renderSubPod(subPod)); + } catch (UncheckedIOException e) { + LOGGER.error( + "Failed to render sub pod (title: '{}') from pod (title: '{}') from the WolframAlpha response (for query: '{}')", + subPod.getTitle(), pod.getTitle(), query, e); + return responseOf( + "Sorry, the Wolfram Alpha API responded with something I do not understand."); + } + } + } + images.add(WolframAlphaImages.renderFooter()); + + // Images will be displayed as tiles in Discord embeds + List tiles = + WolframAlphaImages.combineImagesIntoTiles(images, MAX_IMAGE_HEIGHT_PX); + + List tilesToDisplay = tiles.subList(0, Math.min(tiles.size(), MAX_TILES)); + if (tilesToDisplay.size() < tiles.size()) { + messages.add("That's a lot of results, I had to cut off a few of them."); + } + + return responseOf(messages.toString(), tilesToDisplay); + } + + private @NotNull HandlerResponse responseOf(@NotNull CharSequence text) { + MessageEmbed embed = new EmbedBuilder().setTitle(buildTitle(), userApiQuery) + .setDescription(text) + .setColor(AMBIENT_COLOR) + .build(); + + return new HandlerResponse(List.of(embed), List.of()); + } + + private @NotNull HandlerResponse responseOf(@NotNull CharSequence text, + @NotNull Collection tiles) { + List embeds = new ArrayList<>(); + embeds.add(new EmbedBuilder().setTitle(buildTitle(), userApiQuery) + .setDescription(text) + .setColor(AMBIENT_COLOR) + .build()); + + List attachments = new ArrayList<>(tiles.size()); + + int i = 0; + for (BufferedImage tile : tiles) { + String tileTitle = "result%d.%s".formatted(i, WolframAlphaImages.IMAGE_FORMAT); + + attachments.add(new Attachment(tileTitle, WolframAlphaImages.imageToBytes(tile))); + embeds.add(new EmbedBuilder().setColor(AMBIENT_COLOR) + .setImage("attachment://" + tileTitle) + .build()); + + i++; + } + + return new HandlerResponse(embeds, attachments); + } + + private @NotNull String buildTitle() { + return query + " - " + SERVICE_NAME; + } + + record HandlerResponse(@NotNull List embeds, + @NotNull List attachments) { + } + + + record Attachment(@NotNull String name, byte @NotNull [] data) { + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Attachment that = (Attachment) o; + return name.equals(that.name) && Arrays.equals(data, that.data); + } + + @Override + public int hashCode() { + int result = Objects.hash(name); + result = 31 * result + Arrays.hashCode(data); + return result; + } + + @Override + public String toString() { + return new StringJoiner(", ", Attachment.class.getSimpleName() + "[", "]") + .add("name='" + name + "'") + .add("data=" + Arrays.toString(data)) + .toString(); + } + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java new file mode 100644 index 0000000000..00c319ec6f --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaImages.java @@ -0,0 +1,155 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import org.jetbrains.annotations.NotNull; +import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.SubPod; +import org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.WolframAlphaImage; + +import javax.imageio.ImageIO; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.font.FontRenderContext; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Utility class to work with images returned by the Wolfram Alpha API. For example to render and + * combine them. + */ +enum WolframAlphaImages { + ; + static final String IMAGE_FORMAT = "png"; + private static final Color IMAGE_BACKGROUND = Color.WHITE; + private static final int IMAGE_MARGIN_PX = 10; + + private static final FontRenderContext TITLE_RENDER_CONTEXT = + new FontRenderContext(new AffineTransform(), true, true); + private static final Color TITLE_COLOR = Color.decode("#3C3C3C"); + private static final Font TITLE_FONT = new Font("Arial", Font.BOLD, 15); + private static final int TITLE_HEIGHT_PX = 20; + + static @NotNull BufferedImage renderTitle(@NotNull String title) { + Rectangle2D titleBounds = TITLE_FONT.getStringBounds(title, TITLE_RENDER_CONTEXT); + int widthPx = (int) Math.ceil(titleBounds.getWidth()) + 2 * IMAGE_MARGIN_PX; + int heightPx = (int) Math.ceil(titleBounds.getHeight()) + IMAGE_MARGIN_PX; + + BufferedImage image = new BufferedImage(widthPx, heightPx, BufferedImage.TYPE_4BYTE_ABGR); + Graphics graphics = image.getGraphics(); + + graphics.setFont(TITLE_FONT); + graphics.setColor(TITLE_COLOR); + graphics.drawString(title, IMAGE_MARGIN_PX, + IMAGE_MARGIN_PX + graphics.getFontMetrics().getAscent()); + + return image; + } + + static @NotNull BufferedImage renderSubPod(@NotNull SubPod subPod) { + WolframAlphaImage sourceImage = subPod.getImage(); + + int widthPx = sourceImage.getWidth() + 2 * IMAGE_MARGIN_PX; + int heightPx = sourceImage.getHeight() + IMAGE_MARGIN_PX; + + BufferedImage destinationImage = + new BufferedImage(widthPx, heightPx, BufferedImage.TYPE_4BYTE_ABGR); + Graphics graphics = destinationImage.getGraphics(); + + try { + graphics.drawImage(ImageIO.read(new URL(sourceImage.getSource())), IMAGE_MARGIN_PX, + IMAGE_MARGIN_PX, null); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + return destinationImage; + } + + static @NotNull BufferedImage renderFooter() { + return new BufferedImage(1, IMAGE_MARGIN_PX, BufferedImage.TYPE_4BYTE_ABGR); + } + + static @NotNull List combineImagesIntoTiles( + @NotNull Collection images, int maxTileHeight) { + if (images.isEmpty()) { + throw new IllegalArgumentException("Images must not be empty"); + } + + int widthPx = images.stream().mapToInt(BufferedImage::getWidth).max().orElseThrow(); + + List destinationTiles = new ArrayList<>(); + + Collection currentTile = new ArrayList<>(); + int currentTileHeight = 0; + for (BufferedImage sourceImage : images) { + int sourceImageHeight = sourceImage.getHeight(); + + if (wouldTileBeTooLargeIfAddingImage(currentTileHeight, sourceImageHeight, + maxTileHeight)) { + // Conclude tile and start the next + destinationTiles.add(combineImages(currentTile, widthPx)); + + currentTile.clear(); + currentTileHeight = 0; + } + + // Add image to tile + currentTile.add(sourceImage); + currentTileHeight += sourceImageHeight; + } + + // Conclude last tile + destinationTiles.add(combineImages(currentTile, widthPx)); + + return destinationTiles; + } + + private static boolean wouldTileBeTooLargeIfAddingImage(int tileHeight, int heightOfImageToAdd, + int maxTileHeight) { + // Addition to empty tiles is always allowed, regardless of size. + return tileHeight != 0 && tileHeight + heightOfImageToAdd > maxTileHeight; + } + + private static @NotNull BufferedImage combineImages( + @NotNull Collection images, int widthPx) { + if (images.isEmpty()) { + throw new IllegalArgumentException("Images must not be empty"); + } + + int heightPx = images.stream().mapToInt(BufferedImage::getHeight).sum(); + + BufferedImage destinationImage = + new BufferedImage(widthPx, heightPx, BufferedImage.TYPE_4BYTE_ABGR); + Graphics graphics = destinationImage.getGraphics(); + + // Background + graphics.setColor(IMAGE_BACKGROUND); + graphics.fillRect(0, 0, widthPx, heightPx); + + // Combine + int heightOffsetPx = 0; + for (BufferedImage sourceImage : images) { + graphics.drawImage(sourceImage, 0, heightOffsetPx, null); + heightOffsetPx += sourceImage.getHeight(null); + } + + return destinationImage; + } + + static byte @NotNull [] imageToBytes(@NotNull RenderedImage img) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + ImageIO.write(img, IMAGE_FORMAT, outputStream); + return outputStream.toByteArray(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMean.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMean.java index 9dca4c86e3..f5eed82762 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMean.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMean.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("didyoumean") @JsonIgnoreProperties(ignoreUnknown = true) public final class DidYouMean { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMeans.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMeans.java index a8482e005f..2d5e88e7ab 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMeans.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMeans.java @@ -31,7 +31,7 @@ public final class DidYouMeans { @JsonProperty("didyoumean") @JacksonXmlElementWrapper(useWrapping = false) - private List didYouMeans; + private List didYouMeanTips; @SuppressWarnings("unused") public int getCount() { @@ -43,13 +43,13 @@ public void setCount(int count) { this.count = count; } - public List getDidYouMeans() { - return Collections.unmodifiableList(didYouMeans); + public List getDidYouMeanTips() { + return Collections.unmodifiableList(didYouMeanTips); } @SuppressWarnings("unused") - public void setDidYouMeans(List didYouMeans) { - this.didYouMeans = new ArrayList<>(didYouMeans); + public void setDidYouMeanTips(List didYouMeanTips) { + this.didYouMeanTips = new ArrayList<>(didYouMeanTips); } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Error.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Error.java index 00d8c53765..cc9d6b0136 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Error.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Error.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonRootName; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("error") @JsonIgnoreProperties(ignoreUnknown = true) public final class Error { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/ExamplePage.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/ExamplePage.java index a2382ec403..a07f6bd5f4 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/ExamplePage.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/ExamplePage.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("examplepage") @JsonIgnoreProperties(ignoreUnknown = true) public final class ExamplePage { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/PlainText.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/PlainText.java index 686f7b71fe..8b5d85ac4f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/PlainText.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/PlainText.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("plaintext") @JsonIgnoreProperties(ignoreUnknown = true) public final class PlainText { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Pod.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Pod.java index 93ca256bfe..1b625104ea 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Pod.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Pod.java @@ -10,6 +10,9 @@ import java.util.Collections; import java.util.List; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("pod") @JsonIgnoreProperties(ignoreUnknown = true) public final class Pod { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java index 41547f389c..0ba2d5ba9e 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java @@ -10,6 +10,9 @@ import java.util.List; import java.util.Map; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("queryresult") @JsonIgnoreProperties(ignoreUnknown = true) public final class QueryResult { @@ -217,6 +220,12 @@ public org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error getEr return errorTag; } + /** + * Sets the two error values. + * + * @param name must be "error", otherwise ignored + * @param value the value to set, either a string for the attribute or a map for the tag + */ @JsonAnySetter @SuppressWarnings("ChainOfInstanceofChecks") public void setError(String name, Object value) { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExample.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExample.java index 62bfe13475..4410421dcf 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExample.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExample.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("relatedexample") @JsonIgnoreProperties(ignoreUnknown = true) public final class RelatedExample { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExamples.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExamples.java index df14a99e25..f016a9f6b5 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExamples.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExamples.java @@ -10,6 +10,9 @@ import java.util.ArrayList; import java.util.Collections; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("relatedexamples") @JsonIgnoreProperties(ignoreUnknown = true) public final class RelatedExamples { @@ -18,7 +21,7 @@ public final class RelatedExamples { @JsonProperty("relatedexample") @JacksonXmlElementWrapper(useWrapping = false) - private List relatedExamples; + private List relatedExampleTips; @SuppressWarnings("unused") public int getCount() { @@ -30,12 +33,12 @@ public void setCount(int count) { this.count = count; } - public List getRelatedExamples() { - return Collections.unmodifiableList(relatedExamples); + public List getRelatedExampleTips() { + return Collections.unmodifiableList(relatedExampleTips); } @SuppressWarnings("unused") - public void setRelatedExamples(List relatedExamples) { - this.relatedExamples = new ArrayList<>(relatedExamples); + public void setRelatedExampleTips(List relatedExampleTips) { + this.relatedExampleTips = new ArrayList<>(relatedExampleTips); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/SubPod.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/SubPod.java index 39fcc4970a..e1fc2f1682 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/SubPod.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/SubPod.java @@ -5,6 +5,9 @@ import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("subpod") @JsonIgnoreProperties(ignoreUnknown = true) public final class SubPod { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tip.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tip.java index 0ec29d94e6..04936258cd 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tip.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tip.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("tip") @JsonIgnoreProperties(ignoreUnknown = true) public final class Tip { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tips.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tips.java index 04cfef5a5d..0e3a9947de 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tips.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tips.java @@ -31,7 +31,7 @@ public final class Tips { @JsonProperty("tip") @JacksonXmlElementWrapper(useWrapping = false) - private List tips; + private List tipList; @SuppressWarnings("unused") public int getCount() { @@ -44,13 +44,13 @@ public void setCount(int count) { } @SuppressWarnings("unused") - public List getTips() { - return Collections.unmodifiableList(tips); + public List getTipList() { + return Collections.unmodifiableList(tipList); } @SuppressWarnings("unused") - public void setTips(List tips) { - this.tips = new ArrayList<>(tips); + public void setTipList(List tipList) { + this.tipList = new ArrayList<>(tipList); } } diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/WolframAlphaImage.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/WolframAlphaImage.java index 0573b37e2f..d7d3bdf641 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/WolframAlphaImage.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/WolframAlphaImage.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +/** + * See the Wolfram Alpha API. + */ @JsonRootName("img") @JsonIgnoreProperties(ignoreUnknown = true) public final class WolframAlphaImage { diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/package-info.java new file mode 100644 index 0000000000..f4513dbb6d --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/package-info.java @@ -0,0 +1,6 @@ +/** + * This packages offers POJOs mapping the responses of the official + * WolframAlpha + * API. + */ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api; diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/package-info.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/package-info.java new file mode 100644 index 0000000000..d864dfb435 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/package-info.java @@ -0,0 +1,5 @@ +/** + * This packages offers all the functionality for the wolfram-alpha command. Sending queries to + * their official API, rendering results and displaying them. + */ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; From fbfaf8ffdf5b52b9daa0d63f4d03b762ca9276e8 Mon Sep 17 00:00:00 2001 From: Zabuzard Date: Thu, 12 May 2022 10:32:44 +0200 Subject: [PATCH 5/5] removed obsolete full package name --- .../commands/mathcommands/wolframalpha/api/QueryResult.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java index 0ba2d5ba9e..928e5988d3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java @@ -54,7 +54,7 @@ public final class QueryResult { @JacksonXmlElementWrapper(useWrapping = false) private List pods; @JsonIgnore - private org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error errorTag; + private Error errorTag; public boolean isSuccess() { return success; @@ -242,8 +242,7 @@ public void setError(String name, Object value) { return; } if (value instanceof Map) { - errorTag = XML.convertValue(value, - org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error.class); + errorTag = XML.convertValue(value, Error.class); return; } throw new IllegalArgumentException("Unsupported error format");