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}