1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
//! # hentai
//!
//! The aptly named *hentai* crate provides an easy mechanism to consume nhentai's public facing
//! APIs in Rust. As of now, hentai relies solely on these APIs instead of scraping their website.
//! However, this capability may be added in the near future.
//!
//! hentai will provide information about a doujin given its six-digit code. Alternatively, the
//! JSON response from nhentai's [/api/gallery/{id}](https://nhentai.net/api/gallery/165961)
//! endpoint may be provided directly.
//!
//! hentai is based on the similar [package for python](https://pypi.org/project/hentai/).
//!
//! # Examples
//! *more info for these samples is provided in the documentation for `Hentai`*
//!
//! ```rust
//! use hentai::{Hentai, Result, Website};
//!
//! #[tokio::main]
//! async fn main() -> Result<()> {
//! let response = Hentai::new(165961, Website::NET).await?;
//! println!("{:?}", response); // makes use of the Debug trait on Hentai
//!
//! Ok(())
//! }
//! ```
//!
//! ```rust
//! use hentai::{Hentai, Result, Website};
//!
//! #[tokio::main]
//! async fn main() -> Result<()> {
//! let response = Hentai::random(Website::XXX).await?;
//! println!("{:?}", response);
//!
//! Ok(())
//! }
//! ```
//!
//! ```no_run
//! use hentai::{Hentai, Result, Website};
//! use std::env;
//!
//! fn main() -> Result<()> {
//! let mut path = env::current_exe()?;
//! path.pop();
//! path.push("sample.json");
//!
//! let response = Hentai::from_json(path, Website::XXX)?;
//! println!("{:?}", response);
//!
//! Ok(())
//! }
//! ```
mod doujin;
mod search;
pub(crate) mod utility;
use chrono::{DateTime, Utc};
use doujin::Doujin;
use std::path::PathBuf;
use utility::api::url::Make;
pub use doujin::{Tag, Title};
pub use search::Search;
pub use utility::{
api::url::Website,
error::{HentaiError, Result},
};
/// The main object containing the formatted information retrieved from nhentai. The raw image
/// data is converted to into image URLs. A brief explanation of each field is located below.
///
/// - `id` is the six-digit code of the doujin. This is redundant if you are using the
/// `Hentai::new()` constructor.
/// - `url` is the direct link to the doujin created from the provided six-digit code.
/// - `scanlator` is the user that created translations for the doujin (not always present).
/// - Fields suffixed with "url" or "urls" are links to the doujin's image files.
/// - Fields starting with "num" are statistics provided by nhentai. These may not be accurate
/// for newly uploaded doujins.
/// - `title` contains the three different titles for the doujin provided by nhentai.
/// - Likewise, `tags` contains all the tags for the doujin.
/// - `media_id` is the separate code that nhentai uses to denote where the image files for
/// doujins are from. You will see this in the image urls (it is unrelated to the six-digit codes).
/// - `upload_date` is a
/// [chrono::DateTime](https://docs.rs/chrono/0.4.19/chrono/struct.DateTime.html) object that
/// indicates when the doujin was uploaded to nhentai. This is not the creation date of the doujin.
#[derive(Debug)]
pub struct Hentai {
pub id: u32,
pub url: String,
pub title: Title,
pub tags: Vec<Tag>,
pub num_pages: u32,
pub media_id: String,
pub scanlator: String,
pub cover_url: String,
pub num_favorites: u32,
pub thumbnail_url: String,
pub image_urls: Vec<String>,
pub upload_date: DateTime<Utc>,
}
fn create_urls(raw: &Doujin, builder: &Make) -> Vec<String> {
raw.images
.pages
.iter()
.enumerate()
.map(|(i, v)| builder.page(&raw.media_id, (i + 1) as u32, &v.t))
.collect()
}
fn organize_fields(raw: Doujin, builder: Make) -> Hentai {
let media_id = &raw.media_id;
let id = &raw.id.to_string();
Hentai {
image_urls: create_urls(&raw, &builder),
media_id: media_id.to_string(),
id: id.parse().unwrap_or(0),
tags: raw.tags,
title: raw.title,
num_pages: raw.num_pages,
scanlator: raw.scanlator,
upload_date: raw.upload_date,
num_favorites: raw.num_favorites,
url: builder.doujin_url(id),
cover_url: builder.cover(media_id, &raw.images.cover.t),
thumbnail_url: builder.cover_thumbnail(media_id, &raw.images.thumbnail.t),
}
}
impl Hentai {
/// Generates a new `Hentai` object for the provided six-digit code.
///
/// This makes a request to the primary nhentai endpoint located at
/// [/api/gallery/{id}](https://nhentai.net/api/gallery/165961).
/// The JSON response is deserialized and converted into a `Hentai` object.
/// This can fail at several stages, so it is important to handle the result accordingly.
/// To make things simpler, `Result<Hentai, HentaiError>` is used.
///
/// A malformed code may result in an invalid url
/// ([http::uri::InvalidUri](https://docs.rs/http/0.2.3/http/uri/struct.InvalidUri.html)), the
/// request to the endpoint may fail
/// ([hyper::Error](https://docs.rs/hyper/0.14.4/hyper/struct.Error.html)), or the JSON
/// response may be invalid
/// ([serde_json::Error](https://docs.serde.rs/serde_json/struct.Error.html)).
///
/// More information about the `Website` enum can be found in its section. The sample below
/// depends on [tokio](https://tokio.rs/).
/// ```rust
/// use hentai::{Hentai, Result, Website};
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let response = Hentai::new(165961, Website::NET).await?;
/// println!("{:?}", response); // makes use of the Debug trait on Hentai
///
/// Ok(())
/// }
/// ```
pub async fn new(code: u32, mode: Website) -> Result<Self> {
let result = Doujin::new(code).await?;
Ok(organize_fields(result, Make::new(mode)))
}
/// Generates a `Hentai` object for a randomly selected doujin from nhentai's
/// [random](https://nhentai.xxx/random) endpoint.
///
/// A six-digit code is taken from the response headers from this request. If the code cannot be
/// retrieved from the header, the following errors can be raised:
/// - `BaseError`,
/// - [ToStrError](https://docs.rs/hyper/0.14.4/hyper/header/struct.ToStrError.html),
/// - [ConversionError](https://doc.rust-lang.org/std/num/struct.ParseIntError.html),
/// depending on the format of the response.
///
/// The sample below depends on [tokio](https://tokio.rs/).
/// ```rust
/// use hentai::{Hentai, Result, Website};
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let response = Hentai::random(Website::XXX).await?;
/// println!("{:?}", response);
///
/// Ok(())
/// }
/// ```
pub async fn random(mode: Website) -> Result<Self> {
let code = Doujin::random().await?;
let result = Doujin::new(code).await?;
Ok(organize_fields(result, Make::new(mode)))
}
/// Generates a new `Hentai` object from the doujin JSON located at the provided file path.
///
/// This may fail if there is not a file at the provided path
/// ([std::io::Error](https://doc.rust-lang.org/std/io/struct.Error.html)), or if the contents
/// of the file are invalid
/// ([serde_json::Error](https://docs.serde.rs/serde_json/struct.Error.html)).
///
/// The sample below assumes that you have a valid doujin JSON file called
/// [sample.json](https://github.com/EmperorParzival/hentai/blob/main/src/cli/sample.json) in
/// the same directory as the executable.
/// ```no_run
/// use hentai::{Hentai, Result, Website};
/// use std::env;
///
/// fn main() -> Result<()> {
/// let mut path = env::current_exe()?;
/// path.pop();
/// path.push("sample.json");
///
/// let response = Hentai::from_json(path, Website::XXX)?;
/// println!("{:?}", response);
///
/// Ok(())
/// }
/// ```
pub fn from_json(path: PathBuf, mode: Website) -> Result<Self> {
let result = Doujin::from_json(path)?;
Ok(organize_fields(result, Make::new(mode)))
}
}