action =
+ event.getHook().editOriginalEmbeds(response.embeds());
+
+ for (WolframAlphaHandler.Attachment attachment : response.attachments()) {
+ action = action.addFile(attachment.data(), attachment.name());
+ }
+
+ action.queue();
+ }
+}
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 extends E> 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 extends BufferedImage> 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 extends BufferedImage> 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 extends BufferedImage> 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
new file mode 100644
index 0000000000..f5eed82762
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMean.java
@@ -0,0 +1,23 @@
+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.JacksonXmlText;
+
+/**
+ * See the Wolfram Alpha API.
+ */
+@JsonRootName("didyoumean")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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/api/DidYouMeans.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMeans.java
new file mode 100644
index 0000000000..2d5e88e7ab
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/DidYouMeans.java
@@ -0,0 +1,57 @@
+package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api;
+
+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
+ *
+ * Response:
+ *
+ *
+ * {@code
+ *
+ * tighe
+ *
+ * }
+ *
+ */
+@JsonRootName("didyoumeans")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class DidYouMeans {
+ @JacksonXmlProperty(isAttribute = true)
+ private int count;
+
+ @JsonProperty("didyoumean")
+ @JacksonXmlElementWrapper(useWrapping = false)
+ private List didYouMeanTips;
+
+ @SuppressWarnings("unused")
+ public int getCount() {
+ return count;
+ }
+
+ @SuppressWarnings("unused")
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public List getDidYouMeanTips() {
+ return Collections.unmodifiableList(didYouMeanTips);
+ }
+
+ @SuppressWarnings("unused")
+ 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
new file mode 100644
index 0000000000..cc9d6b0136
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Error.java
@@ -0,0 +1,36 @@
+package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+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 {
+ 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/api/ExamplePage.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/ExamplePage.java
new file mode 100644
index 0000000000..a07f6bd5f4
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/ExamplePage.java
@@ -0,0 +1,37 @@
+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;
+
+/**
+ * See the Wolfram Alpha API.
+ */
+@JsonRootName("examplepage")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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/api/FutureTopic.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/FutureTopic.java
new file mode 100644
index 0000000000..0e6e23f457
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/FutureTopic.java
@@ -0,0 +1,46 @@
+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
+ *
+ * Response:
+ *
+ *
+ * {@code
+ *
+ * }
+ *
+ */
+@JsonRootName("futuretopic")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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/api/LanguageMessage.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/LanguageMessage.java
new file mode 100644
index 0000000000..5b43c51416
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/LanguageMessage.java
@@ -0,0 +1,47 @@
+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?
+ *
+ * Response:
+ *
+ *
+ * {@code
+ *
+ * }
+ *
+ */
+@JsonRootName("langugemsg")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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/api/PlainText.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/PlainText.java
new file mode 100644
index 0000000000..8b5d85ac4f
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/PlainText.java
@@ -0,0 +1,23 @@
+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.JacksonXmlText;
+
+/**
+ * See the Wolfram Alpha API.
+ */
+@JsonRootName("plaintext")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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/api/Pod.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Pod.java
new file mode 100644
index 0000000000..1b625104ea
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Pod.java
@@ -0,0 +1,101 @@
+package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api;
+
+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;
+
+/**
+ * See the Wolfram Alpha API.
+ */
+@JsonRootName("pod")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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/api/QueryResult.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java
new file mode 100644
index 0000000000..928e5988d3
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/QueryResult.java
@@ -0,0 +1,250 @@
+package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api;
+
+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;
+
+/**
+ * See the Wolfram Alpha API.
+ */
+@JsonRootName("queryresult")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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 org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api.Error getErrorTag() {
+ 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) {
+ 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/api/RelatedExample.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExample.java
new file mode 100644
index 0000000000..4410421dcf
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExample.java
@@ -0,0 +1,73 @@
+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;
+
+/**
+ * See the Wolfram Alpha API.
+ */
+@JsonRootName("relatedexample")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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/api/RelatedExamples.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExamples.java
new file mode 100644
index 0000000000..f016a9f6b5
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/RelatedExamples.java
@@ -0,0 +1,44 @@
+package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api;
+
+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;
+
+/**
+ * See the Wolfram Alpha API.
+ */
+@JsonRootName("relatedexamples")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class RelatedExamples {
+ @JacksonXmlProperty(isAttribute = true)
+ private int count;
+
+ @JsonProperty("relatedexample")
+ @JacksonXmlElementWrapper(useWrapping = false)
+ private List relatedExampleTips;
+
+ @SuppressWarnings("unused")
+ public int getCount() {
+ return count;
+ }
+
+ @SuppressWarnings("unused")
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public List getRelatedExampleTips() {
+ return Collections.unmodifiableList(relatedExampleTips);
+ }
+
+ @SuppressWarnings("unused")
+ 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
new file mode 100644
index 0000000000..e1fc2f1682
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/SubPod.java
@@ -0,0 +1,47 @@
+package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api;
+
+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;
+
+/**
+ * See the Wolfram Alpha API.
+ */
+@JsonRootName("subpod")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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/api/Tip.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tip.java
new file mode 100644
index 0000000000..04936258cd
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tip.java
@@ -0,0 +1,23 @@
+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;
+
+/**
+ * See the Wolfram Alpha API.
+ */
+@JsonRootName("tip")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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/api/Tips.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tips.java
new file mode 100644
index 0000000000..0e3a9947de
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/Tips.java
@@ -0,0 +1,56 @@
+package org.togetherjava.tjbot.commands.mathcommands.wolframalpha.api;
+
+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
+ *
+ * Response:
+ *
+ *
+ * {@code
+ *
+ *
+ *
+ * }
+ *
+ */
+@JsonRootName("tips")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public final class Tips {
+ @JacksonXmlProperty(isAttribute = true)
+ private int count;
+
+ @JsonProperty("tip")
+ @JacksonXmlElementWrapper(useWrapping = false)
+ private List tipList;
+
+ @SuppressWarnings("unused")
+ public int getCount() {
+ return count;
+ }
+
+ @SuppressWarnings("unused")
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ @SuppressWarnings("unused")
+ public List getTipList() {
+ return Collections.unmodifiableList(tipList);
+ }
+
+ @SuppressWarnings("unused")
+ 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
new file mode 100644
index 0000000000..d7d3bdf641
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/api/WolframAlphaImage.java
@@ -0,0 +1,64 @@
+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;
+
+/**
+ * See the Wolfram Alpha API.
+ */
+@JsonRootName("img")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public 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/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;
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..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,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;
+ }
}