From c95dc097d4da315d985f3291b85f214e8502a860 Mon Sep 17 00:00:00 2001 From: Uttarayan Mondal Date: Mon, 15 Mar 2021 14:52:11 +0530 Subject: [PATCH] Added upvotes and downvotes Changes: - Added upvotes and downvotes parsing. - Added text to read from selftext and selftext_html. - Added an texttype to imply what type of text is present (HTML or raw text) - Added some documentation. - A few minor changes (I hope). - Removed the pinned field. Todo: - Remove texttype field and parse the selftext_html into raw text --- .gitignore | 2 + src/api.rs | 0 src/lib.rs | 41 +++++++++++++++---- src/rapr.rs | 113 ++++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 118 insertions(+), 38 deletions(-) delete mode 100644 src/api.rs diff --git a/.gitignore b/.gitignore index ac2f8625..72534c4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target Cargo.lock rust.json +post.json +*.json diff --git a/src/api.rs b/src/api.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/lib.rs b/src/lib.rs index a4353bfc..02afcb7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,44 @@ +//!# Reddit api wrapper +//!Example program +//!You need tokio to use this. +//!Make sure to use `tokio = { version = "1.3.*", features = ["full"] }` +//!```rust +//!extern crate rapr; +//!extern crate tokio; +//!use crate::rapr::{RaprClient,RaSub}; +//!#[tokio::main] +//!async fn main() { +//! let client = RaprClient::new(); +//! let mut sub = RaSub::new("pics"); +//! client.fetch(10, &mut sub).await.unwrap(); +//! for post in sub.posts { +//! println!("{}",post.title); +//! } +//!} mod rapr; -pub use crate::rapr::RaprClient; +pub use crate::rapr::{RaPost, RaSub, RaprClient}; #[cfg(test)] mod tests { + use crate::rapr::{RaSub, RaprClient}; #[tokio::test] async fn subreddit() { - use crate::rapr::RaprClient; let client = RaprClient::new(); - let mut sub = RaprClient::subreddit("rust"); + let mut sub = RaSub::new("rust"); client.fetch(10, &mut sub).await.unwrap(); - println!("{:#?}", sub.posts.len()); - client.fetch(5, &mut sub).await.unwrap(); - println!("{:#?}", sub.posts.len()); - println!("{:#?}", sub.pinned_posts()); + // println!("{:#?}", sub.posts.len()); + // client.fetch(5, &mut sub).await.unwrap(); + for post in sub.posts { + println!("{:#?}", post); + } + } + #[tokio::test] + async fn title() { + let client = RaprClient::new(); + let mut sub = RaSub::new("rust"); + client.fetch(10, &mut sub).await.unwrap(); + for post in sub.posts { + println!("{}", post.title); + } } } diff --git a/src/rapr.rs b/src/rapr.rs index 1b300d9a..b3107548 100644 --- a/src/rapr.rs +++ b/src/rapr.rs @@ -10,14 +10,27 @@ pub enum Error { HttpGetError, } +#[derive(Debug, Clone, Copy)] +pub enum TextType { + HTML, + Raw, + Empty, +} + +/// Reddit post object +/// (more like repost object). +/// Keeps track of post information. #[derive(Clone)] pub struct RaPost { - id: String, - datetime: DateTime, - title: String, - text: Option, - json: json::JsonValue, - pinned: bool, + pub id: String, + pub datetime: DateTime, + pub title: String, + pub text: Option, + texttype: TextType, + pub permalink: String, + pub upvotes: u32, + pub downvotes: u32, + pub json: json::JsonValue, } impl fmt::Debug for RaPost { @@ -27,31 +40,73 @@ impl fmt::Debug for RaPost { .field("datetime", &self.datetime) .field("title", &self.title) .field("text", &self.text) + .field("texttype", &self.texttype) + .field("upvotes", &self.upvotes) + .field("downvotes", &self.downvotes) .finish() } } impl RaPost { - pub fn new(id: &str, title: &str, json: &json::JsonValue, pinned: bool) -> Self { + /// Generate new post object (you probably don't need this). + pub fn new( + id: &str, + title: &str, + text: Option<&str>, + texttype: TextType, + permalink: &str, + upvotes: u32, + downvotes: u32, + json: &json::JsonValue, + ) -> Self { + let _text = match text { + Some(text) => Some(text.to_string()), + None => None, + }; Self { id: id.to_string(), title: title.to_string(), datetime: Local::now(), // Temp - text: Some(String::from("")), + text: _text, + texttype, + permalink: String::from(permalink), + upvotes, + downvotes, json: json.clone(), - pinned, } } + /// Parse json from `json['data']['children']` array elements. pub fn parse(post: &json::JsonValue) -> Result { + let mut text: Option<&str> = post["selftext"].as_str(); + let mut texttype: TextType = TextType::Empty; + // Reddit always returns an empty string on selftext + // if there is no text. So this shouldn't panic! + // Some also have both selftext and selftext_html + // I am only taking selftext from these. + if text.unwrap().is_empty() { + text = post["selftext_html"].as_str(); + if text != None { + texttype = TextType::HTML; + } + } else { + texttype = TextType::Raw; + } Ok(RaPost::new( - json::stringify(post["name"].clone()).as_str(), - json::stringify(post["title"].clone()).as_str(), + post["name"].as_str().unwrap(), + post["title"].as_str().unwrap(), + text, + texttype, + post["permalink"].as_str().unwrap(), + post["ups"].as_u32().unwrap(), + post["downs"].as_u32().unwrap(), post, - post["pinned"].as_bool().unwrap(), )) } } +///# RaSub +///Subreddit object +///Keeps track of posts #[derive(Debug)] pub struct RaSub { pub name: String, @@ -60,21 +115,19 @@ pub struct RaSub { } impl RaSub { - pub fn pinned_posts(&self) -> Option> { - let mut pinned_posts: Vec = Vec::new(); - for post in &self.posts { - if post.pinned { - pinned_posts.push(post.clone()); - } - } - if pinned_posts.is_empty() { - return None; - } else { - return Some(pinned_posts); + /// Generate subreddit object + pub fn new(name: &str) -> RaSub { + RaSub { + name: String::from(name), + posts: Vec::new(), + after: None, } } } +/// Reddit api client. +/// Uses a [reqwest::Client](https://docs.rs/reqwest/0.11.2/reqwest/struct.Client.html) internally. +/// Currently no authentication #[derive(Debug)] pub struct RaprClient { oauth: Option, @@ -88,13 +141,9 @@ impl RaprClient { rwclient: reqwest::Client::new(), } } - pub fn subreddit(name: &str) -> RaSub { - RaSub { - name: String::from(name), - posts: Vec::new(), - after: None, - } - } + + /// Fetch posts from subreddit and store them in the subreddit object. + /// Note: First fetch always seems to pull two pinned posts which are not marked pinned in the json pub async fn fetch(&self, count: u32, sub: &mut RaSub) -> Result, Error> { let url = match self.oauth { None => format!("https://reddit.com/r/{}.json", sub.name), @@ -128,7 +177,9 @@ impl RaprClient { let mut parsed_posts: Vec = Vec::new(); for post in raw_posts { - parsed_posts.push(RaPost::parse(&post["data"]).unwrap()); + if post["kind"].as_str().unwrap() == "t3" { + parsed_posts.push(RaPost::parse(&post["data"]).unwrap()); + } } if parsed["data"]["after"].is_string() { sub.after = Some(parsed["data"]["after"].to_string());