1pub mod response;
67extern crate serde_json;
68
69use crate::models::subreddit::response::{SubredditData, SubredditResponse, SubredditsData};
70
71use crate::client::Client;
72use crate::util::defaults::default_client;
73use crate::util::{FeedOption, RouxError};
74
75use crate::models::{Comments, Moderators, Submissions};
76
77pub struct Subreddits;
79
80impl Subreddits {
81 #[maybe_async::maybe_async]
83 pub async fn search(
84 name: &str,
85 limit: Option<u32>,
86 options: Option<FeedOption>,
87 ) -> Result<SubredditsData, RouxError> {
88 let url = &mut format!("https://www.reddit.com/subreddits/search.json?q={}", name);
89
90 if let Some(limit) = limit {
91 url.push_str(&format!("&limit={}", limit));
92 }
93
94 if let Some(options) = options {
95 options.build_url(url);
96 }
97
98 let client = default_client();
99
100 Ok(client
101 .get(&url.to_owned())
102 .send()
103 .await?
104 .json::<SubredditsData>()
105 .await?)
106 }
107}
108
109pub struct Subreddit {
111 pub name: String,
113 url: String,
114 client: Client,
115 is_oauth: bool,
116}
117
118impl Subreddit {
119 pub fn new(name: &str) -> Subreddit {
121 let subreddit_url = format!("https://www.reddit.com/r/{}", name);
122
123 Subreddit {
124 name: name.to_owned(),
125 url: subreddit_url,
126 client: default_client(),
127 is_oauth: false,
128 }
129 }
130
131 pub fn new_oauth(name: &str, client: &Client) -> Subreddit {
134 let subreddit_url = format!("https://oauth.reddit.com/r/{}", name);
135
136 Subreddit {
137 name: name.to_owned(),
138 url: subreddit_url,
139 client: client.to_owned(),
140 is_oauth: true,
141 }
142 }
143
144 #[maybe_async::maybe_async]
146 pub async fn moderators(&self) -> Result<Moderators, RouxError> {
147 if self.is_oauth {
148 Ok(self
149 .client
150 .get(&format!("{}/about/moderators/.json", self.url))
151 .send()
152 .await?
153 .json::<Moderators>()
154 .await?)
155 } else {
156 Err(RouxError::OAuthClientRequired)
157 }
158 }
159
160 #[maybe_async::maybe_async]
162 pub async fn about(&self) -> Result<SubredditData, RouxError> {
163 Ok(self
164 .client
165 .get(&format!("{}/about/.json", self.url))
166 .send()
167 .await?
168 .json::<SubredditResponse>()
169 .await?
170 .data)
171 }
172
173 #[maybe_async::maybe_async]
174 async fn get_feed(
175 &self,
176 ty: &str,
177 limit: u32,
178 options: Option<FeedOption>,
179 ) -> Result<Submissions, RouxError> {
180 let url = &mut format!("{}/{}.json?limit={}", self.url, ty, limit);
181
182 if let Some(options) = options {
183 options.build_url(url);
184 }
185
186 Ok(self
187 .client
188 .get(&url.to_owned())
189 .send()
190 .await?
191 .json::<Submissions>()
192 .await?)
193 }
194
195 #[maybe_async::maybe_async]
196 async fn get_comment_feed(
197 &self,
198 ty: &str,
199 depth: Option<u32>,
200 limit: Option<u32>,
201 ) -> Result<Comments, RouxError> {
202 let url = &mut format!("{}/{}.json?", self.url, ty);
203
204 if let Some(depth) = depth {
205 url.push_str(&format!("&depth={}", depth));
206 }
207
208 if let Some(limit) = limit {
209 url.push_str(&format!("&limit={}", limit));
210 }
211
212 if url.contains("comments/") {
217 let mut comments = self
218 .client
219 .get(&url.to_owned())
220 .send()
221 .await?
222 .json::<Vec<Comments>>()
223 .await?;
224
225 Ok(comments.pop().unwrap())
226 } else {
227 Ok(self
228 .client
229 .get(&url.to_owned())
230 .send()
231 .await?
232 .json::<Comments>()
233 .await?)
234 }
235 }
236
237 #[maybe_async::maybe_async]
239 pub async fn hot(
240 &self,
241 limit: u32,
242 options: Option<FeedOption>,
243 ) -> Result<Submissions, RouxError> {
244 self.get_feed("hot", limit, options).await
245 }
246
247 #[maybe_async::maybe_async]
249 pub async fn rising(
250 &self,
251 limit: u32,
252 options: Option<FeedOption>,
253 ) -> Result<Submissions, RouxError> {
254 self.get_feed("rising", limit, options).await
255 }
256
257 #[maybe_async::maybe_async]
259 pub async fn top(
260 &self,
261 limit: u32,
262 options: Option<FeedOption>,
263 ) -> Result<Submissions, RouxError> {
264 self.get_feed("top", limit, options).await
265 }
266
267 #[maybe_async::maybe_async]
269 pub async fn latest(
270 &self,
271 limit: u32,
272 options: Option<FeedOption>,
273 ) -> Result<Submissions, RouxError> {
274 self.get_feed("new", limit, options).await
275 }
276
277 #[maybe_async::maybe_async]
279 pub async fn latest_comments(
280 &self,
281 depth: Option<u32>,
282 limit: Option<u32>,
283 ) -> Result<Comments, RouxError> {
284 self.get_comment_feed("comments", depth, limit).await
285 }
286
287 #[maybe_async::maybe_async]
289 pub async fn article_comments(
290 &self,
291 article: &str,
292 depth: Option<u32>,
293 limit: Option<u32>,
294 ) -> Result<Comments, RouxError> {
295 self.get_comment_feed(&format!("comments/{}", article), depth, limit)
296 .await
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::Subreddit;
303 use super::Subreddits;
304
305 #[maybe_async::async_impl]
306 #[tokio::test]
307 async fn test_no_auth() {
308 let subreddit = Subreddit::new("astolfo");
309
310 let hot = subreddit.hot(25, None).await;
312 assert!(hot.is_ok());
313
314 let rising = subreddit.rising(25, None).await;
315 assert!(rising.is_ok());
316
317 let top = subreddit.top(25, None).await;
318 assert!(top.is_ok());
319
320 let latest_comments = subreddit.latest_comments(None, Some(25)).await;
321 assert!(latest_comments.is_ok());
322
323 let article_id = &hot.unwrap().data.children.first().unwrap().data.id.clone();
324 let article_comments = subreddit.article_comments(article_id, None, Some(25)).await;
325 assert!(article_comments.is_ok());
326
327 let data_res = subreddit.about().await;
329 assert!(data_res.is_ok());
330
331 let data = data_res.unwrap();
332 assert!(data.title == Some(String::from("Rider of Black, Astolfo")));
333 assert!(data.subscribers.is_some());
334 assert!(data.subscribers.unwrap() > 1000);
335
336 assert!(subreddit.moderators().await.is_err());
337
338 let subreddits_limit = 3u32;
340 let subreddits = Subreddits::search("rust", Some(subreddits_limit), None).await;
341 assert!(subreddits.is_ok());
342 assert!(subreddits.unwrap().data.children.len() == subreddits_limit as usize);
343 }
344}