tosho_rbean/models/
chapters.rs

1//! A module containing information related to chapters.
2//!
3//! If something is missing, please [open an issue](https://github.com/noaione/tosho-mango/issues/new/choose) or a [pull request](https://github.com/noaione/tosho-mango/compare).
4
5use std::collections::HashMap;
6
7use serde::{Deserialize, Serialize};
8use tosho_macros::AutoGetter;
9
10use super::{MangaNode, Volume};
11
12/// A minimal model for chapter information.
13///
14/// Commonly used in [`crate::models::Carousel`]
15#[derive(Debug, Clone, AutoGetter, Serialize, Deserialize)]
16pub struct ChapterListNode {
17    /// The UUID of the chapter.
18    uuid: String,
19    /// The chapter number/label.
20    #[serde(rename = "label")]
21    chapter: String,
22    /// Is this a new chapter?
23    #[serde(rename = "is_new")]
24    new: bool,
25    /// Is this an upcoming chapter?
26    #[serde(rename = "is_upcoming")]
27    upcoming: bool,
28    /// Is this a premium chapter?
29    #[serde(rename = "is_premium")]
30    premium: bool,
31}
32
33/// A struct containing information about a chapter.
34#[derive(Debug, Clone, AutoGetter, Serialize, Deserialize)]
35pub struct Chapter {
36    /// The UUID of the chapter.
37    uuid: String,
38    /// The chapter number/label.
39    #[serde(rename = "label")]
40    chapter: String,
41    /// The title of the chapter.
42    title: Option<String>,
43    /// The release date of the chapter.
44    #[serde(
45        rename = "release_date",
46        serialize_with = "super::datetime::serialize_opt",
47        deserialize_with = "super::datetime::deserialize_opt"
48    )]
49    #[copyable]
50    published: Option<chrono::DateTime<chrono::FixedOffset>>,
51    /// The free release date of the chapter.
52    #[serde(
53        rename = "free_release_date",
54        serialize_with = "super::datetime::serialize_opt",
55        deserialize_with = "super::datetime::deserialize_opt"
56    )]
57    #[copyable]
58    free_published: Option<chrono::DateTime<chrono::FixedOffset>>,
59    /// The original published date of the chapter.
60    #[serde(
61        rename = "original_published_date",
62        serialize_with = "super::datetime::serialize_opt",
63        deserialize_with = "super::datetime::deserialize_opt"
64    )]
65    original_published: Option<chrono::DateTime<chrono::FixedOffset>>,
66    /// Is this a new chapter?
67    #[serde(rename = "is_new")]
68    new: bool,
69    /// Is this an upcoming chapter?
70    #[serde(rename = "is_upcoming")]
71    upcoming: bool,
72    /// Is this a premium chapter?
73    #[serde(rename = "is_premium")]
74    premium: bool,
75    /// Last updated date of the chapter.
76    #[serde(
77        rename = "last_updated_at",
78        serialize_with = "super::datetime::serialize_opt",
79        deserialize_with = "super::datetime::deserialize_opt"
80    )]
81    #[copyable]
82    last_updated: Option<chrono::DateTime<chrono::FixedOffset>>,
83    /// Volume UUID of the chapter.
84    volume_uuid: Option<String>,
85}
86
87impl Chapter {
88    /// Get a formatted chapter number with the title.
89    pub fn formatted_title(&self) -> String {
90        let title = self.title.as_deref().unwrap_or("");
91        if title.is_empty() {
92            format!("Chapter {}", self.chapter)
93        } else {
94            format!("Chapter {} - {}", self.chapter, title)
95        }
96    }
97}
98
99/// A chapter detail response.
100#[derive(Debug, Clone, AutoGetter, Serialize, Deserialize)]
101pub struct ChapterDetailsResponse {
102    /// The chapter information.
103    chapter: Chapter,
104    /// The manga information.
105    manga: MangaNode,
106}
107
108/// A chapter list response for a manga.
109#[derive(Debug, Clone, AutoGetter, Serialize, Deserialize)]
110pub struct ChapterListResponse {
111    /// The chapters of the manga.
112    chapters: Vec<Chapter>,
113    /// The volume mapping of the chapters.
114    ///
115    /// This map the `volume_uuid` to the [`Volume`] information.
116    #[serde(rename = "volume_uuid_to_volume")]
117    volumes: HashMap<String, Volume>,
118    /// The separators of the chapters.
119    separators: Vec<super::common::Separator>,
120    /// The volume UUID sort order
121    #[serde(rename = "volume_uuid_order")]
122    volume_order: Vec<String>,
123}
124
125/// A single spread of a chapter.
126///
127/// If one of them is [`None`], then it's a single page only (should not be a spread).
128pub type Spread = (Option<i32>, Option<i32>);
129
130/// A struct containing a single page information of a chapter.
131#[derive(Debug, Clone, AutoGetter, Serialize, Deserialize)]
132pub struct ChapterPage {
133    /// The UUID of the page.
134    uuid: String,
135    /// The image of the page.
136    image: super::Image,
137    /// The watermarked image of the page.
138    #[serde(rename = "image_wm")]
139    watermarked_image: super::Image,
140    /// Is double page?
141    #[serde(rename = "is_double_page")]
142    double_page: bool,
143    /// The index of the spread info.
144    ///
145    /// This is a tuple value of `(left, right)` from [`ChapterPageDetails`].
146    #[serde(rename = "spread_index")]
147    spread: i32,
148    /// The side of the page in the spread.
149    ///
150    /// Either `left` or `right`, you should realize that you need to reverse the side order if
151    /// using right-to-left reading mode.
152    side: String,
153}
154
155/// A struct containing information about a chapter pages.
156#[derive(Debug, Clone, AutoGetter, Serialize, Deserialize)]
157pub struct ChapterPageDetails {
158    /// Spreads information mapping.
159    spreads: Vec<Spread>,
160    /// The pages of the chapter.
161    pages: Vec<ChapterPage>,
162}
163
164/// A response for chapter pages.
165#[derive(Debug, Clone, AutoGetter, Serialize, Deserialize)]
166pub struct ChapterPageDetailsResponse {
167    /// The pages information.
168    data: ChapterPageDetails,
169}
170
171#[cfg(test)]
172mod tests {
173    #[test]
174    fn test_spreads_unpack() {
175        let json_test = r#"{
176            "spreads": [
177                [null, 0],
178                [1, 2],
179                [3, 4],
180                [5, null]
181            ],
182            "pages": []
183        }"#;
184
185        let spreads: super::ChapterPageDetails = serde_json::from_str(json_test).unwrap();
186        assert_eq!(spreads.spreads.len(), 4);
187        assert_eq!(spreads.spreads[0], (None, Some(0)));
188        assert_eq!(spreads.spreads[1], (Some(1), Some(2)));
189        assert_eq!(spreads.spreads[2], (Some(3), Some(4)));
190        assert_eq!(spreads.spreads[3], (Some(5), None));
191    }
192}