Crate mal_api

source ·
Expand description

mal-api is an asynchronous, fully type-safe MyAnimeList API

§Table of Contents

§Overview

mal-api library is a fully type-safe library that provides asynchronous functionality for interacting with the MyAnimeList (MAL) API.

With mal-api, developers can confidently integrate MAL API functionality into their applications, enabling them to retrieve, update, and manage anime and manga data effortlessly. The library offers a comprehensive set of API endpoints, allowing users to perform operations such as searching for anime, retrieving detailed information about specific titles, managing user lists, and more.

One of the key features of mal-api is its type safety. By utilizing Rust’s strong type system, the library provides compile-time guarantees that the API requests and responses are correctly structured and formatted. This eliminates the risk of runtime errors. Developers can leverage the library’s well-defined structs and enums to easily construct API requests and handle the received data in a type-safe manner.

§OAuth

mal-api provides a method for obtaining MAL OAuth access tokens. This token is necessary to access certain MAL API endpoints. Depending on whether you obtain an OAuth token or just use your ClientId, the mal-api API client you create from either token will ensure you can only access the endpoints your token is comptatible with.

§API Clients

There are four different API clients you can use:

  • AnimeApiClient
    • Implements all of the anime and user animelist MAL API endpoints
    • Can be created from a MAL Oauth access token or a MAL ClientId
  • MangaApiClient
    • Implements all of the manga and user mangalist MAL API endpoints
    • Can be created from a MAL Oauth access token or a MAL ClientId
  • ForumApiClient
    • Implements all of the forum MAL API endpoints
    • Can be created from a MAL Oauth access token or a MAL ClientId
  • UserApiClient
    • Implements all of the user MAL API endpoints
    • Can be created from a MAL Oauth access token

§Anime and Manga Fields

mal-api provides utilities to ensure that the fields you want returned from the anime and manga endpoints are valid fields.

use mal_api::prelude::*;
use mal_api::anime_common_fields;

// Specify which fields you want returned from the Anime endpoint
let fields = anime_common_fields!(
    AnimeField::id,
    AnimeField::num_episodes,
    AnimeField::title,
);

// If you want all of the common fields:
let fields = mal_api::anime::all_common_fields();

// If you want all of the detailed fields:
let fields = mal_api::anime::all_detail_fields();

§Examples

§Using a ClientId

use dotenvy;
use mal_api::anime_common_fields;
use mal_api::oauth::MalClientId;
use mal_api::prelude::*;

#[tokio::main]
async fn main() {
    dotenvy::dotenv().ok();

    let client_id = MalClientId::try_from_env().unwrap();

    // Anime API example
    let api_client = AnimeApiClient::from(&client_id);
    let fields = anime_common_fields!(
        AnimeField::id,
        AnimeField::num_episodes,
        AnimeField::title,
    );

    // Example using builder pattern. The `builder(args...)` method will only require
    // the required arguments for the specific API endpoint, while the
    // other builder instance methods will build up the optional arguments.
    let query = GetAnimeList::builder("One")
        .fields(&fields)
        .limit(5)
        .build()
        .unwrap();
    let result = api_client.get_anime_list(&query).await.unwrap();
    println!("Result: {}", &result);

    // Example iterating through pages
    let result = api_client.next(&result).await.unwrap();
    println!("Next result: {}", &result);

    let result = api_client.prev(&result).await.unwrap();
    println!("Prev result: {}", &result);

    // Manga API example
    let api_client = MangaApiClient::from(&client_id);
    let fields = mal_api::manga::all_common_fields();

    // Example using `new` pattern. Not recommended, but available
    let nsfw = false;
    let limit = Some(5);
    let query = GetMangaList::new("one".to_string(), nsfw, Some(&fields), limit, None).unwrap();
    let result = api_client.get_manga_list(&query).await.unwrap();
    println!("Result: {}", result);
}

§Creating an OAuth token

use dotenvy;
use mal_api::oauth::{OauthClient, RedirectResponse};
use std::io;

#[tokio::main]
async fn main() {
    dotenvy::dotenv().ok();

    let authenticated_client = OauthClient::load_from_config(".mal/config.toml");
    match authenticated_client {
        Ok(_) => {
            println!("An existing authorized Oauth client already exists");
            return;
        }
        Err(_) => println!("No existing Oauth client exists\n"),
    }

    let client_id = OauthClient::load_client_id_from_env().unwrap();
    let client_secret = OauthClient::load_client_secret_from_env().unwrap();
    let redirect_url = OauthClient::load_redirect_url_from_env().unwrap();
    let mut oauth_client =
        OauthClient::new(&client_id, Some(&client_secret), &redirect_url).unwrap();
    println!("Visit this URL: {}\n", oauth_client.generate_auth_url());

    println!("After authorizing, please enter the URL you were redirected to: ");
    let mut input = String::new();
    io::stdin()
        .read_line(&mut input)
        .expect("Failed to read user input");

    let response = RedirectResponse::try_from(input).unwrap();

    // Authentication process
    let result = oauth_client.authenticate(response).await;
    let authenticated_oauth_client = match result {
        Ok(t) => {
            println!("Got token: {:?}\n", t.get_access_token_secret());

            let t = t.refresh().await.unwrap();
            println!("Refreshed token: {:?}", t.get_access_token_secret());
            t
        }
        Err(e) => panic!("Failed: {}", e),
    };

    // Save credentials to config to be re-used later
    let _ = authenticated_oauth_client.save_to_config(".mal/config");
}

§Accessing data from responses

let query = GetAnimeList::builder("One Piece")
    .fields(&common_fields)
    .build()
    .unwrap();
let response = api_client.get_anime_list(&query).await;
if let Ok(response) = response {
    // Iterate through all of the anime entries, printing each anime's title and id
    for entry in response.data.iter() {
        println!("Anime Title: {}  Anime ID: {}", entry.node.title, entry.node.id);
    }
}

Modules§

  • Module for interacting with the anime and user animelist endpoints
  • Module containing common request/response fields, traits, and functions
  • Module for ease-of-use macros
  • Module for interacting with the manga and user mangalist endpoints
  • Module for working through MAL OAuth2 flow
  • Module re-exports

Macros§