#api #client #mangadex #sdk

mangadex-api

SDK for the MangaDex API

11 releases (4 stable)

Uses new Rust 2021

new 1.2.1 Jan 15, 2022
1.1.0 Dec 24, 2021
1.0.0-alpha.9 Nov 13, 2021
0.2.6 Mar 21, 2021
0.2.1 Dec 29, 2020

#5 in #sdk

Download history 13/week @ 2021-09-27 23/week @ 2021-10-04 41/week @ 2021-10-11 1/week @ 2021-10-18 2/week @ 2021-10-25 1/week @ 2021-11-01 62/week @ 2021-11-08 18/week @ 2021-11-15 1/week @ 2021-11-22 18/week @ 2021-11-29 8/week @ 2021-12-06 37/week @ 2021-12-13 16/week @ 2021-12-20 7/week @ 2021-12-27 10/week @ 2022-01-03 28/week @ 2022-01-10

65 downloads per month

MIT/Apache

740KB
16K SLoC

mangadex-api

The mangadex-api crate provides a convenient, high-level wrapper client for the MangaDex API, written in Rust.

It covers all public endpoints covered by their documentation.

Documentation (docs.rs)

Documentation (Project main branch)

Please note that as MangaDex is still in beta, this SDK will be subject to sudden breaking changes.

Disclaimer

mangadex-api is not affiliated with MangaDex.

Table of Contents

Requirements

Back to top

How to install

Back to top

Add mangadex-api to your dependencies:

[dependencies]
# ...
mangadex-api = "1.2.1"

If you are using cargo-edit, run

cargo add mangadex-api

Features

Back to top

All features are not included by default. To enable them, add any of the following to your project's Cargo.toml file.

  • chrono

    Enable the use of the chrono library for fields that contain datetime objects.

    This cannot be used with the time feature.

  • multi-thread

    Enable the MangaDexClient to be thread-safe, at the cost of operations being slightly more expensive.

  • time

    Enable the use of the time library for fields that contain datetime objects.

    This cannot be used with the chrono feature.

  • upload

    Enable endpoints that upload files to MangaDex.

For example, to enable the time feature, add the following to your Cargo.toml file:

mangadex-api = { version = "1.2.1", features = ["time"] }

HTTP Client

Back to top

The mangadex_api::MangaDexClient is asynchronous, using reqwest as the HTTP client.

Response Structs

Back to top

The response structs can be found in the schemas module and contain the fields in a JSON response.

Getting Started

Back to top

This example demonstrates how to fetch a random manga.

use mangadex_api::v5::MangaDexClient;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = MangaDexClient::default();

    let random_manga = client
        .manga()
        .random()
        .build()?
        .send()
        .await?;

    println!("{:?}", random_manga);

    Ok(())
}

Using a custom reqwest Client

Back to top

By default, mangadex_api::MangaDexClient will use the default reqwest::Client settings.

You may provide your own reqwest::Client to customize options such as the request timeout.

use reqwest::Client;

use mangadex_api::v5::MangaDexClient;

# async fn run() -> anyhow::Result<()> {
let reqwest_client = Client::builder()
    .timeout(std::time::Duration::from_secs(10))
    .build()?;

let client = MangaDexClient::new(reqwest_client);
# Ok(())
# }

Searching manga by title

Back to top

Reference: https://api.mangadex.org/docs.html#operation/get-search-manga

use mangadex_api::v5::MangaDexClient;

# async fn run() -> anyhow::Result<()> {
let client = MangaDexClient::default();

let manga_results = client
    .manga()
    .search()
    .title("full metal")
    .build()?
    .send()
    .await?;

println!("manga results = {:?}", manga_results);
# Ok(())
# }

Searching manga by title with reference expansion

Back to top

Every fetch will include all relationships but with minimal information such as the relationship type and ID. Reference expansion will include the full JSON object in the results for the types that are added to the request.

In the example below, any associated authors in the list of relationships will provide detailed information such as the author's name, biography, and website in the results.

References:

use mangadex_api::types::{ReferenceExpansionResource, RelationshipType};
use mangadex_api::v5::schema::RelatedAttributes;
use mangadex_api::v5::MangaDexClient;

# async fn run() -> anyhow::Result<()> {
let client = MangaDexClient::default();

let manga_results = client
    .manga()
    .search()
    .title("full metal")
    .include(&ReferenceExpansionResource::Author)
    .build()?
    .send()
    .await?;

println!("manga results = {:?}", manga_results);

