diff --git a/src/data/fanbox.rs b/src/data/fanbox.rs index b0c79a9..44c1361 100644 --- a/src/data/fanbox.rs +++ b/src/data/fanbox.rs @@ -1,6 +1,6 @@ #[cfg(feature = "exif")] use super::exif::ExifDataSource; -use crate::fanbox::post::FanboxPost; +use crate::ext::json::ToJson2; use crate::opt::author_name_filter::AuthorFiler; use crate::opthelper::get_helper; use crate::pixiv_link::PixivID; @@ -18,11 +18,11 @@ pub struct FanboxData { } impl FanboxData { - pub fn new(id: T, post: &FanboxPost) -> Option { + pub fn new(id: T, data: D) -> Option { match id.to_pixiv_id() { Some(id) => Some(Self { id, - raw: post.get_json().clone(), + raw: data.to_json2(), #[cfg(feature = "exif")] exif_data: None, }), diff --git a/src/download.rs b/src/download.rs index 6dd209f..07fdb7c 100644 --- a/src/download.rs +++ b/src/download.rs @@ -17,6 +17,8 @@ use crate::ext::any::AsAny; use crate::ext::try_err::TryErr; use crate::fanbox::article::block::FanboxArticleBlock; use crate::fanbox::check::CheckUnknown; +use crate::fanbox::creator::FanboxCreator; +use crate::fanbox::creator::FanboxProfileItem; use crate::fanbox::post::FanboxPost; use crate::fanbox_api::FanboxClient; use crate::gettext; @@ -34,6 +36,7 @@ use indicatif::MultiProgress; use json::JsonValue; use reqwest::IntoUrl; use std::fs::create_dir_all; +use std::ops::Deref; use std::path::PathBuf; use std::sync::Arc; use tokio::task::JoinHandle; @@ -87,6 +90,31 @@ impl Main { tasks.join().await; } } + PixivID::FanboxCreator(id) => { + if !fc.is_inited() { + let helper = get_helper(); + if !fc.init(helper.cookies()) { + println!("{}", gettext("Failed to initialize fanbox api client.")); + return 1; + } + if !fc.check_login().await { + return 1; + } + if !fc.logined() { + println!("{}", gettext("Warning: Fanbox client is not logged in.")); + } + } + tasks + .add_task(download_fanbox_creator_info( + Arc::clone(&fc), + id.to_owned(), + None, + )) + .await; + if !download_multiple_posts { + tasks.join().await; + } + } } } let mut re = 0; @@ -679,3 +707,121 @@ pub async fn download_fanbox_post( } re } + +pub async fn download_fanbox_creator_info( + fc: Arc, + id: String, + data: Option, +) -> Result<(), PixivDownloaderError> { + let data = match data { + Some(data) => { + let cid = data + .creator_id() + .try_err(gettext("Failed to get creator's id."))?; + if id == cid { + Some(data) + } else { + None + } + } + None => None, + }; + let data = match data { + Some(data) => data, + None => fc + .get_creator(&id) + .await + .try_err(gettext("Failed to get creator's information."))?, + }; + let data = Arc::new(data); + let helper = get_helper(); + if helper.verbose() { + println!("{:#?}", data); + } + match data.check_unknown() { + Ok(_) => {} + Err(e) => { + println!( + "{} {}", + gettext("Warning: Creator's info contains unknown data:"), + e + ); + return Ok(()); + } + } + let mut fdata = FanboxData::new(PixivID::FanboxCreator(id.clone()), &*data) + .try_err("Failed to create data file.")?; + let base = Arc::new(PathBuf::from(".").join(&id)); + let json_file = base.join("creator.json"); + let data_file = JSONDataFile::from(&fdata); + data_file + .save(&json_file) + .try_err(gettext("Failed to save post data to file."))?; + let tasks = TaskManager::default(); + fdata.exif_data.replace(Box::new(Arc::clone(&data))); + let fdata = Arc::new(fdata); + let download_multiple_files = helper.download_multiple_files(); + let mut np = 0u16; + { + match data.download_cover_image_url()? { + Some(dh) => { + tasks + .add_task(download_fanbox_image( + dh, + np, + Some(get_progress_bar()), + Arc::clone(&fdata), + Arc::clone(&base), + )) + .await; + if !download_multiple_files { + tasks.join().await; + } + np += 1; + } + None => {} + } + } + for i in data.profile_items()?.deref() { + match i { + FanboxProfileItem::Image(img) => { + let dh = img + .download_image_url()? + .try_err(gettext("Can not get image url."))?; + tasks + .add_task(download_fanbox_image( + dh, + np, + Some(get_progress_bar()), + Arc::clone(&fdata), + Arc::clone(&base), + )) + .await; + if !download_multiple_files { + tasks.join().await; + } + np += 1; + } + FanboxProfileItem::Unknown(_) => { + return Err(PixivDownloaderError::from(gettext( + "Unrecognized profile item type.", + ))); + } + } + } + tasks.join().await; + let mut re = Ok(()); + let tasks = tasks.take_finished_tasks(); + for mut task in tasks { + let task = task.as_any_mut(); + if let Some(task) = task.downcast_mut::>>() { + let r = task.await; + let r = match r { + Ok(r) => r, + Err(e) => Err(PixivDownloaderError::from(e)), + }; + concat_pixiv_downloader_error!(re, r); + } + } + re +} diff --git a/src/downloader/helper.rs b/src/downloader/helper.rs index 8f9701a..c16574a 100644 --- a/src/downloader/helper.rs +++ b/src/downloader/helper.rs @@ -29,6 +29,7 @@ pub struct DownloaderHelperBuilder { helper: DownloaderHelper, } +#[allow(dead_code)] impl DownloaderHelper { pub fn builder(url: U) -> Result { Ok(DownloaderHelperBuilder { @@ -110,6 +111,7 @@ impl DownloaderHelper { } } +#[allow(dead_code)] impl DownloaderHelperBuilder { pub fn build(self) -> DownloaderHelper { self.helper diff --git a/src/error.rs b/src/error.rs index adebe9e..c7ab86a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,6 +14,7 @@ pub enum PixivDownloaderError { Hyper(hyper::Error), HTTP(http::Error), IOError(std::io::Error), + Fanbox(crate::fanbox::error::FanboxAPIError), } impl From<&str> for PixivDownloaderError { diff --git a/src/ext/json.rs b/src/ext/json.rs index 43d2d8b..2e23fe1 100644 --- a/src/ext/json.rs +++ b/src/ext/json.rs @@ -7,30 +7,58 @@ pub trait ToJson { fn to_json(&self) -> Option; } +pub trait ToJson2 { + fn to_json2(&self) -> JsonValue; +} + impl ToJson for &str { fn to_json(&self) -> Option { Some(JsonValue::String(String::from(*self))) } } +impl ToJson2 for &str { + fn to_json2(&self) -> JsonValue { + JsonValue::String(String::from(*self)) + } +} + impl ToJson for String { fn to_json(&self) -> Option { Some(JsonValue::String(self.to_string())) } } +impl ToJson2 for String { + fn to_json2(&self) -> JsonValue { + JsonValue::String(self.to_string()) + } +} + impl ToJson for JsonValue { fn to_json(&self) -> Option { Some(self.clone()) } } +impl ToJson2 for JsonValue { + fn to_json2(&self) -> JsonValue { + self.clone() + } +} + impl ToJson for &T { fn to_json(&self) -> Option { (*self).to_json() } } +impl ToJson2 for &T { + fn to_json2(&self) -> JsonValue { + (*self).to_json2() + } +} + impl ToJson for Option { fn to_json(&self) -> Option { match self { @@ -46,12 +74,24 @@ impl ToJson for RwLockReadGuard<'_, T> { } } +impl ToJson2 for RwLockReadGuard<'_, T> { + fn to_json2(&self) -> JsonValue { + self.deref().to_json2() + } +} + impl ToJson for RwLockWriteGuard<'_, T> { fn to_json(&self) -> Option { self.deref().to_json() } } +impl ToJson2 for RwLockWriteGuard<'_, T> { + fn to_json2(&self) -> JsonValue { + self.deref().to_json2() + } +} + pub trait FromJson where Self: Sized, diff --git a/src/fanbox/creator.rs b/src/fanbox/creator.rs index b7b5e01..b97126b 100644 --- a/src/fanbox/creator.rs +++ b/src/fanbox/creator.rs @@ -1,9 +1,13 @@ use super::check::CheckUnknown; use super::error::FanboxAPIError; +#[cfg(feature = "exif")] +use crate::data::exif::ExifDataSource; +use crate::ext::json::ToJson2; use crate::fanbox_api::FanboxClientInternal; use crate::parser::json::parse_u64; use json::JsonValue; use proc_macros::check_json_keys; +use proc_macros::create_fanbox_download_helper; use std::convert::From; use std::fmt::Debug; use std::ops::Deref; @@ -29,6 +33,8 @@ impl FanboxProfileImage { self.data["imageUrl"].as_str() } + create_fanbox_download_helper!(image_url); + #[inline] /// Create a new instance pub fn new(data: &JsonValue, client: Arc) -> Self { @@ -42,6 +48,8 @@ impl FanboxProfileImage { pub fn thumbnail_url(&self) -> Option<&str> { self.data["thumbnailUrl"].as_str() } + + create_fanbox_download_helper!(thumbnail_url); } impl CheckUnknown for FanboxProfileImage { @@ -161,6 +169,8 @@ impl FanboxCreator { self.data["coverImageUrl"].as_str() } + create_fanbox_download_helper!(cover_image_url); + #[inline] pub fn description(&self) -> Option<&str> { self.data["description"].as_str() @@ -292,3 +302,19 @@ impl Debug for FanboxCreator { .finish_non_exhaustive() } } + +impl ExifDataSource for FanboxCreator { + fn image_author(&self) -> Option { + self.user_name().map(|s| s.to_owned()) + } + + fn image_comment(&self) -> Option { + self.description().map(|s| s.to_owned()) + } +} + +impl ToJson2 for FanboxCreator { + fn to_json2(&self) -> JsonValue { + self.data.clone() + } +} diff --git a/src/fanbox/post.rs b/src/fanbox/post.rs index 8698acd..55f120c 100644 --- a/src/fanbox/post.rs +++ b/src/fanbox/post.rs @@ -5,6 +5,7 @@ use super::comment_list::FanboxCommentList; use super::error::FanboxAPIError; #[cfg(feature = "exif")] use crate::data::exif::ExifDataSource; +use crate::ext::json::ToJson2; use crate::fanbox_api::FanboxClientInternal; use crate::parser::json::parse_u64; use json::JsonValue; @@ -1311,6 +1312,12 @@ impl FanboxPost { } } +impl ToJson2 for FanboxPost { + fn to_json2(&self) -> JsonValue { + self.get_json().clone() + } +} + fanbox_api_test!(test_get_post_info, { match client.get_post_info(4070200).await { Some(data) => { diff --git a/src/pixiv_link.rs b/src/pixiv_link.rs index 1415f0f..69f92d5 100644 --- a/src/pixiv_link.rs +++ b/src/pixiv_link.rs @@ -11,6 +11,10 @@ lazy_static! { static ref RE2: Regex = Regex::new("^(https?://)?(www\\.)?fanbox\\.cc/@(?P[^/]+)/posts/(?P\\d+)").unwrap(); #[doc(hidden)] static ref RE3: Regex = Regex::new("^(https?://)?(?P[^./]+)\\.fanbox\\.cc/posts/(?P\\d+)").unwrap(); + #[doc(hidden)] + static ref RE4: Regex = Regex::new("^(https?://)?(?P[^./]+)\\.fanbox\\.cc(/(\\?.*)?)?$").unwrap(); + #[doc(hidden)] + static ref RE5: Regex = Regex::new("^(https?://)?(www\\.)?fanbox\\.cc/@(?P[^/?]+)(/(\\?.*)?)?$").unwrap(); } #[derive(Clone, Debug)] @@ -41,6 +45,8 @@ pub enum PixivID { Artwork(u64), /// Fanbox post FanboxPost(FanboxPostID), + /// Fanbox creator + FanboxCreator(String), } pub trait ToPixivID { @@ -94,6 +100,25 @@ impl PixivID { }, None => {} } + match RE4.captures(s) { + Some(re) => match re.name("creator") { + Some(creator) => match creator.as_str() { + "www" => {} + _ => return Some(Self::FanboxCreator(String::from(creator.as_str()))), + }, + None => {} + }, + None => {} + } + match RE5.captures(s) { + Some(re) => match re.name("creator") { + Some(creator) => { + return Some(Self::FanboxCreator(String::from(creator.as_str()))); + } + None => {} + }, + None => {} + } None } @@ -108,6 +133,9 @@ impl PixivID { id.creator_id, id.post_id ) } + Self::FanboxCreator(id) => { + format!("https://www.fanbox.cc/@{}", id) + } } } } @@ -121,6 +149,9 @@ impl ToJson for PixivID { &PixivID::FanboxPost(id) => Some( json::value!({"type": "fanbox_post", "post_id": id.post_id.clone(), "creator_id": id.creator_id.clone(), "link": self.to_link()}), ), + &PixivID::FanboxCreator(id) => Some( + json::value!({"type": "fanbox_creator", "creator_id": id.clone(), "link": self.to_link()}), + ), } } } @@ -167,6 +198,7 @@ impl TryInto for PixivID { match self { Self::Artwork(id) => Ok(id), Self::FanboxPost(id) => Ok(id.post_id), + Self::FanboxCreator(_) => Err(()), } } } @@ -177,6 +209,7 @@ impl TryInto for &PixivID { match self { PixivID::Artwork(id) => Ok(id.clone()), PixivID::FanboxPost(id) => Ok(id.post_id.clone()), + PixivID::FanboxCreator(_) => Err(()), } } } diff --git a/src/pixiv_web.rs b/src/pixiv_web.rs index 9b26ed5..33f8c90 100644 --- a/src/pixiv_web.rs +++ b/src/pixiv_web.rs @@ -5,7 +5,6 @@ use crate::opthelper::get_helper; use crate::parser::metadata::MetaDataParser; use crate::webclient::WebClient; use json::JsonValue; -use reqwest::IntoUrl; use reqwest::Response; use std::sync::atomic::AtomicBool; use std::sync::RwLock; @@ -161,25 +160,6 @@ impl PixivWebClient { Some(body.clone()) } - pub async fn download_image(&self, url: U) -> Option { - self.auto_init(); - let r = self - .client - .get(url, json::object! {"referer": "https://www.pixiv.net/"}) - .await; - if r.is_none() { - return None; - } - let r = r.unwrap(); - let status = r.status(); - let code = status.as_u16(); - if code >= 400 { - println!("{} {}", gettext("Failed to download image:"), status); - return None; - } - Some(r) - } - pub async fn get_artwork_ajax(&self, id: u64) -> Option { self.auto_init(); let r = self