reddit/
types.rs

1/// Reddit base class.
2/// Listing things have neither name nor id because they are indefinite objects.
3/// That is, they are system generated, not user submitted, and are subject to change quickly and expire.
4/// See https://github.com/reddit-archive/reddit/wiki/JSON#thing-reddit-base-class
5#[derive(Debug, serde::Deserialize)]
6pub struct Thing {
7    /// this item's identifier, e.g. "8xwlg"
8    pub id: Option<Box<str>>,
9
10    /// Fullname of comment, e.g. "t1_c3v7f8u"
11    pub name: Option<Box<str>>,
12
13    /// Data
14    #[serde(flatten)]
15    pub data: ThingData,
16}
17
18/// kind:
19/// All things have a kind. The kind is a String identifier that denotes the object's type.
20/// Some examples: Listing, more, t1, t2
21/// data:
22/// A custom data structure used to hold valuable information.
23/// This object's format will follow the data structure respective of its kind. See below for specific structures.
24/// See https://www.reddit.com/dev/api#fullnames
25#[derive(Debug, serde::Deserialize)]
26#[serde(tag = "kind", content = "data")]
27pub enum ThingData {
28    #[serde(rename = "Listing")]
29    Listing(Box<Listing>),
30
31    // More is small + it already has a vector of things as a vec
32    #[serde(rename = "more")]
33    More(More),
34
35    #[serde(rename = "t1")]
36    Comment(Box<Comment>),
37
38    // TODO: Finish type
39    // #[serde(rename = "t2")]
40    // Account(serde_json::Value),
41    #[serde(rename = "t3")]
42    Link(Box<Link>),
43}
44
45impl ThingData {
46    /// Tries to get this ThingData as a listing
47    pub fn as_listing(&self) -> Option<&Listing> {
48        match self {
49            ThingData::Listing(listing) => Some(listing),
50            _ => None,
51        }
52    }
53
54    /// Tries to get this ThingData as a mutable listing
55    pub fn as_listing_mut(&mut self) -> Option<&mut Listing> {
56        match self {
57            ThingData::Listing(listing) => Some(listing),
58            _ => None,
59        }
60    }
61
62    /// Tries to turn this ThingData into a listing
63    pub fn into_listing(self) -> Option<Box<Listing>> {
64        match self {
65            ThingData::Listing(listing) => Some(listing),
66            _ => None,
67        }
68    }
69
70    /// Tries to get this ThingData as a link
71    pub fn as_link(&self) -> Option<&Link> {
72        match self {
73            ThingData::Link(link) => Some(link),
74            _ => None,
75        }
76    }
77
78    /// Tries to turn this ThingData into a link
79    pub fn into_link(self) -> Option<Box<Link>> {
80        match self {
81            ThingData::Link(link) => Some(link),
82            _ => None,
83        }
84    }
85}
86
87/// Used to paginate content that is too long to display in one go.
88/// Add the query argument before or after with the value given to get the previous or next page.
89/// This is usually used in conjunction with a count argument.
90/// Exception: Unlike the other classes documented on this page, Listing is not a thing subclass, as it inherits directly from the Python base class, Object.
91/// Although it does have data and kind as parameters, it does not have id and name.
92/// A listing's kind will always be Listing and its data will be a List of things.
93/// kind == "Listing"
94/// See https://github.com/reddit-archive/reddit/wiki/JSON#listing
95#[derive(Debug, serde::Deserialize)]
96pub struct Listing {
97    /// The fullname of the listing that follows before this page. null if there is no previous page.
98    pub before: Option<Box<str>>,
99
100    /// The fullname of the listing that follows after this page. null if there is no next page.
101    pub after: Option<Box<str>>,
102
103    /// This modhash is not the same modhash provided upon login.
104    /// You do not need to update your user's modhash everytime you get a new modhash.
105    /// You can reuse the modhash given upon login.
106    pub modhash: Box<str>,
107
108    /// A list of things that this Listing wraps.
109    pub children: Vec<Thing>,
110}
111
112/// Implementation
113/// See: https://github.com/reddit-archive/reddit/wiki/JSON#votable-implementation
114#[derive(Debug, serde::Deserialize)]
115pub struct Votable {
116    /// the number of upvotes. (includes own)
117    pub ups: i64,
118
119    /// the number of downvotes. (includes own)
120    pub downs: u64,
121
122    /// true if thing is liked by the user, false if thing is disliked, null if the user has not voted or you are not logged in.
123    /// Certain languages such as Java may need to use a boolean wrapper that supports null assignment.
124    pub likes: Option<bool>,
125}
126
127/// Implementation
128/// See https://github.com/reddit-archive/reddit/wiki/JSON#created-implementation
129#[derive(Debug, serde::Deserialize)]
130pub struct Created {
131    // TODO: use chrono?
132    /// the time of creation in local epoch-second format. ex: 1331042771.0
133    pub created: f64,
134
135    /// the time of creation in UTC epoch-second format. Note that neither of these ever have a non-zero fraction.
136    pub created_utc: f64,
137}
138
139/// Implements votable | created
140/// kind == "t1"
141/// See https://github.com/reddit-archive/reddit/wiki/JSON#comment-implements-votable--created
142#[derive(Debug, serde::Deserialize)]
143pub struct Comment {
144    /// who approved this comment. null if nobody or you are not a mod
145    pub approved_by: Option<Box<str>>,
146
147    /// the account name of the poster
148    pub author: Box<str>,
149
150    /// the CSS class of the author's flair. subreddit specific
151    pub author_flair_css_class: Option<Box<str>>,
152
153    /// the text of the author's flair. subreddit specific
154    pub author_flair_text: Option<Box<str>>,
155
156    /// who removed this comment. null if nobody or you are not a mod
157    pub banned_by: Option<Box<str>>,
158
159    /// the raw text.
160    /// this is the unformatted text which includes the raw markup characters such as ** for bold. <, >, and & are escaped.
161    pub body: Box<str>,
162
163    /// the formatted HTML text as displayed on reddit.
164    /// For example, text that is emphasised by * will now have <em> tags wrapping it.
165    /// Additionally, bullets and numbered lists will now be in HTML list format.
166    /// NOTE: The HTML string will be escaped. You must unescape to get the raw HTML.
167    pub body_html: Box<str>,
168
169    /// false if not edited, edit date in UTC epoch-seconds otherwise.
170    /// NOTE: for some old edited comments on reddit.com, this will be set to true instead of edit date.
171    pub special: Option<serde_json::Value>,
172
173    /// the number of times this comment received reddit gold
174    pub gilded: u64,
175
176    /// how the logged-in user has voted on the comment - True = upvoted, False = downvoted, null = no vote
177    pub likes: Option<bool>,
178
179    /// present if the comment is being displayed outside its thread (user pages, /r/subreddit/comments/.json, etc.).
180    /// Contains the author of the parent link
181    pub link_author: Option<Box<str>>,
182
183    /// ID of the link this comment is in
184    pub link_id: Box<str>,
185
186    /// present if the comment is being displayed outside its thread (user pages, /r/subreddit/comments/.json, etc.).
187    /// Contains the title of the parent link
188    pub link_title: Option<Box<str>>,
189
190    /// present if the comment is being displayed outside its thread (user pages, /r/subreddit/comments/.json, etc.).
191    /// Contains the URL of the parent link
192    pub link_url: Option<Box<str>>,
193
194    /// how many times this comment has been reported, null if not a mod
195    pub num_reports: Option<u64>,
196
197    /// ID of the thing this comment is a reply to, either the link or a comment in it
198    pub parent_id: Box<str>,
199
200    // TODO: Find out why this is a string sometimes
201    // /// A list of replies to this comment
202    // pub replies: Thing,
203
204    /// true if this post is saved by the logged in user
205    pub saved: bool,
206
207    /// the net-score of the comment
208    pub score: i64,
209
210    /// Whether the comment's score is currently hidden.
211    pub score_hidden: bool,
212
213    /// subreddit of thing excluding the /r/ prefix. "pics"
214    pub subreddit: Box<str>,
215
216    /// the id of the subreddit in which the thing is locatedss
217    pub subreddit_id: Box<str>,
218
219    /// to allow determining whether they have been distinguished by moderators/admins.
220    /// null = not distinguished.
221    /// moderator = the green \[M\].
222    /// admin = the red \[A\].
223    /// special = various other special distinguishes http://redd.it/19ak1b
224    pub distinguished: Option<Box<str>>,
225
226    /// Voting Implementation
227    #[serde(flatten)]
228    pub votable: Votable,
229
230    /// Created Implementation
231    #[serde(flatten)]
232    pub created: Created,
233    // Experimentally determined fields
234    // TODO: These are VERY best-effort, but i should still try to document what i can
235}
236
237/// Implements votable | created
238/// kind == "t3"
239/// See https://github.com/reddit-archive/reddit/wiki/JSON#link-implements-votable--created
240#[derive(Debug, serde::Deserialize)]
241pub struct Link {
242    /// the account name of the poster. null if this is a promotional link
243    pub author: Box<str>,
244
245    /// the CSS class of the author's flair. subreddit specific
246    pub author_flair_css_class: Option<Box<str>>,
247
248    /// the text of the author's flair. subreddit specific
249    pub author_flair_text: Option<Box<str>>,
250
251    /// probably always returns false
252    pub clicked: bool,
253
254    /// the domain of this link.
255    /// Self posts will be self.<subreddit> while other examples include en.wikipedia.org and s3.amazon.com
256    pub domain: Box<str>,
257
258    /// true if the post is hidden by the logged in user. false if not logged in or not hidden.
259    pub hidden: bool,
260
261    /// true if this link is a selfpost
262    pub is_self: bool,
263
264    /// how the logged-in user has voted on the link - True = upvoted, False = downvoted, null = no vote
265    pub likes: Option<bool>,
266
267    /// the CSS class of the link's flair.
268    pub link_flair_css_class: Option<Box<str>>,
269
270    /// the text of the link's flair.
271    pub link_flair_text: Option<Box<str>>,
272
273    /// whether the link is locked (closed to new comments) or not.
274    pub locked: bool,
275
276    // TODO: Finish type
277    /// Used for streaming video. Detailed information about the video and it's origins are placed here
278    pub media: serde_json::Value,
279
280    // TODO: Finish type
281    /// Used for streaming video. Technical embed specific information is found here.
282    pub media_embed: serde_json::Value,
283
284    /// the number of comments that belong to this link. includes removed comments.
285    pub num_comments: u64,
286
287    /// true if the post is tagged as NSFW. False if otherwise
288    pub over_18: bool,
289
290    /// relative URL of the permanent link for this link
291    pub permalink: Box<str>,
292
293    /// true if this post is saved by the logged in user
294    pub saved: bool,
295
296    /// the net-score of the link.
297    /// note: A submission's score is simply the number of upvotes minus the number of downvotes.
298    /// If five users like the submission and three users don't it will have a score of 2.
299    /// Please note that the vote numbers are not "real" numbers, they have been "fuzzed" to prevent spam bots etc.
300    /// So taking the above example, if five users upvoted the submission, and three users downvote it,
301    /// the upvote/downvote numbers may say 23 upvotes and 21 downvotes, or 12 upvotes, and 10 downvotes.
302    /// The points score is correct, but the vote totals are "fuzzed".
303    pub score: i64,
304
305    /// the raw text.
306    /// this is the unformatted text which includes the raw markup characters such as ** for bold.
307    /// <, >, and & are escaped.
308    /// Empty if not present.
309    pub selftext: Box<str>,
310
311    /// the formatted escaped HTML text.
312    /// this is the HTML formatted version of the marked up text.
313    /// Items that are boldened by ** or *** will now have <em> or *** tags on them.
314    /// Additionally, bullets and numbered lists will now be in HTML list format.
315    /// NOTE: The HTML string will be escaped. You must unescape to get the raw HTML. Null if not present.
316    pub selftext_html: Option<Box<str>>,
317
318    /// subreddit of thing excluding the /r/ prefix. "pics"
319    pub subreddit: Box<str>,
320
321    /// the id of the subreddit in which the thing is located
322    pub subreddit_id: Box<str>,
323
324    /// full URL to the thumbnail for this link;
325    /// "self" if this is a self post;
326    /// "image" if this is a link to an image but has no thumbnail;
327    /// "default" if a thumbnail is not available
328    pub thumbnail: Box<str>,
329
330    /// the title of the link. may contain newlines for some reason
331    pub title: Box<str>,
332
333    /// the link of this post. the permalink if this is a self-post. May be a relative Url.
334    pub url: Box<str>,
335
336    /// Indicates if link has been edited.
337    /// Will be the edit timestamp if the link has been edited and return false otherwise.
338    /// https://github.com/reddit/reddit/issues/581
339    pub edited: serde_json::Value,
340
341    /// to allow determining whether they have been distinguished by moderators/admins.
342    /// null = not distinguished.
343    /// moderator = the green \[M\].
344    /// admin = the red \[A\].
345    /// special = various other special distinguishes
346    /// http://bit.ly/ZYI47B
347    pub distinguished: Option<Box<str>>,
348
349    /// true if the post is set as the sticky in its subreddit.
350    pub stickied: bool,
351
352    /// Voting Implementation
353    #[serde(flatten)]
354    pub votable: Votable,
355
356    /// Created Implementation
357    #[serde(flatten)]
358    pub created: Created,
359
360    // Experimentally determined fields
361    // TODO: These are VERY best-effort, but i should still try to document what i can
362    pub archived: bool,
363    pub author_flair_template_id: Option<Box<str>>,
364    pub author_flair_text_color: Option<Box<str>>,
365    pub author_flair_type: Option<Box<str>>,
366    pub author_fullname: Option<Box<str>>,
367    pub author_patreon_flair: Option<bool>,
368    pub can_gild: bool,
369    pub can_mod_post: bool,
370    pub contest_mode: bool,
371
372    /// I believe that this is included for crossposted posts to get the data for the main post
373    pub crosspost_parent_list: Option<Vec<Link>>,
374
375    pub gilded: u64,
376    pub hide_score: bool,
377    pub id: Box<str>,
378    pub is_crosspostable: bool,
379    pub is_meta: bool,
380    pub is_original_content: bool,
381    pub is_reddit_media_domain: bool,
382    pub is_robot_indexable: bool,
383
384    /// Returns true if its a video
385    pub is_video: bool,
386
387    pub link_flair_text_color: Option<Box<str>>,
388    pub link_flair_type: Box<str>,
389    pub media_only: bool,
390    pub name: Box<str>,
391    pub no_follow: bool,
392    pub num_crossposts: u64,
393    pub parent_whitelist_status: Option<Box<str>>,
394
395    /// Whether this post is pinned
396    pub pinned: bool,
397
398    /// A "hint" about what this post may be
399    pub post_hint: Option<PostHint>,
400
401    pub pwls: Option<u64>,
402    pub quarantine: bool,
403    pub send_replies: bool,
404
405    /// Whether this post has a spoiler
406    pub spoiler: bool,
407
408    pub subreddit_name_prefixed: Box<str>,
409    pub subreddit_subscribers: u64,
410    pub subreddit_type: Box<str>,
411    pub suggested_sort: Option<Box<str>>,
412    pub thumbnail_height: Option<u32>,
413    pub thumbnail_width: Option<u32>,
414    pub visited: bool,
415    pub whitelist_status: Option<Box<str>>,
416    pub wls: Option<u32>,
417}
418
419/// kind == "more"
420/// See https://github.com/reddit-archive/reddit/wiki/JSON#more
421#[derive(Debug, serde::Deserialize)]
422pub struct More {
423    /// A list of String ids that are the additional things that can be downloaded but are not because there are too many to list.
424    pub children: Vec<Box<str>>,
425}
426
427/// Info on what the post may contain
428#[derive(Debug, serde::Deserialize, PartialEq, Eq)]
429pub enum PostHint {
430    /// The post is an image
431    #[serde(rename = "image")]
432    Image,
433
434    /// The post is a link
435    #[serde(rename = "link")]
436    Link,
437
438    /// A video hosted on reddit
439    #[serde(rename = "hosted:video")]
440    HostedVideo,
441
442    #[serde(rename = "rich:video")]
443    RichVideo,
444
445    #[serde(rename = "self")]
446    DataSelf,
447
448    #[serde(rename = "gallery")]
449    Gallery,
450}
451
452#[cfg(test)]
453mod test {
454    use super::*;
455
456    const SUBREDDIT_SAMPLE_1: &str = include_str!("../test_data/subreddit_dankmemes.json");
457    const SUBREDDIT_SAMPLE_2: &str = include_str!("../test_data/subreddit_cromch.json");
458    const SUBREDDIT_SAMPLE_3: &str = include_str!("../test_data/subreddit_cuddleroll.json");
459    const SUBREDDIT_SAMPLE_4: &str = include_str!("../test_data/subreddit_cursed_images.json");
460
461    const COMMENT_SAMPLE_1: &str = include_str!("../test_data/comment_h966lq.json");
462    const COMMENT_SAMPLE_2: &str = include_str!("../test_data/comment_h8p0py.json");
463
464    #[test]
465    fn parse_subreddit_1() {
466        let res = serde_json::from_str::<Thing>(SUBREDDIT_SAMPLE_1).unwrap();
467        dbg!(res);
468    }
469
470    #[test]
471    fn parse_subreddit_2() {
472        let res = serde_json::from_str::<Thing>(SUBREDDIT_SAMPLE_2).unwrap();
473        dbg!(res);
474    }
475
476    #[test]
477    fn parse_subreddit_3() {
478        let res = serde_json::from_str::<Thing>(SUBREDDIT_SAMPLE_3).unwrap();
479        dbg!(res);
480    }
481
482    #[test]
483    fn parse_subreddit_4() {
484        let res = serde_json::from_str::<Thing>(SUBREDDIT_SAMPLE_4).unwrap();
485        dbg!(res);
486    }
487
488    #[test]
489    fn parse_comments_1() {
490        let res = serde_json::from_str::<Vec<Thing>>(COMMENT_SAMPLE_1).unwrap();
491        dbg!(res);
492    }
493
494    #[test]
495    fn parse_comments_2() {
496        let res = serde_json::from_str::<Vec<Thing>>(COMMENT_SAMPLE_2).unwrap();
497        dbg!(res);
498    }
499}