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/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 e8528391c0..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; @@ -105,6 +106,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/WolframAlphaCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java new file mode 100644 index 0000000000..90109410c2 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/mathcommands/wolframalpha/WolframAlphaCommand.java @@ -0,0 +1,87 @@ +package org.togetherjava.tjbot.commands.mathcommands.wolframalpha; + +import io.mikael.urlbuilder.UrlBuilder; +import net.dv8tion.jda.api.entities.Message; +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.togetherjava.tjbot.commands.SlashCommandAdapter; +import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.config.Config; + +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +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 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"; + private static final HttpClient CLIENT = HttpClient.newHttpClient(); + + private final String appId; + + /** + * 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, QUERY_OPTION, "the query to send to WolframAlpha", + true); + appId = config.getWolframAlphaAppId(); + } + + @Override + public void onSlashCommand(@NotNull SlashCommandInteractionEvent event) { + String query = event.getOption(QUERY_OPTION).getAsString(); + WolframAlphaHandler handler = new WolframAlphaHandler(query); + + // The API call takes a bit + event.deferReply().queue(); + + // Send query + HttpRequest request = HttpRequest + .newBuilder(UrlBuilder.fromString(API_ENDPOINT) + .addParameter("appid", appId) + .addParameter("format", "image,plaintext") + .addParameter("input", query) + .toUri()) + .GET() + .build(); + + CompletableFuture> apiResponse = + CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()); + + // Parse and respond + apiResponse.thenApply(handler::handleApiResponse) + .thenAccept(response -> sendResponse(response, event)); + } + + private static void sendResponse(@NotNull WolframAlphaHandler.HandlerResponse response, + @NotNull IDeferrableCallback event) { + WebhookMessageUpdateAction 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 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 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; + } }