Giter Club home page Giter Club logo

mangadex-api's Introduction

mangadex-api

Rust Crates.io Documentation Crates.io (recent)

Important

This git repo is just a fork from gondolyr/mangadex-api but the project and crate has been yanked so I will now maintain this crate for special-eureka and eureka-manager

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 (Old repo main branch)

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

Disclaimer

mangadex-api is not affiliated with MangaDex.

Table of Contents

Workspace details

  • mangadex-api is the SDK itself
  • mangadex-api-types is the crate containing all enums, and static data. Those are non_exhaustive by default
  • mangadex-api-schema contains all the response structs required. These support serialization with the serialize feature
  • mangadex-api-inputs-types contains input types for endpoint. Please note that this input crate only contain those with multiple parameters.

Requirements

Back to top

How to install

Back to top

Add mangadex-api to your dependencies:

[dependencies]
# ...
# Types and schemas are always required
mangadex-api-types-rust = "0.9"
mangadex-api-schema-rust = "0.9"
mangadex-api = "3.3"

If you are using cargo-edit, run

cargo add mangadex-api

Dependency Justification

Dependency Used for Included
anyhow Capturing unexpected errors. always
mangadex-api-types-rust Enums and static data for Mangadex API always
mangadex-api-schema-rust Types used for Mangadex API always
clap Examples demonstrating the library's capabilities dev builds
derive_builder Conveniently generating setters for the API endpoint builders. always
fake Generating random data for unit tests. dev builds
futures Async request processing. always
reqwest Making HTTP requests to the MangaDex API. always
serde Se/dese/rializing HTTP response bodies into structs. always
serde_json Creating JSON objects for unit tests. dev builds
serde_qs Query string serialization for HTTP requests. always
thiserror Customized error handling. always
time Convenience types for handing time fields. always
tokio Async runtime to handle futures in (only) examples and utils feature in chapter reporting, tokio-mutli-thread, and rw-mutli-thread dev builds + utils features
url Convenient Url type for validating and containing URLs. always
uuid Convenient Uuid type for validating and containing UUIDs for requests and responses. Also used to randomly generate UUIDs for testing. always
wiremock HTTP mocking to test the MangaDex API. dev builds

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.

  • multi-thread

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

  • legacy-auth Deprecated

    Enable the usage of the < 5.9.0 login system in the SDK. Please visit the Mangadex Discord for more details

  • utils

    Enable the usage of the MangaDexClient::download(). Allows you to download chapters or covers image without tears and long code.

  • tokio-multi-thread

    Enable the usage of tokio::sync::Mutex, instead of futures::lock::Mutex

  • rw-mutli-thread

    Enable the usage of tokio::sync::RwLock, instead of futures::lock::Mutex in the client. It can be useful if you want a flexible concurent mutli-thread.

  • legacy-user-delete Deprecated

    Enable the usage of the user delete endpoints.

  • oauth (Enabled by default)

    Enable the use of the brand new OAuth 2.0 login introduced in MangaDex API 5.9.0.

    Quick Note: This oauth feature use the personal-client approach which means that you need to register a personal client and wait that it'll be validated. More details here here

  • custom_list_v2 : Enable the usage of the upcoming custom list system. Please note that these endpoints are deployed yet on api.mangadex.org but you can use them on api.mangadex.dev (their live dev API). For more information, please refer to Follows/CustomList API Changelog - BREAKING CHANGES on the MangaDex Forums

For example, to enable the multi-thread feature, add the following to your Cargo.toml file:

mangadex-api = { version = "3.2.0", features = ["multi-thread"] }

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 mangadex-api-schema-rust crate 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()
        .get()
        .send()
        .await?;

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

    Ok(())
}

This example demonstates how to fetch the Mangadex Popular Titles

