reddit_rs/models/
link.rs

1use std::{collections::HashMap, fmt::Display, str::FromStr};
2
3use chrono::{DateTime, Utc};
4use iri_string::types::{IriAbsoluteString, IriRelativeString, IriString};
5use mime::Mime;
6use serde::{de::Error, Deserialize, Serialize};
7use uuid::Uuid;
8
9pub use iri_string;
10
11use crate::models::{author::Author, award::{Awarding, TopAwardedType}, color::Color, comment::Comment, flair::{FlairTextColor, FlairType}, fullname::FullName, guilding::Gildings, media::{Media, MediaEmbed}, richtext::Richtext};
12
13#[allow(clippy::struct_excessive_bools)] // reddit api sucks
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
15pub struct Link {
16    pub approved_at_utc: Option<DateTime<Utc>>,
17
18    pub selftext: String,
19
20    #[serde(deserialize_with = "crate::utils::bool_from_int")]
21    pub gilded: bool,
22    pub title: String,
23    pub pwls: Option<u32>,
24
25    #[serde(flatten)]
26    pub thumbnail: LinkThumbnail,
27
28    #[serde(flatten)]
29    pub post_link: PostLink,
30
31    #[serde(flatten)]
32    pub author: Author,
33
34    #[serde(flatten)]
35    pub flair: LinkFlair,
36
37    #[serde(flatten)]
38    pub subreddit_info: SubredditInfo,
39
40    #[serde(flatten)]
41    pub mod_info: ModInfo,
42
43    pub allow_live_comments: bool,
44    pub archived: bool,
45    pub can_gild: bool,
46    pub can_mod_post: bool,
47    pub clicked: bool,
48    pub contest_mode: bool,
49    pub hidden: bool,
50    pub hide_score: bool,
51    pub is_created_from_ads_ui: bool,
52    pub is_crosspostable: bool,
53    pub is_meta: bool,
54    pub is_original_content: bool,
55    pub is_reddit_media_domain: bool,
56    pub is_robot_indexable: bool,
57    pub is_self: bool,
58    pub is_video: bool,
59    pub locked: bool,
60    pub no_follow: bool,
61    pub over_18: bool,
62    pub pinned: bool,
63    pub quarantine: bool,
64    pub saved: bool,
65    pub send_replies: bool,
66    pub spoiler: bool,
67    pub stickied: bool,
68    pub visited: bool,
69
70    pub name: FullName,
71
72    pub ups: u32,
73    pub downs: u32,
74    pub upvote_ratio: f32,
75
76    pub total_awards_received: u32,
77
78    pub score: u32,
79    pub num_comments: u32,
80    pub num_crossposts: u32,
81
82    pub media_metadata: Option<MediaMetadatas>,
83
84    pub media_only: bool,
85    pub media: Option<Media>,
86    #[serde(deserialize_with = "crate::utils::object_empty_as_none")]
87    pub media_embed: Option<MediaEmbed>,
88
89    pub secure_media: Option<Media>,
90    #[serde(deserialize_with = "crate::utils::object_empty_as_none")]
91    pub secure_media_embed: Option<MediaEmbed>,
92
93    pub user_reports: Vec<UserReport>,
94    pub category: Option<()>,
95    pub approved_by: Option<()>,
96    #[serde(deserialize_with = "crate::utils::false_or_datetime")]
97    pub edited: Option<DateTime<Utc>>,
98    pub gildings: Gildings,
99    pub content_categories: Option<Vec<ContentCategories>>,
100    pub wls: Option<u32>,
101    pub removed_by_category: Option<()>,
102    pub banned_by: Option<()>,
103    pub domain: String,
104    pub selftext_html: Option<String>,
105    pub likes: Option<()>,
106    pub suggested_sort: Option<Sort>,
107    pub banned_at_utc: Option<()>,
108    pub view_count: Option<()>,
109
110    // awards
111    pub all_awardings: Vec<Awarding>,
112    pub awarders: Vec<()>,
113    pub top_awarded_type: Option<TopAwardedType>,
114
115    pub treatment_tags: Vec<()>,
116    pub removed_by: Option<()>,
117    pub num_reports: Option<()>,
118    /// "moderator"
119    pub distinguished: Option<Distinguished>,
120    pub removal_reason: Option<()>,
121    /// REVIEW: base 36? "q4550i"
122    pub id: String,
123    pub report_reasons: Option<()>,
124    pub discussion_type: Option<()>,
125
126    pub whitelist_status: Option<WhitelistStatus>,
127    pub parent_whitelist_status: Option<WhitelistStatus>,
128    pub permalink: IriRelativeString,
129
130    #[serde(deserialize_with = "crate::utils::integer_or_float_to_datetime")]
131    pub created: DateTime<Utc>,
132    #[serde(deserialize_with = "crate::utils::integer_or_float_to_datetime")]
133    pub created_utc: DateTime<Utc>,
134
135    /// REVIEW: Condense `post_hint` and `preview`?
136    pub post_hint: Option<PostHint>,
137    pub preview: Option<Preview>,
138
139    pub url_overridden_by_dest: Option<IriString>,
140    // #[serde(flatten)]
141    // pub rest: HashMap<String, serde_json::value::Value>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
145#[serde(rename_all = "snake_case")]
146pub enum Distinguished {
147    Moderator,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
151pub struct MediaMetadatas(HashMap<MediaMetadataId, MediaMetadata>);
152
153#[derive(Debug, Clone, Serialize, PartialEq, Hash, Eq, PartialOrd, Ord)]
154pub struct MediaMetadataId(u128);
155
156impl<'de> Deserialize<'de> for MediaMetadataId {
157    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
158    where
159        D: serde::Deserializer<'de>,
160    {
161        let id = String::deserialize(deserializer)
162            .map(|s| u128::from_str_radix(&s, 36).map_err(D::Error::custom))??;
163
164        Ok(Self(id))
165    }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
169pub struct MediaMetadata {
170    status: MediaMetadataStatus,
171    #[serde(rename = "e")]
172    ty: MediaMetadataType,
173    #[serde(rename = "m", with = "crate::utils::mime_serde")]
174    mime_type: Mime,
175    #[serde(rename = "p")]
176    p: Vec<P>,
177    #[serde(rename = "s")]
178    s: P,
179    #[serde(rename = "id")]
180    id: MediaMetadataId,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
184pub struct P {
185    y: u16,
186    x: u16,
187    u: IriString,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
191// #[serde(rename_all = "snake_case")]
192pub enum MediaMetadataType {
193    Image,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
197#[serde(rename_all = "snake_case")]
198pub enum MediaMetadataStatus {
199    Valid,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
203#[serde(untagged)]
204pub enum PostLink {
205    Url {
206        url: IriString,
207    },
208    Crosspost {
209        #[serde(rename = "crosspost_parent")]
210        parent: FullName,
211        #[serde(rename = "crosspost_parent_list")]
212        list: Vec<Link>,
213        url: IriRelativeString,
214    },
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
218pub struct ModInfo {
219    #[serde(rename = "mod_reason_title")]
220    pub reason_title: Option<String>,
221    #[serde(rename = "mod_reports")]
222    pub reports: Vec<()>,
223    #[serde(rename = "mod_note")]
224    pub note: Option<()>,
225    #[serde(rename = "mod_reason_by")]
226    pub reason_by: Option<()>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
230pub struct SubredditInfo {
231    #[serde(rename = "subreddit")]
232    pub name: String,
233    #[serde(rename = "subreddit_name_prefixed")]
234    pub name_prefixed: String,
235    #[serde(rename = "subreddit_type")]
236    pub ty: SubredditType,
237    #[serde(rename = "subreddit_subscribers")]
238    pub subscribers: u32,
239    #[serde(rename = "subreddit_id")]
240    pub id: FullName,
241}
242
243#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
244pub struct LinkFlairTemplateId(Uuid);
245
246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
247pub struct LinkFlair {
248    #[serde(deserialize_with = "Color::from_hex_str")]
249    #[serde(rename = "link_flair_background_color")]
250    pub background_color: Option<Color>,
251
252    #[serde(rename = "link_flair_template_id")]
253    pub template_id: Option<LinkFlairTemplateId>,
254
255    #[serde(rename = "link_flair_text_color")]
256    pub text_color: FlairTextColor,
257
258    #[serde(rename = "link_flair_text")]
259    pub text: Option<String>,
260
261    #[serde(rename = "link_flair_type")]
262    pub ty: FlairType,
263
264    #[serde(rename = "link_flair_richtext")]
265    pub richtext: Vec<Richtext>,
266
267    #[serde(rename = "link_flair_css_class")]
268    pub css_class: Option<String>,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
272pub struct LinkThumbnail {
273    #[serde(rename = "thumbnail")]
274    pub link: LinkThumbnailType,
275    #[serde(rename = "thumbnail_height")]
276    pub height: Option<u16>,
277    #[serde(rename = "thumbnail_width")]
278    pub width: Option<u16>,
279}
280
281#[derive(Debug, Clone, Serialize, PartialEq)]
282#[serde(untagged)]
283pub enum LinkThumbnailType {
284    /// thumbnail link is `self`
285    This,
286    Default,
287    Nsfw,
288    Spoiler,
289    Url(IriAbsoluteString),
290}
291
292impl FromStr for LinkThumbnailType {
293    type Err = String;
294
295    fn from_str(s: &str) -> Result<Self, Self::Err> {
296        match s {
297            "self" => Ok(Self::This),
298            "default" => Ok(Self::Default),
299            "nsfw" => Ok(Self::Nsfw),
300            "spoiler" => Ok(Self::Spoiler),
301            _ => Ok(Self::Url(IriAbsoluteString::from_str(s).map_err(
302                |why| format!("invalid IRI or thumbnail type: {}, error: {}", s, why),
303            )?)),
304        }
305    }
306}
307
308impl<'de> Deserialize<'de> for LinkThumbnailType {
309    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
310    where
311        D: serde::Deserializer<'de>,
312    {
313        LinkThumbnailType::from_str(&String::deserialize(deserializer)?)
314            .map_err(serde::de::Error::custom)
315    }
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
319pub enum PostHint {
320    #[serde(rename = "image")]
321    Image,
322
323    #[serde(rename = "link")]
324    Link,
325
326    #[serde(rename = "gallery")]
327    Gallery,
328
329    #[serde(rename = "self")]
330    This,
331
332    #[serde(rename = "rich:video")]
333    RichVideo,
334
335    #[serde(rename = "hosted:video")]
336    HostedVideo,
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
340pub struct Preview {
341    enabled: bool,
342    images: Vec<Images>,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
346pub struct Images {
347    id: String,
348    resolutions: Vec<Image>,
349    source: Image,
350    variants: Variants,
351}
352
353#[serde_with::skip_serializing_none]
354#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
355pub struct Variants {
356    obfuscated: Option<Variant>,
357    nsfw: Option<Variant>,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
361pub struct Variant {
362    resolutions: Vec<Image>,
363    source: Image,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
367pub struct Image {
368    height: u16,
369    width: u16,
370    url: IriAbsoluteString,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
374#[serde(rename_all = "snake_case")]
375pub enum ContentCategories {
376    Photography,
377    DrawingAndPainting,
378}
379
380#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
381pub struct UserReport;
382
383#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
384#[serde(tag = "kind", content = "data")]
385pub enum RedditListing {
386    Listing {
387        after: Option<FullName>,
388        dist: Option<u32>,
389        modhash: String,
390        geo_filter: Option<String>,
391        children: Vec<RedditListing>,
392    },
393    #[serde(rename = "t3")]
394    Link(Box<Link>),
395    #[serde(rename = "t1")]
396    Comment(Box<Comment>),
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
400#[serde(rename_all = "snake_case")]
401pub enum Sort {
402    New,
403    Top,
404    Confidence,
405}
406
407impl Display for Sort {
408    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409        f.write_str(match self {
410            Sort::New => "new",
411            Sort::Top => "top",
412            Sort::Confidence => "confidence",
413        })
414    }
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
418#[serde(rename_all = "snake_case")]
419pub enum SubredditType {
420    Public,
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
424#[serde(rename_all = "snake_case")]
425pub enum WhitelistStatus {
426    AllAds,
427    SomeAds,
428    NoAds,
429    PromoAdultNsfw,
430}