mangadexrs/src/structs.rs

174 lines
6.6 KiB
Rust

use std::io;
use std::fmt;
use std::marker::PhantomData;
use std::collections::BTreeMap;
use regex::Regex;
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer};
extern crate reqwest;
extern crate serde_json;
const TAGS_AND_TYPE: &[(&str, &str)] = &[("4-Koma", "Format"), ("Action", "Genre"), ("Adventure", "Genre"), ("Award Winning", "Format"), ("Comedy", "Genre"), ("Cooking", "Theme"), ("Doujinshi", "Format"), ("Drama", "Genre"), ("Ecchi", "Content"), ("Fantasy", "Genre"), ("Gyaru", "Theme"), ("Harem", "Theme"), ("Historical", "Genre"), ("Horror", "Genre"), ("Martial Arts", "Theme"), ("Mecha", "Genre"), ("Medical", "Genre"), ("Music", "Theme"), ("Mystery", "Genre"), ("Oneshot", "Format"), ("Psychological", "Genre"), ("Romance", "Genre"), ("School Life", "Theme"), ("Sci-Fi", "Genre"), ("Shoujo Ai", "Genre"), ("Shounen Ai", "Genre"), ("Slice of Life", "Genre"), ("Smut", "Content"), ("Sports", "Genre"), ("Supernatural", "Theme"), ("Tragedy", "Genre"), ("Long Strip", "Format"), ("Yaoi", "Genre"), ("Yuri", "Genre"), ("Video Games", "Theme"), ("Isekai", "Genre"), ("Adaptation", "Format"), ("Anthology", "Format"), ("Web Comic", "Format"), ("Full Color", "Format"), ("User Created", "Format"), ("Official Colored", "Format"), ("Fan Colored", "Format"), ("Gore", "Content"), ("Sexual Violence", "Content"), ("Crime", "Genre"), ("Magical Girls", "Genre"), ("Philosophical", "Genre"), ("Superhero", "Genre"), ("Thriller", "Genre"), ("Wuxia", "Genre"), ("Aliens", "Theme"), ("Animals", "Theme"), ("Crossdressing", "Theme"), ("Demons", "Theme"), ("Delinquents", "Theme"), ("Genderswap", "Theme"), ("Ghosts", "Theme"), ("Monster Girls", "Theme"), ("Loli", "Theme"), ("Magic", "Theme"), ("Military", "Theme"), ("Monsters", "Theme"), ("Ninja", "Theme"), ("Office Workers", "Theme"), ("Police", "Theme"), ("Post-Apocalyptic", "Theme"), ("Reincarnation", "Theme"), ("Reverse Harem", "Theme"), ("Samurai", "Theme"), ("Shota", "Theme"), ("Survival", "Theme"), ("Time Travel", "Theme"), ("Vampires", "Theme"), ("Traditional Games", "Theme"), ("Virtual Reality", "Theme"), ("Zombies", "Theme"), ("Incest", "Theme"), ("Mafia", "Theme"), ("Villainess", "Theme")];
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct MangaInfo {
pub id: i32,
pub title: String,
#[serde(deserialize_with = "sanitize_description")]
pub description: String,
pub artist: Vec<String>,
pub author: Vec<String>,
pub tags: Vec<usize>,
pub views: usize,
pub last_chapter: Option<String>
}
#[derive(Deserialize, Debug)]
pub struct MangaData {
pub manga: MangaInfo,
pub chapters: Vec<MiniChapterData>
}
#[derive(Deserialize, Debug)]
pub struct Manga {
pub data: MangaData
}
#[derive(Deserialize, Debug)]
pub struct MiniChapterData {
pub id: i32,
pub chapter: String,
#[serde(default)]
pub volume: String,
pub title: String,
pub language: String
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ChapterData {
pub id: i32,
pub manga_title: String,
pub chapter: String,
pub hash: String,
pub pages: Vec<String>,
pub server: String,
#[serde(default)]
pub server_fallback: String
}
#[derive(Deserialize, Debug)]
pub struct Chapter {
pub data: ChapterData
}
impl fmt::Display for MangaInfo {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut text = format!("ID: {}\nTitle: {}\nViews: {}\n", self.id, &self.title, self.views);
if !self.artist.is_empty() {
text.push_str(&format!("Artists: {}\n", &self.artist.join(", ")));
}
if !self.author.is_empty() {
text.push_str(&format!("Authors: {}\n", &self.author.join(", ")));
}
if !self.tags.is_empty() {
let mut treemap = BTreeMap::new();
for tag_id in &self.tags {
let (tag_name, tag_group) = match TAGS_AND_TYPE.get(tag_id - 1) {
Some(&(i, j)) => (i.to_string(), j),
None => (tag_id.to_string(), "Unknown")
};
treemap.entry(tag_group.clone()).or_insert_with(|| Vec::new()).push(tag_name);
}
for (tag_key, tag_value) in treemap {
text.push_str(&format!("{}: {}\n", tag_key, tag_value.join(", ")));
}
}
write!(formatter, "{}Description:\n{}", &text, &self.description)
}
}
impl fmt::Display for MiniChapterData {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut text = format!("{}: ", self.id);
if !self.volume.is_empty() {
text.push_str(&format!("Volume {}: ", &self.volume));
}
text.push_str(&format!("Chapter {}", &self.chapter));
if !self.title.is_empty() {
text.push_str(&format!(": {}", &self.title));
}
write!(formatter, "{} [{}]", &text, &self.language)
}
}
fn sanitize_description<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>
{
struct SanitizeDescription<T>(PhantomData<fn() -> T>);
impl<'de> Visitor<'de> for SanitizeDescription<String>
{
type Value = String;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("unknown error with sanitize_description idfk lmao")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error
{
Ok(match Regex::new(r"\[\w+\].+\[/\w+\]") {
Ok(regex) => regex.replace_all(&value, "").trim().to_string(),
Err(_) => value.to_string()
})
}
}
deserializer.deserialize_any(SanitizeDescription(PhantomData))
}
#[derive(Debug)]
pub enum Error {
IO(io::Error),
Reqwest(reqwest::Error),
SerdeJSON(serde_json::Error),
}
impl From<io::Error> for Error {
#[inline]
fn from(error: io::Error) -> Error {
Error::IO(error)
}
}
impl From<reqwest::Error> for Error {
#[inline]
fn from(error: reqwest::Error) -> Error {
Error::Reqwest(error)
}
}
impl From<serde_json::Error> for Error {
#[inline]
fn from(error: serde_json::Error) -> Error {
Error::SerdeJSON(error)
}
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(
&match self {
Error::IO(err) => format!("io error: {}", err),
Error::Reqwest(err) => format!("reqwest error: {}", err),
Error::SerdeJSON(err) => format!("serde_json error: {}", err),
}
)
}
}