use mangadex_api::MangaDexClient;
use mangadex_api_schema_rust::v5::RelatedAttributes;
use mangadex_api_types_rust::{
    Language, MangaDexDateTime, MangaSortOrder, OrderDirection, ReferenceExpansionResource,
};
use time::{Duration, OffsetDateTime};
use url::Url;

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

    // Take the local date and put substract it with 30 days
    let created_at_since = OffsetDateTime::now_utc()
        .checked_sub(Duration::days(30))
        .unwrap();

    let created_at_since = MangaDexDateTime::new(&created_at_since);

    let res = client
        .manga()
        .get()
        // We pick up all manga that has been created during these last 30 days
        .created_at_since(created_at_since)
        // Mangadex Popular Titles is ordered by followedCount descending
        .order(MangaSortOrder::FollowedCount(OrderDirection::Descending))
        // We include the author data
        .include(ReferenceExpansionResource::Author)
        // We include the arstits data
        .include(ReferenceExpansionResource::Artist)
        .send()
        .await?;

    let not_found = String::from("Not found");
    // Just a simple index :3
    let mut index = 1;
    for manga in res.data {
        // Find the English title
        let title = manga
            .attributes
            .title
            .get(&Language::English)
            .unwrap_or(&not_found);
        println!("{index} ~ {title}");
        // Find the author name
        let author = manga
            .find_first_relationships(mangadex_api_types::RelationshipType::Author)
            .and_then(|e| {
                e.attributes.clone().map(|rel| match rel {
                    RelatedAttributes::Author(a) => a.name,
                    _ => not_found.clone(),
                })
            })
            .unwrap_or(not_found.clone());
        println!("\tAuthor: {author}");
        // Find the author name
        let artist = manga
            .find_first_relationships(mangadex_api_types::RelationshipType::Artist)
            .and_then(|e| {
                e.attributes.clone().map(|rel| match rel {
                    RelatedAttributes::Author(a) => a.name,
                    _ => not_found.clone(),
                })
            })
            .unwrap_or(not_found.clone());
        // Print the artist name if it's different of the author
        if artist != author {
            println!("\tArtist: {artist}");
        }
        // We generate the link that goes to the Mangadex page
        let title_link =
            Url::parse("https://mangadex.org/title/")?.join(manga.id.to_string().as_str())?;
        println!("\tLink: {title_link}");
        println!();
        index += 1;
    }
    println!("Done :3");
    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/swagger.html#/Manga/get-search-manga

use mangadex_api::v5::MangaDexClient;

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

    let manga_results = client
        .manga()
        .get()
        .title("full metal")
        .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::v5::schema::RelatedAttributes;
use mangadex_api::v5::MangaDexClient;
// use mangadex_api_types_rust::{ReferenceExpansionResource, RelationshipType};
use mangadex_api_types::{ReferenceExpansionResource, RelationshipType};

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

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

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

    let authors = manga_results.data.iter().filter_map(|manga| {
        manga
            .relationships
            .iter()
            .find(|&rel| rel.type_ == RelationshipType::Author)
    });

    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/reading-chapter/

Using the old way

// 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 uuid::Uuid;

use mangadex_api::v5::MangaDexClient;

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

    // Yeah, i'm a [`100 girlfriend`](https://mangadex.org/title/efb4278c-a761-406b-9d69-19603c5e4c8b/the-100-girlfriends-who-really-really-really-really-really-love-you) simp and what! >:)
    let chapter_id = Uuid::parse_str("f2a09509-3c09-4371-a810-ecb99242bd90")?;

    let at_home = client
        .at_home()
        .server()
        .id(chapter_id)
        .get()
        .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(())
}

Using the utils feature

Via (filename, Result<bytes>) vector based

Not recommended if you want to handle each response error

use anyhow::Result;
use mangadex_api::{utils::download::chapter::DownloadMode, MangaDexClient};
/// used for file exporting
use std::{
    fs::{create_dir_all, File},
    io::Write,
};

