aboutsummaryrefslogtreecommitdiff
path: root/src/hooks/wolfram_alpha.rs
diff options
context:
space:
mode:
authorLorenz Leitner <lrnz.ltnr@gmail.com>2021-10-11 18:25:43 +0200
committerLorenz Leitner <lrnz.ltnr@gmail.com>2021-10-12 12:06:57 +0200
commitc08f0edcb8da6a78a8e9edcf3eaa14e633bf0e91 (patch)
tree6f066189130baf04b67dd28124258d9a90b79ef5 /src/hooks/wolfram_alpha.rs
parentRemove passthrough method (diff)
Rename wa, change anyhow version, make reqwest dependency more specific
Due to MR comments Are you happy now audron?
Diffstat (limited to 'src/hooks/wolfram_alpha.rs')
-rw-r--r--src/hooks/wolfram_alpha.rs141
1 files changed, 141 insertions, 0 deletions
diff --git a/src/hooks/wolfram_alpha.rs b/src/hooks/wolfram_alpha.rs
new file mode 100644
index 0000000..cb4bc4c
--- /dev/null
+++ b/src/hooks/wolfram_alpha.rs
@@ -0,0 +1,141 @@
+use crate::util::web::shorten_url;
+use anyhow::{bail, Context, Error, Result};
+use futures::try_join;
+use irc::client::prelude::*;
+use macros::privmsg;
+use reqwest::{get, Url};
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Debug)]
+struct WaResponse {
+ queryresult: QueryResult,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+struct QueryResult {
+ pods: Vec<Pod>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+struct Pod {
+ title: String,
+ id: String,
+ primary: Option<bool>,
+ subpods: Vec<SubPod>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+struct SubPod {
+ plaintext: String,
+}
+
+/// Reduces all 'pod' plaintexts to a single string.
+/// Same as gonzobot does it.
+fn to_single_string(wa_res: WaResponse) -> String {
+ wa_res
+ .queryresult
+ .pods
+ .iter()
+ .filter(|it| it.id.to_lowercase() != "input" && it.primary.is_some())
+ .map(|pod| {
+ let subpod_texts = pod
+ .subpods
+ .iter()
+ .map(|subpod| subpod.plaintext.clone())
+ .collect::<Vec<String>>()
+ .join(", ");
+
+ format!("{}: {}", &pod.title, subpod_texts)
+ })
+ .collect::<Vec<String>>()
+ .join(" - ")
+}
+
+fn get_url(query_str: &str, api_key: Option<&str>, base_url: Option<&str>) -> String {
+ let wa_url = "http://api.wolframalpha.com";
+ let api_url = format!(
+ "{}/v2/query?input={}&appid={}&output=json",
+ base_url.unwrap_or(wa_url),
+ query_str,
+ api_key.unwrap_or("XXX"), // Allow tests to run without a key
+ );
+ api_url
+}
+
+async fn send_wa_req(url: &str) -> Result<String, Error> {
+ let body = get(Url::parse(url).context("Failed to parse url")?)
+ .await
+ .context("Failed to make request")?
+ .text()
+ .await
+ .context("failed to get request response text")?;
+ Ok(body)
+}
+
+async fn handle_wa_req(url: &str) -> Result<WaResponse, Error> {
+ let res_body = send_wa_req(url).await?;
+ let parsed = serde_json::from_str(&res_body)?;
+ Ok(parsed)
+}
+
+/// Sends a request to the Wolfram Alpha API, returns a plain text response.
+#[tracing::instrument]
+async fn wa_query(
+ query_str: &str,
+ api_key: Option<&str>,
+ base_url: Option<&str>,
+) -> Result<String, Error> {
+ let user_url = format!("http://www.wolframalpha.com/input/?i={}", query_str);
+ let user_url_shortened_fut = shorten_url(&user_url);
+
+ let url = get_url(query_str, api_key, base_url);
+ let wa_res_fut = handle_wa_req(&url);
+
+ // Can't just (foo.await, bar.await), smh
+ // https://rust-lang.github.io/async-book/06_multiple_futures/02_join.html
+ let (wa_res, user_url_shortened) = try_join!(wa_res_fut, user_url_shortened_fut)?;
+
+ Ok(format!(
+ "{} - {}",
+ &user_url_shortened,
+ to_single_string(wa_res)
+ ))
+}
+
+pub async fn wa(bot: &crate::Bot, msg: Message) -> Result<()> {
+ privmsg!(msg, {
+ let input = text.chars().as_str().splitn(2, " ").collect::<Vec<&str>>();
+ if input.len() != 2 {
+ bail!("Empty input for WA query");
+ }
+ let content = input[1].trim();
+ bot.send_privmsg(
+ msg.response_target()
+ .context("failed to get response target")?,
+ &wa_query(content, Some(&bot.config.settings.wa_api_key), None).await?,
+ )?;
+ })
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::wa_query;
+ use anyhow::{Error, Result};
+ use mockito::{self, Matcher};
+
+ #[tokio::test]
+ async fn test_query_result_json_parsing() -> Result<(), Error> {
+ let body = include_str!("../../tests/resources/wolfram_alpha_api_response.json");
+ let _m = mockito::mock("GET", Matcher::Any)
+ // Trimmed down version of a full WA response:
+ .with_body(body)
+ .create();
+ mockito::start();
+
+ let res = wa_query("5/10", None, Some(&mockito::server_url())).await?;
+ let res_without_link = res.splitn(2, "-").collect::<Vec<&str>>()[1].trim();
+ assert_eq!(res_without_link, "Exact result: 1/2 - Decimal form: 0.5");
+ Ok(())
+ }
+}