reddit_tube/client.rs
1use crate::{
2 Error,
3 GetVideoResponse,
4 MainPage,
5};
6use scraper::Html;
7
8const DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
9
10/// Client
11#[derive(Clone, Debug)]
12pub struct Client {
13 /// The inner http client
14 pub client: reqwest::Client,
15}
16
17impl Client {
18 /// Makes a new [`Client`].
19 ///
20 /// # Panics
21 /// Panics if the [`Client`] could not be created.
22 pub fn new() -> Self {
23 let client = reqwest::ClientBuilder::new()
24 .cookie_store(true)
25 .user_agent(DEFAULT_USER_AGENT)
26 .build()
27 .expect("failed to build client");
28
29 Client { client }
30 }
31
32 /// Gets [`MainPage`] data.
33 ///
34 /// Useful only to fetch csrf token and pass it to another api call.
35 ///
36 /// # Errors
37 /// Returns an error if the [`MainPage`] could not be fetched.
38 pub async fn get_main_page(&self) -> Result<MainPage, Error> {
39 let body = self
40 .client
41 .get("https://www.redd.tube/")
42 .send()
43 .await?
44 .error_for_status()?
45 .text()
46 .await?;
47
48 Ok(tokio::task::spawn_blocking(move || {
49 let html = Html::parse_document(body.as_str());
50 MainPage::from_html(&html)
51 })
52 .await??)
53 }
54
55 /// Get a video for a reddit url.
56 ///
57 /// `main_page` is exposed publicly as the same [`MainPage`] may be used for multiple [`Client::get_video`] calls as long as they are close together chronologically,
58 /// most likely at least a few seconds or minutes
59 ///
60 /// Calling [`Client::get_main_page`] will also aquire a new session cookie if necessary,
61 /// so make sure to call get_main_page to refresh the csrf token if it expires
62 ///
63 /// # Errors
64 /// Returns an error if the video url could not be fetched.
65 pub async fn get_video(
66 &self,
67 main_page: &MainPage,
68 url: &str,
69 ) -> Result<GetVideoResponse, Error> {
70 Ok(self
71 .client
72 .post("https://www.redd.tube/services/get_video")
73 .form(&[
74 ("url", url),
75 ("zip", ""),
76 ("hash", ""),
77 (&main_page.csrf_key, &main_page.csrf_value),
78 ])
79 .send()
80 .await?
81 .error_for_status()?
82 .json()
83 .await?)
84 }
85}
86
87impl Default for Client {
88 fn default() -> Client {
89 Client::new()
90 }
91}