use serde::{Deserialize, Serialize};
use crate::{
directory_list::DirectoryListChapterItem, recent_chapter::RecentChapterItem, tag::TagItem,
DynastyReaderRoute, DYNASTY_READER_BASE,
};
use self::utils::chapter_name_to_permalink;
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChapterConfig {
pub name: String,
}
impl From<String> for ChapterConfig {
fn from(s: String) -> Self {
ChapterConfig { name: s }
}
}
impl From<DirectoryListChapterItem> for ChapterConfig {
fn from(item: DirectoryListChapterItem) -> Self {
ChapterConfig { name: item.title }
}
}
impl From<RecentChapterItem> for ChapterConfig {
fn from(item: RecentChapterItem) -> Self {
ChapterConfig { name: item.title }
}
}
#[cfg(feature = "search")]
impl TryFrom<crate::search::SearchItem> for ChapterConfig {
type Error = anyhow::Error;
fn try_from(value: crate::search::SearchItem) -> Result<Self, Self::Error> {
if matches!(value.kind, crate::search::SearchCategory::Chapter) {
Ok(ChapterConfig { name: value.title })
} else {
Err(anyhow::anyhow!("this search item is not a chapter"))
}
}
}
impl DynastyReaderRoute for ChapterConfig {
fn request_builder(
&self,
client: &reqwest::Client,
url: reqwest::Url,
) -> reqwest::RequestBuilder {
client.get(url)
}
fn request_url(&self) -> reqwest::Url {
let permalink = chapter_name_to_permalink(&self.name);
DYNASTY_READER_BASE
.join(&format!("chapters/{}.json", permalink))
.unwrap()
}
}
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Chapter {
pub title: String,
pub long_title: String,
pub permalink: String,
pub released_on: String,
pub added_on: String,
pub tags: Vec<TagItem>,
pub pages: Vec<ChapterPage>,
}
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct ChapterPage {
pub name: String,
#[serde(deserialize_with = "crate::utils::join_path_with_dynasty_reader_base")]
pub url: String,
}
mod utils {
use lazy_regex::regex;
use crate::utils::name_to_permalink;
pub(super) fn chapter_name_to_permalink(chapter_name: &str) -> String {
let chapter_re = regex!(r"(ch[\d.]+):");
let name = chapter_re
.find(chapter_name)
.map(|matches| &chapter_name[..matches.end()])
.unwrap_or(chapter_name);
name_to_permalink(name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_convert_chapter_name_to_permalink() {
let predicates = [
("\"Toda-san,\"", "toda_san"),
("B.G.M.R.S.P.", "b_g_m_r_s_p"),
("Assorted NicoMaki (03015)", "assorted_nicomaki_03015"),
(
"Adachi and Shimamura (Moke ver.) ch27.1: Leaving Azure",
"adachi_and_shimamura_moke_ver_ch27_1",
),
(
"Love Live! Comic Anthology μ’s Precious Days ch01",
"love_live_comic_anthology_μs_precious_days_ch01",
),
(
"a_story_about_doing_xx_to_girls_from_different_species_ch51",
"a_story_about_doing_xx_to_girls_from_different_species_ch51",
),
];
for (left, right) in predicates {
assert_eq!(chapter_name_to_permalink(left), right)
}
}
}
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use crate::test_utils::tryhard_configs;
use super::*;
fn create_config(c: &str) -> ChapterConfig {
c.to_string().into()
}
#[tokio::test]
#[ignore = "requires internet"]
async fn response_structure() -> Result<()> {
let configs = [
"4-Koma Starlight ch01",
"4-Koma Starlight ch01: Act 1: Nice To Meet You!",
"4_koma_starlight_ch01",
"Just ChisaTaki Kissing",
"just_chisataki_kissing",
]
.map(create_config);
tryhard_configs(configs, |client, config| client.chapter(config)).await?;
Ok(())
}
}