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}