/// It's from this manga called [`The Grim Reaper Falls In Love With A Human`](https://mangadex.org/title/be2efc56-1669-4e42-9f27-3bd232bca8ea/the-grim-reaper-falls-in-love-with-a-human)
///
/// [Chapter 1 English](https://mangadex.org/chapter/2b4e39a5-fba0-4055-a176-8b7e19faacdb) by [`Kredim`](https://mangadex.org/group/0b870e54-c75f-4d2e-8068-c40f939135fd/kredim)
#[tokio::main]
async fn main() -> Result<()> {
    let output_dir = "your-output-dir";
    let client = MangaDexClient::default();
    let chapter_id = uuid::Uuid::parse_str("32b229f6-e9bf-41a0-9694-63c11191704c")?;
    let chapter_files = client
        // We use the download builder
        .download()
        // Chapter id (accept uuid::Uuid)
        .chapter(chapter_id)
        // You also use `DownloadMode::Normal` if you want some the original quality
        //
        // Default : Normal
        .mode(DownloadMode::DataSaver)
        // Enable the [`The MangaDex@Home report`](https://api.mangadex.org/docs/retrieving-chapter/#the-mangadexhome-report-endpoint) if true
        //
        // Default : false
        .report(true)
        // Something that i don`t really know about
        //
        // More details at : https://api.mangadex.org/docs/retrieving-chapter/#basics
        .force_port_443(false)
        .build()?
        .download_element_vec()
        .await?;
    create_dir_all(format!("{}{}", output_dir, chapter_id))?;
    for (filename, bytes_) in chapter_files {
        if let Ok(bytes) = bytes_ {
            let mut file: File =
                File::create(format!("{}{}/{}", output_dir, chapter_id, filename))?;
            file.write_all(&bytes)?;
        } else if let Err(e) = bytes_ {
            eprintln!("{}", e);
        }
    }
    Ok(())
}

Via tokio-stream

With tokio-stream, you can handle each response result

Without checker
use anyhow::Result;
use mangadex_api::{utils::download::chapter::DownloadMode, MangaDexClient};
use std::{
    fs::{create_dir_all, File},
    io::Write,
};
use tokio::pin;
use tokio_stream::StreamExt;

/// It's from this manga called [`Keiken Zumi na Kimi to, Keiken Zero na Ore ga, Otsukiai Suru Hanashi`](https://mangadex.org/title/1c8f0358-d663-4d60-8590-b5e82890a1e3/keiken-zumi-na-kimi-to-keiken-zero-na-ore-ga-otsukiai-suru-hanashi)
///
/// [Chapter 13 English](https://mangadex.org/chapter/250f091f-4166-4831-9f45-89ff54bf433b) by [`Galaxy Degen Scans`](https://mangadex.org/group/ab24085f-b16c-4029-8c05-38fe16592a85/galaxy-degen-scans)
#[tokio::main]
async fn main() -> Result<()> {
    let output_dir = "./test-outputs";
    let client = MangaDexClient::default();
    let chapter_id = uuid::Uuid::parse_str("250f091f-4166-4831-9f45-89ff54bf433b")?;
    create_dir_all(format!("{}/{}", output_dir, chapter_id))?;
    let download = client
        // We use the download builder
        .download()
        // Chapter id (accept uuid::Uuid)
        .chapter(chapter_id)
        // You also use `DownloadMode::Normal` if you want some the original quality
        //
        // Default : Normal
        .mode(DownloadMode::DataSaver)
        // Enable the [`The MangaDex@Home report`](https://api.mangadex.org/docs/04-chapter/retrieving-chapter/) if true
        //
        // Default : false
        .report(true)
        // Something that i don`t really know about
        //
        // More details at : https://api.mangadex.org/docs/04-chapter/retrieving-chapter/
        .force_port_443(false)
        .build()?;
    let chapter_files = download.download_stream().await?;
    // `pin!` Required for iteration
    pin!(chapter_files);
    while let Some((data, index, total)) = chapter_files.next().await {
        let (filename, bytes_) = data;
        // Prin the progression in the standart output
        println!("{index} / {total} : {filename} ");
        if let Ok(bytes) = bytes_ {
            let mut file: File =
                File::create(format!("{}/{}/{}", output_dir, chapter_id, filename))?;
            file.write_all(&bytes)?;
            println!("downloaded");
        } else if let Err(e) = bytes_ {
            eprintln!("{e}");
        }
    }
    Ok(())
}
with checker

