mangadex_api/
v5.rs

1pub mod api_client;
2pub mod at_home;
3pub mod auth;
4pub mod author;
5pub mod captcha;
6pub mod chapter;
7pub mod cover;
8pub mod custom_list;
9pub mod feed;
10pub mod forums;
11pub mod legacy;
12pub mod manga;
13cfg_oauth! {
14    pub mod oauth;
15}
16pub mod ping;
17pub mod rating;
18pub mod report;
19pub mod scanlation_group;
20pub mod search;
21pub mod settings;
22pub mod statistics;
23pub mod upload;
24pub mod user;
25
26#[cfg(all(feature = "multi-thread", not(feature = "tokio-multi-thread")))]
27use futures::lock::Mutex;
28pub use mangadex_api_schema::v5 as schema;
29use mangadex_api_schema::v5::oauth::ClientInfo;
30pub(crate) use mangadex_api_schema::v5::AuthTokens;
31use mangadex_api_types::error::Result;
32
33use reqwest::Client;
34#[cfg(all(
35    not(feature = "multi-thread"),
36    not(feature = "tokio-multi-thread"),
37    not(feature = "rw-multi-thread")
38))]
39use std::cell::RefCell;
40#[cfg(all(
41    not(feature = "multi-thread"),
42    not(feature = "tokio-multi-thread"),
43    not(feature = "rw-multi-thread")
44))]
45use std::rc::Rc;
46#[cfg(any(
47    feature = "multi-thread",
48    feature = "tokio-multi-thread",
49    feature = "rw-multi-thread"
50))]
51use std::sync::Arc;
52#[cfg(feature = "tokio-multi-thread")]
53use tokio::sync::Mutex;
54#[cfg(feature = "rw-multi-thread")]
55use tokio::sync::RwLock;
56
57#[cfg(feature = "utils")]
58use crate::utils::download::DownloadBuilder;
59use crate::v5::api_client::ApiClientEndpoint;
60use crate::v5::at_home::AtHomeBuilder;
61use crate::v5::auth::AuthBuilder;
62use crate::v5::author::AuthorBuilder;
63use crate::v5::captcha::CaptchaBuilder;
64use crate::v5::chapter::ChapterBuilder;
65use crate::v5::cover::CoverBuilder;
66use crate::v5::custom_list::CustomListBuilder;
67use crate::v5::feed::FeedBuilder;
68use crate::v5::forums::ForumsEndpoint;
69use crate::v5::legacy::LegacyBuilder;
70use crate::v5::manga::MangaBuilder;
71#[cfg(feature = "oauth")]
72use crate::v5::oauth::OAuthBuider;
73use crate::v5::ping::PingEndpointBuilder;
74use crate::v5::rating::RatingBuilder;
75use crate::v5::report::ReportBuilder;
76use crate::v5::scanlation_group::ScanlationGroupBuilder;
77use crate::v5::search::SearchBuilder;
78use crate::v5::settings::SettingsBuilder;
79use crate::v5::statistics::StatisticsBuilder;
80use crate::v5::upload::UploadBuilder;
81use crate::v5::user::UserBuilder;
82use crate::HttpClient;
83use crate::HttpClientRef;
84
85/// API client to make requests to the MangaDex v5 API.
86#[derive(Clone, Debug)]
87pub struct MangaDexClient {
88    pub http_client: HttpClientRef,
89}
90
91impl Default for MangaDexClient {
92    /// Create a new `MangaDexClient` with the default [`reqwest::Client`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html) settings.
93    ///
94    /// # Examples
95    ///
96    /// ```rust
97    /// use reqwest::Client;
98    ///
99    /// use mangadex_api::v5::MangaDexClient;
100    ///
101    /// # async fn run() -> Result<(), reqwest::Error> {
102    /// let client = MangaDexClient::default();
103    /// # Ok(())
104    /// # }
105    /// ```
106    fn default() -> Self {
107        Self::new_with_http_client(HttpClient::default())
108    }
109}
110
111impl MangaDexClient {
112    /// Create a new `MangaDexClient` with a custom [`reqwest::Client`](https://docs.rs/reqwest/latest/reqwest/struct.Client.html).
113    ///
114    /// # Examples
115    ///
116    /// ```rust
117    /// use reqwest::Client;
118    ///
119    /// use mangadex_api::v5::MangaDexClient;
120    ///
121    /// # async fn run() -> Result<(), reqwest::Error> {
122    /// let reqwest_client = Client::builder()
123    ///     .timeout(std::time::Duration::from_secs(10))
124    ///     .build()?;
125    ///
126    /// let client = MangaDexClient::new(reqwest_client);
127    /// # Ok(())
128    /// # }
129    /// ```
130    pub fn new(client: Client) -> Self {
131        Self::new_with_http_client_ref(create_ref_counted_http_client(HttpClient::new(client)))
132    }
133
134    /// Create a new `MangaDexClient` with a custom client reference
135    pub fn new_with_http_client_ref(http_client: HttpClientRef) -> Self {
136        Self { http_client }
137    }
138    /// Create a new `MangaDexClient` with a custom [`HttpClient`].
139    ///
140    /// In most cases, providing a custom [`HttpClient`] isn't necessary.
141    /// This function is primarily useful for mock testing but is available for anyone that needs to
142    /// change the base URL if it changes due to an unforeseen event.
143    ///
144    /// # Examples
145    ///
146    /// ```rust
147    /// use reqwest::Client;
148    /// use url::Url;
149    ///
150    /// use mangadex_api::v5::MangaDexClient;
151    /// use mangadex_api::HttpClient;
152    ///
153    /// # async fn run() -> anyhow::Result<()> {
154    /// let reqwest_client = Client::builder()
155    ///     .timeout(std::time::Duration::from_secs(10))
156    ///     .build()?;
157    ///
158    /// let http_client = HttpClient::builder()
159    ///     .client(reqwest_client)
160    ///     .base_url(Url::parse("127.0.0.1:8080")?)
161    ///     .build()?;
162    ///
163    /// let client = MangaDexClient::new_with_http_client(http_client);
164    /// # Ok(())
165    /// # }
166    /// ```
167    pub fn new_with_http_client(http_client: HttpClient) -> Self {
168        Self::new_with_http_client_ref(create_ref_counted_http_client(http_client))
169    }
170
171    /// Return the Reqwest `Client`.
172    ///
173    /// This can be used to create manual HTTP requests.
174    ///
175    /// Using this is generally not advised as it can provide mutable access to the [`HttpClient`].
176    pub fn get_http_client(&self) -> HttpClientRef {
177        self.http_client.clone()
178    }
179    #[cfg(all(
180        not(feature = "multi-thread"),
181        not(feature = "tokio-multi-thread"),
182        not(feature = "rw-multi-thread")
183    ))]
184    pub async fn set_auth_tokens(&mut self, auth_tokens: &AuthTokens) -> Result<()> {
185        let client = &mut self.http_client.try_borrow_mut()?;
186        client.set_auth_tokens(auth_tokens);
187        Ok(())
188    }
189    #[cfg(any(
190        feature = "multi-thread",
191        feature = "tokio-multi-thread",
192        feature = "rw-multi-thread"
193    ))]
194    pub async fn set_auth_tokens(&self, auth_tokens: &AuthTokens) -> Result<()> {
195        let mut client = {
196            #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
197            {
198                self.http_client.lock().await
199            }
200            #[cfg(feature = "rw-multi-thread")]
201            {
202                self.http_client.write().await
203            }
204        };
205        client.set_auth_tokens(auth_tokens);
206        Ok(())
207    }
208
209    #[cfg(all(
210        not(feature = "multi-thread"),
211        not(feature = "tokio-multi-thread"),
212        not(feature = "rw-multi-thread")
213    ))]
214    pub async fn clear_auth_tokens(&mut self) -> Result<()> {
215        let client = {
216            #[cfg(all(
217                not(feature = "multi-thread"),
218                not(feature = "tokio-multi-thread"),
219                not(feature = "rw-multi-thread")
220            ))]
221            {
222                &mut self.http_client.try_borrow_mut()?
223            }
224            #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
225            {
226                &mut self.http_client.lock().await
227            }
228            #[cfg(feature = "rw-multi-thread")]
229            {
230                &mut self.http_client.write().await
231            }
232        };
233        client.clear_auth_tokens();
234        Ok(())
235    }
236    #[cfg(any(
237        feature = "multi-thread",
238        feature = "tokio-multi-thread",
239        feature = "rw-multi-thread"
240    ))]
241    pub async fn clear_auth_tokens(&self) -> Result<()> {
242        let mut client = {
243            #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
244            {
245                self.http_client.lock().await
246            }
247            #[cfg(feature = "rw-multi-thread")]
248            {
249                self.http_client.write().await
250            }
251        };
252        client.clear_auth_tokens();
253        Ok(())
254    }
255    pub async fn get_auth_tokens(&self) -> Result<AuthTokens> {
256        let client = {
257            #[cfg(all(
258                not(feature = "multi-thread"),
259                not(feature = "tokio-multi-thread"),
260                not(feature = "rw-multi-thread")
261            ))]
262            {
263                &self.http_client.try_borrow()?
264            }
265            #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
266            {
267                &self.http_client.lock().await
268            }
269            #[cfg(feature = "rw-multi-thread")]
270            {
271                &self.http_client.read().await
272            }
273        };
274        client
275            .get_tokens()
276            .cloned()
277            .ok_or(mangadex_api_types::error::Error::MissingTokens)
278    }
279
280    #[cfg(all(
281        not(feature = "multi-thread"),
282        not(feature = "tokio-multi-thread"),
283        not(feature = "rw-multi-thread")
284    ))]
285    pub async fn set_captcha<A: Into<String>>(&mut self, captcha: A) -> Result<()> {
286        let client = &mut self.http_client.try_borrow_mut()?;
287        client.set_captcha(captcha);
288        Ok(())
289    }
290    #[cfg(any(
291        feature = "multi-thread",
292        feature = "tokio-multi-thread",
293        feature = "rw-multi-thread"
294    ))]
295    pub async fn set_captcha<A: Into<String>>(&self, captcha: A) -> Result<()> {
296        let mut client = {
297            #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
298            {
299                self.http_client.lock().await
300            }
301            #[cfg(feature = "rw-multi-thread")]
302            {
303                self.http_client.write().await
304            }
305        };
306        client.set_captcha(captcha);
307        Ok(())
308    }
309    pub async fn get_captcha(&self) -> Result<String> {
310        let client = {
311            #[cfg(all(
312                not(feature = "multi-thread"),
313                not(feature = "tokio-multi-thread"),
314                not(feature = "rw-multi-thread")
315            ))]
316            {
317                &self.http_client.try_borrow()?
318            }
319            #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
320            {
321                &self.http_client.lock().await
322            }
323            #[cfg(feature = "rw-multi-thread")]
324            {
325                &self.http_client.read().await
326            }
327        };
328        client
329            .get_captcha()
330            .cloned()
331            .ok_or(mangadex_api_types::error::Error::MissingCaptcha)
332    }
333    #[cfg(all(
334        not(feature = "multi-thread"),
335        not(feature = "tokio-multi-thread"),
336        not(feature = "rw-multi-thread")
337    ))]
338    pub async fn clear_captcha(&mut self) -> Result<()> {
339        let client = &mut self.http_client.try_borrow_mut()?;
340        client.clear_captcha();
341        Ok(())
342    }
343    #[cfg(any(
344        feature = "multi-thread",
345        feature = "tokio-multi-thread",
346        feature = "rw-multi-thread"
347    ))]
348    pub async fn clear_captcha(&self) -> Result<()> {
349        let mut client = {
350            #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
351            {
352                self.http_client.lock().await
353            }
354            #[cfg(feature = "rw-multi-thread")]
355            {
356                self.http_client.write().await
357            }
358        };
359        client.clear_captcha();
360        Ok(())
361    }
362
363    cfg_oauth! {
364        pub async fn get_client_info(&self) -> Result<ClientInfo> {
365            let client = {
366                #[cfg(all(
367                    not(feature = "multi-thread"),
368                    not(feature = "tokio-multi-thread"),
369                    not(feature = "rw-multi-thread")
370                ))]
371                {
372                    &self.http_client.try_borrow()?
373                }
374                #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
375                {
376                    &self.http_client.lock().await
377                }
378                #[cfg(feature = "rw-multi-thread")]
379                {
380                    &self.http_client.read().await
381                }
382            };
383            client
384                .get_client_info()
385                .cloned()
386                .ok_or(mangadex_api_types::error::Error::MissingClientInfo)
387        }
388    }
389    /// Get a builder for handling the At-Home endpoints.
390    ///
391    /// <https://api.mangadex.org/swagger.html#/AtHome>
392    pub fn at_home(&self) -> AtHomeBuilder {
393        AtHomeBuilder::new(self.http_client.clone())
394    }
395
396    /// Get a builder for handling the authentication endpoints.
397    ///
398    /// This builder is deprecated
399    ///
400    /// <https://api.mangadex.org/docs/redoc.html#tag/Authentication>
401    pub fn auth(&self) -> AuthBuilder {
402        AuthBuilder::new(self.http_client.clone())
403    }
404
405    /// Get a builder for handling the author endpoints.
406    ///
407    /// <https://api.mangadex.org/swagger.html#/Author>
408    pub fn author(&self) -> AuthorBuilder {
409        AuthorBuilder::new(self.http_client.clone())
410    }
411
412    /// Get a builder for handling the captcha endpoints.
413    ///
414    /// <https://api.mangadex.org/swagger.html#/Captcha>
415    pub fn captcha(&self) -> CaptchaBuilder {
416        CaptchaBuilder::new(self.http_client.clone())
417    }
418
419    /// Get a builder for handling the chapter endpoints.
420    ///
421    /// <https://api.mangadex.org/swagger.html#/Chapter>
422    pub fn chapter(&self) -> ChapterBuilder {
423        ChapterBuilder::new(self.http_client.clone())
424    }
425
426    pub fn client(&self) -> ApiClientEndpoint {
427        ApiClientEndpoint::new(self.http_client.clone())
428    }
429
430    /// Get a builder for handling manga volume cover art endpoints.
431    ///
432    /// <https://api.mangadex.org/swagger.html#/Cover>
433    pub fn cover(&self) -> CoverBuilder {
434        CoverBuilder::new(self.http_client.clone())
435    }
436
437    /// Get a builder for handling the custom list endpoints.
438    ///
439    /// <https://api.mangadex.org/swagger.html#/CustomList>
440    pub fn custom_list(&self) -> CustomListBuilder {
441        CustomListBuilder::new(self.http_client.clone())
442    }
443
444    /// Get a builder for handling the feed endpoints.
445    ///
446    /// <https://api.mangadex.org/swagger.html#/Feed>
447    pub fn feed(&self) -> FeedBuilder {
448        FeedBuilder::new(self.http_client.clone())
449    }
450
451    /// Get a builder for handling the infrastructure endpoints.
452    ///
453    /// <https://api.mangadex.org/swagger.html#/Infrastructure>
454    pub fn ping(&self) -> PingEndpointBuilder {
455        PingEndpointBuilder::new(self.http_client.clone())
456    }
457
458    /// Get a builder for handling the legacy endpoints.
459    ///
460    /// <https://api.mangadex.org/swagger.html#/Legacy>
461    pub fn legacy(&self) -> LegacyBuilder {
462        LegacyBuilder::new(self.http_client.clone())
463    }
464
465    /// Get a builder for handling the manga endpoints.
466    ///
467    /// <https://api.mangadex.org/swagger.html#/Manga>
468    pub fn manga(&self) -> MangaBuilder {
469        MangaBuilder::new(self.http_client.clone())
470    }
471
472    /// Get a builder for handling the rating endpoints.
473    ///
474    /// <https://api.mangadex.org/swagger.html#/Rating>
475    pub fn rating(&self) -> RatingBuilder {
476        RatingBuilder::new(self.http_client.clone())
477    }
478
479    /// Get a builder for handling the report endpoints.
480    ///
481    /// <https://api.mangadex.org/swagger.html#/Report>
482    pub fn report(&self) -> ReportBuilder {
483        ReportBuilder::new(self.http_client.clone())
484    }
485
486    /// Get a builder for handling the scanlation group endpoints.
487    ///
488    /// <https://api.mangadex.org/swagger.html#/ScanlationGroup>
489    pub fn scanlation_group(&self) -> ScanlationGroupBuilder {
490        ScanlationGroupBuilder::new(self.http_client.clone())
491    }
492
493    /// Get a builder for handling the search endpoints.
494    ///
495    /// This is a convenience builder that aggregates search endpoints from various categories.
496    pub fn search(&self) -> SearchBuilder {
497        SearchBuilder::new(self.http_client.clone())
498    }
499
500    /// Get a builder for handling the settings endpoints.
501    ///
502    /// <https://api.mangadex.org/swagger.html#/Settings>
503    // Not public yet as the settings endpoints are not stable as of MangaDex API v5.4.9.
504    pub fn settings(&self) -> SettingsBuilder {
505        SettingsBuilder::new(self.http_client.clone())
506    }
507
508    /// Get a builder for handling the statistics endpoints.
509    ///
510    /// <https://api.mangadex.org/swagger.html#/Statistics>
511    pub fn statistics(&self) -> StatisticsBuilder {
512        StatisticsBuilder::new(self.http_client.clone())
513    }
514
515    /// Get a builder for handling uploads.
516    ///
517    /// <https://api.mangadex.org/swagger.html#/Upload>
518    pub fn upload(&self) -> UploadBuilder {
519        UploadBuilder::new(self.http_client.clone())
520    }
521
522    /// Get a builder for handling the user endpoints.
523    ///
524    /// <https://api.mangadex.org/swagger.html#/User>
525    pub fn user(&self) -> UserBuilder {
526        UserBuilder::new(self.http_client.clone())
527    }
528
529    /// This is an api client for
530    /// `api.mangadex.dev`
531    pub fn api_dev_client() -> Self {
532        Self::new_with_http_client(HttpClient::api_dev_client())
533    }
534    cfg_utils! {
535        pub fn download(&self) -> DownloadBuilder {
536            DownloadBuilder::new(self.http_client.clone())
537        }
538    }
539
540    pub fn forums(&self) -> ForumsEndpoint {
541        ForumsEndpoint::new(self.http_client.clone())
542    }
543
544    cfg_oauth! {
545        pub fn oauth(&self) -> OAuthBuider {
546            OAuthBuider::new(self.http_client.clone())
547        }
548    }
549}
550
551/// Create a new reference counted `HttpClient`.
552fn create_ref_counted_http_client(http_client: HttpClient) -> HttpClientRef {
553    #[cfg(all(
554        not(feature = "multi-thread"),
555        not(feature = "tokio-multi-thread"),
556        not(feature = "rw-multi-thread")
557    ))]
558    {
559        Rc::new(RefCell::new(http_client))
560    }
561    #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
562    {
563        Arc::new(Mutex::new(http_client))
564    }
565    #[cfg(feature = "rw-multi-thread")]
566    {
567        Arc::new(RwLock::new(http_client))
568    }
569}
570
571#[cfg(all(
572    feature = "oauth",
573    all(
574        not(feature = "multi-thread"),
575        not(feature = "tokio-multi-thread"),
576        not(feature = "rw-multi-thread")
577    )
578))]
579#[cfg_attr(
580    docsrs,
581    doc(cfg(all(
582        feature = "oauth",
583        all(
584            not(feature = "multi-thread"),
585            not(feature = "tokio-multi-thread"),
586            not(feature = "rw-multi-thread")
587        )
588    )))
589)]
590impl MangaDexClient {
591    pub async fn set_client_info(&mut self, client_info: &ClientInfo) -> Result<()> {
592        let client = &mut self.http_client.try_borrow_mut()?;
593
594        client.set_client_info(client_info);
595        Ok(())
596    }
597    pub async fn clear_client_info(&mut self) -> Result<()> {
598        let client = &mut self.http_client.try_borrow_mut()?;
599        client.clear_client_info();
600        Ok(())
601    }
602}
603
604#[cfg(all(
605    feature = "oauth",
606    any(
607        feature = "multi-thread",
608        feature = "tokio-multi-thread",
609        feature = "rw-multi-thread"
610    )
611))]
612#[cfg_attr(
613    docsrs,
614    doc(all(
615        feature = "oauth",
616        any(
617            feature = "multi-thread",
618            feature = "tokio-multi-thread",
619            feature = "rw-multi-thread"
620        )
621    ))
622)]
623impl MangaDexClient {
624    pub async fn set_client_info(&self, client_info: &ClientInfo) -> Result<()> {
625        let mut client = {
626            #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
627            {
628                self.http_client.lock().await
629            }
630            #[cfg(feature = "rw-multi-thread")]
631            {
632                self.http_client.write().await
633            }
634        };
635        client.set_client_info(client_info);
636        Ok(())
637    }
638    pub async fn clear_client_info(&self) -> Result<()> {
639        let mut client = {
640            #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
641            {
642                self.http_client.lock().await
643            }
644            #[cfg(feature = "rw-multi-thread")]
645            {
646                self.http_client.write().await
647            }
648        };
649        client.clear_client_info();
650        Ok(())
651    }
652}