diff --git a/src/fanbox/error.rs b/src/fanbox/error.rs new file mode 100644 index 0000000..ad431a6 --- /dev/null +++ b/src/fanbox/error.rs @@ -0,0 +1,10 @@ +#[derive(Debug, derive_more::From, derive_more::Display)] +pub enum FanboxAPIError { + String(String), +} + +impl From<&'static str> for FanboxAPIError { + fn from(v: &'static str) -> Self { + Self::String(v.to_owned()) + } +} diff --git a/src/fanbox/item.rs b/src/fanbox/item.rs new file mode 100644 index 0000000..c03bf60 --- /dev/null +++ b/src/fanbox/item.rs @@ -0,0 +1,58 @@ +use json::JsonValue; +use std::fmt::Debug; + +/// To represent a fanbox item +pub struct FanboxItem { + /// JSON object + pub data: JsonValue, +} + +impl FanboxItem { + /// Returns the id of the item + pub fn id(&self) -> Option { + let id = &self.data["id"]; + match id.as_u64() { + Some(id) => Some(id), + None => match id.as_str() { + Some(id) => match id.trim().parse::() { + Ok(id) => Some(id), + Err(_) => None, + }, + None => None, + }, + } + } + + #[inline] + pub fn is_liked(&self) -> Option { + self.data["isLiked"].as_bool() + } + + #[inline] + /// Returns item's title + pub fn title(&self) -> Option<&str> { + self.data["title"].as_str() + } +} + +impl Debug for FanboxItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FanboxItem") + .field("id", &self.id()) + .field("title", &self.title()) + .field("is_liked", &self.is_liked()) + .finish_non_exhaustive() + } +} + +impl From for FanboxItem { + fn from(data: JsonValue) -> Self { + Self { data } + } +} + +impl From<&JsonValue> for FanboxItem { + fn from(data: &JsonValue) -> Self { + Self::from(data.clone()) + } +} diff --git a/src/fanbox/item_list.rs b/src/fanbox/item_list.rs new file mode 100644 index 0000000..7f1eac0 --- /dev/null +++ b/src/fanbox/item_list.rs @@ -0,0 +1,51 @@ +use super::error::FanboxAPIError; +use super::item::FanboxItem; +use crate::fanbox_api::FanboxClientInternal; +use crate::gettext; +use json::JsonValue; +use std::fmt::Debug; +use std::sync::Arc; + +/// A list of fanbox item. +pub struct FanboxItemList { + /// Item list + pub items: Vec, + /// The url of the next page + next_url: Option, + /// Fanbox api client + client: Arc, +} + +impl FanboxItemList { + pub fn new( + value: &JsonValue, + client: Arc, + ) -> Result { + let oitems = &value["items"]; + if !oitems.is_array() { + return Err(FanboxAPIError::from(gettext("Failed to parse item list."))); + } + let mut items = Vec::new(); + for item in oitems.members() { + items.push(FanboxItem::from(item)); + } + let next_url = match value["nextUrl"].as_str() { + Some(next_url) => Some(next_url.to_owned()), + None => None, + }; + Ok(Self { + items, + next_url, + client, + }) + } +} + +impl Debug for FanboxItemList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FanboxItemList") + .field("items", &self.items) + .field("next_url", &self.next_url) + .finish_non_exhaustive() + } +} diff --git a/src/fanbox/mod.rs b/src/fanbox/mod.rs index 539bb71..f06b845 100644 --- a/src/fanbox/mod.rs +++ b/src/fanbox/mod.rs @@ -1 +1,4 @@ +pub mod error; +pub mod item; +pub mod item_list; pub mod paginated_creator_posts; diff --git a/src/fanbox/paginated_creator_posts.rs b/src/fanbox/paginated_creator_posts.rs index 6334177..71ad3c5 100644 --- a/src/fanbox/paginated_creator_posts.rs +++ b/src/fanbox/paginated_creator_posts.rs @@ -1,3 +1,5 @@ +use super::error::FanboxAPIError; +use super::item_list::FanboxItemList; #[cfg(test)] use crate::fanbox_api::FanboxClient; use crate::fanbox_api::FanboxClientInternal; @@ -7,17 +9,6 @@ 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 @@ -33,10 +24,10 @@ impl PaginatedCreatorPosts { pub fn new( value: &JsonValue, client: Arc, - ) -> Result { + ) -> Result { let body = &value["body"]; if !body.is_array() { - return Err(PaginatedCreatorPostsError::from(gettext( + return Err(FanboxAPIError::from(gettext( "Failed to parse paginated data.", ))); } @@ -47,7 +38,7 @@ impl PaginatedCreatorPosts { pages.push(s.to_owned()); } None => { - return Err(PaginatedCreatorPostsError::from(gettext( + return Err(FanboxAPIError::from(gettext( "Failed to parse paginated data.", ))); } @@ -55,6 +46,39 @@ impl PaginatedCreatorPosts { } Ok(Self { client, pages }) } + + /// Get posts' data in specified page. + /// * `index` - The index of the page + pub async fn get_page(&self, index: usize) -> Option { + if index >= self.pages.len() { + None + } else { + match self + .client + .get_url( + self.pages[index].as_str(), + gettext("Failed to get posts' data:"), + gettext("Posts' data:"), + ) + .await + { + Some(d) => match FanboxItemList::new(&d["body"], Arc::clone(&self.client)) { + Ok(d) => Some(d), + Err(e) => { + println!("{} {}", gettext("Failed to parse posts's data:"), e); + None + } + }, + None => None, + } + } + } + + #[inline] + /// Returns the total pages + pub fn len(&self) -> usize { + self.pages.len() + } } impl Debug for PaginatedCreatorPosts { @@ -69,7 +93,7 @@ impl Debug for PaginatedCreatorPosts { f.write_fmt(format_args!("{} {}\n", index, page))?; index += 1; } - f.write_str("}\n") + f.write_str("}") } } } @@ -78,6 +102,14 @@ fanbox_api_test!(test_paginate_creator_posts, { match client.paginate_creator_post("mozukun43").await { Some(pages) => { println!("{:?}", pages); + if pages.len() > 0 { + match pages.get_page(0).await { + Some(data) => { + println!("{:?}", data) + } + None => {} + } + } } None => { panic!("Failed to paginate creator's posts.") diff --git a/src/fanbox_api.rs b/src/fanbox_api.rs index 6a4c877..077cbcb 100644 --- a/src/fanbox_api.rs +++ b/src/fanbox_api.rs @@ -8,6 +8,7 @@ use crate::parser::metadata::MetaDataParser; use crate::webclient::WebClient; use json::JsonValue; use proc_macros::fanbox_api_quick_test; +use reqwest::IntoUrl; use std::ops::Deref; use std::sync::atomic::AtomicBool; use std::sync::Arc; @@ -157,6 +158,24 @@ impl FanboxClientInternal { ) } + /// Send requests to speicfied url. + /// * `url` - The url + /// * `errmsg` - The error message when error occured. + /// * `infomsg` - Verbose output info message. + pub async fn get_url + ?Sized, I: AsRef + ?Sized>( + &self, + url: U, + errmsg: &S, + infomsg: &I, + ) -> Option { + self.auto_init(); + handle_data!( + self.client.get(url, None), + errmsg.as_ref(), + infomsg.as_ref() + ) + } + #[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.