The checker is a function called after the response fetching but before retreiving the byte content. Example :

    /// Some code here
    let download = client
        .download()
        .chapter(chapter_id)
        .mode(DownloadMode::DataSaver)
        .report(true)
        .build()?;
    let chapter_files = download
        .download_stream_with_checker(move |filename, response| {
            /// if this function return `true`, the current response will be skipped
            true
        })
        .await?;
    /// Some code here too

Real example :

The checker will check return true if a file with the response content length has been created

use anyhow::Result;
use mangadex_api::{utils::download::chapter::DownloadMode, MangaDexClient};
use std::{
    fs::{create_dir_all, File},
    io::Write,
};
use tokio::pin;
use tokio_stream::StreamExt;

/// It's from this manga called [`Keiken Zumi na Kimi to, Keiken Zero na Ore ga, Otsukiai Suru Hanashi`](https://mangadex.org/title/1c8f0358-d663-4d60-8590-b5e82890a1e3/keiken-zumi-na-kimi-to-keiken-zero-na-ore-ga-otsukiai-suru-hanashi)
///
/// [Chapter 13 English](https://mangadex.org/chapter/250f091f-4166-4831-9f45-89ff54bf433b) by [`Galaxy Degen Scans`](https://mangadex.org/group/ab24085f-b16c-4029-8c05-38fe16592a85/galaxy-degen-scans)
#[tokio::main]
async fn main() -> Result<()> {
    let output_dir = "./test-outputs";
    let client = MangaDexClient::default();
    let chapter_id = uuid::Uuid::parse_str("250f091f-4166-4831-9f45-89ff54bf433b")?;
    create_dir_all(format!("{}/{}", output_dir, chapter_id))?;
    let download = client
        // We use the download builder
        .download()
        // Chapter id (accept uuid::Uuid)
        .chapter(chapter_id)
        // You also use `DownloadMode::Normal` if you want some the original quality
        //
        // Default : Normal
        .mode(DownloadMode::DataSaver)
        // Enable the [`The MangaDex@Home report`](https://api.mangadex.org/docs/04-chapter/retrieving-chapter/) if true
        //
        // Default : false
        .report(true)
        // Something that i don`t really know about
        //
        // More details at : https://api.mangadex.org/docs/04-chapter/retrieving-chapter/
        .force_port_443(false)
        .build()?;
    let chapter_files = download
        .download_stream_with_checker(move |filename, response| {
            let is_skip: bool = {
                // Get the response content length
                let content_length = match response.content_length() {
                    None => return false,
                    Some(d) => d,
                };
                // open the chapter image file
                File::open(format!(
                    "{}/{}/{}",
                    output_dir,
                    chapter_id,
                    filename.filename.clone()
                ))
                .map(|pre_file| {
                    pre_file
                        .metadata()
                        .map(|metadata| metadata.len() == content_length)
                        .unwrap_or(false)
                })
                .unwrap_or(false)
            };
            is_skip
        })
        .await?;
    // `pin!` Required for iteration
    pin!(chapter_files);
    while let Some((data, index, total)) = chapter_files.next().await {
        let (filename, bytes_) = data;
        // Prin the progression in the standart output
        println!("{index} / {total} : {filename} ");
        if let Ok(bytes) = bytes_ {
            let mut file: File =
                File::create(format!("{}/{}/{}", output_dir, chapter_id, filename))?;
            file.write_all(&bytes)?;
            println!("downloaded");
        } else if let Err(e) = bytes_ {
            eprintln!("{e}");
        }
    }
    Ok(())
}

