diff --git a/src/download.rs b/src/download.rs index 306a3ff..64d38d5 100644 --- a/src/download.rs +++ b/src/download.rs @@ -259,6 +259,9 @@ pub async fn download_artwork( let app_ok = helper.refresh_token().is_some(); if app_ok && helper.use_app_api() { if let Err(e) = download_artwork_app(ac, pw.clone(), id).await { + if e.is_not_found() { + return Err(e); + } println!("{}{}", gettext("Warning: Failed to download artwork with app api, trying to download with web api: "), e); download_artwork_web(pw.clone(), id).await?; } diff --git a/src/error.rs b/src/error.rs index b3f906e..acf76d2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,6 +28,7 @@ pub enum PixivDownloaderError { OpenSSLError(openssl::error::ErrorStack), ParseIntError(std::num::ParseIntError), ReqwestError(reqwest::Error), + PixivAppError(crate::pixivapp::error::PixivAppError), } impl std::error::Error for PixivDownloaderError {} @@ -44,6 +45,15 @@ impl From for PixivDownloaderError { } } +impl PixivDownloaderError { + pub fn is_not_found(&self) -> bool { + match self { + Self::PixivAppError(e) => e.is_not_found(), + _ => false, + } + } +} + #[macro_export] macro_rules! concat_pixiv_downloader_error { ($exp1:expr, $exp2:expr) => { diff --git a/src/main.rs b/src/main.rs index 2c0e2fd..3699731 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,6 +41,7 @@ mod parser; mod pixiv_app; mod pixiv_link; mod pixiv_web; +mod pixivapp; mod push; mod retry_interval; #[cfg(feature = "server")] diff --git a/src/pixiv_app.rs b/src/pixiv_app.rs index 199372a..2a4cc99 100644 --- a/src/pixiv_app.rs +++ b/src/pixiv_app.rs @@ -5,6 +5,7 @@ use crate::ext::atomic::AtomicQuick; use crate::ext::replace::ReplaceWith2; use crate::ext::rw_lock::GetRwLock; use crate::opthelper::OptHelper; +use crate::pixivapp::error::handle_error; use crate::webclient::{ReqMiddleware, WebClient}; use crate::{get_helper, gettext}; use chrono::{DateTime, Local, SecondsFormat, Utc}; @@ -69,6 +70,7 @@ pub struct PixivAppClientInternal { } impl PixivAppClientInternal { + #[cfg(not(feature = "db"))] pub fn new() -> Self { Self { client: WebClient::default(), @@ -247,8 +249,7 @@ impl PixivAppClientInternal { ) .await .ok_or(gettext("Failed to get illust details."))?; - let status = re.status(); - let obj = json::parse(&re.text().await?)?; + let obj = handle_error(re).await?; if get_helper().verbose() { println!("{}{}", gettext("Illust details:"), obj.pretty(2).as_str()); } @@ -264,6 +265,7 @@ pub struct PixivAppClient { } impl PixivAppClient { + #[cfg(not(feature = "db"))] pub fn new() -> Self { let r = Self { internal: Arc::new(PixivAppClientInternal::new()), diff --git a/src/pixivapp/check.rs b/src/pixivapp/check.rs new file mode 100644 index 0000000..164a031 --- /dev/null +++ b/src/pixivapp/check.rs @@ -0,0 +1,4 @@ +/// Check if have data that we don't handle +pub trait CheckUnknown { + fn check_unknown(&self) -> Result<(), String>; +} diff --git a/src/pixivapp/error.rs b/src/pixivapp/error.rs new file mode 100644 index 0000000..016e435 --- /dev/null +++ b/src/pixivapp/error.rs @@ -0,0 +1,150 @@ +use super::check::CheckUnknown; +use crate::error::PixivDownloaderError; +use json::JsonValue; +use proc_macros::check_json_keys; +use reqwest::{Response, StatusCode}; + +#[derive(Debug)] +pub struct PixivAppHTTPError { + status: StatusCode, +} + +impl PixivAppHTTPError { + pub fn new(status: StatusCode) -> Self { + Self { status } + } +} + +impl std::fmt::Display for PixivAppHTTPError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} {}", + self.status.as_u16(), + self.status.canonical_reason().unwrap_or("") + ) + } +} + +pub struct PixivAppAPIError { + data: JsonValue, + status: StatusCode, +} + +impl CheckUnknown for PixivAppAPIError { + fn check_unknown(&self) -> Result<(), String> { + check_json_keys!( + "user_message"+, + "message"+, + "reason"+, + "user_message_details" + ); + Ok(()) + } +} + +impl PixivAppAPIError { + pub fn new(data: JsonValue, status: StatusCode) -> Self { + Self { data, status } + } + + pub fn user_message(&self) -> Option<&str> { + self.data["user_message"].as_str() + } + + pub fn message(&self) -> Option<&str> { + self.data["message"].as_str() + } + + pub fn reason(&self) -> Option<&str> { + self.data["reason"].as_str() + } + + pub fn user_message_details(&self) -> &JsonValue { + &self.data["user_message_details"] + } +} + +impl std::error::Error for PixivAppAPIError {} + +impl std::fmt::Debug for PixivAppAPIError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PixivAppAPIError") + .field("user_message", &self.user_message()) + .field("message", &self.message()) + .field("reason", &self.reason()) + .field("user_message_details", &self.user_message_details()) + .field("status", &self.status) + .finish_non_exhaustive() + } +} + +impl std::fmt::Display for PixivAppAPIError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(user_message) = self.user_message() { + write!( + f, + "{} ({} {})", + user_message, + self.status.as_u16(), + self.status.canonical_reason().unwrap_or("") + ) + } else if let Some(message) = self.message() { + write!(f, "{} (", message)?; + if let Some(reason) = self.reason() { + write!(f, "{}, ", reason)?; + } + write!( + f, + "{} {})", + self.status.as_u16(), + self.status.canonical_reason().unwrap_or("") + ) + } else { + write!( + f, + "{} {}: ", + self.status.as_u16(), + self.status.canonical_reason().unwrap_or("") + )?; + write!(f, "{}", self.data.pretty(2).as_str()) + } + } +} + +#[derive(Debug, derive_more::From, derive_more::Display)] +pub enum PixivAppError { + HTTP(PixivAppHTTPError), + API(PixivAppAPIError), +} + +impl PixivAppError { + pub fn is_not_found(&self) -> bool { + match self { + Self::HTTP(err) => err.status.as_u16() == 404, + Self::API(err) => err.status.as_u16() == 404, + } + } +} + +pub async fn handle_error(res: Response) -> Result { + let status = res.status(); + if let Ok(obj) = json::parse(&res.text().await?) { + let err = &obj["error"]; + if status.is_success() && err.is_null() { + Ok(obj) + } else if !err.is_null() { + Err(PixivDownloaderError::from(PixivAppError::from( + PixivAppAPIError::new(err.clone(), status), + ))) + } else { + Err(PixivDownloaderError::from(PixivAppError::from( + PixivAppHTTPError::new(status), + ))) + } + } else { + Err(PixivDownloaderError::from(PixivAppError::from( + PixivAppHTTPError::new(status), + ))) + } +} diff --git a/src/pixivapp/mod.rs b/src/pixivapp/mod.rs new file mode 100644 index 0000000..3d3c4ff --- /dev/null +++ b/src/pixivapp/mod.rs @@ -0,0 +1,3 @@ +pub mod check; +/// Error handling for pixiv app api +pub mod error;