let authors = manga_results.data.iter().filter_map(|manga| {
    for rel in &manga.relationships {
        if rel.type_ == RelationshipType::Author {
            return Some(rel);
        }
    }

    None
});

for author in authors {
    if let Some(RelatedAttributes::Author(author_attributes)) = &author.attributes {
        println!("{} - {}", author.id, author_attributes.name);
    }
}
# Ok(())
# }

Downloading chapter pages

Back to top

Reference: https://api.mangadex.org/docs.html#section/Reading-a-chapter-using-the-API

// Imports used for downloading the pages to a file.
// They are not used because we're just printing the raw bytes.
// use std::fs::File;
// use std::io::Write;

use reqwest::Url;
use uuid::Uuid;

use mangadex_api::v5::MangaDexClient;

# async fn run() -> anyhow::Result<()> {
let client = MangaDexClient::default();

let chapter_id = Uuid::new_v4();

let at_home = client
    .at_home()
    .server()
    .chapter_id(&chapter_id)
    .build()?
    .send()
    .await?;

let http_client = reqwest::Client::new();

// Original quality. Use `.data.attributes.data_saver` for smaller, compressed images.
let page_filenames = at_home.chapter.data;
for filename in page_filenames {
    // If using the data-saver option, use "/data-saver/" instead of "/data/" in the URL.
    let page_url = at_home
        .base_url
        .join(&format!(
            "/{quality_mode}/{chapter_hash}/{page_filename}",
            quality_mode = "data",
            chapter_hash = at_home.chapter.hash,
            page_filename = filename
        ))
        .unwrap();

    let res = http_client.get(page_url).send().await?;
    // The data should be streamed rather than downloading the data all at once.
    let bytes = res.bytes().await?;

    // This is where you would download the file but for this example,
    // we're just printing the raw data.
    // let mut file = File::create(&filename)?;
    // let _ = file.write_all(&bytes);
    println!("Chunk: {:?}", bytes);
}

# Ok(())
# }

Downloading a manga's main cover image

Back to top

While this example could directly get the cover information by passing in the cover ID, it is not often that one would have the ID off-hand, so the most common method would be from a manga result.

If you want to get all of a manga's cover images, you will need to use the cover list endpoint and use the manga[] query parameter.

// Imports used for downloading the cover to a file.
// They are not used because we're just printing the raw bytes.
// use std::fs::File;
// use std::io::Write;

use reqwest::Url;
use uuid::Uuid;

use mangadex_api::types::RelationshipType;
use mangadex_api::v5::MangaDexClient;
use mangadex_api::CDN_URL;

# async fn run() -> anyhow::Result<()> {
let client = MangaDexClient::default();

let manga_id = Uuid::new_v4();
let manga = client
    .manga()
    .get()
    .manga_id(&manga_id)
    .build()?
    .send()
    .await?;

let cover_id = manga
    .data
    .relationships
    .iter()
    .find(|related| related.type_ == RelationshipType::CoverArt)
    .expect("no cover art found for manga")
    .id;
let cover = client
    .cover()
    .get()
    .cover_id(&cover_id)
    .build()?
    .send()
    .await?;

// This uses the best quality image.
// To use smaller, thumbnail-sized images, append any of the following:
//
// - .512.jpg
// - .256.jpg
//
// For example, "https://uploads.mangadex.org/covers/8f3e1818-a015-491d-bd81-3addc4d7d56a/4113e972-d228-4172-a885-cb30baffff97.jpg.512.jpg"
let cover_url = Url::parse(&format!(
        "{}/covers/{}/{}",
        CDN_URL, manga_id, cover.data.attributes.file_name
    ))
    .unwrap();

let http_client = reqwest::Client::new();

let res = http_client.get(cover_url).send().await?;
// The data should be streamed rather than downloading the data all at once.
let bytes = res.bytes().await?;

// This is where you would download the file but for this example, we're just printing the raw data.
// let mut file = File::create(&filename)?;
// let _ = file.write_all(&bytes);
println!("Chunk: {:?}", bytes);
# Ok(())
# }

Changelog

Back to top

The changelog can be found here.

Changes are added manually to keep the changelog human-readable with summaries of the changes from each version.

License

Back to top

Licensed under either of

at your option.

Contribution

Back to top

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Contributing

Back to top

We welcome contributions from everyone. There are many ways to contribute and the CONTRIBUTING.md document explains how you can contribute and get started.

Dependencies

~4–8.5MB
~197K SLoC