Downloading a manga's main cover image

Back to top

Use the legacy way

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::v5::MangaDexClient;
use mangadex_api::CDN_URL;
// use mangadex_api_types_rust::RelationshipType;
use mangadex_api_types::RelationshipType;

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

    let manga_id = Uuid::new_v4();
    let manga = client.manga().id(manga_id).get().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().cover_id(cover_id).get().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(())
}

Using the utils feature (recommended)

via a cover id

    use anyhow::Result;
    use uuid::Uuid;
    use crate::MangaDexClient;
    use std::{io::Write, fs::File};

    /// Download the volume 2 cover of [Lycoris Recoil](https://mangadex.org/title/9c21fbcd-e22e-4e6d-8258-7d580df9fc45/lycoris-recoil)
    #[tokio::main]
    async fn main() -> Result<()>{
        let cover_id : Uuid = Uuid::parse_str("0bc12ff4-3cec-4244-8582-965b8be496ea")?;
        let client : MangaDexClient = MangaDexClient::default();
        let (filename, bytes) = client.download().cover().build()?.via_cover_id(cover_id).await?;
        let mut file = File::create(format!("{}/{}", "your-output-dir", filename))?;
        file.write_all(&bytes)?;
        Ok(())
    }

via a manga id

use anyhow::Result;
use mangadex_api::MangaDexClient;
use std::{fs::File, io::Write};
use uuid::Uuid;

/// Download the [Kimi tte Watashi no Koto Suki Nandesho?](https://mangadex.org/title/f75c2845-0241-4e69-87c7-b93575b532dd/kimi-tte-watashi-no-koto-suki-nandesho) cover
///
/// For test... of course :3
#[tokio::main]
async fn main() -> Result<()> {
    let output_dir = String::from("test-outputs");
    let manga_id: Uuid = Uuid::parse_str("f75c2845-0241-4e69-87c7-b93575b532dd")?;
    let client: MangaDexClient = MangaDexClient::default();
    let (filename, bytes) = client
        .download()
        .cover()
        // you can use
        //
        // ```rust
        // .quality(CoverQuality::Size512)
        // ``` for 512
        // or
        // ```rust
        // .quality(CoverQuality::Size256)
        // ``` for 256
        .build()?
        .via_manga_id(manga_id)
        .await?;
    let bytes = bytes?;
    let mut file = File::create(format!("{}/{}", output_dir, filename))?;
    file.write_all(&bytes)?;
    println!("donwloaded :3");
    Ok(())
}

Authentification (via the oauth feature)

Before paste copying uhm,... touching the example code below, I recommend that you read the Mangadex Authentification Section

First, register a personal client at Mangadex Profile Settings, and wait until it's approved by staff. It can take 2 or 3 three days for now so just wait :>

After a long time, you can now login via the oauth feature.

Login

use mangadex_api::MangaDexClient;
use mangadex_api_schema::v5::oauth::ClientInfo;
use mangadex_api_types::{Password, Username};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut client = MangaDexClient::default();
    client
        .set_client_info(&ClientInfo {
            client_id: String::from("<SET YOUR CLIENT ID HERE>"),
            client_secret: String::from("<SET YOUR CLIENT INFO HERE>"),
        })
        .await?;
    let response = client
        .oauth()
        .login()
        .username(Username::parse("<YOUR USERNAME HERE>")?)
        .password(Password::parse("<YOUR PASSWORD HERE>")?)
        .send()
        .await?;
    /*
       println!("Access Token: {}", response.access_token);
    */
    println!("Expires in {} minutes", response.expires_in / 60);
    Ok(())
}

Resfresh your token

You just call mangadex_api::MangaDexClient::oauth().refresh()

    ...
    client
        .oauth()
        .refresh()
        .send()
        .await?;
    ...

Example:

