#![crate_name = "tanoshi_lib"]
pub static CORE_VERSION: &str = env!("CARGO_PKG_VERSION");
pub static RUSTC_VERSION: &str = env!("RUSTC_VERSION");
#[cfg(feature = "model")]
pub mod manga {
use chrono::Local;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct SourceLogin {
pub username: String,
pub password: String,
pub remember_me: Option<bool>,
pub two_factor: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct SourceLoginResult {
pub source_id: i32,
pub source_name: String,
pub auth_type: String,
pub value: String,
}
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct Source {
pub id: i32,
pub name: String,
pub url: String,
pub version: String,
}
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct Manga {
pub id: i32,
pub title: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub author: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub genre: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub path: String,
pub thumbnail_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_read: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_page: Option<i32>,
pub is_favorite: bool,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Chapter {
pub id: i32,
pub manga_id: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub vol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub no: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub read: Option<i32>,
pub uploaded: chrono::NaiveDateTime,
}
impl Default for Chapter {
fn default() -> Self {
Chapter {
id: 0,
manga_id: 0,
vol: None,
no: None,
title: None,
url: "".to_string(),
read: None,
uploaded: chrono::NaiveDateTime::from_timestamp(0, 0),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Image {
pub source_id: i32,
pub source_name: String,
pub path: String,
pub file_name: String,
pub url: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub enum SortByParam {
LastUpdated,
Title,
Comment,
Views,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub enum SortOrderParam {
Asc,
Desc,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Params {
pub keyword: Option<String>,
pub genres: Option<Vec<String>>,
pub page: Option<String>,
pub sort_by: Option<SortByParam>,
pub sort_order: Option<SortOrderParam>,
pub refresh: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GetParams {
pub refresh: Option<bool>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct History {
pub manga_id: i32,
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumbnail_url: Option<String>,
pub chapter: String,
pub chapter_id: i32,
pub read: i32,
pub at: chrono::NaiveDateTime,
#[serde(skip_serializing_if = "Option::is_none")]
pub days: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub show_sep: Option<bool>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Update {
pub manga_id: i32,
pub title: String,
pub thumbnail_url: String,
pub number: String,
pub chapter_id: i32,
pub uploaded: chrono::NaiveDateTime,
pub days: Option<i64>,
pub show_sep: Option<bool>,
}
impl Default for Update {
fn default() -> Self {
Update {
manga_id: 0,
title: "".to_string(),
thumbnail_url: "".to_string(),
number: "".to_string(),
chapter_id: 0,
uploaded: Local::now().naive_local(),
days: None,
show_sep: None,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct FavoriteManga {
pub manga_id: i32,
pub title: String,
pub path: String,
pub thumbnail_url: String,
}
}
#[cfg(feature = "rest")]
pub mod rest {
use crate::manga::{Chapter, FavoriteManga, History, Manga, Source, Update};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GetSourceResponse {
pub sources: Vec<Source>,
pub status: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GetMangasResponse {
pub mangas: Vec<Manga>,
pub status: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GetMangaResponse {
pub manga: Manga,
pub status: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GetChaptersResponse {
pub chapters: Vec<Chapter>,
pub status: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GetPagesResponse {
pub manga_id: i32,
pub pages: Vec<String>,
pub status: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct HistoryRequest {
pub chapter_id: i32,
pub read: i32,
pub at: chrono::NaiveDateTime,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct HistoryResponse {
pub history: Vec<History>,
pub status: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpdatesResponse {
pub updates: Vec<Update>,
pub status: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GetFavoritesResponse {
pub favorites: Option<Vec<FavoriteManga>>,
pub status: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AddFavoritesResponse {
pub status: String,
}
}
#[cfg(feature = "extensions")]
pub mod extensions {
use super::{tanoshi_cache_dir, tanoshi_dir};
use crate::manga::{Chapter, Image, Manga, Params, Source, SourceLogin, SourceLoginResult};
use anyhow::{anyhow, Result};
use serde_yaml;
use std::io::Read;
pub trait Extension: Send + Sync {
fn info(&self) -> Source;
fn get_mangas(
&self,
url: &String,
param: Params,
refresh: bool,
auth: String,
) -> Result<Vec<Manga>>;
fn get_manga_info(&self, url: &String, refresh: bool) -> Result<Manga>;
fn get_chapters(&self, url: &String, refresh: bool) -> Result<Vec<Chapter>>;
fn get_pages(&self, url: &String, refresh: bool) -> Result<Vec<String>>;
fn get_page(&self, image: Image, refresh: bool) -> Result<Vec<u8>> {
let cache_path = tanoshi_cache_dir!(image.path);
let image_path = cache_path.join(&image.file_name);
if refresh {
let _ = std::fs::remove_file(&image_path);
}
let bytes = match std::fs::read(&image_path) {
Ok(data) => data,
Err(_) => {
let resp = ureq::get(&image.url).call();
let mut reader = resp.into_reader();
let mut bytes = vec![];
if reader.read_to_end(&mut bytes).is_err() {
return Err(anyhow!("error write image"));
}
if std::fs::create_dir_all(&cache_path).is_ok() {
if std::fs::write(&image_path, &bytes).is_err() {
return Err(anyhow!("error write image"));
}
}
bytes
}
};
Ok(bytes)
}
fn login(&self, _: SourceLogin) -> Result<SourceLoginResult> {
Err(anyhow!("not implemented"))
}
}
#[macro_export]
macro_rules! tanoshi_dir {
($($path:expr),*) => {
{
let mut dir = dirs::home_dir().expect("should have home dir").join(".tanoshi");
$(
dir = dir.join($path);
)*
dir
}
};
}
#[macro_export]
macro_rules! tanoshi_cache_dir {
($($path:expr),*) => {
{
let mut dir = tanoshi_dir!("cache");
$(
dir = dir.join($path);
)*
dir
}
};
}
#[macro_export]
macro_rules! tanoshi_plugin_dir {
($($path:expr),*) => {
{
let mut dir = tanoshi_dir!("plugins");
$(
dir = dir.join($path);
)*
dir
}
};
}
pub struct PluginDeclaration {
pub rustc_version: &'static str,
pub core_version: &'static str,
pub register: unsafe extern "C" fn(&mut dyn PluginRegistrar, Option<&serde_yaml::Value>),
}
pub trait PluginRegistrar {
fn register_function(&mut self, name: &str, extension: Box<dyn Extension>);
}
#[macro_export]
macro_rules! export_plugin {
($register:expr) => {
#[doc(hidden)]
#[no_mangle]
pub static plugin_declaration: $crate::extensions::PluginDeclaration =
$crate::extensions::PluginDeclaration {
rustc_version: $crate::RUSTC_VERSION,
core_version: $crate::CORE_VERSION,
register: $register,
};
};
}
}