From 5683137f7dbc99f47b3d6d044ddefc803b71905c Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 22 Apr 2021 15:36:20 +0200 Subject: [PATCH] Add xkcd retrievel functions Signed-off-by: trivernis --- .gitignore | 3 +++ Cargo.toml | 17 +++++++++++++++++ src/archive.rs | 32 ++++++++++++++++++++++++++++++++ src/comic.rs | 36 ++++++++++++++++++++++++++++++++++++ src/error.rs | 18 ++++++++++++++++++ src/lib.rs | 9 +++++++++ src/tests.rs | 17 +++++++++++++++++ 7 files changed, 132 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/archive.rs create mode 100644 src/comic.rs create mode 100644 src/error.rs create mode 100644 src/lib.rs create mode 100644 src/tests.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..408b8a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c81ab60 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "xkcd-search" +version = "0.1.0" +authors = ["trivernis "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +reqwest = {version="0.11.3", features = ["json", "serde_json"]} +scraper = "0.12.0" +thiserror = "1.0.24" +lazy_static = "1.4.0" +serde = {version = "1.0.125", features = ["serde_derive"]} + +[dev-dependencies] +tokio = {version = "1.5.0", features = ["macros", "rt-multi-thread"]} \ No newline at end of file diff --git a/src/archive.rs b/src/archive.rs new file mode 100644 index 0000000..6dabf6c --- /dev/null +++ b/src/archive.rs @@ -0,0 +1,32 @@ +use crate::error::XKCDResult; +use scraper::{Html, Selector}; +use std::collections::HashMap; +use std::iter::FromIterator; + +static ARCHIVE_URL: &str = "https://xkcd.com/archive/"; + +/// Returns the xkcd archive list +pub async fn get_archive() -> XKCDResult> { + let response = reqwest::get(ARCHIVE_URL).await?; + let html = response.text().await?; + + parse_archive_list(html) +} + +fn parse_archive_list(html: String) -> XKCDResult> { + let document = Html::parse_document(&html); + let archive_selector = Selector::parse(r#"#middleContainer > a"#).unwrap(); + let archive = HashMap::from_iter(document.select(&archive_selector).filter_map(|element| { + Some(( + element.inner_html(), + element + .value() + .attr("href")? + .replace("/", "") + .parse::() + .ok()?, + )) + })); + + Ok(archive) +} diff --git a/src/comic.rs b/src/comic.rs new file mode 100644 index 0000000..2136556 --- /dev/null +++ b/src/comic.rs @@ -0,0 +1,36 @@ +use crate::error::XKCDResult; +use serde::Deserialize; + +static INFO_JSON: &str = "info.0.json"; +static BASE_URL: &str = "https://xkcd.com"; +static LATEST_URL: &str = "https://xkcd.com/info.0.json"; + +/// Returns the latest comic +pub async fn get_latest_comic() -> XKCDResult { + retrieve_comic(LATEST_URL.to_string()).await +} + +/// Returns a comic for an ID +pub async fn get_comic(id: u32) -> XKCDResult { + retrieve_comic(format!("{}/{}/{}", BASE_URL, id, INFO_JSON)).await +} + +async fn retrieve_comic(url: String) -> XKCDResult { + let response = reqwest::get(url).await?; + let response = response.json::().await?; + + Ok(response) +} + +#[derive(Deserialize)] +pub struct Comic { + pub day: String, + pub month: String, + pub year: String, + pub num: u32, + pub safe_title: String, + pub transcript: String, + pub alt: String, + pub img: String, + pub title: String, +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..145906c --- /dev/null +++ b/src/error.rs @@ -0,0 +1,18 @@ +use thiserror::Error; + +pub type XKCDResult = Result; + +#[derive(Debug, Error)] +pub enum XKCDError { + #[error(transparent)] + Reqwest(#[from] reqwest::Error), + + #[error("Parse Error: {0}")] + ParseError(String), +} + +impl From<&str> for XKCDError { + fn from(s: &str) -> Self { + Self::ParseError(s.to_string()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3981d8c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +mod archive; +mod comic; +mod error; + +#[cfg(test)] +mod tests; + +pub use archive::*; +pub use comic::*; diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..a34fc7f --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,17 @@ +use crate::{get_archive, get_comic, get_latest_comic}; + +#[tokio::test] +async fn it_retrieves_the_archive() { + let archive = get_archive().await.unwrap(); + assert!(archive.get("Password Strength").is_some()); +} + +#[tokio::test] +async fn it_retrieves_a_comic() { + assert!(get_comic(1000).await.is_ok()) +} + +#[tokio::test] +async fn it_retrieves_the_latest_comic() { + assert!(get_latest_comic().await.is_ok()) +}