deviantart/types/
deviation.rs

1use super::Media;
2use std::{collections::HashMap, path::Path};
3use url::Url;
4
5/// A Deviation
6#[derive(Debug, serde::Deserialize)]
7pub struct Deviation {
8    // TODO: This is a number in a scraped deviation. Make either parse here.
9    // /// DeviantArt Author
10    // pub author: Author,
11    /// ?
12    #[serde(rename = "blockReasons")]
13    pub block_reasons: Vec<serde_json::Value>,
14
15    /// Deviation ID
16    #[serde(rename = "deviationId")]
17    pub deviation_id: u64,
18
19    /// Deviation Type
20    #[serde(rename = "type")]
21    pub kind: String,
22
23    /// Image Url
24    pub url: Url,
25
26    /// Media Info
27    pub media: Media,
28
29    /// Title
30    pub title: String,
31
32    /// Text content for literature
33    #[serde(rename = "textContent")]
34    pub text_content: Option<TextContext>,
35
36    /// Whether this is downloadable
37    #[serde(rename = "isDownloadable")]
38    pub is_downloadable: bool,
39
40    /// Unknown K/Vs
41    #[serde(flatten)]
42    pub unknown: HashMap<String, serde_json::Value>,
43}
44
45impl Deviation {
46    /// Get the media url for this [`Deviation`].
47    pub fn get_media_url(&self) -> Option<Url> {
48        let mut url = self.media.base_uri.as_ref()?.clone();
49        url.query_pairs_mut()
50            .append_pair("token", self.media.token.first()?);
51        Some(url)
52    }
53
54    /// Get the "download" url for this [`Deviation`].
55    pub fn get_download_url(&self) -> Option<Url> {
56        let mut url = self.media.base_uri.as_ref()?.clone();
57        url.query_pairs_mut()
58            .append_pair("token", self.media.token.get(1)?);
59        Some(url)
60    }
61
62    /// Get the fullview url for this [`Deviation`].
63    pub fn get_fullview_url(&self) -> Option<Url> {
64        self.media.get_fullview_url()
65    }
66
67    /// Get the GIF url for this [`Deviation`].
68    pub fn get_gif_url(&self) -> Option<Url> {
69        let mut url = self.media.get_gif_media_type()?.b.clone()?;
70        url.query_pairs_mut()
71            .append_pair("token", self.media.token.first()?);
72        Some(url)
73    }
74
75    /// Get the best video url
76    pub fn get_best_video_url(&self) -> Option<&Url> {
77        let url = self.media.get_best_video_media_type()?.b.as_ref()?;
78        Some(url)
79    }
80
81    /// Whether this is an image
82    pub fn is_image(&self) -> bool {
83        self.kind == "image"
84    }
85
86    /// Whether this is literature
87    pub fn is_literature(&self) -> bool {
88        self.kind == "literature"
89    }
90
91    /// Whether this is a film
92    pub fn is_film(&self) -> bool {
93        self.kind == "film"
94    }
95
96    /// Get the most "fitting" url to download an image.
97    ///
98    /// Usually, [`DeviationExtended`] holds better data than a [`Deviation`], so prefer that instead.
99    pub fn get_image_download_url(&self) -> Option<Url> {
100        // Try to get the download url.
101        if let Some(url) = self.get_download_url() {
102            return Some(url);
103        }
104
105        // If that fails, this is probably a gif, so we try to get the gif url.
106        if let Some(url) = self.get_gif_url() {
107            return Some(url);
108        }
109
110        // Otherwise, assume failure
111        None
112    }
113    /// Try to get the original extension of this [`Deviation`]
114    pub fn get_extension(&self) -> Option<&str> {
115        if self.is_image() {
116            let url = self
117                .media
118                .get_gif_media_type()
119                .and_then(|media_type| media_type.b.as_ref())
120                .or(self.media.base_uri.as_ref())?;
121            Path::new(url.as_str()).extension()?.to_str()
122        } else if self.is_literature() {
123            None
124        } else if self.is_film() {
125            let url = self.media.get_best_video_media_type()?.b.as_ref()?;
126            Path::new(url.as_str()).extension()?.to_str()
127        } else {
128            None
129        }
130    }
131}
132
133#[derive(Debug, serde::Deserialize)]
134pub struct Author {
135    /// is the user new
136    #[serde(rename = "isNewDeviant")]
137    pub is_new_deviant: bool,
138
139    /// User UUID
140    #[serde(rename = "useridUuid")]
141    pub userid_uuid: String,
142
143    /// User icon url
144    pub usericon: Url,
145
146    /// User ID
147    #[serde(rename = "userId")]
148    pub user_id: u64,
149
150    /// Username
151    pub username: String,
152
153    /// Unknown K/Vs
154    #[serde(flatten)]
155    pub unknown: HashMap<String, serde_json::Value>,
156}
157
158/// Text Content for literature
159#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
160pub struct TextContext {
161    /// Excerpt of text
162    pub excerpt: String,
163
164    /// Html data
165    pub html: Html,
166
167    /// Unknown K/Vs
168    #[serde(flatten)]
169    pub unknown: HashMap<String, serde_json::Value>,
170}
171
172/// Text Context html
173#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
174pub struct Html {
175    /// ?
176    pub features: String,
177
178    /// Text markup data
179    pub markup: Option<String>,
180
181    /// The kind of text data
182    #[serde(rename = "type")]
183    pub kind: String,
184
185    /// Unknown K/Vs
186    #[serde(flatten)]
187    pub unknown: HashMap<String, serde_json::Value>,
188}
189
190impl Html {
191    /// Try to parse the markup field
192    pub fn get_markup(&self) -> Option<Result<Markup, serde_json::Error>> {
193        let markup = self.markup.as_ref()?;
194        Some(serde_json::from_str(markup))
195    }
196}
197
198/// Text Context Html Markup
199#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
200pub struct Markup {
201    pub version: u32,
202    pub document: MarkupDocument,
203}
204
205#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
206pub struct MarkupDocument {
207    pub content: Vec<MarkupDocumentContent>,
208
209    #[serde(rename = "type")]
210    pub kind: String,
211}
212
213#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
214pub struct MarkupDocumentContent {
215    #[serde(rename = "type")]
216    pub kind: String,
217    /// This is not just html element attributes,
218    /// it also contains associated data only relavent for the given element.
219    pub attrs: HashMap<String, serde_json::Value>,
220
221    pub content: Option<Vec<MarkupDocumentContentInner>>,
222}
223
224/// This may be the same type as MarkupDocumentContent.
225#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
226pub struct MarkupDocumentContentInner {
227    #[serde(rename = "type")]
228    pub kind: String,
229    /// Only Some when kind is "text".
230    pub text: Option<String>,
231}