diff --git a/Cargo.lock b/Cargo.lock index 1169907..0245136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,180 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "autocfg" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chrono" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fimfareader" version = "0.1.0" +dependencies = [ + "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", +] +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num-integer" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ryu" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.15.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" +"checksum chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "77d81f58b7301084de3b958691458a53c3f7e0b1d702f77e550b6a88e3a88abe" +"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" +"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" +"checksum serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)" = "d46b3dfedb19360a74316866cef04687cd4d6a70df8e6a506c63512790769b72" +"checksum serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)" = "c22a0820adfe2f257b098714323563dd06426502abbbce4f51b72ef544c5027f" +"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" +"checksum syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)" = "bc945221ccf4a7e8c31222b9d1fc77aefdd6638eb901a6ce457a3dc29d4c31e8" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 68569f1..da1eb73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,17 @@ edition = "2018" [profile.release] lto = true + +[dependencies.chrono] +version = "*" +features = ["serde"] + +[dependencies.hex] +version = "*" + +[dependencies.serde] +version = "*" +features = ["derive"] + +[dependencies.serde_json] +version = "*" diff --git a/src/main.rs b/src/main.rs index 2f167a3..79ea707 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ //! Main module. +pub mod story; + fn main() { println!("Hellopaca, World!"); } diff --git a/src/story.rs b/src/story.rs new file mode 100644 index 0000000..61169be --- /dev/null +++ b/src/story.rs @@ -0,0 +1,243 @@ +//! Story meta. + +use std::collections::BTreeMap; + +use chrono::prelude::*; +use serde::de::Error; +use serde::{Deserialize, Deserializer}; +use serde_json::Value; + +#[derive(Clone, Debug, Deserialize)] +pub struct Story { + pub archive: Archive, + pub author: Author, + pub chapters: Vec, + pub color: Option, + pub completion_status: CompletionStatus, + pub content_rating: ContentRating, + pub cover_image: Option, + pub date_modified: Option>, + pub date_published: Option>, + pub date_updated: Option>, + #[serde(deserialize_with = "null_to_html")] + pub description_html: String, + pub id: i64, + pub num_chapters: i32, + pub num_comments: i32, + pub num_dislikes: i32, + pub num_likes: i32, + pub num_views: i32, + pub num_words: i32, + pub prequel: Option, + pub published: bool, + pub rating: i32, + #[serde(deserialize_with = "null_to_text")] + pub short_description: String, + pub status: Status, + pub submitted: bool, + pub tags: Vec, + #[serde(deserialize_with = "null_to_text")] + pub title: String, + pub total_num_views: i32, + pub url: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Archive { + pub date_checked: Option>, + pub date_created: Option>, + pub date_fetched: Option>, + pub date_updated: Option>, + pub path: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Author { + pub avatar: Option>, + pub bio_html: Option, + pub date_joined: Option>, + #[serde(deserialize_with = "string_to_id")] + pub id: i64, + pub name: String, + pub num_blog_posts: Option, + pub num_followers: Option, + pub num_stories: Option, + pub url: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Chapter { + pub chapter_number: i32, + pub date_modified: Option>, + pub date_published: Option>, + pub id: i64, + pub num_views: i32, + pub num_words: i32, + pub published: bool, + #[serde(deserialize_with = "null_to_text")] + pub title: String, + pub url: String, +} + +#[derive(Clone, Debug)] +pub struct Color { + red: u8, + green: u8, + blue: u8, +} + +#[derive(Clone, Debug)] +pub enum CompletionStatus { + Cancelled, + Complete, + Hiatus, + Incomplete, +} + +#[derive(Clone, Debug)] +pub enum ContentRating { + Everyone, + Mature, + Teen, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct CoverImage { + pub full: String, + pub large: String, + pub medium: String, + pub thumbnail: String, +} + +#[derive(Clone, Debug)] +pub enum Status { + ApproveQueue, + NotVisible, + PostQueue, + Visible, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Tag { + pub id: i64, + pub name: String, + pub old_id: String, + pub r#type: String, + pub url: String, +} + +pub fn null_to_html<'de, D>(d: D) -> Result +where + D: Deserializer<'de>, +{ + match Option::deserialize(d)? { + Some(text) => Ok(text), + None => Ok(String::from("

")), + } +} + +pub fn null_to_text<'de, D>(d: D) -> Result +where + D: Deserializer<'de>, +{ + match Option::deserialize(d)? { + Some(text) => Ok(text), + None => Ok(String::from("")), + } +} + +pub fn string_to_id<'de, D>(d: D) -> Result +where + D: Deserializer<'de>, +{ + let value = Value::deserialize(d)?; + + if value.is_i64() { + Ok(value.as_i64().unwrap()) + } else if value.is_string() { + value.as_str().unwrap().parse().map_err(|e| match e { + _ => Error::custom("Could not parse ID string."), + }) + } else { + Err(Error::custom("Invalid type for ID value.")) + } +} + +impl<'de> Deserialize<'de> for Color { + fn deserialize(d: D) -> Result + where + D: Deserializer<'de>, + { + let object = Value::deserialize(d)?; + + let text = object["hex"] + .as_str() + .ok_or(Error::custom("Color is missing hex value."))?; + + let array = hex::decode(text).map_err(|e| match e { + _ => Error::custom("Color hex has invalid value."), + })?; + + match array[..] { + [red, green, blue] => Ok(Color { red, green, blue }), + _ => Err(Error::custom("Color hex has invalid length.")), + } + } +} + +impl CompletionStatus { + const FIELDS: &'static [&'static str] = &["cancelled", "complete", "hiatus", "incomplete"]; +} + +impl<'de> Deserialize<'de> for CompletionStatus { + fn deserialize(d: D) -> Result + where + D: Deserializer<'de>, + { + match String::deserialize(d)?.as_ref() { + "cancelled" => Ok(CompletionStatus::Cancelled), + "complete" => Ok(CompletionStatus::Complete), + "hiatus" | "on hiatus" => Ok(CompletionStatus::Hiatus), + "incomplete" => Ok(CompletionStatus::Incomplete), + value => Err(Error::unknown_field(value, Self::FIELDS)), + } + } +} + +impl ContentRating { + const FIELDS: &'static [&'static str] = &["everyone", "mature", "teen"]; +} + +impl<'de> Deserialize<'de> for ContentRating { + fn deserialize(d: D) -> Result + where + D: Deserializer<'de>, + { + match String::deserialize(d)?.as_ref() { + "everyone" => Ok(ContentRating::Everyone), + "mature" => Ok(ContentRating::Mature), + "teen" => Ok(ContentRating::Teen), + value => Err(Error::unknown_field(value, Self::FIELDS)), + } + } +} + +impl Status { + const FIELDS: &'static [&'static str] = + &["approve_queue", "not_visible", "post_queue", "visible"]; +} + +impl<'de> Deserialize<'de> for Status { + fn deserialize(d: D) -> Result + where + D: Deserializer<'de>, + { + match String::deserialize(d)?.as_ref() { + "approve_queue" => Ok(Status::ApproveQueue), + "not_visible" => Ok(Status::NotVisible), + "post_queue" => Ok(Status::PostQueue), + "visible" => Ok(Status::Visible), + value => Err(Error::unknown_field(value, Self::FIELDS)), + } + } +}