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