mal_api/
lib.rs

1//! `mal-api` is an asynchronous, fully type-safe MyAnimeList API
2//!
3//! # Table of Contents
4//! - [Overview](#overview)
5//! - [OAuth](#oauth)
6//! - [API Clients](#api-clients)
7//! - [Anime and Manga Fields](#anime-and-manga-fields)
8//! - [Examples](#examples)
9//!
10//! # Overview
11//!
12//! `mal-api` library is a fully type-safe library
13//! that provides asynchronous functionality for interacting with the [MyAnimeList](https://myanimelist.net/apiconfig/references/api/v2) (MAL)
14//! API.
15//!
16//! With `mal-api`, developers can confidently integrate MAL API
17//! functionality into their applications, enabling them to retrieve, update,
18//! and manage anime and manga data effortlessly. The library offers a comprehensive
19//! set of API endpoints, allowing users to perform operations such as searching for
20//! anime, retrieving detailed information about specific titles, managing user
21//! lists, and more.
22//!
23//! One of the key features of `mal-api` is its type safety. By utilizing Rust's
24//! strong type system, the library provides compile-time guarantees that the API
25//! requests and responses are correctly structured and formatted. This eliminates
26//! the risk of runtime errors. Developers can leverage the library's well-defined
27//! structs and enums to easily construct API requests and handle the received
28//! data in a type-safe manner.
29//!
30//! # OAuth
31//!
32//! `mal-api` provides a method for obtaining MAL OAuth access tokens.
33//! This token is necessary to access certain MAL API endpoints.
34//! Depending on whether you obtain an OAuth token or just use your ClientId,
35//! the `mal-api` API client you create from either token will ensure you can only
36//! access the endpoints your token is comptatible with.
37//!
38//! # API Clients
39//!
40//! There are four different API clients you can use:
41//! - AnimeApiClient
42//!   - Implements all of the [anime](https://myanimelist.net/apiconfig/references/api/v2#tag/anime)
43//! and [user animelist](https://myanimelist.net/apiconfig/references/api/v2#tag/user-animelist) MAL API endpoints
44//!   - Can be created from a MAL Oauth access token or a MAL ClientId
45//! - MangaApiClient
46//!     - Implements all of the [manga](https://myanimelist.net/apiconfig/references/api/v2#tag/manga)
47//! and [user mangalist](https://myanimelist.net/apiconfig/references/api/v2#tag/user-mangalist) MAL API endpoints
48//!     - Can be created from a MAL Oauth access token or a MAL ClientId
49//! - ForumApiClient
50//!     - Implements all of the [forum](https://myanimelist.net/apiconfig/references/api/v2#tag/forum) MAL API endpoints
51//!     - Can be created from a MAL Oauth access token or a MAL ClientId
52//! - UserApiClient
53//!     - Implements all of the [user](https://myanimelist.net/apiconfig/references/api/v2#tag/user) MAL API endpoints
54//!     - Can be created from a MAL Oauth access token
55//!
56//! # Anime and Manga Fields
57//!
58//! `mal-api` provides utilities to ensure that the fields you want returned from the
59//! anime and manga endpoints are valid fields.
60//!
61//! ```rust,no_run
62//! use mal_api::prelude::*;
63//! use mal_api::anime_common_fields;
64//!
65//! // Specify which fields you want returned from the Anime endpoint
66//! let fields = anime_common_fields!(
67//!     AnimeField::id,
68//!     AnimeField::num_episodes,
69//!     AnimeField::title,
70//! );
71//!
72//! // If you want all of the common fields:
73//! let fields = mal_api::anime::all_common_fields();
74//!
75//! // If you want all of the detailed fields:
76//! let fields = mal_api::anime::all_detail_fields();
77//! ```
78//!
79//! # Examples
80//!
81//! ## Using a ClientId
82//!
83//! ```rust,ignore
84//! use dotenvy;
85//! use mal_api::anime_common_fields;
86//! use mal_api::oauth::MalClientId;
87//! use mal_api::prelude::*;
88//!
89//! #[tokio::main]
90//! async fn main() {
91//!     dotenvy::dotenv().ok();
92//!
93//!     let client_id = MalClientId::try_from_env().unwrap();
94//!
95//!     // Anime API example
96//!     let api_client = AnimeApiClient::from(&client_id);
97//!     let fields = anime_common_fields!(
98//!         AnimeField::id,
99//!         AnimeField::num_episodes,
100//!         AnimeField::title,
101//!     );
102//!
103//!     // Example using builder pattern. The `builder(args...)` method will only require
104//!     // the required arguments for the specific API endpoint, while the
105//!     // other builder instance methods will build up the optional arguments.
106//!     let query = GetAnimeList::builder("One")
107//!         .fields(&fields)
108//!         .limit(5)
109//!         .build()
110//!         .unwrap();
111//!     let result = api_client.get_anime_list(&query).await.unwrap();
112//!     println!("Result: {}", &result);
113//!
114//!     // Example iterating through pages
115//!     let result = api_client.next(&result).await.unwrap();
116//!     println!("Next result: {}", &result);
117//!
118//!     let result = api_client.prev(&result).await.unwrap();
119//!     println!("Prev result: {}", &result);
120//!
121//!     // Manga API example
122//!     let api_client = MangaApiClient::from(&client_id);
123//!     let fields = mal_api::manga::all_common_fields();
124//!
125//!     // Example using `new` pattern. Not recommended, but available
126//!     let nsfw = false;
127//!     let limit = Some(5);
128//!     let query = GetMangaList::new("one".to_string(), nsfw, Some(&fields), limit, None).unwrap();
129//!     let result = api_client.get_manga_list(&query).await.unwrap();
130//!     println!("Result: {}", result);
131//! }
132//! ```
133//!
134//! ## Creating an OAuth token
135//!
136//! ```rust,ignore
137//! use dotenvy;
138//! use mal_api::oauth::{OauthClient, RedirectResponse};
139//! use std::io;
140//!
141//! #[tokio::main]
142//! async fn main() {
143//!     dotenvy::dotenv().ok();
144//!
145//!     let authenticated_client = OauthClient::load_from_config(".mal/config.toml");
146//!     match authenticated_client {
147//!         Ok(_) => {
148//!             println!("An existing authorized Oauth client already exists");
149//!             return;
150//!         }
151//!         Err(_) => println!("No existing Oauth client exists\n"),
152//!     }
153//!
154//!     let client_id = OauthClient::load_client_id_from_env().unwrap();
155//!     let client_secret = OauthClient::load_client_secret_from_env().unwrap();
156//!     let redirect_url = OauthClient::load_redirect_url_from_env().unwrap();
157//!     let mut oauth_client =
158//!         OauthClient::new(&client_id, Some(&client_secret), &redirect_url).unwrap();
159//!     println!("Visit this URL: {}\n", oauth_client.generate_auth_url());
160//!
161//!     println!("After authorizing, please enter the URL you were redirected to: ");
162//!     let mut input = String::new();
163//!     io::stdin()
164//!         .read_line(&mut input)
165//!         .expect("Failed to read user input");
166//!
167//!     let response = RedirectResponse::try_from(input).unwrap();
168//!
169//!     // Authentication process
170//!     let result = oauth_client.authenticate(response).await;
171//!     let authenticated_oauth_client = match result {
172//!         Ok(t) => {
173//!             println!("Got token: {:?}\n", t.get_access_token_secret());
174//!
175//!             let t = t.refresh().await.unwrap();
176//!             println!("Refreshed token: {:?}", t.get_access_token_secret());
177//!             t
178//!         }
179//!         Err(e) => panic!("Failed: {}", e),
180//!     };
181//!
182//!     // Save credentials to config to be re-used later
183//!     let _ = authenticated_oauth_client.save_to_config(".mal/config");
184//! }
185//! ```
186//!
187//! ## Accessing data from responses
188//! ```rust,ignore
189//! let query = GetAnimeList::builder("One Piece")
190//!     .fields(&common_fields)
191//!     .build()
192//!     .unwrap();
193//! let response = api_client.get_anime_list(&query).await;
194//! if let Ok(response) = response {
195//!     // Iterate through all of the anime entries, printing each anime's title and id
196//!     for entry in response.data.iter() {
197//!         println!("Anime Title: {}  Anime ID: {}", entry.node.title, entry.node.id);
198//!     }
199//! }
200//! ```
201
202pub mod anime;
203pub mod manga;
204
205#[cfg(feature = "forum")]
206pub mod forum;
207
208#[cfg(feature = "user")]
209pub mod user;
210
211pub mod common;
212pub mod macros;
213pub mod oauth;
214
215const OAUTH_URL: &'static str = "https://myanimelist.net/v1/oauth2/authorize";
216const OAUTH_TOKEN_URL: &'static str = "https://myanimelist.net/v1/oauth2/token";
217const ANIME_URL: &'static str = "https://api.myanimelist.net/v2/anime";
218const MANGA_URL: &'static str = "https://api.myanimelist.net/v2/manga";
219const USER_URL: &'static str = "https://api.myanimelist.net/v2/users";
220
221#[cfg(feature = "forum")]
222const FORUM_URL: &'static str = "https://api.myanimelist.net/v2/forum";
223
224/// Module re-exports
225pub mod prelude {
226    pub use crate::oauth::{MalClientId, OauthClient};
227
228    pub use crate::anime::{
229        api::{AnimeApi, AnimeApiClient},
230        requests::*,
231        responses::*,
232    };
233
234    pub use crate::manga::{
235        api::{MangaApi, MangaApiClient},
236        requests::*,
237        responses::*,
238    };
239
240    #[cfg(feature = "forum")]
241    pub use crate::forum::{
242        api::{ForumApi, ForumApiClient},
243        requests::*,
244        responses::*,
245    };
246
247    #[cfg(feature = "user")]
248    pub use crate::user::{api::UserApiClient, requests::*};
249}