mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-07-08 01:32:41 +08:00
413 lines
13 KiB
Rust
413 lines
13 KiB
Rust
use crate::ext::atomic::AtomicQuick;
|
|
use crate::ext::replace::ReplaceWith2;
|
|
use crate::ext::rw_lock::GetRwLock;
|
|
use crate::fanbox::creator::FanboxCreator;
|
|
use crate::fanbox::item_list::FanboxItemList;
|
|
use crate::fanbox::paginated_creator_posts::PaginatedCreatorPosts;
|
|
use crate::fanbox::plan::FanboxPlanList;
|
|
use crate::fanbox::post::FanboxPost;
|
|
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 reqwest::IntoUrl;
|
|
use std::ops::Deref;
|
|
use std::sync::atomic::AtomicBool;
|
|
use std::sync::Arc;
|
|
use std::sync::RwLock;
|
|
|
|
/// Fanbox API client
|
|
pub struct FanboxClientInternal {
|
|
/// Web client
|
|
client: Arc<WebClient>,
|
|
/// true if in is initialized
|
|
inited: AtomicBool,
|
|
/// Fanbox global data
|
|
data: RwLock<Option<JsonValue>>,
|
|
}
|
|
|
|
macro_rules! handle_data {
|
|
($get_data:expr, $err:expr, $info:expr) => {
|
|
match $get_data.await {
|
|
Some(r) => {
|
|
let status = r.status();
|
|
if status.as_u16() >= 400 {
|
|
println!("{} {}", $err, status);
|
|
return None;
|
|
}
|
|
match r.text_with_charset("UTF-8").await {
|
|
Ok(data) => match json::parse(data.as_str()) {
|
|
Ok(obj) => {
|
|
if get_helper().verbose() {
|
|
println!("{}\n{}", $info, obj.pretty(2),);
|
|
}
|
|
Some(obj)
|
|
}
|
|
Err(e) => {
|
|
println!("{} {}", $err, e);
|
|
None
|
|
}
|
|
},
|
|
Err(e) => {
|
|
println!("{} {}", $err, e);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
None => None,
|
|
}
|
|
};
|
|
}
|
|
|
|
impl FanboxClientInternal {
|
|
/// Create an new instance
|
|
pub fn new() -> Self {
|
|
Self {
|
|
client: Arc::new(WebClient::default()),
|
|
inited: AtomicBool::new(false),
|
|
data: RwLock::new(None),
|
|
}
|
|
}
|
|
|
|
/// Initiailze the interface.
|
|
/// * `cookies` - The path to cookies file
|
|
///
|
|
/// Returns true if successed
|
|
pub fn init(&self, cookies: Option<String>) -> 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.client.set_header("referer", "https://www.fanbox.cc/");
|
|
self.client.set_header("origin", "https://www.fanbox.cc");
|
|
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 helper = get_helper();
|
|
let r = self.init(helper.cookies());
|
|
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,
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
/// Get creator's info
|
|
/// * `creator_id` - The id of the creator
|
|
pub async fn get_creator<S: AsRef<str> + ?Sized>(&self, creator_id: &S) -> Option<JsonValue> {
|
|
self.auto_init();
|
|
handle_data!(
|
|
self.client.get_with_param(
|
|
"https://api.fanbox.cc/creator.get",
|
|
json::object! {"creatorId": creator_id.as_ref()},
|
|
None,
|
|
),
|
|
gettext("Failed to get creator's info:"),
|
|
gettext("Creator's info:")
|
|
)
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
/// Get post info
|
|
/// * `post_id` - The id of the post
|
|
pub async fn get_post_info(&self, post_id: u64) -> Option<JsonValue> {
|
|
self.auto_init();
|
|
handle_data!(
|
|
self.client.get_with_param(
|
|
"https://api.fanbox.cc/post.info",
|
|
json::object! { "postId": post_id },
|
|
None,
|
|
),
|
|
gettext("Failed to get post info:"),
|
|
gettext("Post info:")
|
|
)
|
|
}
|
|
|
|
/// 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<U: IntoUrl + Clone, S: AsRef<str> + ?Sized, I: AsRef<str> + ?Sized>(
|
|
&self,
|
|
url: U,
|
|
errmsg: &S,
|
|
infomsg: &I,
|
|
) -> Option<JsonValue> {
|
|
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.
|
|
pub async fn list_home_post(&self, limit: u64) -> Option<JsonValue> {
|
|
self.auto_init();
|
|
handle_data!(
|
|
self.client.get_with_param(
|
|
"https://api.fanbox.cc/post.listHome",
|
|
json::object! {"limit": limit},
|
|
None,
|
|
),
|
|
gettext("Failed to list home page's posts:"),
|
|
gettext("Home page's posts:")
|
|
)
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
/// List all supporting plans.
|
|
pub async fn list_supporting_plan(&self) -> Option<JsonValue> {
|
|
self.auto_init();
|
|
handle_data!(
|
|
self.client
|
|
.get("https://api.fanbox.cc/plan.listSupporting", None),
|
|
gettext("Failed to list all supporting plans."),
|
|
gettext("All supporting plans:")
|
|
)
|
|
}
|
|
|
|
#[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<JsonValue> {
|
|
self.auto_init();
|
|
handle_data!(
|
|
self.client.get_with_param(
|
|
"https://api.fanbox.cc/post.listSupporting",
|
|
json::object! {"limit": limit},
|
|
None,
|
|
),
|
|
gettext("Failed to list supported creators' posts:"),
|
|
gettext("Supported creators' posts:")
|
|
)
|
|
}
|
|
|
|
/// 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["urlContext"]["user"]["isLoggedIn"].as_bool() {
|
|
Some(b) => b,
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
/// Paginate creator posts
|
|
/// * `creator_id` - The id of the creator
|
|
pub async fn paginate_creator_post<S: AsRef<str> + ?Sized>(
|
|
&self,
|
|
creator_id: &S,
|
|
) -> Option<JsonValue> {
|
|
self.auto_init();
|
|
handle_data!(
|
|
self.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:")
|
|
)
|
|
}
|
|
}
|
|
|
|
impl AsRef<Arc<WebClient>> for FanboxClientInternal {
|
|
fn as_ref(&self) -> &Arc<WebClient> {
|
|
&self.client
|
|
}
|
|
}
|
|
|
|
/// Fanbox API Client
|
|
pub struct FanboxClient {
|
|
/// Internal client
|
|
client: Arc<FanboxClientInternal>,
|
|
}
|
|
|
|
impl FanboxClient {
|
|
#[allow(dead_code)]
|
|
/// Get creator's info
|
|
/// * `creator_id` - The id of the creator
|
|
pub async fn get_creator<S: AsRef<str> + ?Sized>(
|
|
&self,
|
|
creator_id: &S,
|
|
) -> Option<FanboxCreator> {
|
|
match self.client.get_creator(creator_id).await {
|
|
Some(s) => Some(FanboxCreator::new(&s["body"], Arc::clone(&self.client))),
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
/// Get post info
|
|
/// * `post_id` - The id of the post
|
|
pub async fn get_post_info(&self, post_id: u64) -> Option<FanboxPost> {
|
|
match self.client.get_post_info(post_id).await {
|
|
Some(s) => Some(FanboxPost::new(&s["body"], Arc::clone(&self.client))),
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
/// Create an new instance
|
|
pub fn new() -> Self {
|
|
Self {
|
|
client: Arc::new(FanboxClientInternal::new()),
|
|
}
|
|
}
|
|
|
|
#[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<FanboxItemList> {
|
|
match self.client.list_home_post(limit).await {
|
|
Some(s) => match FanboxItemList::new(&s["body"], Arc::clone(&self.client)) {
|
|
Ok(item) => Some(item),
|
|
Err(e) => {
|
|
println!("{}", e);
|
|
None
|
|
}
|
|
},
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
/// List all supporting plans.
|
|
pub async fn list_supporting_plan(&self) -> Option<FanboxPlanList> {
|
|
match self.client.list_supporting_plan().await {
|
|
Some(s) => match FanboxPlanList::new(&s["body"]) {
|
|
Ok(item) => Some(item),
|
|
Err(e) => {
|
|
println!("{}", e);
|
|
None
|
|
}
|
|
},
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
#[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<FanboxItemList> {
|
|
match self.client.list_supporting_post(limit).await {
|
|
Some(s) => match FanboxItemList::new(&s["body"], Arc::clone(&self.client)) {
|
|
Ok(item) => Some(item),
|
|
Err(e) => {
|
|
println!("{}", e);
|
|
None
|
|
}
|
|
},
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
/// Paginate creator posts
|
|
/// * `creator_id` - The id of the creator
|
|
pub async fn paginate_creator_post<S: AsRef<str> + ?Sized>(
|
|
&self,
|
|
creator_id: &S,
|
|
) -> Option<PaginatedCreatorPosts> {
|
|
match self.client.paginate_creator_post(creator_id).await {
|
|
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),
|
|
"Failed to list home page's posts."
|
|
);
|
|
fanbox_api_quick_test!(
|
|
test_list_supporting_post,
|
|
client.list_supporting_post(10),
|
|
"Failed to list supported creators' posts."
|
|
);
|
|
fanbox_api_quick_test!(
|
|
test_list_supporting_plan,
|
|
client.list_supporting_plan(),
|
|
"Failed to list all supporting plans."
|
|
);
|
|
fanbox_api_quick_test!(
|
|
test_get_creator,
|
|
client.get_creator("mozukun43"),
|
|
"Failed to list all supporting plans."
|
|
);
|