diff --git a/Cargo.lock b/Cargo.lock index cd179b7c..5186ad0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1471,6 +1471,7 @@ dependencies = [ "regex", "reqwest", "rusqlite", + "serde", "tokio", "url", "urlparse", diff --git a/Cargo.toml b/Cargo.toml index 4484cdea..049d01e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ regex = "1" reqwest = { version = "0.11", features = ["brotli", "deflate", "gzip", "socks", "stream"] } rusqlite = { version = "0.29", features = ["bundled", "chrono"], optional = true } RustyXML = "0.3" +serde = "1" tokio = { version = "1.27", features = ["rt", "macros", "rt-multi-thread", "time"] } url = "2.3" urlparse = "0.7" diff --git a/src/download.rs b/src/download.rs index ae90f0dd..bf4bd56b 100644 --- a/src/download.rs +++ b/src/download.rs @@ -386,8 +386,10 @@ pub async fn download_artwork_app( let json_file = base.join(format!("{}.json", id)); let mut datas = PixivData::new(id).unwrap(); datas.from_app_illust(&data); + let mut web_used = false; if data.caption_is_empty() && helper.use_web_description() { if let Some(data) = pw.get_artwork_ajax(id).await { + web_used = true; if let Some(desc) = data["description"] .as_str() .or_else(|| data["illustComment"].as_str()) @@ -396,6 +398,15 @@ pub async fn download_artwork_app( } } } + if helper.add_history() && !web_used { + if let Err(e) = ac.add_illust_to_browsing_history(vec![id]).await { + println!( + "{} {}", + gettext("Warning: Failed to add artwork to history:"), + e + ); + } + } let datas = Arc::new(datas); let json_data = JSONDataFile::from(Arc::clone(&datas)); if !json_data.save(&json_file) { diff --git a/src/opthelper.rs b/src/opthelper.rs index 0d3c0cf5..a998dd45 100644 --- a/src/opthelper.rs +++ b/src/opthelper.rs @@ -51,6 +51,18 @@ pub struct OptHelper { } impl OptHelper { + /// Whether to add artworks to pixiv's history. Only works for APP API. + pub fn add_history(&self) -> bool { + if self.opt.get_ref().add_history.is_some() { + return self.opt.get_ref().add_history.unwrap(); + } + if self.settings.get_ref().have_bool("add-history") { + return self.settings.get_ref().get_bool("add-history").unwrap(); + } + false + } + + /// return author name filters pub fn author_name_filters<'a>(&'a self) -> Option>> { if self.settings.get_ref().have("author-name-filters") { return Some(self._author_name_filters.get_ref()); diff --git a/src/opts.rs b/src/opts.rs index 82a544c4..c9683931 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -128,6 +128,8 @@ pub struct CommandOpts { pub use_app_api: Option, /// Whether to use description from Web API when description from APP API is empty. pub use_web_description: Option, + /// Whether to add artworks to pixiv's history. Only works for APP API. + pub add_history: Option, } impl CommandOpts { @@ -174,6 +176,7 @@ impl CommandOpts { refresh_token: None, use_app_api: None, use_web_description: None, + add_history: None, } } @@ -645,6 +648,20 @@ pub fn parse_cmd() -> Option { HasArg::Maybe, getopts::Occur::Optional, ); + opts.opt( + "", + "add-history", + format!( + "{} ({} {})", + gettext("Whether to add artworks to pixiv's history. Only works for APP API."), + gettext("Default:"), + "yes" + ) + .as_str(), + "yes/no", + HasArg::Maybe, + getopts::Occur::Optional, + ); let result = match opts.parse(&argv[1..]) { Ok(m) => m, Err(err) => { @@ -1033,6 +1050,19 @@ pub fn parse_cmd() -> Option { return None; } } + match parse_optional_opt(&result, "add-history", true, parse_bool) { + Ok(b) => re.as_mut().unwrap().add_history = b, + Err(e) => { + println!( + "{} {}", + gettext("Failed to parse :") + .replace("", "add-history") + .as_str(), + e + ); + return None; + } + } re } diff --git a/src/pixiv_app.rs b/src/pixiv_app.rs index 08554027..b5724c2d 100644 --- a/src/pixiv_app.rs +++ b/src/pixiv_app.rs @@ -256,6 +256,25 @@ impl PixivAppClientInternal { } Ok(obj) } + + pub async fn add_illust_to_browsing_history( + &self, + ids: Vec, + ) -> Result<(), PixivDownloaderError> { + self.auto_handle().await?; + let params: Vec<_> = ids.iter().map(|id| ("illust_ids[]", id)).collect(); + let re = self + .client + .post( + "https://app-api.pixiv.net/v2/user/browsing-history/illust/add", + None, + Some(params), + ) + .await + .ok_or("Failed to add illust to browsing history.")?; + handle_error(re).await?; + Ok(()) + } } #[derive(Clone)] diff --git a/src/push/every_push.rs b/src/push/every_push.rs index 6eac5b18..9bdae023 100644 --- a/src/push/every_push.rs +++ b/src/push/every_push.rs @@ -9,16 +9,22 @@ pub enum EveryPushTextType { Markdown, } -impl std::fmt::Display for EveryPushTextType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl AsRef for EveryPushTextType { + fn as_ref(&self) -> &str { match self { - EveryPushTextType::Text => write!(f, "text"), - EveryPushTextType::Image => write!(f, "image"), - EveryPushTextType::Markdown => write!(f, "markdown"), + EveryPushTextType::Text => "text", + EveryPushTextType::Image => "image", + EveryPushTextType::Markdown => "markdown", } } } +impl std::fmt::Display for EveryPushTextType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", AsRef::::as_ref(self)) + } +} + pub struct EveryPushClient { client: WebClient, server: String, @@ -39,22 +45,26 @@ impl EveryPushClient { /// * `typ` - text type /// /// For more information, see [API document](https://github.com/PeanutMelonSeedBigAlmond/EveryPush.Server/blob/main/api.md#推送消息) - pub async fn push_message( + pub async fn push_message< + P: AsRef + ?Sized, + T: AsRef + ?Sized, + I: AsRef + ?Sized, + >( &self, - push_token: String, - text: String, - title: Option, + push_token: &P, + text: &T, + title: Option<&I>, typ: Option, ) -> Result<(), String> { - let mut params: HashMap = HashMap::new(); - params.insert(String::from("pushToken"), push_token); - params.insert(String::from("text"), text); + let mut params = HashMap::new(); + params.insert("pushToken", push_token.as_ref()); + params.insert("text", text.as_ref()); match title { - Some(t) => params.insert(String::from("title"), t), + Some(t) => params.insert("title", t.as_ref()), None => None, }; - match typ { - Some(t) => params.insert(String::from("type"), format!("{}", t)), + match &typ { + Some(t) => params.insert("type", t.as_ref()), None => None, }; let re = self @@ -92,9 +102,9 @@ async fn test_every_push_push() { let client = EveryPushClient::new(server); match client .push_message( - token, - String::from("Push Test"), - Some(String::from("Push")), + &token, + "Push Test", + Some("Push"), Some(EveryPushTextType::Text), ) .await diff --git a/src/push/pushdeer.rs b/src/push/pushdeer.rs index b4420ac6..f3838de4 100644 --- a/src/push/pushdeer.rs +++ b/src/push/pushdeer.rs @@ -38,10 +38,14 @@ impl PushdeerClient { /// push text message to server /// * `pushkey` - push key /// * `text` - text - pub async fn push_text_message(&self, pushkey: String, text: String) -> Result<(), String> { - let mut params: HashMap = HashMap::new(); - params.insert(String::from("pushkey"), pushkey); - params.insert(String::from("text"), text); + pub async fn push_text_message + ?Sized, T: AsRef + ?Sized>( + &self, + pushkey: &P, + text: &T, + ) -> Result<(), String> { + let mut params = HashMap::new(); + params.insert("pushkey", pushkey.as_ref()); + params.insert("text", text.as_ref()); let re = self .client .post(format!("{}/message/push", self.server), None, Some(params)) @@ -53,11 +57,15 @@ impl PushdeerClient { /// push image message to server /// * `pushkey` - push key /// * `image` - image URL - pub async fn push_image(&self, pushkey: String, image: String) -> Result<(), String> { - let mut params: HashMap = HashMap::new(); - params.insert(String::from("pushkey"), pushkey); - params.insert(String::from("text"), image); - params.insert(String::from("type"), String::from("image")); + pub async fn push_image + ?Sized, I: AsRef + ?Sized>( + &self, + pushkey: &P, + image: &I, + ) -> Result<(), String> { + let mut params = HashMap::new(); + params.insert("pushkey", pushkey.as_ref()); + params.insert("text", image.as_ref()); + params.insert("type", "image"); let re = self .client .post(format!("{}/message/push", self.server), None, Some(params)) @@ -70,17 +78,21 @@ impl PushdeerClient { /// * `pushkey` - push key /// * `title` - title /// * `text` - markdown text - pub async fn push_markdown_message( + pub async fn push_markdown_message< + P: AsRef + ?Sized, + T: AsRef + ?Sized, + E: AsRef + ?Sized, + >( &self, - pushkey: String, - title: String, - text: String, + pushkey: &P, + title: &T, + text: &E, ) -> Result<(), String> { - let mut params: HashMap = HashMap::new(); - params.insert(String::from("pushkey"), pushkey); - params.insert(String::from("text"), title); - params.insert(String::from("desp"), text); - params.insert(String::from("type"), String::from("markdown")); + let mut params = HashMap::new(); + params.insert("pushkey", pushkey.as_ref()); + params.insert("text", title.as_ref()); + params.insert("desp", text.as_ref()); + params.insert("type", "markdown"); let re = self .client .post(format!("{}/message/push", self.server), None, Some(params)) @@ -92,22 +104,22 @@ impl PushdeerClient { pushdeer_api_quick_test!( test_push_text_message, - client.push_text_message(token, String::from("Test message")), + client.push_text_message(&token, "Test message"), "Failed to send text message:" ); pushdeer_api_quick_test!( test_push_image, - client.push_image(token, String::from("https://pd.lifegpc.com/proxy/pixiv/112199539_p0.jpg?url=https%3A%2F%2Fi.pximg.net%2Fimg-original%2Fimg%2F2023%2F10%2F02%2F00%2F00%2F38%2F112199539_p0.jpg&sign=4f429e69218669e226e1171d2499f49ac7f41f56c3a8275619641c0a3327a394dae63dcd145367d66a3735279dc59b39219eb4d4c20bfe5afb7e801a2e7c2496")), + client.push_image(&token, "https://pd.lifegpc.com/proxy/pixiv/112199539_p0.jpg?url=https%3A%2F%2Fi.pximg.net%2Fimg-original%2Fimg%2F2023%2F10%2F02%2F00%2F00%2F38%2F112199539_p0.jpg&sign=4f429e69218669e226e1171d2499f49ac7f41f56c3a8275619641c0a3327a394dae63dcd145367d66a3735279dc59b39219eb4d4c20bfe5afb7e801a2e7c2496"), "Failed to send image message:" ); pushdeer_api_quick_test!( test_push_markdown_message, client.push_markdown_message( - token, - String::from("Test title"), - String::from("# Test Header\n[link](https://github.com/lifegpc/pixiv_downloader)") + &token, + "Test title", + "# Test Header\n[link](https://github.com/lifegpc/pixiv_downloader)" ), "Failed to send markdown message:" ); diff --git a/src/settings_list.rs b/src/settings_list.rs index 8433f146..3fe46f16 100644 --- a/src/settings_list.rs +++ b/src/settings_list.rs @@ -66,6 +66,7 @@ pub fn get_settings_list() -> Vec { SettingDes::new("cors-allow-all", gettext("Whether to allow all domains to send CORS requests."), JsonValueType::Boolean, None).unwrap(), SettingDes::new("use-app-api", gettext("Whether to use Pixiv APP API first."), JsonValueType::Boolean, None).unwrap(), SettingDes::new("use-web-description", gettext("Whether to use description from Web API when description from APP API is empty."), JsonValueType::Boolean, None).unwrap(), + SettingDes::new("add-history", gettext("Whether to add artworks to pixiv's history. Only works for APP API."), JsonValueType::Boolean, None).unwrap(), ] } diff --git a/src/webclient.rs b/src/webclient.rs index a4ca4da7..f696811e 100644 --- a/src/webclient.rs +++ b/src/webclient.rs @@ -10,6 +10,7 @@ use crate::opthelper::get_helper; use json::JsonValue; use proc_macros::print_error; use reqwest::{Client, ClientBuilder, IntoUrl, Request, Response}; +use serde::ser::Serialize; use std::collections::HashMap; use std::default::Default; use std::sync::atomic::AtomicBool; @@ -390,11 +391,11 @@ impl WebClient { self.handle_req_middlewares(r.build()?) } - pub async fn post( + pub async fn post( &self, url: U, headers: H, - form: Option>, + form: Option, ) -> Option { let mut count = 0i64; let retry = self.get_retry(); @@ -429,11 +430,11 @@ impl WebClient { None } - pub async fn _apost2( + pub async fn _apost2( &self, url: U, headers: H, - form: Option>, + form: Option, ) -> Option { let r = print_error!( gettext("Failed to generate request:"), @@ -448,11 +449,11 @@ impl WebClient { } /// Generate a POST request - pub fn _apost( + pub fn _apost( &self, url: U, headers: H, - form: Option>, + form: Option, ) -> Result { let s = url.as_str(); if self.get_verbose() {