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)] #[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 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 pub distinguished: Option<Distinguished>,
120 pub removal_reason: Option<()>,
121 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 pub post_hint: Option<PostHint>,
137 pub preview: Option<Preview>,
138
139 pub url_overridden_by_dest: Option<IriString>,
140 }
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)]
191pub 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 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}