diff --git a/src/download.rs b/src/download.rs index 3779c96..4a09e5b 100644 --- a/src/download.rs +++ b/src/download.rs @@ -63,7 +63,8 @@ impl Main { } PixivID::FanboxPost(_) => { if !fc.is_inited() { - if !fc.init() { + let helper = get_helper(); + if !fc.init(helper.cookies()) { println!("{}", gettext("Failed to initialize fanbox api client.")); return 1; } diff --git a/src/ext/replace.rs b/src/ext/replace.rs index 170fb5f..18c6a8b 100644 --- a/src/ext/replace.rs +++ b/src/ext/replace.rs @@ -1,5 +1,6 @@ use crate::ext::rw_lock::GetRwLock; use std::ops::DerefMut; +use std::sync::MutexGuard; use std::sync::RwLock; use std::sync::RwLockWriteGuard; @@ -30,6 +31,20 @@ impl ReplaceWith for T { } } +impl<'a, T> ReplaceWith for MutexGuard<'a, T> { + #[inline] + fn replace_with(&mut self, another: T) -> T { + self.deref_mut().replace_with(another) + } +} + +impl<'a, T> ReplaceWith for futures_util::lock::MutexGuard<'a, T> { + #[inline] + fn replace_with(&mut self, another: T) -> T { + self.deref_mut().replace_with(another) + } +} + impl<'a, T> ReplaceWith for RwLockWriteGuard<'a, T> { #[inline] fn replace_with(&mut self, another: T) -> T { diff --git a/src/fanbox_api.rs b/src/fanbox_api.rs index c8479b9..0492407 100644 --- a/src/fanbox_api.rs +++ b/src/fanbox_api.rs @@ -1,12 +1,20 @@ use crate::ext::atomic::AtomicQuick; +#[cfg(test)] +use crate::ext::replace::ReplaceWith; 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; +#[cfg(test)] +use futures_util::lock::Mutex; use json::JsonValue; +#[cfg(test)] +use std::ops::Deref; use std::sync::atomic::AtomicBool; +#[cfg(test)] +use std::sync::Arc; use std::sync::RwLock; /// Fanbox API client @@ -30,15 +38,17 @@ impl FanboxClient { } /// Initiailze the interface. + /// * `cookies` - The path to cookies file /// /// 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; + pub fn init(&self, cookies: Option) -> bool { + match cookies { + Some(c) => { + if !self.client.read_cookies(c.as_str()) { + return false; + } } + None => {} } 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); @@ -53,7 +63,8 @@ impl FanboxClient { /// Initialize the client if needed. fn auto_init(&self) { if !self.is_inited() { - let r = self.init(); + let helper = get_helper(); + let r = self.init(helper.cookies()); if !r { panic!("{}", gettext("Failed to initialize pixiv web api client.")); } @@ -99,6 +110,43 @@ impl FanboxClient { } } + /// List home page's post list. All supported and followed creators' posts are included. + /// * `limit` - The max count. 10 is used on Fanbox website. + pub async fn list_home(&self, limit: u64) -> Option { + self.auto_init(); + match self + .client + .get_with_param( + "https://api.fanbox.cc/post.listHome", + json::object! {"limit": limit}, + json::object! {"referer": "https://www.fanbox.cc/", "origin": "https://www.fanbox.cc"}, + ) + .await + { + Some(r) => { + let status = r.status(); + if status.as_u16() >= 400 { + println!("{} {}", gettext("Failed to list home page's posts:"), status); + return None; + } + match r.text_with_charset("UTF-8").await { + Ok(data) => match json::parse(data.as_str()) { + Ok(obj) => Some(obj), + Err(e) => { + println!("{} {}", gettext("Failed to list home page's posts:"), e); + None + } + }, + Err(e) => { + println!("{} {}", gettext("Failed to list home page's posts:"), e); + None + } + } + } + None => None, + } + } + /// Returns true if is logged in. pub fn logined(&self) -> bool { let data = self.data.get_ref(); @@ -112,3 +160,63 @@ impl FanboxClient { } } } + +#[cfg(test)] +lazy_static! { + #[doc(hidden)] + static ref TEST_CLIENT: Arc = Arc::new(FanboxClient::new()); + #[doc(hidden)] + /// Used to lock initilize process. The data is set to true after trying check login. + static ref LOCK: Mutex = Mutex::new(false); +} + +#[cfg(test)] +async fn init_test_client(path: String) -> bool { + let _ = LOCK.lock().await; + if !TEST_CLIENT.is_inited() { + if !TEST_CLIENT.init(Some(path)) { + return false; + } + } + true +} + +#[cfg(test)] +async fn check_login() -> bool { + let mut ctx = LOCK.lock().await; + if !ctx.deref() { + ctx.replace_with(true); + if !TEST_CLIENT.check_login().await { + return false; + } + } + TEST_CLIENT.logined() +} + +#[proc_macros::async_timeout_test(120s)] +#[tokio::test(flavor = "multi_thread")] +async fn test_list_home() { + match std::env::var("FANBOX_COOKIES_FILE") { + Ok(path) => { + let re = init_test_client(path).await; + if !re { + panic!("Failed to initiailze the client."); + } + if !check_login().await { + println!("The client is not logined. Skip test."); + return; + } + match TEST_CLIENT.list_home(10).await { + Some(data) => { + println!("{}", data.pretty(2)); + } + None => { + panic!("Failed to list home."); + } + } + } + Err(_) => { + println!("No cookies files specified, skip test.") + } + } +}