diff --git a/Cargo.lock b/Cargo.lock index dcb54f5..a34ff4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,6 +215,18 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "color-art" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "097466279436dfec57a8d00b9abf55df1496a326e01b57918d5fd776598ad91f" +dependencies = [ + "lazy_static", + "rand", + "serde", + "thiserror", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -436,6 +448,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -553,6 +576,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.153" @@ -589,6 +618,7 @@ version = "0.1.0" dependencies = [ "clap", "clio", + "color-art", "comrak", "serde", "serde_json", @@ -687,6 +717,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.79" @@ -714,6 +750,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "regex" version = "1.10.4" @@ -1054,6 +1120,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -1072,6 +1139,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.92" diff --git a/Cargo.toml b/Cargo.toml index 25b65a2..f39960d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,10 @@ edition = "2021" [dependencies] clap = "4.5.3" clio = { version = "0.3.5", features = ["clap-parse"] } +color-art = "0.3.8" comrak = "0.21.0" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.114" serde_with = { version = "3.7.0", features = [ "macros" ] } serde_yaml = "0.9.34" -url = "2.5.0" +url = { version = "2.5.0", features = ["serde"] } diff --git a/src/convert.rs b/src/convert.rs index b41a6ae..4d4fa6b 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,187 +1,3 @@ -use std::{cell::RefCell, fmt::Error}; +pub mod npf; -use comrak::{arena_tree::Node, nodes::Ast, nodes::NodeValue}; - -mod npf; - -use crate::utils; -use npf::content_types::text::{Formatting, FormattingType, Subtypes}; -use npf::{content_types, objects::Media, ContentType, NPF}; - -#[derive(clap::ValueEnum, Clone, Debug)] -pub enum Formats { - TumblrNPF, -} - -pub fn to_tumblr_npf<'a>(ast: &'a Node<'a, RefCell>) -> Result, Error> { - let npf = RefCell::new(NPF::<'a>::new()); - - utils::iter_nodes_shallow(ast, &|node| match &node.data.borrow().value { - NodeValue::Paragraph => { - let text = RefCell::new(String::new()); - let formatting = RefCell::new(Vec::::new()); - - utils::iter_nodes_shallow(node, &|node| { - let mut text = text.borrow_mut(); - let mut formatting = formatting.borrow_mut(); - - match &node.data.borrow().value { - NodeValue::Link(l) => { - let t = utils::extract_text(node); - formatting.push(Formatting { - r#type: FormattingType::Link, - start: text.chars().count(), - end: text.chars().count() + t.chars().count() + 1, - url: Some(String::from(&l.url)), - color: None, - blog: None, - }); - text.push_str(&format!("{} ", &t)) - } - NodeValue::Strong => { - let t = utils::extract_text(node); - formatting.push(Formatting { - r#type: FormattingType::Bold, - start: text.chars().count(), - end: text.chars().count() + t.chars().count() + 1, - url: None, - color: None, - blog: None, - }); - text.push_str(&format!("{} ", &t)) - } - NodeValue::Emph => { - let t = utils::extract_text(node); - formatting.push(Formatting { - r#type: FormattingType::Italic, - start: text.chars().count(), - end: text.chars().count() + t.chars().count() + 1, - url: None, - color: None, - blog: None, - }); - text.push_str(&format!("{} ", &t)) - } - NodeValue::Text(t) => text.push_str(&format!("{} ", &t)), - NodeValue::Image(i) => { - if let Ok(u) = url::Url::parse(&i.url) { - if [ - Some("www.youtube.com"), - Some("youtube.com"), - Some("youtu.be"), - ] - .contains(&u.host_str()) - { - let mut video = content_types::Video::from(String::from(u)); - video.provider = Some(String::from("youtube")); - video.embed_iframe = Some(npf::objects::IFrame { - url: String::from(&video.url.clone().unwrap()), - width: None, - height: None, - }); - npf.borrow_mut().content.push(ContentType::Video(video)); - } - } else { - let mut image = content_types::Image::from(vec![Media { - r#type: None, - url: String::from(&i.url), - provider: None, - poster: None, - width: None, - height: None, - has_original_dimensions: None, - cropped: None, - original_dimensions_missing: None, - }]); - image.caption = Some(String::from(&i.title)); - image.alt_text = Some(utils::extract_text(node)); - npf.borrow_mut().content.push(ContentType::Image(image)); - } - } - _ => (), - }; - - Ok::<(), Error>(()) - })?; - - let text = text.borrow().trim().to_string().replace(" ", " "); - let mut block = content_types::Text::from(text); - - block.formatting = if formatting.borrow().len() > 0 { - Some(formatting.borrow().to_vec()) - } else { - None - }; - - npf.borrow_mut().content.push(ContentType::Text(block)); - - Ok::<(), Error>(()) - } - NodeValue::BlockQuote => { - let block_quote = to_tumblr_npf(node)?; - let final_block = RefCell::new(content_types::Text::new()); - - block_quote.borrow_mut().content.iter_mut().for_each(|b| { - if let ContentType::Text(t) = b { - let mut fb = final_block.borrow_mut(); - let text_len = fb.text.chars().count(); - - if let Some(formattings) = &t.formatting { - for formatting in formattings { - match fb.formatting { - Some(ref mut v) => v.push(Formatting { - start: text_len + formatting.start, - end: text_len + formatting.end, - ..formatting.clone() - }), - None => { - fb.formatting = Some(vec![Formatting { - start: text_len + formatting.start, - end: text_len + formatting.end, - ..formatting.clone() - }]); - } - } - } - } - fb.text.push_str(&format!("{}\n\n", &t.text)); - } else { - } - }); - - Ok(()) - } - NodeValue::Heading(h) => { - let mut text = content_types::Text::from(utils::extract_text(node)); - - if h.level == 1 { - text.subtype = Some(Subtypes::Heading1); - } else if h.level == 2 { - text.subtype = Some(Subtypes::Heading2); - } else if h.level == 3 { - text.subtype = Some(Subtypes::UnorderedListItem); - let formatting = Formatting { - r#type: FormattingType::Bold, - start: 0, - end: text.text.chars().count() + 1, - url: None, - color: None, - blog: None, - }; - match text.formatting { - Some(ref mut f) => f.push(formatting), - None => text.formatting = Some(vec![formatting]), - }; - } else { - text.subtype = Some(Subtypes::UnorderedListItem); - } - - npf.borrow_mut().content.push(ContentType::Text(text)); - - Ok(()) - } - _ => Ok(()), - })?; - - Ok(npf) -} +fn test() {} diff --git a/src/convert/npf.rs b/src/convert/npf.rs index 1f08ea0..0a411b1 100644 --- a/src/convert/npf.rs +++ b/src/convert/npf.rs @@ -1,170 +1,142 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize)] -pub struct NPF<'a> { - #[serde(borrow)] - pub content: Vec>, +pub struct NPF { + pub content: Vec, } -impl<'a> NPF<'a> { - pub fn new() -> NPF<'a> { - NPF { content: vec![] } - } -} +pub mod blocks { + use std::{collections::HashMap, str::FromStr}; -#[derive(Debug, Deserialize, Serialize, Clone)] -#[serde(untagged)] -pub enum ContentType<'a> { - #[serde(borrow)] - Text(content_types::Text<'a>), - #[serde(borrow)] - Link(content_types::Link<'a>), - #[serde(borrow)] - Audio(content_types::Audio<'a>), - #[serde(borrow)] - Image(content_types::Image<'a>), - #[serde(borrow)] - Video(content_types::Video<'a>), -} - -pub mod content_types { - use super::objects; use serde::{Deserialize, Serialize}; - use std::collections::HashMap; - pub mod text { - use serde::{Deserialize, Serialize}; - #[derive(Debug, Clone, Deserialize, Serialize)] - #[serde(rename_all = "snake_case")] - pub enum Subtypes { - Heading1, - Heading2, - Quirky, - Quote, - Indented, - Chat, - OrderedListItem, - UnorderedListItem, - } + use super::{attributions, objects}; - #[serde_with::skip_serializing_none] - #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct Formatting { - pub start: usize, - pub end: usize, - pub r#type: FormattingType, - pub url: Option, - pub blog: Option, - pub color: Option, - } + #[derive(Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum BlockValue { + Text(BlockText), + Image(BlockImage), + Link(BlockLink), + Audio(BlockAudio), + Video(BlockVideo), + } - #[derive(Debug, Clone, Deserialize, Serialize)] - #[serde(rename_all = "snake_case")] - pub enum FormattingType { - Bold, - Italic, - Small, - Strikethrough, - Link, - Mention, + #[derive(Debug, Deserialize, Serialize)] + #[serde(rename_all = "snake_case")] + pub enum BlockTextSubtype { + Heading1, + Heading2, + Quirky, + Quote, + Indented, + Chat, + OrderedListItem, + UnordoredListItem, + } + + trait BlockType: Default { + fn new() -> Self { + Self::default() } } #[serde_with::skip_serializing_none] - #[derive(Debug, Deserialize, Serialize, Clone)] - pub struct Text<'a> { - r#type: &'a str, + #[derive(Debug, Deserialize, Serialize)] + pub struct BlockText { + r#type: String, + pub subtype: Option, pub text: String, - pub subtype: Option, - pub indent_level: Option, - pub formatting: Option>, + pub formatting: Option>, + pub ident_level: Option, } - impl<'a> Text<'a> { - pub fn new() -> Text<'a> { - Text { - r#type: "text", + impl Default for BlockText { + fn default() -> Self { + Self { + r#type: String::from("text"), + subtype: None, text: String::new(), - subtype: None, - indent_level: None, - formatting: None, - } - } - pub fn from(str: String) -> Text<'a> { - Text { - r#type: "text", - text: str, - subtype: None, - indent_level: None, formatting: None, + ident_level: None, } } } + impl From for BlockText { + fn from(value: String) -> Self { + Self { + text: value, + ..Self::default() + } + } + } + impl From<&str> for BlockText { + fn from(value: &str) -> Self { + Self { + text: String::from(value), + ..Self::default() + } + } + } + impl BlockType for BlockText {} #[serde_with::skip_serializing_none] - #[derive(Debug, Deserialize, Serialize, Clone)] - pub struct Image<'a> { - r#type: &'a str, + #[derive(Debug, Deserialize, Serialize)] + pub struct BlockImage { + r#type: String, pub media: Vec, + pub colors: Option>, + pub feedback_token: Option, + pub poster: Option, + pub attribution: Option, pub alt_text: Option, pub caption: Option, - pub feedback_token: Option, - pub colors: Option>, - pub attribution: Option, } - impl<'a> Image<'a> { - pub fn new() -> Image<'a> { - Image { - r#type: "image", + impl Default for BlockImage { + fn default() -> Self { + Self { + r#type: String::from("image"), media: vec![], + colors: None, + feedback_token: None, + poster: None, + attribution: None, alt_text: None, caption: None, - feedback_token: None, - colors: None, - attribution: None, - } - } - pub fn from(media: Vec) -> Image<'a> { - Image { - media, - r#type: "image", - alt_text: None, - caption: None, - feedback_token: None, - colors: None, - attribution: None, } } } + impl From> for BlockImage { + fn from(value: Vec) -> Self { + Self { + media: value, + ..Self::default() + } + } + } + impl From for BlockImage { + fn from(value: objects::Media) -> Self { + Self::from(vec![value]) + } + } + impl BlockType for BlockImage {} #[serde_with::skip_serializing_none] - #[derive(Debug, Deserialize, Serialize, Clone)] - pub struct Link<'a> { - r#type: &'a str, - pub url: String, + #[derive(Debug, Deserialize, Serialize)] + pub struct BlockLink { + r#type: String, + pub url: url::Url, pub title: Option, pub description: Option, pub author: Option, pub site_name: Option, - pub display_url: Option, - pub poster: Option>, + pub display_url: Option, + pub poster: Option, } - impl<'a> Link<'a> { - pub fn new() -> Link<'a> { - Link { - r#type: "link", - url: String::new(), - title: None, - description: None, - author: None, - site_name: None, - display_url: None, - poster: None, - } - } - pub fn from(url: String) -> Link<'a> { - Link { - r#type: "link", - url, + impl Default for BlockLink { + fn default() -> Self { + Self { + r#type: String::from("link"), + url: url::Url::from_str("https://tumblr.com").unwrap(), title: None, description: None, author: None, @@ -174,194 +146,463 @@ pub mod content_types { } } } + impl From for BlockLink { + fn from(value: url::Url) -> Self { + Self { + url: value, + ..Self::default() + } + } + } + impl BlockType for BlockLink {} #[serde_with::skip_serializing_none] - #[derive(Debug, Deserialize, Serialize, Clone)] - pub struct Audio<'a> { - r#type: &'a str, - pub url: Option, + #[derive(Debug, Deserialize, Serialize)] + pub struct BlockAudio { + r#type: String, pub media: Option, + pub url: Option, pub provider: Option, pub title: Option, pub artist: Option, pub album: Option, - pub poster: Option>, + pub poster: Option, pub embed_html: Option, - pub embed_url: Option, - pub metadata: Option, - pub attribution: Option, + pub embed_iframe: Option, + pub metadata: Option>, + pub attribution: Option, } - impl<'a> Audio<'a> { - pub fn new() -> Audio<'a> { - Audio { - r#type: "audio", - url: Some(String::new()), - media: None, - provider: None, - title: None, - artist: None, - album: None, - poster: None, - embed_html: None, - embed_url: None, - metadata: None, - attribution: None, + impl BlockAudio { + pub fn is_valid(&self) -> bool { + if self.url.is_some() || self.media.is_some() { + true + } else { + false } } - pub fn from(url: String) -> Audio<'a> { - Audio { - r#type: "audio", - url: Some(url), + } + impl Default for BlockAudio { + fn default() -> Self { + Self { + r#type: String::from("audio"), media: None, - provider: None, - title: None, - artist: None, - album: None, - poster: None, - embed_html: None, - embed_url: None, - metadata: None, - attribution: None, - } - } - pub fn from_media(media: objects::Media) -> Audio<'a> { - Audio { - r#type: "audio", url: None, - media: Some(media), provider: None, title: None, artist: None, album: None, poster: None, embed_html: None, - embed_url: None, + embed_iframe: None, metadata: None, attribution: None, } } } + impl From for BlockAudio { + fn from(value: objects::Media) -> Self { + Self { + media: Some(value), + ..Self::default() + } + } + } + impl From for BlockAudio { + fn from(value: url::Url) -> Self { + Self { + url: Some(value), + ..Self::default() + } + } + } + impl BlockType for BlockAudio {} #[serde_with::skip_serializing_none] - #[derive(Debug, Deserialize, Serialize, Clone)] - pub struct Video<'a> { - r#type: &'a str, + #[derive(Debug, Deserialize, Serialize)] + pub struct BlockVideo { + r#type: String, + pub url: Option, pub media: Option, - pub url: Option, pub provider: Option, pub embed_html: Option, - pub embed_iframe: Option, - pub embed_url: Option, - pub poster: Option>, - pub filmstrip: Option>, - pub metadata: Option, - pub attribution: Option, + pub embed_iframe: Option, + pub embed_url: Option, + pub poster: Option, + pub filmstrip: Option, + pub attribution: Option, pub can_autoplay_on_cellular: Option, } - impl<'a> Video<'a> { - pub fn new() -> Video<'a> { - Video { - r#type: "video", - url: Some(String::new()), - media: None, - provider: None, - embed_html: None, - embed_iframe: None, - embed_url: None, - poster: None, - filmstrip: None, - metadata: None, - attribution: None, - can_autoplay_on_cellular: None, + impl BlockVideo { + pub fn is_valid(&self) -> bool { + if self.url.is_some() || self.media.is_some() { + true + } else { + false } } - pub fn from(url: String) -> Video<'a> { - Video { - r#type: "video", - url: Some(url), + } + impl Default for BlockVideo { + fn default() -> Self { + Self { + r#type: String::from("audio"), media: None, - provider: None, - embed_html: None, - embed_iframe: None, - embed_url: None, - poster: None, - filmstrip: None, - metadata: None, - attribution: None, - can_autoplay_on_cellular: None, - } - } - pub fn from_media(media: objects::Media) -> Video<'a> { - Video { - r#type: "video", url: None, - media: Some(media), provider: None, embed_html: None, embed_iframe: None, embed_url: None, poster: None, filmstrip: None, - metadata: None, attribution: None, can_autoplay_on_cellular: None, } } } + impl From for BlockVideo { + fn from(value: objects::Media) -> Self { + Self { + media: Some(value), + ..Self::default() + } + } + } + impl From for BlockVideo { + fn from(value: url::Url) -> Self { + Self { + url: Some(value), + ..Self::default() + } + } + } + impl BlockType for BlockVideo {} +} + +pub mod text_formatting { + use std::{ops::Range, str::FromStr}; + + use serde::{Deserialize, Serialize}; + + use super::objects; + + #[derive(Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum FormatValue { + Bold(FormatTypeBold), + Italic(FormatTypeItalic), + StrikeThrough(FormatTypeStrikeThrough), + Small(FormatTypeSmall), + Link(FormatTypeLink), + Mention(FormatTypeMention), + Color(FormatTypeColor), + } + + // TODO: Make default() be private, removing the Default implementation + // since private implementations does't appear outside the module + trait FormatType: Default + From> + From { + fn new() -> Self { + Self::default() + } + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct FormatTypeLink { + r#type: String, + pub start: u64, + pub end: u64, + pub url: url::Url, + } + impl Default for FormatTypeLink { + fn default() -> Self { + Self { + r#type: String::from("link"), + start: 0, + end: 0, + url: url::Url::from_str("https://tumblr.com").unwrap(), + } + } + } - // TODO: Paywall type + #[derive(Debug, Deserialize, Serialize)] + pub struct FormatTypeMention { + r#type: String, + pub start: u64, + pub end: u64, + pub blog: objects::BlogInfo, + } + impl Default for FormatTypeMention { + fn default() -> Self { + Self { + r#type: String::from("mention"), + start: 0, + end: 0, + blog: objects::BlogInfo::default(), + } + } + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct FormatTypeColor { + r#type: String, + pub start: u64, + pub end: u64, + pub hex: String, + } + impl Default for FormatTypeColor { + fn default() -> Self { + Self { + r#type: String::from("link"), + start: 0, + end: 0, + hex: String::from("#ffffff"), + } + } + } + + macro_rules! ImplInlines { + // If passed token is already defined (is a type), implements + // the FormattingInline traits + (for $($t:ty),+) => { + $(impl From> for $t { + fn from(value: Range) -> Self { + Self { + start: value.start, + end: value.end, + ..Self::default() + } + } + })* + $(impl From for $t { + fn from(value: String) -> Self { + Self { + start: 0, + end: value.chars().count() as u64, + ..Self::default() + } + } + })* + $(impl From<&str> for $t { + fn from(value: &str) -> Self { + Self { + start: 0, + end: value.chars().count() as u64, + ..Self::default() + } + } + })* + $(impl FormatType for $t {})* + }; + // Defines the struct and implements Default trait if the token is an + // identifier and a literal + (for $($t:ident $s:literal),+) => { + $(#[derive(Debug, Deserialize, Serialize)] + pub struct $t { + r#type: String, + pub start: u64, + pub end: u64, + })* + $(impl Default for $t { + fn default() -> Self { + Self { + r#type: String::from($s), + start: 0, + end: 0, + } + } + })* + $(ImplInlines!(for $t);)* + } + } + ImplInlines!(for + FormatTypeBold "bold", + FormatTypeItalic "italic", + FormatTypeStrikeThrough "strikethrough", + FormatTypeSmall "small" + ); + ImplInlines!(for + FormatTypeLink, + FormatTypeMention, + FormatTypeColor + ); } + pub mod objects { use serde::{Deserialize, Serialize}; - #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct Blog { + #[serde_with::skip_serializing_none] + #[derive(Debug, Deserialize, Serialize)] + pub struct BlogInfo { pub uuid: String, - pub name: String, - pub url: String, + pub name: Option, + pub url: Option, + } + impl BlogInfo { + pub fn new(uuid: &str) -> Self { + Self::from(uuid) + } + } + impl Default for BlogInfo { + fn default() -> Self { + Self { + uuid: String::new(), + name: None, + url: None, + } + } + } + impl From for BlogInfo { + fn from(value: String) -> Self { + Self { + uuid: value, + ..Self::default() + } + } + } + impl From<&str> for BlogInfo { + fn from(value: &str) -> Self { + Self { + uuid: String::from(value), + ..Self::default() + } + } + } + impl From for BlogInfo { + fn from(value: url::Url) -> Self { + Self { + url: Some(value), + ..Self::default() + } + } } - #[serde_with::skip_serializing_none] - #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct Media { - pub r#type: Option, - pub url: String, - pub width: Option, - pub height: Option, - pub poster: Option, - pub provider: Option, - pub original_dimensions_missing: Option, - pub cropped: Option, - pub has_original_dimensions: Option, - } - - #[serde_with::skip_serializing_none] - #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct IFrame { - pub url: String, - pub width: Option, - pub height: Option, - } - - #[serde_with::skip_serializing_none] - #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct MediaPoster { - pub r#type: String, - pub url: String, - pub width: Option, - pub height: Option, - } - - #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct Attribution { - pub r#type: String, - pub url: String, - pub post: Post, - pub blug: Blog, - } - - #[derive(Debug, Clone, Deserialize, Serialize)] + #[derive(Debug, Deserialize, Serialize)] pub struct Post { pub id: u64, } + impl Post { + pub fn new(id: u64) -> Self { + Self::from(id) + } + } + impl Default for Post { + fn default() -> Self { + Self { id: 0 } + } + } + impl From for Post { + fn from(value: u64) -> Self { + Self { id: value } + } + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct Media { + pub r#type: Option, + pub url: url::Url, + pub width: Option, + pub height: Option, + pub original_dimensions_missing: Option, + pub cropped: Option, + pub has_original_dimentions: Option, + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct EmbedIframe { + pub url: url::Url, + pub width: u64, + pub height: u64, + } +} + +pub mod attributions { + use std::str::FromStr; + + use serde::{Deserialize, Serialize}; + + use super::objects; + + #[derive(Debug, Deserialize, Serialize)] + #[serde(untagged)] + pub enum AttributionValue { + Post(AttributionPost), + Link(AttributionLink), + Blog(AttributionBlog), + } + + trait AttributionType: Default { + fn new() -> Self { + Self::default() + } + } + + #[derive(Debug, Deserialize, Serialize)] + pub struct AttributionPost { + r#type: String, + pub url: url::Url, + pub post: objects::Post, + pub blog: objects::BlogInfo, + } + impl Default for AttributionPost { + fn default() -> Self { + Self { + r#type: String::from("post"), + url: url::Url::from_str("https://tumblr.com").unwrap(), + post: objects::Post::new(0), + blog: objects::BlogInfo::new("t:0"), + } + } + } + impl AttributionType for AttributionPost {} + + #[derive(Debug, Deserialize, Serialize)] + pub struct AttributionLink { + r#type: String, + pub url: url::Url, + } + impl Default for AttributionLink { + fn default() -> Self { + Self { + r#type: String::from("link"), + url: url::Url::from_str("https://tumblr.com").unwrap(), + } + } + } + impl AttributionType for AttributionLink {} + + #[derive(Debug, Deserialize, Serialize)] + pub struct AttributionBlog { + r#type: String, + pub url: Option, + pub blog: objects::BlogInfo, + } + impl Default for AttributionBlog { + fn default() -> Self { + Self { + r#type: String::from("blog"), + url: None, + blog: objects::BlogInfo::new("t:0"), + } + } + } + impl AttributionType for AttributionBlog {} + + #[derive(Debug, Deserialize, Serialize)] + pub struct AttributionApp { + r#type: String, + pub url: url::Url, + pub app_name: Option, + pub display_text: Option, + pub logo: Option, + } + impl Default for AttributionApp { + fn default() -> Self { + Self { + r#type: String::from("blog"), + url: url::Url::from_str("https://tumblr.com").unwrap(), + app_name: None, + display_text: None, + logo: None, + } + } + } + impl AttributionType for AttributionApp {} }