use mangadex_api::MangaDexClient;
// use mangadex_api_schema_rust::v5::oauth::ClientInfo;
// use mangadex_api_types_rust::{Password, Username};
use mangadex_api_schema::v5::oauth::ClientInfo;
use mangadex_api_types::{Password, Username};
use tokio::time::{sleep, Duration};

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

    // Register your client info
    client
        .set_client_info(&ClientInfo {
            client_id: String::from("<SET YOUR CLIENT ID HERE>"),
            client_secret: String::from("<SET YOUR CLIENT INFO HERE>"),
        })
        .await?;

    // Login to your account
    let response = client
        .oauth()
        .login()
        .username(Username::parse("<YOUR USERNAME HERE>")?)
        .password(Password::parse("<YOUR PASSWORD HERE>")?)
        .send()
        .await?;
    /*
       println!("Access Token: {}", response.access_token);
    */
    println!("Expires in {} minutes", response.expires_in / 60);
    // Wait until the token expires

    sleep(Duration::from_secs(<u64 as TryFrom<usize>>::try_from(
        response.expires_in,
    )?))
    .await;

    // Refresh the session token
    let response = client.oauth().refresh().send().await?;
    /*
       println!("Access Token: {}", response.access_token);
    */
    println!("Expires in {} minutes", response.expires_in / 60);
    Ok(())
}

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.

mangadex-api's People

Contributors

curstantine avatar dependabot[bot] avatar renovate[bot] avatar tonymushah avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

mangadex-api's Issues

gondolyr request list

i need to know what feature you wanted to add in the crate.

