diff --git a/proc_macros/proc_macros.rs b/proc_macros/proc_macros.rs index 53dedaa..47815a3 100644 --- a/proc_macros/proc_macros.rs +++ b/proc_macros/proc_macros.rs @@ -3,6 +3,7 @@ use quote::quote; use syn::parse::Parse; use syn::parse_macro_input; use syn::token; +use syn::Block; use syn::Expr; use syn::Ident; use syn::ItemFn; @@ -112,12 +113,55 @@ pub fn async_timeout_test(attr: TokenStream, item: TokenStream) -> TokenStream { } struct FanboxApiTest { + pub name: Ident, + pub block: Block, +} + +impl Parse for FanboxApiTest { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let name = Ident::parse(input)?; + token::Comma::parse(input)?; + let block = syn::Block::parse(input)?; + Ok(Self { name, block }) + } +} + +#[proc_macro] +pub fn fanbox_api_test(item: TokenStream) -> TokenStream { + let FanboxApiTest { name, block } = parse_macro_input!(item as FanboxApiTest); + let stmts = block.stmts; + let stream = quote! { + #[proc_macros::async_timeout_test(120s)] + #[tokio::test(flavor = "multi_thread")] + async fn #name() { + match std::env::var("FANBOX_COOKIES_FILE") { + Ok(path) => { + let client = FanboxClient::new(); + if !client.init(Some(path)) { + panic!("Failed to initiailze the client."); + } + if !client.check_login().await { + println!("The client is not logined. Skip test."); + return; + } + #(#stmts)* + } + Err(_) => { + println!("No cookies files specified, skip test.") + } + } + } + }; + stream.into() +} + +struct FanboxApiQuickTest { pub name: Ident, pub expr: Expr, pub errmsg: LitStr, } -impl Parse for FanboxApiTest { +impl Parse for FanboxApiQuickTest { fn parse(input: syn::parse::ParseStream) -> syn::Result { let name = Ident::parse(input)?; token::Comma::parse(input)?; @@ -133,7 +177,7 @@ impl Parse for FanboxApiTest { #[proc_macro] pub fn fanbox_api_quick_test(item: TokenStream) -> TokenStream { - let FanboxApiTest { name, expr, errmsg } = parse_macro_input!(item as FanboxApiTest); + let FanboxApiQuickTest { name, expr, errmsg } = parse_macro_input!(item as FanboxApiQuickTest); let stream = quote! { #[proc_macros::async_timeout_test(120s)] #[tokio::test(flavor = "multi_thread")] diff --git a/src/fanbox/mod.rs b/src/fanbox/mod.rs new file mode 100644 index 0000000..539bb71 --- /dev/null +++ b/src/fanbox/mod.rs @@ -0,0 +1 @@ +pub mod paginated_creator_posts; diff --git a/src/fanbox/paginated_creator_posts.rs b/src/fanbox/paginated_creator_posts.rs new file mode 100644 index 0000000..6334177 --- /dev/null +++ b/src/fanbox/paginated_creator_posts.rs @@ -0,0 +1,86 @@ +#[cfg(test)] +use crate::fanbox_api::FanboxClient; +use crate::fanbox_api::FanboxClientInternal; +use crate::gettext; +use json::JsonValue; +use proc_macros::fanbox_api_test; +use std::fmt::Debug; +use std::sync::Arc; + +#[derive(Debug, derive_more::From, derive_more::Display)] +pub enum PaginatedCreatorPostsError { + String(String), +} + +impl From<&'static str> for PaginatedCreatorPostsError { + fn from(v: &'static str) -> Self { + Self::String(v.to_owned()) + } +} + +/// List creator posts +pub struct PaginatedCreatorPosts { + /// Fanbox API Client + client: Arc, + /// Pages + pages: Vec, +} + +impl PaginatedCreatorPosts { + /// Create a new instance. + /// * `value` - Data from fanbox api + /// * `client` - The instance of the fanbox api client + pub fn new( + value: &JsonValue, + client: Arc, + ) -> Result { + let body = &value["body"]; + if !body.is_array() { + return Err(PaginatedCreatorPostsError::from(gettext( + "Failed to parse paginated data.", + ))); + } + let mut pages = Vec::new(); + for i in body.members() { + match i.as_str() { + Some(s) => { + pages.push(s.to_owned()); + } + None => { + return Err(PaginatedCreatorPostsError::from(gettext( + "Failed to parse paginated data.", + ))); + } + } + } + Ok(Self { client, pages }) + } +} + +impl Debug for PaginatedCreatorPosts { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let size = self.pages.len(); + let mut index = 0usize; + if size == 0 { + f.write_str("PaginatedCreatorPosts { Empty }") + } else { + f.write_str("PaginatedCreatorPosts {\n")?; + for page in self.pages.iter() { + f.write_fmt(format_args!("{} {}\n", index, page))?; + index += 1; + } + f.write_str("}\n") + } + } +} + +fanbox_api_test!(test_paginate_creator_posts, { + match client.paginate_creator_post("mozukun43").await { + Some(pages) => { + println!("{:?}", pages); + } + None => { + panic!("Failed to paginate creator's posts.") + } + } +}); diff --git a/src/fanbox_api.rs b/src/fanbox_api.rs index 5a56c6e..6a4c877 100644 --- a/src/fanbox_api.rs +++ b/src/fanbox_api.rs @@ -1,17 +1,20 @@ use crate::ext::atomic::AtomicQuick; use crate::ext::replace::ReplaceWith2; use crate::ext::rw_lock::GetRwLock; +use crate::fanbox::paginated_creator_posts::PaginatedCreatorPosts; use crate::gettext; use crate::opthelper::get_helper; use crate::parser::metadata::MetaDataParser; use crate::webclient::WebClient; use json::JsonValue; use proc_macros::fanbox_api_quick_test; +use std::ops::Deref; use std::sync::atomic::AtomicBool; +use std::sync::Arc; use std::sync::RwLock; /// Fanbox API client -pub struct FanboxClient { +pub struct FanboxClientInternal { /// Web client client: WebClient, /// true if in is initialized @@ -53,7 +56,7 @@ macro_rules! handle_data { }; } -impl FanboxClient { +impl FanboxClientInternal { /// Create an new instance pub fn new() -> Self { Self { @@ -138,6 +141,7 @@ impl FanboxClient { } } + #[allow(dead_code)] /// Get creator's info /// * `creator_id` - The id of the creator pub async fn get_creator + ?Sized>(&self, creator_id: &S) -> Option { @@ -153,6 +157,7 @@ impl FanboxClient { ) } + #[allow(dead_code)] /// 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_post(&self, limit: u64) -> Option { @@ -168,6 +173,7 @@ impl FanboxClient { ) } + #[allow(dead_code)] /// List all supporting plans. pub async fn list_supporting_plan(&self) -> Option { self.auto_init(); @@ -179,6 +185,7 @@ impl FanboxClient { ) } + #[allow(dead_code)] /// List supported creators' posts. /// * `limit` - The max count. 10 is used on Fanbox website. pub async fn list_supporting_post(&self, limit: u64) -> Option { @@ -208,6 +215,56 @@ impl FanboxClient { } } +/// Fanbox API Client +pub struct FanboxClient { + /// Internal client + client: Arc, +} + +impl FanboxClient { + /// Create an new instance + pub fn new() -> Self { + Self { + client: Arc::new(FanboxClientInternal::new()), + } + } + + #[allow(dead_code)] + /// Paginate creator posts + /// * `creator_id` - The id of the creator + pub async fn paginate_creator_post + ?Sized>( + &self, + creator_id: &S, + ) -> Option { + self.auto_init(); + match handle_data!( + self.client.client.get_with_param( + "https://api.fanbox.cc/post.paginateCreator", + json::object! {"creatorId": creator_id.as_ref()}, + None, + ), + gettext("Failed to paginate creator post:"), + gettext("Paginated data:") + ) { + Some(s) => match PaginatedCreatorPosts::new(&s, Arc::clone(&self.client)) { + Ok(re) => Some(re), + Err(e) => { + println!("{}", e); + None + } + }, + None => None, + } + } +} + +impl Deref for FanboxClient { + type Target = FanboxClientInternal; + fn deref(&self) -> &Self::Target { + &self.client + } +} + fanbox_api_quick_test!( test_list_home_post, client.list_home_post(10), diff --git a/src/main.rs b/src/main.rs index a3895d8..d5eb115 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ mod error; mod exif; /// Used to extend some thirdparty library mod ext; +mod fanbox; mod fanbox_api; mod i18n; mod list;