deviantart/types/
media.rs

1use std::collections::HashMap;
2use url::Url;
3
4/// DeviantArt [`DeviationMedia`] media type.
5#[derive(Debug, serde::Deserialize)]
6pub struct MediaType {
7    /// The content. A uri used with base_uri.
8    #[serde(rename = "c")]
9    pub content: Option<String>,
10
11    /// Image Height
12    #[serde(rename = "h")]
13    pub height: u64,
14
15    // /// ?
16    // // pub r: u64,
17    /// The kind of media
18    #[serde(rename = "t")]
19    pub kind: String,
20
21    /// Image Width
22    #[serde(rename = "w")]
23    pub width: u64,
24
25    // /// ?
26    // // pub f: Option<u64>,
27    /// ?
28    pub b: Option<Url>,
29
30    /// Unknown K/Vs
31    #[serde(flatten)]
32    pub unknown: HashMap<String, serde_json::Value>,
33}
34
35impl MediaType {
36    /// Whether this is the fullview
37    pub fn is_fullview(&self) -> bool {
38        self.kind == "fullview"
39    }
40
41    /// Whether this is a gif
42    pub fn is_gif(&self) -> bool {
43        self.kind == "gif"
44    }
45
46    /// Whether this is a video
47    pub fn is_video(&self) -> bool {
48        self.kind == "video"
49    }
50}
51
52/// A structure that stores media metadata.
53///
54/// Needed to create image urls.
55#[derive(Debug, serde::Deserialize)]
56pub struct Media {
57    /// The base uri
58    #[serde(rename = "baseUri")]
59    pub base_uri: Option<Url>,
60
61    /// Image token
62    #[serde(default)]
63    pub token: Vec<String>,
64
65    /// Types
66    pub types: Vec<MediaType>,
67
68    /// Pretty Name
69    #[serde(rename = "prettyName")]
70    pub pretty_name: Option<String>,
71
72    /// Unknown K/Vs
73    #[serde(flatten)]
74    pub unknown: HashMap<String, serde_json::Value>,
75}
76
77impl Media {
78    /// Try to get the fullview [`MediaType`].
79    pub fn get_fullview_media_type(&self) -> Option<&MediaType> {
80        self.types.iter().find(|t| t.is_fullview())
81    }
82
83    /// Try to get the gif [`MediaType`].
84    pub fn get_gif_media_type(&self) -> Option<&MediaType> {
85        self.types.iter().find(|t| t.is_gif())
86    }
87
88    /// Try to get the video [`MediaType`]
89    pub fn get_best_video_media_type(&self) -> Option<&MediaType> {
90        self.types
91            .iter()
92            .filter(|media_type| media_type.is_video())
93            .max_by_key(|media_type| media_type.width)
94    }
95
96    /// Get the fullview url for this [`Media`].
97    pub fn get_fullview_url(&self) -> Option<Url> {
98        let mut url = self.base_uri.as_ref()?.clone();
99
100        // Allow the "content" section of the path to not exist, but the fullview data MUST exist.
101        if let Some(path) = self.get_fullview_media_type()?.content.as_ref() {
102            let mut path_segments_mut = url.path_segments_mut().ok()?;
103
104            for path in path.split('/').filter(|p| !p.is_empty()) {
105                // Replace "<pretty-name>" with the actual pretty name.
106                let pretty_name = self.pretty_name.as_ref()?;
107                let path = path.replace("<prettyName>", pretty_name);
108                path_segments_mut.push(&path);
109            }
110        }
111
112        // We assume that a token is not provided in cases where it is not needed.
113        // As such, this part is optional.
114        // So far, a token is allowed to be missing when the "content" section of the fullview data is missing
115        // Correct this if these assumptions are wrong.
116        if let Some(token) = self.token.first() {
117            url.query_pairs_mut().append_pair("token", token);
118        }
119
120        Some(url)
121    }
122}