diff --git a/src/download.rs b/src/download.rs index d109579..3779c96 100644 --- a/src/download.rs +++ b/src/download.rs @@ -12,6 +12,7 @@ use crate::downloader::DownloaderResult; use crate::downloader::LocalFile; use crate::error::PixivDownloaderError; use crate::ext::try_err::TryErr; +use crate::fanbox_api::FanboxClient; use crate::gettext; use crate::opthelper::get_helper; use crate::pixiv_link::PixivID; @@ -29,22 +30,22 @@ use std::sync::Arc; impl Main { pub async fn download(&mut self) -> i32 { let pw = Arc::new(PixivWebClient::new()); - if !pw.init() { - println!("{}", gettext("Failed to initialize pixiv web api client.")); - return 1; - } - if !pw.check_login().await { - return 1; - } - if !pw.logined() { - println!( - "{}", - gettext("Warning: Web api client not logined, some future may not work.") - ); - } + let fc = Arc::new(FanboxClient::new()); for id in self.cmd.as_ref().unwrap().ids.iter() { match id { PixivID::Artwork(id) => { + if !pw.is_inited() { + if !pw.init() { + println!("{}", gettext("Failed to initialize pixiv web api client.")); + return 1; + } + if !pw.check_login().await { + return 1; + } + if !pw.logined() { + println!("{}", gettext("Warning: Web api client not logined, some future may not work.")); + } + } let r = self.download_artwork(Arc::clone(&pw), id.clone()).await; let r = if r.is_ok() { 0 @@ -60,6 +61,18 @@ impl Main { return r; } } + PixivID::FanboxPost(_) => { + if !fc.is_inited() { + if !fc.init() { + println!("{}", gettext("Failed to initialize fanbox api client.")); + return 1; + } + if !fc.check_login().await { + return 1; + } + println!("Logined: {}", fc.logined()); + } + } } } 0 diff --git a/src/fanbox_api.rs b/src/fanbox_api.rs new file mode 100644 index 0000000..d2b2dfb --- /dev/null +++ b/src/fanbox_api.rs @@ -0,0 +1,114 @@ +use crate::ext::atomic::AtomicQuick; +use crate::ext::replace::ReplaceWith2; +use crate::ext::rw_lock::GetRwLock; +use crate::gettext; +use crate::opthelper::get_helper; +use crate::parser::metadata::MetaDataParser; +use crate::webclient::WebClient; +use json::JsonValue; +use std::sync::atomic::AtomicBool; +use std::sync::RwLock; + +/// Fanbox API client +pub struct FanboxClient { + /// Web client + client: WebClient, + /// true if in is initialized + inited: AtomicBool, + /// Fanbox global data + data: RwLock>, +} + +impl FanboxClient { + /// Create an new instance + pub fn new() -> Self { + Self { + client: WebClient::default(), + inited: AtomicBool::new(false), + data: RwLock::new(None), + } + } + + /// Initiailze the interface. + /// + /// Returns true if successed + pub fn init(&self) -> bool { + let helper = get_helper(); + let c = helper.cookies(); + if c.is_some() { + if !self.client.read_cookies(c.as_ref().unwrap()) { + return false; + } + } + self.client.set_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"); + self.inited.qstore(true); + true + } + + /// Returns true if is initialized. + pub fn is_inited(&self) -> bool { + self.inited.qload() + } + + /// Initialize the client if needed. + fn auto_init(&self) { + if !self.is_inited() { + let r = self.init(); + if !r { + panic!("{}", gettext("Failed to initialize pixiv web api client.")); + } + } + } + + /// Check login status. + pub async fn check_login(&self) -> bool { + self.auto_init(); + let r = self.client.get("https://www.fanbox.cc", None).await; + match r { + Some(r) => { + let status = r.status(); + let code = status.as_u16(); + if code >= 400 { + println!("{} {}", gettext("Failed to get fanbox main page:"), status); + return false; + } + match r.text_with_charset("UTF-8").await { + Ok(data) => { + let mut parser = MetaDataParser::new("metadata"); + if !parser.parse(data.as_str()) { + println!("{}", gettext("Failed to parse fanbox main page.")); + return false; + } + if get_helper().verbose() { + println!( + "{}\n{}", + gettext("Fanbox main page's data:"), + parser.value.as_ref().unwrap().pretty(2).as_str() + ); + } + self.data.replace_with2(parser.value); + true + } + Err(e) => { + println!("{} {}", gettext("Failed to get fanbox main page:"), e); + false + } + } + } + None => false, + } + } + + /// Returns true if is logged in. + pub fn logined(&self) -> bool { + let data = self.data.get_ref(); + if data.is_none() { + return false; + } + let value = data.as_ref().unwrap(); + match value["user"]["isLoggedIn"].as_bool() { + Some(b) => b, + None => false, + } + } +} diff --git a/src/main.rs b/src/main.rs index 4386a7e..bc9a482 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ mod error; mod exif; /// Used to extend some thirdparty library mod ext; +mod fanbox_api; mod i18n; mod list; mod opt; diff --git a/src/parser/metadata.rs b/src/parser/metadata.rs index 40a1e0b..4e87bcb 100644 --- a/src/parser/metadata.rs +++ b/src/parser/metadata.rs @@ -38,7 +38,9 @@ impl MetaDataParser { return false; } let mkey = format!("meta-{}", self.key.as_str()); - if e.id.as_ref().unwrap() != mkey.as_str() { + if e.id.as_ref().unwrap() != mkey.as_str() + && e.id.as_ref().unwrap() != self.key.as_str() + { return false; } let c = e.attributes.get("content"); diff --git a/src/pixiv_link.rs b/src/pixiv_link.rs index 741e54e..87cf3d0 100644 --- a/src/pixiv_link.rs +++ b/src/pixiv_link.rs @@ -7,6 +7,29 @@ use std::convert::TryInto; lazy_static! { #[doc(hidden)] static ref RE: Regex = Regex::new("^(https?://)?(www\\.)?pixiv\\.net/artworks/(?P\\d+)").unwrap(); + #[doc(hidden)] + static ref RE2: Regex = Regex::new("^(https?://)?(www\\.)?fanbox\\.cc/@(?P[^/]+)/posts/(?P\\d+)").unwrap(); +} + +#[derive(Clone, Debug)] +/// Fanbox post ID +pub struct FanboxPostID { + /// Creator ID + creator_id: String, + /// Post ID + post_id: u64, +} + +impl FanboxPostID { + /// Create a new id. + /// * `creator_id` - Creator ID + /// * `post_id` - Post ID + pub fn new + ?Sized>(creator_id: &C, post_id: u64) -> Self { + Self { + creator_id: String::from(creator_id.as_ref()), + post_id, + } + } } /// Repesent an Pixiv ID @@ -14,6 +37,8 @@ lazy_static! { pub enum PixivID { /// Artwork Id, include illust and manga Artwork(u64), + /// Fanbox post + FanboxPost(FanboxPostID), } pub trait ToPixivID { @@ -27,14 +52,30 @@ impl PixivID { if num.is_ok() { return Some(PixivID::Artwork(num.unwrap())); } - let re = RE.captures(s); - if re.is_some() { - let r = re.unwrap().name("id"); - if r.is_some() { - let r = r.unwrap().as_str(); - let num = r.parse::(); - return Some(PixivID::Artwork(num.unwrap())); - } + match RE.captures(s) { + Some(re) => match re.name("id") { + Some(r) => match r.as_str().parse::() { + Ok(r) => return Some(Self::Artwork(r)), + Err(_) => {} + }, + None => {} + }, + None => {} + } + match RE2.captures(s) { + Some(re) => match re.name("creator") { + Some(creator) => match re.name("id") { + Some(id) => match id.as_str().parse::() { + Ok(id) => { + return Some(Self::FanboxPost(FanboxPostID::new(creator.as_str(), id))); + } + Err(_) => {} + }, + None => {} + }, + None => {} + }, + None => {} } None } @@ -44,16 +85,25 @@ impl PixivID { Self::Artwork(id) => { format!("https://www.pixiv.net/artworks/{}", id) } + Self::FanboxPost(id) => { + format!( + "https://www.fanbox.cc/@{}/posts/{}", + id.creator_id, id.post_id + ) + } } } } impl ToJson for PixivID { fn to_json(&self) -> Option { - match *self { - PixivID::Artwork(id) => { - Some(json::value!({"type": "artwork", "id": id, "link": self.to_link()})) + match &self { + &PixivID::Artwork(id) => { + Some(json::value!({"type": "artwork", "id": id.clone(), "link": self.to_link()})) } + &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()}), + ), } } } @@ -93,6 +143,7 @@ impl TryInto for PixivID { fn try_into(self) -> Result { match self { Self::Artwork(id) => Ok(id), + Self::FanboxPost(id) => Ok(id.post_id), } } } @@ -100,8 +151,9 @@ impl TryInto for PixivID { impl TryInto for &PixivID { type Error = (); fn try_into(self) -> Result { - match *self { - PixivID::Artwork(id) => Ok(id), + match self { + PixivID::Artwork(id) => Ok(id.clone()), + PixivID::FanboxPost(id) => Ok(id.post_id.clone()), } } }