Skip to main content

torbox_ddl_rs/
lib.rs

1use torbox_core_rs::{
2    api::ApiResponse,
3    client::{Endpoint, EndpointSpec, TorboxClient},
4    data::{
5        creation::DownloadLinkResponse,
6        webdownload::{
7            WebDownloadCacheAvailability, WebdownloadCreationResponse, WebdownloadHosterList,
8            WebdownloadStatus,
9        },
10    },
11    enums::OneOrMany,
12    error::ApiError,
13};
14
15use crate::{
16    body::{WebdownloadControlReq, WebdownloadCreateBody},
17    endpoint::{
18        ListWebdownloadsGetEp, WebdownloadCachedAvailabilityGetEp, WebdownloadControlPostEp,
19        WebdownloadCreatePostEp, WebdownloadHosterListGetEp, WebdownloadRequestLinkGetEp,
20    },
21    query::{
22        ListWebdownloadsQuery, WebdownloadCachedAvailabilityQuery, WebdownloadRequestLinkQuery,
23    },
24};
25
26//todo: Add the rest for ddl
27pub mod body;
28pub mod endpoint;
29pub mod payload;
30pub mod query;
31pub mod tests;
32pub mod types;
33
34#[cfg_attr(feature = "specta", derive(specta::Type))]
35pub struct WebdownloadApi<'a> {
36    client: &'a TorboxClient,
37}
38
39impl<'a> WebdownloadApi<'a> {
40    pub fn new(client: &'a TorboxClient) -> Self {
41        Self { client }
42    }
43
44    pub async fn create(
45        &self,
46        body: WebdownloadCreateBody,
47    ) -> Result<ApiResponse<WebdownloadCreationResponse>, ApiError> {
48        Endpoint::<WebdownloadCreatePostEp>::new(self.client)
49            .call_multipart(body)
50            .await
51    }
52
53    pub async fn control(&self, req: WebdownloadControlReq) -> Result<ApiResponse<()>, ApiError> {
54        let (query, body) = req.into_parts();
55
56        Endpoint::<WebdownloadControlPostEp>::new(self.client)
57            .call_query_json(query, body)
58            .await
59    }
60
61    /// Requests a download link for a torrent
62    ///
63    /// Links are valid for 3 hours. Once downloading starts, the transfer
64    /// can continue indefinitely. Permalinks can and should be created by setting
65    /// `redirect=true`.
66    ///
67    /// Setting `redirect = true` truly helps torbox servers not to be overwhelmed.
68    ///
69    /// # Arguments
70    ///
71    /// * `query` - Contains torrent ID and download options
72    ///
73    /// # Returns
74    ///
75    /// Either a JSON response or redirect URL
76    pub async fn request_download_link(
77        &self,
78        query: WebdownloadRequestLinkQuery,
79    ) -> Result<DownloadLinkResponse, ApiError> {
80        let endpoint = format!(
81            "{}/{}",
82            self.client.base_url,
83            WebdownloadRequestLinkGetEp::PATH
84        );
85        let request = self.client.client.get(&endpoint).query(&query);
86
87        let response = request.send().await?;
88
89        if query.redirect {
90            if response.status().is_redirection() {
91                let location = response
92                    .headers()
93                    .get("Location")
94                    .ok_or(ApiError::RedirectError("Missing Location header".into()))?
95                    .to_str()
96                    .map_err(|_| ApiError::RedirectError("Invalid Location header".into()))?;
97
98                Ok(DownloadLinkResponse::Redirect(location.to_string()))
99            } else {
100                match response.json::<ApiResponse<String>>().await {
101                    Ok(json) => Ok(DownloadLinkResponse::Json(json)),
102                    Err(_) => Err(ApiError::UnexpectedPayload),
103                }
104            }
105        } else {
106            let json = response.json::<ApiResponse<String>>().await?;
107            Ok(DownloadLinkResponse::Json(json))
108        }
109    }
110
111    /// Gets the user's torrent list. This gives you the needed information to perform other torrent actions.
112    ///
113    /// This information only gets updated every 600 seconds, or when the _Request Update On Torrent request_ is sent to the relay API.
114    ///
115    /// # Returns
116    ///
117    /// A deserialized `ApiResponse` containing the list of torrents.
118    pub async fn list_query(
119        &self,
120        query: ListWebdownloadsQuery,
121    ) -> Result<ApiResponse<Option<Vec<WebdownloadStatus>>>, ApiError> {
122        let resp: ApiResponse<Option<OneOrMany<WebdownloadStatus>>> =
123            Endpoint::<ListWebdownloadsGetEp>::new(self.client)
124                .call_query(query)
125                .await?;
126
127        let normalized = resp.map(|opt| {
128            opt.map(|one_or_many| match one_or_many {
129                OneOrMany::One(item) => vec![item],
130                OneOrMany::Many(list) => list,
131            })
132        });
133
134        Ok(normalized)
135    }
136
137    pub async fn is_cached(
138        &self,
139        query: WebdownloadCachedAvailabilityQuery,
140    ) -> Result<ApiResponse<Option<Vec<WebDownloadCacheAvailability>>>, ApiError> {
141        let resp = Endpoint::<WebdownloadCachedAvailabilityGetEp>::new(self.client)
142            .call_query(query)
143            .await?;
144
145        let normalized = resp.map(|opt| {
146            opt.map(|one_or_many| match one_or_many {
147                OneOrMany::One(item) => vec![item],
148                OneOrMany::Many(list) => list,
149            })
150        });
151
152        Ok(normalized)
153    }
154
155    /// ## Overview
156    /// A dynamic list of hosters that TorBox is capable of downloading through its paid service.
157    /// - Name - a clean name for display use, the well known name of the service, should be recognizable to users.
158    /// - Domains - an array of known domains that the hoster uses. While each may serve a different purpose it is still included.
159    /// - URL - the main url of the service. This should take you to the home page or a service page of the hoster.
160    /// - Icon - a square image, usually a favicon or logo, that represents the service, should be recognizable as the hoster's icon.
161    /// - Status - whether this hoster can be used on TorBox or not at the current time. It is usually a good idea to check this value before submitting a download to TorBox's servers for download.
162    /// - Type - values are either "hoster" or "stream". Both do the same thing, but is good to differentiate services used for different things.
163    /// - Note - a string value (or null) that may give helpful information about the current status or state of a hoster. This can and should be shown to end users.
164    /// - Daily Link Limit - the number of downloads a user can use per day. As a user submits links, once they hit this number, the API will deny them from adding anymore of this type of link. A zero value means that it is unlimited.
165    /// - Daily Link Used - the number of downloads a user has already used. Usually zero unless you send authentication to the endpoint. This will return accurate values.
166    /// - Daily Bandwidth Limit - the value in bytes that a user is allowed to download from this hoster. A zero value means that it is unlimited. It is recommended to use the Daily Link Limit instead.
167    /// - Daily Bandwidth Used - the value in bytes that a user has already used to download from this hoster. Usually zero unless you send authentication to the endpoint. This will return accurate values.
168    ///
169    /// ## Authorization
170    /// Optional authorization. Authorization is not required in this endpoint unless you want to get the user's live data. Requires an API key using the Authorization Bearer Header to get the live and accurate data for Daily Link Used and Daily Bandwidth Used
171    pub async fn list_hosters(&self) -> Result<ApiResponse<Vec<WebdownloadHosterList>>, ApiError> {
172        Endpoint::<WebdownloadHosterListGetEp>::new(self.client)
173            .call_query(())
174            .await
175    }
176
177    /// ## TODO:
178    /// This function hasn't yet been made due to laziness.
179    ///
180    /// ## DANGEROUS
181    ///
182    /// ## Overview
183    /// Updates a download item based on the item's ID.
184    /// Sending a PUT request with valid items overwrites the previous items entirely, so use this endpoint with caution. Please make sure to read the restrictions below as they are important.
185    ///
186    /// ## Restrictions
187    /// - Item must be cached to update.
188    /// - Does not affect the cached database (or any other user's item).
189    /// - Name must pass validation:
190    ///     - Above 1 character long.
191    ///     - Below 200 characters long.
192    ///     - Cannot contain any non-url or non filesystem safe characters. (This is removed automatically).
193    ///     - Cannot contain any leading or trailing whitespace. You can have spaces in the middle. (This is removed automatically).
194    ///
195    /// - Tags follow the same rules as the name.
196    /// - Alternative hashes must be of MD5, SHA1, or SHA256.
197    pub async fn edit_item_put(&self) {}
198}