commit
5683137f7d
@ -0,0 +1,3 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
.idea
|
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "xkcd-search"
|
||||
version = "0.1.0"
|
||||
authors = ["trivernis <trivernis@protonmail.com>"]
|
||||
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"]}
|
@ -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<HashMap<String, u32>> {
|
||||
let response = reqwest::get(ARCHIVE_URL).await?;
|
||||
let html = response.text().await?;
|
||||
|
||||
parse_archive_list(html)
|
||||
}
|
||||
|
||||
fn parse_archive_list(html: String) -> XKCDResult<HashMap<String, u32>> {
|
||||
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::<u32>()
|
||||
.ok()?,
|
||||
))
|
||||
}));
|
||||
|
||||
Ok(archive)
|
||||
}
|
@ -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<Comic> {
|
||||
retrieve_comic(LATEST_URL.to_string()).await
|
||||
}
|
||||
|
||||
/// Returns a comic for an ID
|
||||
pub async fn get_comic(id: u32) -> XKCDResult<Comic> {
|
||||
retrieve_comic(format!("{}/{}/{}", BASE_URL, id, INFO_JSON)).await
|
||||
}
|
||||
|
||||
async fn retrieve_comic(url: String) -> XKCDResult<Comic> {
|
||||
let response = reqwest::get(url).await?;
|
||||
let response = response.json::<Comic>().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,
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type XKCDResult<T> = Result<T, XKCDError>;
|
||||
|
||||
#[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())
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
mod archive;
|
||||
mod comic;
|
||||
mod error;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use archive::*;
|
||||
pub use comic::*;
|
@ -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())
|
||||
}
|
Loading…
Reference in New Issue