{"id":27104068,"url":"https://github.com/tonymushah/mangadex-api","last_synced_at":"2026-02-18T18:06:16.747Z","repository":{"id":100989079,"uuid":"609021510","full_name":"tonymushah/mangadex-api","owner":"tonymushah","description":"A Mangadex API Wrapper for Rust","archived":false,"fork":false,"pushed_at":"2024-04-22T14:04:32.000Z","size":3171,"stargazers_count":13,"open_issues_count":6,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-04-22T14:04:55.070Z","etag":null,"topics":["asynchronous","builder-pattern","mangadex","mangadex-api","rust","rust-sdks"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tonymushah.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-03-03T07:58:37.000Z","updated_at":"2024-04-25T09:44:11.022Z","dependencies_parsed_at":"2023-09-22T03:52:27.078Z","dependency_job_id":"bfff0d2a-00d9-4925-b556-e1671b7debca","html_url":"https://github.com/tonymushah/mangadex-api","commit_stats":{"total_commits":26,"total_committers":1,"mean_commits":26.0,"dds":0.0,"last_synced_commit":"ee9cf257ef8d635db4731abffbcf00eca1af72ea"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonymushah%2Fmangadex-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonymushah%2Fmangadex-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonymushah%2Fmangadex-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonymushah%2Fmangadex-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tonymushah","download_url":"https://codeload.github.com/tonymushah/mangadex-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247521785,"owners_count":20952416,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous","builder-pattern","mangadex","mangadex-api","rust","rust-sdks"],"created_at":"2025-04-06T17:30:17.229Z","updated_at":"2026-01-03T14:22:32.590Z","avatar_url":"https://github.com/tonymushah.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mangadex-api\n\n[![Rust](https://github.com/tonymushah/mangadex-api/actions/workflows/rust.yml/badge.svg)](https://github.com/tonymushah/mangadex-api/actions/workflows/rust.yml)\n[![Crates.io][crates-badge]][crates-url]\n[![Documentation][docs-badge]][docs-url]\n[![Crates.io (recent)](https://img.shields.io/crates/dr/mangadex-api)][crates-url]\n\n[crates-badge]: https://img.shields.io/crates/v/mangadex-api.svg\n[crates-url]: https://crates.io/crates/mangadex-api\n[docs-badge]: https://img.shields.io/docsrs/mangadex-api.svg\n[docs-url]: https://docs.rs/mangadex-api\n\n## Important\n\nThis git repo is just a fork from [gondolyr/mangadex-api](https://gitlab.com/gondolyr/mangadex-api) but the project and crate has been yanked so I will now maintain this crate for [special-eureka](https://github.com/tonymushah/special-eureka) and [eureka-manager](https://github.com/tonymushah/eureka-mmanager)\n\nThe `mangadex-api` crate provides a convenient, high-level wrapper\n[client][library-client] for the [MangaDex API][mangadex-api-url],\nwritten in [Rust][rust-homepage].\n\nIt covers all public endpoints covered by [their documentation][mangadex-api-docs-url].\n\n[Documentation (docs.rs)](https://docs.rs/mangadex_api)\n\n[Documentation (Old repo `main` branch)](https://gondolyr.gitlab.io/mangadex-api/mangadex_api)\n\nPlease note that as MangaDex is still in beta, this SDK will be subject to sudden breaking changes (a lot).\n\n## Disclaimer\n\n`mangadex-api` is not affiliated with [MangaDex][mangadex-homepage].\n\n## Table of Contents\n\n- [mangadex-api](#mangadex-api)\n  - [Important](#important)\n  - [Disclaimer](#disclaimer)\n  - [Table of Contents](#table-of-contents)\n  - [Workspace details](#workspace-details)\n  - [Requirements](#requirements)\n  - [How to install](#how-to-install)\n  - [Dependency Justification](#dependency-justification)\n  - [Features](#features)\n  - [HTTP Client](#http-client)\n  - [Response Structs](#response-structs)\n  - [Getting Started](#getting-started)\n  - [Using a custom reqwest Client](#using-a-custom-reqwest-client)\n  - [Searching manga by title](#searching-manga-by-title)\n  - [Searching manga by title with reference expansion](#searching-manga-by-title-with-reference-expansion)\n  - [Downloading chapter pages](#downloading-chapter-pages)\n  - [Using the old way](#using-the-old-way)\n    - [Using the `utils` feature](#using-the-utils-feature)\n      - [Via `(filename, Result\u003cbytes\u003e)` vector based](#via-filename-resultbytes-vector-based)\n      - [Via `tokio-stream`](#via-tokio-stream)\n        - [Without checker](#without-checker)\n        - [with checker](#with-checker)\n  - [Downloading a manga's main cover image](#downloading-a-mangas-main-cover-image)\n    - [Use the legacy way](#use-the-legacy-way)\n    - [Using the `utils` feature (recommended)](#using-the-utils-feature-recommended)\n      - [via a cover id](#via-a-cover-id)\n      - [via a manga id](#via-a-manga-id)\n  - [Authentification (via the `oauth` feature)](#authentification-via-the-oauth-feature)\n    - [Login](#login)\n    - [Resfresh your token](#resfresh-your-token)\n  - [License](#license)\n    - [Contribution](#contribution)\n  - [Contributing](#contributing)\n\n## Workspace details\n\n- [`mangadex-api`](./mangadex-api/) is the SDK itself\n- [`mangadex-api-types`](./mangadex-api-types/) is the crate containing all enums, and static data. Those are `non_exhaustive` by default\n- [`mangadex-api-schema`](./mangadex-api-schema/) contains all the response structs required. These support serialization with the `serialize` feature\n- [`mangadex-api-inputs-types`](./mangadex-api-input-types/) contains input types for endpoint. Please note that this input crate only contain those with multiple parameters.\n\n## Requirements\n\n[Back to top][readme-section-toc]\n\n- [Rust 1.85+][rust-homepage]\n\n## How to install\n\n[Back to top][readme-section-toc]\n\nAdd `mangadex-api` to your dependencies:\n\n```toml\n[dependencies]\n# ...\n# Types and schemas are always required\nmangadex-api-types-rust = \"1\"\nmangadex-api-schema-rust = \"1\"\nmangadex-api = \"4\"\n```\n\nIf you are using [`cargo-edit`](https://github.com/killercup/cargo-edit), run\n\n```bash\ncargo add mangadex-api\n```\n\n## Dependency Justification\n\n| Dependency                                         | Used for                                                                                                                                 | Included   |\n|:---------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------|:-----------|\n| [`anyhow`][dependency-anyhow-docs]                 | Capturing unexpected errors.                                                                                                             | always     |\n| [`mangadex-api-types-rust`][dependency-mangadex-api-types]                 | Enums and static data for Mangadex API                                                                                                              | always     |\n| [`mangadex-api-schema-rust`][dependency-mangadex-api-schema]                 | Types used for Mangadex API                                                                                                              | always     |\n| [`clap`][dependency-clap-docs]                     | Examples demonstrating the library's capabilities                                                                                        | dev builds |\n| [`derive_builder`][dependency-derive_builder-docs] | Conveniently generating setters for the API endpoint builders.                                                                           | always     |\n| [`fake`][dependency-fake-docs]                     | Generating random data for unit tests.                                                                                                   | dev builds |\n| [`reqwest`][dependency-reqwest-docs]               | Making HTTP requests to the [MangaDex API][mangadex-api-url].                                                                            | always     |\n| [`serde`][dependency-serde-docs]                   | Se/dese/rializing HTTP response bodies into structs.                                                                                     | always     |\n| [`serde_json`][dependency-serde_json-docs]         | Creating JSON objects for unit tests.                                                                                                    | dev builds |\n| [`serde_qs`][dependency-serde_qs-docs]             | Query string serialization for HTTP requests.                                                                                            | always     |\n| [`thiserror`][dependency-thiserror-docs]           | Customized error handling.                                                                                                               | always     |\n| [`time`][dependency-time-docs]                     | Convenience types for handing time fields.                                                                                               | always     |\n| [`tokio`][dependency-tokio-docs]                   | Async [`RwLock`](https://docs.rs/tokio/latest/tokio/sync/struct.RwLock.html) support                                                                      | `sync` feature only |\n| [`url`][dependency-url-docs]                       | Convenient `Url` type for validating and containing URLs.                                                                                | always     |\n| [`uuid`][dependency-uuid-docs]                     | Convenient `Uuid` type for validating and containing UUIDs for requests and responses. Also used to randomly generate UUIDs for testing. | always     |\n| [`wiremock`][dependency-wiremock-docs]             | HTTP mocking to test the [MangaDex API][mangadex-api-url].                                                                               | dev builds |\n\n## Features\n\n[Back to top][readme-section-toc]\n\nAll features are not included by default. To enable them, add any of the following to your project's `Cargo.toml` file.\n\n- `utils`\n\n  Enable the usage of the `MangaDexClient::download()`. Allows you to download chapters or covers image without tears and long code.\n\n- `oauth` (Enabled by default)\n  \n  Enable the use of the *brand* new OAuth 2.0 login introduced in MangaDex API 5.9.0.\n  \n  __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.\n  More details [here](#authentification-via-the-oauth-feature)\n\n- `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`][custom-list-v2] on the MangaDex Forums\n\nFor example, to enable the `utils` feature, add the following to your `Cargo.toml` file:\n\n```toml\nmangadex-api = { version = \"4\", features = [\"utils\"] }\n```\n\n## HTTP Client\n\n[Back to top][readme-section-toc]\n\nThe [`mangadex_api::MangaDexClient`][library-client] is asynchronous, using\n[`reqwest`][reqwest] as the HTTP client.\n\n## Response Structs\n\n[Back to top][readme-section-toc]\n\nThe response structs can be found in the [`mangadex-api-schema-rust` crate][library-schema-module] and contain the fields in a JSON response.\n\n## Getting Started\n\n[Back to top][readme-section-toc]\n\nThis example demonstrates how to fetch a random manga.\n\n```rust\nuse mangadex_api::v5::MangaDexClient;\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    let client = MangaDexClient::default();\n\n    let random_manga = client\n        .manga()\n        .random()\n        .get()\n        .send()\n        .await?;\n\n    println!(\"{:?}\", random_manga);\n\n    Ok(())\n}\n```\n\nThis example demonstates how to fetch the [Mangadex Popular Titles](https://mangadex.org/)\n\n```rust\nuse mangadex_api::MangaDexClient;\nuse mangadex_api_schema_rust::v5::RelatedAttributes;\nuse mangadex_api_types_rust::{\n    Language, MangaDexDateTime, MangaSortOrder, OrderDirection, ReferenceExpansionResource,\n};\nuse time::{Duration, OffsetDateTime};\nuse url::Url;\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    let client = MangaDexClient::default();\n\n    // Take the local date and put substract it with 30 days\n    let created_at_since = OffsetDateTime::now_utc()\n        .checked_sub(Duration::days(30))\n        .unwrap();\n\n    let created_at_since = MangaDexDateTime::new(\u0026created_at_since);\n\n    let res = client\n        .manga()\n        .get()\n        // We pick up all manga that has been created during these last 30 days\n        .created_at_since(created_at_since)\n        // Mangadex Popular Titles is ordered by followedCount descending\n        .order(MangaSortOrder::FollowedCount(OrderDirection::Descending))\n        // We include the author data\n        .include(ReferenceExpansionResource::Author)\n        // We include the arstits data\n        .include(ReferenceExpansionResource::Artist)\n        .send()\n        .await?;\n\n    let not_found = String::from(\"Not found\");\n    // Just a simple index :3\n    let mut index = 1;\n    for manga in res.data {\n        // Find the English title\n        let title = manga\n            .attributes\n            .title\n            .get(\u0026Language::English)\n            .unwrap_or(\u0026not_found);\n        println!(\"{index} ~ {title}\");\n        // Find the author name\n        let author = manga\n            .find_first_relationships(mangadex_api_types::RelationshipType::Author)\n            .and_then(|e| {\n                e.attributes.clone().map(|rel| match rel {\n                    RelatedAttributes::Author(a) =\u003e a.name,\n                    _ =\u003e not_found.clone(),\n                })\n            })\n            .unwrap_or(not_found.clone());\n        println!(\"\\tAuthor: {author}\");\n        // Find the author name\n        let artist = manga\n            .find_first_relationships(mangadex_api_types::RelationshipType::Artist)\n            .and_then(|e| {\n                e.attributes.clone().map(|rel| match rel {\n                    RelatedAttributes::Author(a) =\u003e a.name,\n                    _ =\u003e not_found.clone(),\n                })\n            })\n            .unwrap_or(not_found.clone());\n        // Print the artist name if it's different of the author\n        if artist != author {\n            println!(\"\\tArtist: {artist}\");\n        }\n        // We generate the link that goes to the Mangadex page\n        let title_link =\n            Url::parse(\"https://mangadex.org/title/\")?.join(manga.id.to_string().as_str())?;\n        println!(\"\\tLink: {title_link}\");\n        println!();\n        index += 1;\n    }\n    println!(\"Done :3\");\n    Ok(())\n}\n```\n\n## Using a custom reqwest Client\n\n[Back to top][readme-section-toc]\n\nBy default, [`mangadex_api::MangaDexClient`][library-client] will use the default\n[`reqwest::Client`][reqwest-client] settings.\n\nYou may provide your own [`reqwest::Client`][reqwest-client] to customize options such as the\nrequest timeout.\n\n```rust\nuse reqwest::Client;\n\nuse mangadex_api::v5::MangaDexClient;\n\n# async fn run() -\u003e anyhow::Result\u003c()\u003e {\nlet reqwest_client = Client::builder()\n    .timeout(std::time::Duration::from_secs(10))\n    .build()?;\n\nlet client = MangaDexClient::new(reqwest_client);\n# Ok(())\n# }\n```\n\n## Searching manga by title\n\n[Back to top][readme-section-toc]\n\nReference: \u003chttps://api.mangadex.org/swagger.html#/Manga/get-search-manga\u003e\n\n```rust\nuse mangadex_api::v5::MangaDexClient;\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    let client = MangaDexClient::default();\n\n    let manga_results = client\n        .manga()\n        .get()\n        .title(\"full metal\")\n        .send()\n        .await?;\n\n    println!(\"manga results = {:?}\", manga_results);\n    Ok(())\n}\n```\n\n## Searching manga by title with reference expansion\n\n[Back to top][readme-section-toc]\n\nEvery 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.\n\nIn 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.\n\nReferences:\n\n- \u003chttps://api.mangadex.org/docs/01-concepts/reference-expansion/\u003e\n- Endpoint: \u003chttps://api.mangadex.org/docs/swagger.html#/Manga/get-search-manga\u003e\n- Author object: \u003chttps://api.mangadex.org/docs/swagger.html#/Author/get-author-id\u003e\n\n```rust\nuse mangadex_api::v5::schema::RelatedAttributes;\nuse mangadex_api::v5::MangaDexClient;\n// use mangadex_api_types_rust::{ReferenceExpansionResource, RelationshipType};\nuse mangadex_api_types::{ReferenceExpansionResource, RelationshipType};\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    let client = MangaDexClient::default();\n\n    let manga_results = client\n        .manga()\n        .get()\n        .title(\"full metal\")\n        .include(\u0026ReferenceExpansionResource::Author)\n        .send()\n        .await?;\n\n    println!(\"manga results = {:?}\", manga_results);\n\n    let authors = manga_results.data.iter().filter_map(|manga| {\n        manga\n            .relationships\n            .iter()\n            .find(|\u0026rel| rel.type_ == RelationshipType::Author)\n    });\n\n    for author in authors {\n        if let Some(RelatedAttributes::Author(author_attributes)) = \u0026author.attributes {\n            println!(\"{} - {}\", author.id, author_attributes.name);\n        }\n    }\n    Ok(())\n}\n```\n\n## Downloading chapter pages\n\n[Back to top][readme-section-toc]\n\nReference: \u003chttps://api.mangadex.org/docs/reading-chapter/\u003e\n\n## Using the old way\n\n```rust\n// Imports used for downloading the pages to a file.\n// They are not used because we're just printing the raw bytes.\n// use std::fs::File;\n// use std::io::Write;\n\nuse uuid::Uuid;\n\nuse mangadex_api::v5::MangaDexClient;\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    let client = MangaDexClient::default();\n\n    // 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! \u003e:)\n    let chapter_id = Uuid::parse_str(\"f2a09509-3c09-4371-a810-ecb99242bd90\")?;\n\n    let at_home = client\n        .at_home()\n        .server()\n        .id(chapter_id)\n        .get()\n        .send()\n        .await?;\n\n    let http_client = reqwest::Client::new();\n\n    // Original quality. Use `.data.attributes.data_saver` for smaller, compressed images.\n    let page_filenames = \u0026at_home.chapter.data;\n    for filename in page_filenames {\n        // If using the data-saver option, use \"/data-saver/\" instead of \"/data/\" in the URL.\n        let page_url = at_home\n            .base_url\n            .join(\u0026format!(\n                \"/{quality_mode}/{chapter_hash}/{page_filename}\",\n                quality_mode = \"data\",\n                chapter_hash = at_home.chapter.hash,\n                page_filename = filename\n            ))\n            .unwrap();\n\n        let res = http_client.get(page_url).send().await?;\n        // The data should be streamed rather than downloading the data all at once.\n        let bytes = res.bytes().await?;\n\n        // This is where you would download the file but for this example,\n        // we're just printing the raw data.\n        // let mut file = File::create(\u0026filename)?;\n        // let _ = file.write_all(\u0026bytes);\n        println!(\"Chunk: {:?}\", bytes);\n    }\n\n    Ok(())\n}\n\n```\n\n### Using the `utils` feature\n\n#### Via `(filename, Result\u003cbytes\u003e)` vector based\n\nNot recommended if you want to handle each response error\n\n```rust\nuse anyhow::Result;\nuse mangadex_api::{utils::download::chapter::DownloadMode, MangaDexClient};\n/// used for file exporting\nuse std::{\n    fs::{create_dir_all, File},\n    io::Write,\n};\n\n/// 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)\n///\n/// [Chapter 1 English](https://mangadex.org/chapter/2b4e39a5-fba0-4055-a176-8b7e19faacdb) by [`Kredim`](https://mangadex.org/group/0b870e54-c75f-4d2e-8068-c40f939135fd/kredim)\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let output_dir = \"your-output-dir\";\n    let client = MangaDexClient::default();\n    let chapter_id = uuid::Uuid::parse_str(\"32b229f6-e9bf-41a0-9694-63c11191704c\")?;\n    let chapter_files = client\n        // We use the download builder\n        .download()\n        // Chapter id (accept uuid::Uuid)\n        .chapter(chapter_id)\n        // You also use `DownloadMode::Normal` if you want some the original quality\n        //\n        // Default : Normal\n        .mode(DownloadMode::DataSaver)\n        // Enable the [`The MangaDex@Home report`](https://api.mangadex.org/docs/retrieving-chapter/#the-mangadexhome-report-endpoint) if true\n        //\n        // Default : false\n        .report(true)\n        // Something that i don`t really know about\n        //\n        // More details at : https://api.mangadex.org/docs/retrieving-chapter/#basics\n        .force_port_443(false)\n        .build()?\n        .download_element_vec()\n        .await?;\n    create_dir_all(format!(\"{}{}\", output_dir, chapter_id))?;\n    for (filename, bytes_) in chapter_files {\n        if let Ok(bytes) = bytes_ {\n            let mut file: File =\n                File::create(format!(\"{}{}/{}\", output_dir, chapter_id, filename))?;\n            file.write_all(\u0026bytes)?;\n        } else if let Err(e) = bytes_ {\n            eprintln!(\"{}\", e);\n        }\n    }\n    Ok(())\n}\n```\n\n#### Via `tokio-stream`\n\nWith [`tokio-stream`](https://docs.rs/tokio-stream/), you can handle each response result\n\n##### Without checker\n\n```rust\nuse anyhow::Result;\nuse mangadex_api::{utils::download::chapter::DownloadMode, MangaDexClient};\nuse std::{\n    fs::{create_dir_all, File},\n    io::Write,\n};\nuse tokio::pin;\nuse tokio_stream::StreamExt;\n\n/// 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)\n///\n/// [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)\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let output_dir = \"./test-outputs\";\n    let client = MangaDexClient::default();\n    let chapter_id = uuid::Uuid::parse_str(\"250f091f-4166-4831-9f45-89ff54bf433b\")?;\n    create_dir_all(format!(\"{}/{}\", output_dir, chapter_id))?;\n    let download = client\n        // We use the download builder\n        .download()\n        // Chapter id (accept uuid::Uuid)\n        .chapter(chapter_id)\n        // You also use `DownloadMode::Normal` if you want some the original quality\n        //\n        // Default : Normal\n        .mode(DownloadMode::DataSaver)\n        // Enable the [`The MangaDex@Home report`](https://api.mangadex.org/docs/04-chapter/retrieving-chapter/) if true\n        //\n        // Default : false\n        .report(true)\n        // Something that i don`t really know about\n        //\n        // More details at : https://api.mangadex.org/docs/04-chapter/retrieving-chapter/\n        .force_port_443(false)\n        .build()?;\n    let chapter_files = download.download_stream().await?;\n    // `pin!` Required for iteration\n    pin!(chapter_files);\n    while let Some((data, index, total)) = chapter_files.next().await {\n        let (filename, bytes_) = data;\n        // Prin the progression in the standart output\n        println!(\"{index} / {total} : {filename} \");\n        if let Ok(bytes) = bytes_ {\n            let mut file: File =\n                File::create(format!(\"{}/{}/{}\", output_dir, chapter_id, filename))?;\n            file.write_all(\u0026bytes)?;\n            println!(\"downloaded\");\n        } else if let Err(e) = bytes_ {\n            eprintln!(\"{e}\");\n        }\n    }\n    Ok(())\n}\n```\n\n##### with checker\n\nThe checker is a function called after the response fetching but before retreiving the byte content.\nExample :\n\n```rust\n    /// Some code here\n    let download = client\n        .download()\n        .chapter(chapter_id)\n        .mode(DownloadMode::DataSaver)\n        .report(true)\n        .build()?;\n    let chapter_files = download\n        .download_stream_with_checker(move |filename, response| {\n            /// if this function return `true`, the current response will be skipped\n            true\n        })\n        .await?;\n    /// Some code here too\n```\n\nReal example :\n\nThe checker will check return `true` if a file with the response content length has been created\n\n```rust\nuse anyhow::Result;\nuse mangadex_api::{utils::download::chapter::DownloadMode, MangaDexClient};\nuse std::{\n    fs::{create_dir_all, File},\n    io::Write,\n};\nuse tokio::pin;\nuse tokio_stream::StreamExt;\n\n/// 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)\n///\n/// [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)\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let output_dir = \"./test-outputs\";\n    let client = MangaDexClient::default();\n    let chapter_id = uuid::Uuid::parse_str(\"250f091f-4166-4831-9f45-89ff54bf433b\")?;\n    create_dir_all(format!(\"{}/{}\", output_dir, chapter_id))?;\n    let download = client\n        // We use the download builder\n        .download()\n        // Chapter id (accept uuid::Uuid)\n        .chapter(chapter_id)\n        // You also use `DownloadMode::Normal` if you want some the original quality\n        //\n        // Default : Normal\n        .mode(DownloadMode::DataSaver)\n        // Enable the [`The MangaDex@Home report`](https://api.mangadex.org/docs/04-chapter/retrieving-chapter/) if true\n        //\n        // Default : false\n        .report(true)\n        // Something that i don`t really know about\n        //\n        // More details at : https://api.mangadex.org/docs/04-chapter/retrieving-chapter/\n        .force_port_443(false)\n        .build()?;\n    let chapter_files = download\n        .download_stream_with_checker(move |filename, response| {\n            let is_skip: bool = {\n                // Get the response content length\n                let content_length = match response.content_length() {\n                    None =\u003e return false,\n                    Some(d) =\u003e d,\n                };\n                // open the chapter image file\n                File::open(format!(\n                    \"{}/{}/{}\",\n                    output_dir,\n                    chapter_id,\n                    filename.filename.clone()\n                ))\n                .map(|pre_file| {\n                    pre_file\n                        .metadata()\n                        .map(|metadata| metadata.len() == content_length)\n                        .unwrap_or(false)\n                })\n                .unwrap_or(false)\n            };\n            is_skip\n        })\n        .await?;\n    // `pin!` Required for iteration\n    pin!(chapter_files);\n    while let Some((data, index, total)) = chapter_files.next().await {\n        let (filename, bytes_) = data;\n        // Prin the progression in the standart output\n        println!(\"{index} / {total} : {filename} \");\n        if let Ok(bytes) = bytes_ {\n            let mut file: File =\n                File::create(format!(\"{}/{}/{}\", output_dir, chapter_id, filename))?;\n            file.write_all(\u0026bytes)?;\n            println!(\"downloaded\");\n        } else if let Err(e) = bytes_ {\n            eprintln!(\"{e}\");\n        }\n    }\n    Ok(())\n}\n```\n\n## Downloading a manga's main cover image\n\n[Back to top][readme-section-toc]\n\n### Use the legacy way\n\nWhile this example could directly get the cover information by passing in the cover ID,\nit is not often that one would have the ID off-hand, so the most common method would be from a\nmanga result.\n\nIf you want to get all of a manga's cover images, you will need to use the [cover list endpoint](https://api.mangadex.org/docs/swagger.html#/Cover/get-cover)\nand use the `manga[]` query parameter.\n\n```rust\n// Imports used for downloading the cover to a file.\n// They are not used because we're just printing the raw bytes.\n// use std::fs::File;\n// use std::io::Write;\n\nuse reqwest::Url;\nuse uuid::Uuid;\n\nuse mangadex_api::v5::MangaDexClient;\nuse mangadex_api::CDN_URL;\n// use mangadex_api_types_rust::RelationshipType;\nuse mangadex_api_types::RelationshipType;\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    let client = MangaDexClient::default();\n\n    let manga_id = Uuid::new_v4();\n    let manga = client.manga().id(manga_id).get().send().await?;\n\n    let cover_id = manga\n        .data\n        .relationships\n        .iter()\n        .find(|related| related.type_ == RelationshipType::CoverArt)\n        .expect(\"no cover art found for manga\")\n        .id;\n    let cover = client.cover().cover_id(cover_id).get().send().await?;\n\n    // This uses the best quality image.\n    // To use smaller, thumbnail-sized images, append any of the following:\n    //\n    // - .512.jpg\n    // - .256.jpg\n    //\n    // For example, \"https://uploads.mangadex.org/covers/8f3e1818-a015-491d-bd81-3addc4d7d56a/4113e972-d228-4172-a885-cb30baffff97.jpg.512.jpg\"\n    let cover_url = Url::parse(\u0026format!(\n        \"{}/covers/{}/{}\",\n        CDN_URL, manga_id, cover.data.attributes.file_name\n    ))\n    .unwrap();\n\n    let http_client = reqwest::Client::new();\n\n    let res = http_client.get(cover_url).send().await?;\n    // The data should be streamed rather than downloading the data all at once.\n    let bytes = res.bytes().await?;\n\n    // This is where you would download the file but for this example, we're just printing the raw data.\n    // let mut file = File::create(\u0026filename)?;\n    // let _ = file.write_all(\u0026bytes);\n    println!(\"Chunk: {:?}\", bytes);\n    Ok(())\n}\n```\n\n### Using the `utils` feature (recommended)\n\n#### via a cover id\n\n```rust\n    use anyhow::Result;\n    use uuid::Uuid;\n    use crate::MangaDexClient;\n    use std::{io::Write, fs::File};\n\n    /// Download the volume 2 cover of [Lycoris Recoil](https://mangadex.org/title/9c21fbcd-e22e-4e6d-8258-7d580df9fc45/lycoris-recoil)\n    #[tokio::main]\n    async fn main() -\u003e Result\u003c()\u003e{\n        let cover_id : Uuid = Uuid::parse_str(\"0bc12ff4-3cec-4244-8582-965b8be496ea\")?;\n        let client : MangaDexClient = MangaDexClient::default();\n        let (filename, bytes) = client.download().cover().build()?.via_cover_id(cover_id).await?;\n        let mut file = File::create(format!(\"{}/{}\", \"your-output-dir\", filename))?;\n        file.write_all(\u0026bytes)?;\n        Ok(())\n    }\n```\n\n#### via a manga id\n\n```rust\nuse anyhow::Result;\nuse mangadex_api::MangaDexClient;\nuse std::{fs::File, io::Write};\nuse uuid::Uuid;\n\n/// 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\n///\n/// For test... of course :3\n#[tokio::main]\nasync fn main() -\u003e Result\u003c()\u003e {\n    let output_dir = String::from(\"test-outputs\");\n    let manga_id: Uuid = Uuid::parse_str(\"f75c2845-0241-4e69-87c7-b93575b532dd\")?;\n    let client: MangaDexClient = MangaDexClient::default();\n    let (filename, bytes) = client\n        .download()\n        .cover()\n        // you can use\n        //\n        // ```rust\n        // .quality(CoverQuality::Size512)\n        // ``` for 512\n        // or\n        // ```rust\n        // .quality(CoverQuality::Size256)\n        // ``` for 256\n        .build()?\n        .via_manga_id(manga_id)\n        .await?;\n    let bytes = bytes?;\n    let mut file = File::create(format!(\"{}/{}\", output_dir, filename))?;\n    file.write_all(\u0026bytes)?;\n    println!(\"donwloaded :3\");\n    Ok(())\n}\n```\n\n## Authentification (via the `oauth` feature)\n\nBefore ~~paste copying~~ *uhm*,... touching the example code below, I recommend that you read the [Mangadex Authentification Section](https://api.mangadex.org/docs/02-authentication/)\n\nFirst, register a personal client at [Mangadex Profile Settings][mangadex-settings], ~~and wait until it's approved by staff. It can take 2 or 3 three days for now so just wait :\u003e~~ (it should normally be auto-approved).\n\nAfter a long time, you can now `login` via the `oauth` feature.\n\n### Login\n\n```rust\nuse mangadex_api::MangaDexClient;\nuse mangadex_api_schema::v5::oauth::ClientInfo;\nuse mangadex_api_types::{Password, Username};\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    let mut client = MangaDexClient::default();\n    client\n        .set_client_info(\u0026ClientInfo {\n            client_id: String::from(\"\u003cSET YOUR CLIENT ID HERE\u003e\"),\n            client_secret: String::from(\"\u003cSET YOUR CLIENT INFO HERE\u003e\"),\n        })\n        .await?;\n    let response = client\n        .oauth()\n        .login()\n        .username(Username::parse(\"\u003cYOUR USERNAME HERE\u003e\")?)\n        .password(Password::parse(\"\u003cYOUR PASSWORD HERE\u003e\")?)\n        .send()\n        .await?;\n    /*\n       println!(\"Access Token: {}\", response.access_token);\n    */\n    println!(\"Expires in {} minutes\", response.expires_in / 60);\n    Ok(())\n}\n```\n\n### Resfresh your token\n\nYou just call `mangadex_api::MangaDexClient::oauth().refresh()`\n\n```rust\n    ...\n    client\n        .oauth()\n        .refresh()\n        .send()\n        .await?;\n    ...\n```\n\nExample:\n\n```rust\nuse mangadex_api::MangaDexClient;\n// use mangadex_api_schema_rust::v5::oauth::ClientInfo;\n// use mangadex_api_types_rust::{Password, Username};\nuse mangadex_api_schema::v5::oauth::ClientInfo;\nuse mangadex_api_types::{Password, Username};\nuse tokio::time::{sleep, Duration};\n\n#[tokio::main]\nasync fn main() -\u003e anyhow::Result\u003c()\u003e {\n    let mut client = MangaDexClient::default();\n\n    // Register your client info\n    client\n        .set_client_info(\u0026ClientInfo {\n            client_id: String::from(\"\u003cSET YOUR CLIENT ID HERE\u003e\"),\n            client_secret: String::from(\"\u003cSET YOUR CLIENT INFO HERE\u003e\"),\n        })\n        .await?;\n\n    // Login to your account\n    let response = client\n        .oauth()\n        .login()\n        .username(Username::parse(\"\u003cYOUR USERNAME HERE\u003e\")?)\n        .password(Password::parse(\"\u003cYOUR PASSWORD HERE\u003e\")?)\n        .send()\n        .await?;\n    /*\n       println!(\"Access Token: {}\", response.access_token);\n    */\n    println!(\"Expires in {} minutes\", response.expires_in / 60);\n    // Wait until the token expires\n\n    sleep(Duration::from_secs(\u003cu64 as TryFrom\u003cusize\u003e\u003e::try_from(\n        response.expires_in,\n    )?))\n    .await;\n\n    // Refresh the session token\n    let response = client.oauth().refresh().send().await?;\n    /*\n       println!(\"Access Token: {}\", response.access_token);\n    */\n    println!(\"Expires in {} minutes\", response.expires_in / 60);\n    Ok(())\n}\n```\n\n## License\n\n[Back to top][readme-section-toc]\n\nLicensed under either of\n\n- Apache License, Version 2.0\n  ([LICENSE-APACHE][license-apache] or \u003chttp://www.apache.org/licenses/LICENSE-2.0\u003e)\n- MIT license\n  ([LICENSE-MIT][license-mit] or \u003chttp://opensource.org/licenses/MIT\u003e)\n\nat your option.\n\n### Contribution\n\n[Back to top][readme-section-toc]\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall be\ndual licensed as above, without any additional terms or conditions.\n\n## Contributing\n\n[Back to top][readme-section-toc]\n\nWe welcome contributions from everyone. There are many ways to contribute and the\n[CONTRIBUTING.md][contributing] document explains how you can contribute and get started.\n\n[dependency-anyhow-docs]: https://docs.rs/anyhow\n[dependency-mangadex-api-types]: https://github.com/tonymushah/mangadex-api/tree/main/mangadex-api-types\n[dependency-mangadex-api-schema]: https://github.com/tonymushah/mangadex-api/tree/main/mangadex-api-schema\n[dependency-clap-docs]: https://docs.rs/clap\n[dependency-fake-docs]: https://docs.rs/fake\n[dependency-derive_builder-docs]: https://docs.rs/derive_builder\n[dependency-reqwest-docs]: https://docs.rs/reqwest\n[dependency-serde-docs]: https://docs.rs/serde\n[dependency-serde_json-docs]: https://docs.rs/serde_json\n[dependency-serde_qs-docs]: https://docs.rs/serde_qs\n[dependency-thiserror-docs]: https://docs.rs/thiserror\n[dependency-time-docs]: https://docs.rs/time\n[dependency-tokio-docs]: https://docs.rs/tokio\n[dependency-url-docs]: https://docs.rs/url\n[dependency-uuid-docs]: https://docs.rs/uuid\n[dependency-wiremock-docs]: https://docs.rs/wiremock\n\n[mangadex-api-url]: https://api.mangadex.org\n[mangadex-api-docs-url]: https://api.mangadex.org/docs/swagger.html\n[mangadex-homepage]: https://mangadex.org\n[reqwest]: https://docs.rs/reqwest\n[reqwest-client]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html\n[rust-homepage]: https://rust-lang.org\n\n[contributing]: https://gitlab.com/gondolyr/mangadex-api/-/blob/main/CONTRIBUTING.md\n[library-client]: ./v5/struct.MangaDexClient.html\n[library-schema-module]: https://crates.io/crates/mangadex-api-schema-rust\n[license-apache]: https://gitlab.com/gondolyr/mangadex-api/-/blob/main/LICENSE-APACHE\n[license-mit]: https://gitlab.com/gondolyr/mangadex-api/-/blob/main/LICENSE-MIT\n\n[readme-section-toc]: #table-of-contents\n[personal-client]: https://api.mangadex.org/docs/02-authentication/personal-clients/\n[mangadex-settings]: https://mangadex.org/settings\n[custom-list-v2]: https://forums.mangadex.org/threads/follows-customlist-api-changelog-breaking-changes.1419832/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonymushah%2Fmangadex-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftonymushah%2Fmangadex-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonymushah%2Fmangadex-api/lists"}