I have a bunch of stuff ๐Ÿ˜… ...
There are probably more I can list but this is already a lot.

  • Compile times are painfully slow. As a library, minimizing the compile times should be a priority. Removing external dependencies in favor of building things manually in the project would probably be a good starting place.
  • More structs and modules should be made public. This is currently making it impossible for some to compile their programs simply because they can't define the type in their functions.
    error[E0603]: module `manga_aggregate` is private
      --> src/events/components/manga_reader.rs:48:132
       |
    48 | ...ponentInteraction, manga: mangadex_api::v5::schema::manga_aggregate::MangaAggregate) {
       |                                                        ^^^^^^^^^^^^^^^ private module
       |
    note: the module `manga_aggregate` is defined here
      --> /home/jp/.cargo/registry/src/github.com-1ecc6299db9ec823/mangadex-api-1.3.0/src/v5/schema.rs:14:1
       |
    14 | mod manga_aggregate;
       | ^^^^^^^^^^^^^^^^^^^^
    
  • The API could be made more ergonomic. The builder pattern felt clunky whenever I tried to use my own library.
    let manga = client
        .manga()
        .aggregate()
        .manga_id(&manga_id)
        .translated_language(languages)
        .build()
        .unwrap()
        .send()
        .await;

Owner Edit: This syntax doesn't work since v3
So the following example should be:

    let manga = client
        .manga()
        .id(your_manga_id)
        .aggregate()
        .get()
        .translated_language(languages)
        .send()
        .await
        .unwrap();
  • #112
    Owner Edit : It won't work since the old login system is deprecated
  • I'm not sure that breaking up the project into workspaces (mangadex-api-types, mangadex-api-schema) was worth the trouble. It might be good to put them back into a single workspace but I can't say which is better honestly.
  • The v5 module is probably unnecessary. I made it because of the potential of future major API versions since at the time I made it, there was a v2 and v5 of the MangaDex API.
  • Having WASM compatibility would be nice.
  • It would be good to see more String fields converted to an enum type where appropriate. Here is one example:
    pub roles: Vec<String>, // TODO: Deserialize the strings into `UserRole` enum.
  • I'm not maintaining this crate for the foreseeable future so I don't need to be included in the list of authors

Notes about some of the custom deserialization I added:

Originally posted by @gondolyr in #1 (comment)

Add la as part of the list of available languages.

Example: https://api.mangadex.org/chapter?translatedLanguage[]=la

RequestError(
    reqwest::Error {
        kind: Decode,
        source: Error("unknown variant `la`, expected one of `ar`, `bn`, `bg`, `my`, `ca`, `zh-ro`, `zh`, `zh-hk`, `cs`, `da`, `nl`, `en`, `tl`, `fi`, `fr`, `de`, `el`, `he`, `hi`, `hu`, `id`, `it`, `ja`, `ja-ro`, `ko`, `ko-ro`, `lt`, `ms`, `mn`, `ne`, `kr`, `no`, `fa`, `pl`, `pt-br`, `pt`, `rm`, `ro`, `ru`, `sr`, `es`, `es-la`, `sv`, `th`, `tr`, `uk`, `vi`, `NULL`", line: 0, column: 0),
    },
)

Add Rate Limiting Handling

Rate limits can change depending on the number of load balancers that are active. The API call responses includes header meta data related to these rate limits.

In the short term I would suggest adding these headers to the mangadex_api_schema::ApiData struct to allow user code to utilise this meta data.

Header Description
X-RateLimit-Limit Maximal number of requests this endpoint allows per its time period
X-RateLimit-Remaining Remaining number of requests within your quota for the current time period
X-RateLimit-Retry-After Timestamp of the end of the current time period, as UNIX timestamp

https://api.mangadex.org/docs/rate-limits/

implement missing languages

As always, some languages are missing.
I don't know how many languages missing but it seems like Mangadex added a bunch recently.

The Plan:

1- List all Mangadex supported languages:

Should be easy:

  • Just go to the mangadex.org,
  • click to user icon at the top right
  • go to chapter languages
  • list them all

2- Implement them :

Mangadex uses the ISO 639-1 standard.
All "currently" supported languages is here : mangadex-api-types::language::Language

The property chapters is not defined and the definition does not allow additional properties

When using chapters or add_chapter, such as the below

let mut chapters_res = client
    .search()
    .chapter()
    .add_chapter(String::from("1"))
    .manga_id(&manga_id)
    .limit(limit)
    .build()?
    .send()
    .await?;

Sending it yields an error like so

Error: an error occurred with the MangaDex API request: MangaDexErrorResponse { errors: [MangaDexError { id: 9c346772-7b14-5982-b4b6-7b5888522762, status: 400, title: Some("validation_exception"), detail: Some("Error validating : The property chapters is not defined and the definition does not allow additional properties"), context: None }] }

Upon serializing it, I see this output

{"limit":10,"offset":null,"ids":[],"title":null,"groups":[],"uploader":[],"manga":"4e7a4a0f-8391-4069-839b-de2352297dab","volume":[],"chapters":["1"],"translatedLanguage":["en"],"originalLanguage":[],"excludedOriginalLanguage":[],"contentRating":[],"excludedGroups":[],"excludedUploaders":[],"includeFutureUpdates":null,"createdAtSince":null,"updatedAtSince":null,"publishAtSince":null,"includeEmptyPages":null,"includeExternalUrl":null,"includeFuturePublishAt":null,"order":null,"includes":[]}

Notice chapters. From the mangadex api I see a parameter named chapter but not chapters

Indeed, using a query like this results in the same error
https://api.mangadex.org/chapter?limit=10&manga=4e7a4a0f-8391-4069-839b-de2352297dab&chapters[0]=1&translatedLanguage[0]=en
Whereas this one works
https://api.mangadex.org/chapter?limit=10&manga=4e7a4a0f-8391-4069-839b-de2352297dab&chapter[0]=1&translatedLanguage[0]=en

Annoucement for 3.0.0

NEED ATTENTION

If you don't know, Mangadex API Team (that doesn't include me) realease a new breaking change in their follows and custom list system.
Reference here
As for that, i'm now preparing for having a new breakin-change realease this weekend or next week.
Owner Edit: The v3 developpement took 5 months though ๐Ÿ˜….
As a preparation, i will tell about what we will change :

