diff options
| author | Lorenz Leitner <lrnz.ltnr@gmail.com> | 2021-10-11 18:25:43 +0200 |
|---|---|---|
| committer | Lorenz Leitner <lrnz.ltnr@gmail.com> | 2021-10-12 12:06:57 +0200 |
| commit | c08f0edcb8da6a78a8e9edcf3eaa14e633bf0e91 (patch) | |
| tree | 6f066189130baf04b67dd28124258d9a90b79ef5 /src/hooks/wolfram_alpha.rs | |
| parent | Remove 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.rs | 141 |
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(()) + } +} |
