deviantart/types/
deviation.rs

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