mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-07-08 01:32:41 +08:00
Add support for download fanbox creator's info
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#[cfg(feature = "exif")]
|
||||
use super::exif::ExifDataSource;
|
||||
use crate::fanbox::post::FanboxPost;
|
||||
use crate::ext::json::ToJson2;
|
||||
use crate::opt::author_name_filter::AuthorFiler;
|
||||
use crate::opthelper::get_helper;
|
||||
use crate::pixiv_link::PixivID;
|
||||
@@ -18,11 +18,11 @@ pub struct FanboxData {
|
||||
}
|
||||
|
||||
impl FanboxData {
|
||||
pub fn new<T: ToPixivID>(id: T, post: &FanboxPost) -> Option<Self> {
|
||||
pub fn new<T: ToPixivID, D: ToJson2>(id: T, data: D) -> Option<Self> {
|
||||
match id.to_pixiv_id() {
|
||||
Some(id) => Some(Self {
|
||||
id,
|
||||
raw: post.get_json().clone(),
|
||||
raw: data.to_json2(),
|
||||
#[cfg(feature = "exif")]
|
||||
exif_data: None,
|
||||
}),
|
||||
|
||||
146
src/download.rs
146
src/download.rs
@@ -17,6 +17,8 @@ use crate::ext::any::AsAny;
|
||||
use crate::ext::try_err::TryErr;
|
||||
use crate::fanbox::article::block::FanboxArticleBlock;
|
||||
use crate::fanbox::check::CheckUnknown;
|
||||
use crate::fanbox::creator::FanboxCreator;
|
||||
use crate::fanbox::creator::FanboxProfileItem;
|
||||
use crate::fanbox::post::FanboxPost;
|
||||
use crate::fanbox_api::FanboxClient;
|
||||
use crate::gettext;
|
||||
@@ -34,6 +36,7 @@ use indicatif::MultiProgress;
|
||||
use json::JsonValue;
|
||||
use reqwest::IntoUrl;
|
||||
use std::fs::create_dir_all;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::task::JoinHandle;
|
||||
@@ -87,6 +90,31 @@ impl Main {
|
||||
tasks.join().await;
|
||||
}
|
||||
}
|
||||
PixivID::FanboxCreator(id) => {
|
||||
if !fc.is_inited() {
|
||||
let helper = get_helper();
|
||||
if !fc.init(helper.cookies()) {
|
||||
println!("{}", gettext("Failed to initialize fanbox api client."));
|
||||
return 1;
|
||||
}
|
||||
if !fc.check_login().await {
|
||||
return 1;
|
||||
}
|
||||
if !fc.logined() {
|
||||
println!("{}", gettext("Warning: Fanbox client is not logged in."));
|
||||
}
|
||||
}
|
||||
tasks
|
||||
.add_task(download_fanbox_creator_info(
|
||||
Arc::clone(&fc),
|
||||
id.to_owned(),
|
||||
None,
|
||||
))
|
||||
.await;
|
||||
if !download_multiple_posts {
|
||||
tasks.join().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut re = 0;
|
||||
@@ -679,3 +707,121 @@ pub async fn download_fanbox_post(
|
||||
}
|
||||
re
|
||||
}
|
||||
|
||||
pub async fn download_fanbox_creator_info(
|
||||
fc: Arc<FanboxClient>,
|
||||
id: String,
|
||||
data: Option<FanboxCreator>,
|
||||
) -> Result<(), PixivDownloaderError> {
|
||||
let data = match data {
|
||||
Some(data) => {
|
||||
let cid = data
|
||||
.creator_id()
|
||||
.try_err(gettext("Failed to get creator's id."))?;
|
||||
if id == cid {
|
||||
Some(data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let data = match data {
|
||||
Some(data) => data,
|
||||
None => fc
|
||||
.get_creator(&id)
|
||||
.await
|
||||
.try_err(gettext("Failed to get creator's information."))?,
|
||||
};
|
||||
let data = Arc::new(data);
|
||||
let helper = get_helper();
|
||||
if helper.verbose() {
|
||||
println!("{:#?}", data);
|
||||
}
|
||||
match data.check_unknown() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"{} {}",
|
||||
gettext("Warning: Creator's info contains unknown data:"),
|
||||
e
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
let mut fdata = FanboxData::new(PixivID::FanboxCreator(id.clone()), &*data)
|
||||
.try_err("Failed to create data file.")?;
|
||||
let base = Arc::new(PathBuf::from(".").join(&id));
|
||||
let json_file = base.join("creator.json");
|
||||
let data_file = JSONDataFile::from(&fdata);
|
||||
data_file
|
||||
.save(&json_file)
|
||||
.try_err(gettext("Failed to save post data to file."))?;
|
||||
let tasks = TaskManager::default();
|
||||
fdata.exif_data.replace(Box::new(Arc::clone(&data)));
|
||||
let fdata = Arc::new(fdata);
|
||||
let download_multiple_files = helper.download_multiple_files();
|
||||
let mut np = 0u16;
|
||||
{
|
||||
match data.download_cover_image_url()? {
|
||||
Some(dh) => {
|
||||
tasks
|
||||
.add_task(download_fanbox_image(
|
||||
dh,
|
||||
np,
|
||||
Some(get_progress_bar()),
|
||||
Arc::clone(&fdata),
|
||||
Arc::clone(&base),
|
||||
))
|
||||
.await;
|
||||
if !download_multiple_files {
|
||||
tasks.join().await;
|
||||
}
|
||||
np += 1;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
for i in data.profile_items()?.deref() {
|
||||
match i {
|
||||
FanboxProfileItem::Image(img) => {
|
||||
let dh = img
|
||||
.download_image_url()?
|
||||
.try_err(gettext("Can not get image url."))?;
|
||||
tasks
|
||||
.add_task(download_fanbox_image(
|
||||
dh,
|
||||
np,
|
||||
Some(get_progress_bar()),
|
||||
Arc::clone(&fdata),
|
||||
Arc::clone(&base),
|
||||
))
|
||||
.await;
|
||||
if !download_multiple_files {
|
||||
tasks.join().await;
|
||||
}
|
||||
np += 1;
|
||||
}
|
||||
FanboxProfileItem::Unknown(_) => {
|
||||
return Err(PixivDownloaderError::from(gettext(
|
||||
"Unrecognized profile item type.",
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks.join().await;
|
||||
let mut re = Ok(());
|
||||
let tasks = tasks.take_finished_tasks();
|
||||
for mut task in tasks {
|
||||
let task = task.as_any_mut();
|
||||
if let Some(task) = task.downcast_mut::<JoinHandle<Result<(), PixivDownloaderError>>>() {
|
||||
let r = task.await;
|
||||
let r = match r {
|
||||
Ok(r) => r,
|
||||
Err(e) => Err(PixivDownloaderError::from(e)),
|
||||
};
|
||||
concat_pixiv_downloader_error!(re, r);
|
||||
}
|
||||
}
|
||||
re
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ pub struct DownloaderHelperBuilder {
|
||||
helper: DownloaderHelper,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl DownloaderHelper {
|
||||
pub fn builder<U: IntoUrl>(url: U) -> Result<DownloaderHelperBuilder, DownloaderError> {
|
||||
Ok(DownloaderHelperBuilder {
|
||||
@@ -110,6 +111,7 @@ impl DownloaderHelper {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl DownloaderHelperBuilder {
|
||||
pub fn build(self) -> DownloaderHelper {
|
||||
self.helper
|
||||
|
||||
@@ -14,6 +14,7 @@ pub enum PixivDownloaderError {
|
||||
Hyper(hyper::Error),
|
||||
HTTP(http::Error),
|
||||
IOError(std::io::Error),
|
||||
Fanbox(crate::fanbox::error::FanboxAPIError),
|
||||
}
|
||||
|
||||
impl From<&str> for PixivDownloaderError {
|
||||
|
||||
@@ -7,30 +7,58 @@ pub trait ToJson {
|
||||
fn to_json(&self) -> Option<JsonValue>;
|
||||
}
|
||||
|
||||
pub trait ToJson2 {
|
||||
fn to_json2(&self) -> JsonValue;
|
||||
}
|
||||
|
||||
impl ToJson for &str {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
Some(JsonValue::String(String::from(*self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson2 for &str {
|
||||
fn to_json2(&self) -> JsonValue {
|
||||
JsonValue::String(String::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson for String {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
Some(JsonValue::String(self.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson2 for String {
|
||||
fn to_json2(&self) -> JsonValue {
|
||||
JsonValue::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson for JsonValue {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson2 for JsonValue {
|
||||
fn to_json2(&self) -> JsonValue {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToJson> ToJson for &T {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
(*self).to_json()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToJson2> ToJson2 for &T {
|
||||
fn to_json2(&self) -> JsonValue {
|
||||
(*self).to_json2()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToJson> ToJson for Option<T> {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
match self {
|
||||
@@ -46,12 +74,24 @@ impl<T: ToJson> ToJson for RwLockReadGuard<'_, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToJson2> ToJson2 for RwLockReadGuard<'_, T> {
|
||||
fn to_json2(&self) -> JsonValue {
|
||||
self.deref().to_json2()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToJson> ToJson for RwLockWriteGuard<'_, T> {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
self.deref().to_json()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToJson2> ToJson2 for RwLockWriteGuard<'_, T> {
|
||||
fn to_json2(&self) -> JsonValue {
|
||||
self.deref().to_json2()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromJson
|
||||
where
|
||||
Self: Sized,
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use super::check::CheckUnknown;
|
||||
use super::error::FanboxAPIError;
|
||||
#[cfg(feature = "exif")]
|
||||
use crate::data::exif::ExifDataSource;
|
||||
use crate::ext::json::ToJson2;
|
||||
use crate::fanbox_api::FanboxClientInternal;
|
||||
use crate::parser::json::parse_u64;
|
||||
use json::JsonValue;
|
||||
use proc_macros::check_json_keys;
|
||||
use proc_macros::create_fanbox_download_helper;
|
||||
use std::convert::From;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
@@ -29,6 +33,8 @@ impl FanboxProfileImage {
|
||||
self.data["imageUrl"].as_str()
|
||||
}
|
||||
|
||||
create_fanbox_download_helper!(image_url);
|
||||
|
||||
#[inline]
|
||||
/// Create a new instance
|
||||
pub fn new(data: &JsonValue, client: Arc<FanboxClientInternal>) -> Self {
|
||||
@@ -42,6 +48,8 @@ impl FanboxProfileImage {
|
||||
pub fn thumbnail_url(&self) -> Option<&str> {
|
||||
self.data["thumbnailUrl"].as_str()
|
||||
}
|
||||
|
||||
create_fanbox_download_helper!(thumbnail_url);
|
||||
}
|
||||
|
||||
impl CheckUnknown for FanboxProfileImage {
|
||||
@@ -161,6 +169,8 @@ impl FanboxCreator {
|
||||
self.data["coverImageUrl"].as_str()
|
||||
}
|
||||
|
||||
create_fanbox_download_helper!(cover_image_url);
|
||||
|
||||
#[inline]
|
||||
pub fn description(&self) -> Option<&str> {
|
||||
self.data["description"].as_str()
|
||||
@@ -292,3 +302,19 @@ impl Debug for FanboxCreator {
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExifDataSource for FanboxCreator {
|
||||
fn image_author(&self) -> Option<String> {
|
||||
self.user_name().map(|s| s.to_owned())
|
||||
}
|
||||
|
||||
fn image_comment(&self) -> Option<String> {
|
||||
self.description().map(|s| s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson2 for FanboxCreator {
|
||||
fn to_json2(&self) -> JsonValue {
|
||||
self.data.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use super::comment_list::FanboxCommentList;
|
||||
use super::error::FanboxAPIError;
|
||||
#[cfg(feature = "exif")]
|
||||
use crate::data::exif::ExifDataSource;
|
||||
use crate::ext::json::ToJson2;
|
||||
use crate::fanbox_api::FanboxClientInternal;
|
||||
use crate::parser::json::parse_u64;
|
||||
use json::JsonValue;
|
||||
@@ -1311,6 +1312,12 @@ impl FanboxPost {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson2 for FanboxPost {
|
||||
fn to_json2(&self) -> JsonValue {
|
||||
self.get_json().clone()
|
||||
}
|
||||
}
|
||||
|
||||
fanbox_api_test!(test_get_post_info, {
|
||||
match client.get_post_info(4070200).await {
|
||||
Some(data) => {
|
||||
|
||||
@@ -11,6 +11,10 @@ lazy_static! {
|
||||
static ref RE2: Regex = Regex::new("^(https?://)?(www\\.)?fanbox\\.cc/@(?P<creator>[^/]+)/posts/(?P<id>\\d+)").unwrap();
|
||||
#[doc(hidden)]
|
||||
static ref RE3: Regex = Regex::new("^(https?://)?(?P<creator>[^./]+)\\.fanbox\\.cc/posts/(?P<id>\\d+)").unwrap();
|
||||
#[doc(hidden)]
|
||||
static ref RE4: Regex = Regex::new("^(https?://)?(?P<creator>[^./]+)\\.fanbox\\.cc(/(\\?.*)?)?$").unwrap();
|
||||
#[doc(hidden)]
|
||||
static ref RE5: Regex = Regex::new("^(https?://)?(www\\.)?fanbox\\.cc/@(?P<creator>[^/?]+)(/(\\?.*)?)?$").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -41,6 +45,8 @@ pub enum PixivID {
|
||||
Artwork(u64),
|
||||
/// Fanbox post
|
||||
FanboxPost(FanboxPostID),
|
||||
/// Fanbox creator
|
||||
FanboxCreator(String),
|
||||
}
|
||||
|
||||
pub trait ToPixivID {
|
||||
@@ -94,6 +100,25 @@ impl PixivID {
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
match RE4.captures(s) {
|
||||
Some(re) => match re.name("creator") {
|
||||
Some(creator) => match creator.as_str() {
|
||||
"www" => {}
|
||||
_ => return Some(Self::FanboxCreator(String::from(creator.as_str()))),
|
||||
},
|
||||
None => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
match RE5.captures(s) {
|
||||
Some(re) => match re.name("creator") {
|
||||
Some(creator) => {
|
||||
return Some(Self::FanboxCreator(String::from(creator.as_str())));
|
||||
}
|
||||
None => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -108,6 +133,9 @@ impl PixivID {
|
||||
id.creator_id, id.post_id
|
||||
)
|
||||
}
|
||||
Self::FanboxCreator(id) => {
|
||||
format!("https://www.fanbox.cc/@{}", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,6 +149,9 @@ impl ToJson for PixivID {
|
||||
&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()}),
|
||||
),
|
||||
&PixivID::FanboxCreator(id) => Some(
|
||||
json::value!({"type": "fanbox_creator", "creator_id": id.clone(), "link": self.to_link()}),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,6 +198,7 @@ impl TryInto<u64> for PixivID {
|
||||
match self {
|
||||
Self::Artwork(id) => Ok(id),
|
||||
Self::FanboxPost(id) => Ok(id.post_id),
|
||||
Self::FanboxCreator(_) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,6 +209,7 @@ impl TryInto<u64> for &PixivID {
|
||||
match self {
|
||||
PixivID::Artwork(id) => Ok(id.clone()),
|
||||
PixivID::FanboxPost(id) => Ok(id.post_id.clone()),
|
||||
PixivID::FanboxCreator(_) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::opthelper::get_helper;
|
||||
use crate::parser::metadata::MetaDataParser;
|
||||
use crate::webclient::WebClient;
|
||||
use json::JsonValue;
|
||||
use reqwest::IntoUrl;
|
||||
use reqwest::Response;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::RwLock;
|
||||
@@ -161,25 +160,6 @@ impl PixivWebClient {
|
||||
Some(body.clone())
|
||||
}
|
||||
|
||||
pub async fn download_image<U: IntoUrl + Clone>(&self, url: U) -> Option<Response> {
|
||||
self.auto_init();
|
||||
let r = self
|
||||
.client
|
||||
.get(url, json::object! {"referer": "https://www.pixiv.net/"})
|
||||
.await;
|
||||
if r.is_none() {
|
||||
return None;
|
||||
}
|
||||
let r = r.unwrap();
|
||||
let status = r.status();
|
||||
let code = status.as_u16();
|
||||
if code >= 400 {
|
||||
println!("{} {}", gettext("Failed to download image:"), status);
|
||||
return None;
|
||||
}
|
||||
Some(r)
|
||||
}
|
||||
|
||||
pub async fn get_artwork_ajax(&self, id: u64) -> Option<JsonValue> {
|
||||
self.auto_init();
|
||||
let r = self
|
||||
|
||||
Reference in New Issue
Block a user