1- The new follow/custom list system

I don't know about the details of it but i will reference this in a new issue

2- The RateLimit metadata

Ref #26

3- A new syntax

Example :
Let's say we have an endpoint like this:
GET /manga/{id}
then you should have an invoke syntax like this:

// Note: the `client` is the `mangadex-api::v5::Client` here 
client
    .manga()
    .id(/*your manga id here*/)
    .get()
    .send()
    .await?;

or

GET /chapter
Path Parameters
=============
- chapter : uuid[]
- manga : uuid
- includes : IncludesReferenceExpansion[]

should be :

client.chapter().get().chapter(/*your chapter id list*/).manga(/*your manga id*/).includes(/*your include reference enum list*/).send().await?;

Notice that there is no .build()? in this syntax.

I want to make it like that because it's overhelming to maintain the API with all of those weird function.

4- A dedicated tauri-plugin

ref : #10

Thanks for understanding.

Preparation for the `MangaDex API` v5.10.2 update

Just ealier, an OpenAPI spec changes pull request have been opened on the MangaDex API Docs repository

Edit:
It's now deployed so this update will come very soon

To summarize, there is some notable changes:

1- a new endpoint have been added: POST /upload/check-approval-required

Check if a given manga / locale for a User needs moderation approval
Body Schema :

  • manga: Uuid (the mangaID)
  • locale: Language

Possible response schema:

  • 200 : ok
{
   "result": "ok",
   "requiresApproval": true | false
}
  • 404: the manga is not found
{
    "result": "ok"
}

2- Removed all /account endpoint:

Since MangaDex Authentification migrated to OAuth 2.0, these endpoint will be removed in the next version.
but DELETE /user/{id} and POST /user/delete/{code} is still there so... I don't know what to tell about this.

3- Added namicomi on the AuthorLinks structs.

Todo list:

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

cargo
mangadex-api-input-types/Cargo.toml
  • serde 1.0
  • specta 1.0
  • tokio 1
  • anyhow 1.0.82
  • async-graphql 7.0.3
  • uuid 1.8
  • url 2.5
mangadex-api-schema/Cargo.toml
  • reqwest 0.12
  • serde 1.0
  • serde_qs 0.13
  • thiserror 1.0
  • url 2.5
  • uuid 1.8
  • specta 1.0
  • tokio 1
  • serde_json 1
  • jsonxf 1
  • color-print 0.3.6
mangadex-api-tests/Cargo.toml
  • tokio 1.37
  • anyhow 1.0.82
  • reqwest 0.12
  • uuid 1
mangadex-api-types/Cargo.toml
  • anyhow 1.0.82
  • derive_builder 0.20
  • reqwest 0.12
  • serde 1
  • thiserror 1
  • time 0.3
  • url 2.5
  • uuid 1.8
  • async-graphql 7.0.3
  • specta 1.0
  • tokio 1
  • serde_json 1
mangadex-api/Cargo.toml
  • derive_builder 0.20
  • futures 0.3
  • reqwest 0.12
  • serde 1.0
  • serde_qs 0.13
  • thiserror 1.0
  • time 0.3
  • url 2.5
  • uuid 1.8
  • bytes 1.6
  • anyhow 1.0.82
  • tokio 1.37
  • async-stream 0.3.5
  • tokio-stream 0.1
  • getset 0
  • anyhow 1.0.82
  • serde_urlencoded 0.7
  • clap 4.5.4
  • fake 2.9.2
  • serde_json 1.0
  • tokio 1.37
  • wiremock 0.6
devcontainer
.devcontainer/devcontainer.json
  • mcr.microsoft.com/devcontainers/universal 2
  • ghcr.io/devcontainers/features/rust 1
github-actions
.github/workflows/rust.yml
  • actions/checkout v4
  • actions/checkout v4
  • actions/checkout v4
  • actions/checkout v4

  • Check this box to trigger a request for Renovate to run again on this repository

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.