diff --git a/Cargo.lock b/Cargo.lock index f2cd6142..21ef2c78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1576,6 +1576,7 @@ dependencies = [ "multipart", "openssl", "parse-size", + "percent-encoding", "proc_macros", "rand", "regex", diff --git a/Cargo.toml b/Cargo.toml index 4a75e50e..a591ef63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ modular-bitfield = "0.11" multipart = { features = ["server"], git = 'https://github.com/lifegpc/multipart', optional = true, default-features = false } openssl = { version = "0.10", optional = true } parse-size = "1" +percent-encoding = { version = "*", optional = true } proc_macros = { path = "proc_macros" } rand = { version = "0", optional = true } regex = "1" @@ -65,7 +66,7 @@ db_all = ["db", "db_sqlite"] db_sqlite = ["rusqlite"] docker = [] exif = ["bindgen", "c_fixed_string", "cmake", "link-cplusplus", "utf16string"] -server = ["async-trait", "base64", "db", "hex", "hyper", "multipart", "openssl", "serde_json", "rand", "serde_urlencoded"] +server = ["async-trait", "base64", "db", "hex", "hyper", "multipart", "openssl", "serde_json", "rand", "serde_urlencoded", "percent-encoding"] ugoira = ["avdict", "bindgen", "cmake", "link-cplusplus"] [patch.crates-io] diff --git a/proc_macros/proc_macros.rs b/proc_macros/proc_macros.rs index 225d6754..818ba303 100644 --- a/proc_macros/proc_macros.rs +++ b/proc_macros/proc_macros.rs @@ -996,7 +996,7 @@ pub fn pushdeer_api_quick_test(item: TokenStream) -> TokenStream { match std::env::var("PUSHDEER_SERVER") { Ok(server) => match std::env::var("PUSHDEER_TOKEN") { Ok(token) => { - let client = PushdeerClient::new(server); + let client = PushdeerClient::new(&server); match #expr.await { Ok(_) => {}, Err(e) => { diff --git a/src/db/push_task.rs b/src/db/push_task.rs index bed8d225..4e52b6a2 100644 --- a/src/db/push_task.rs +++ b/src/db/push_task.rs @@ -93,10 +93,48 @@ pub struct EveryPushConfig { pub add_translated_tag: bool, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PushDeerConfig { + /// Push server + pub push_server: String, + /// Push key + pub pushkey: String, + /// Push type + pub typ: EveryPushTextType, + #[serde(default = "defualt_author_locations")] + /// Author locations + /// Not supported when type is `Image`. + pub author_locations: Vec, + #[serde(default = "default_true")] + /// Whether to filter author name + pub filter_author: bool, + #[serde(default = "default_true")] + /// Whether to add artwork link + pub add_link: bool, + #[serde(default = "default_true")] + /// Whether to add artwork link to title + /// Supported when type is `Text`. + pub add_link_to_title: bool, + #[serde(default = "default_true")] + /// Whether to add image link to image + pub add_link_to_image: bool, + #[serde(default = "default_true")] + /// Whether to add tags + pub add_tags: bool, + #[serde(default = "default_true")] + /// Whether to add AI tag + pub add_ai_tag: bool, + #[serde(default = "default_true")] + /// Whether to add translated tag + pub add_translated_tag: bool, +} + #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "camelCase")] pub enum PushConfig { EveryPush(EveryPushConfig), + PushDeer(PushDeerConfig), } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/push/pushdeer.rs b/src/push/pushdeer.rs index f3838de4..8a2682ab 100644 --- a/src/push/pushdeer.rs +++ b/src/push/pushdeer.rs @@ -9,10 +9,10 @@ pub struct PushdeerClient { } impl PushdeerClient { - pub fn new(server: String) -> Self { + pub fn new + ?Sized>(server: &S) -> Self { Self { client: WebClient::default(), - server, + server: server.as_ref().to_owned(), } } diff --git a/src/server/context.rs b/src/server/context.rs index 3799dbb9..be11b3b8 100644 --- a/src/server/context.rs +++ b/src/server/context.rs @@ -11,6 +11,7 @@ use crate::get_helper; use crate::gettext; use crate::pixiv_app::PixivAppClient; use crate::pixiv_web::PixivWebClient; +use crate::utils::get_file_name_from_url; use futures_util::lock::Mutex; use hyper::{http::response::Builder, Body, Request, Response}; use json::JsonValue; @@ -63,7 +64,15 @@ impl ServerContext { sha512.update(u.as_str().as_bytes()); let sign = hex::encode(sha512.finish()); map.insert("sign", &sign); - let url = format!("{}/proxy/pixiv?{}", base, serde_urlencoded::to_string(map)?); + let name = get_file_name_from_url(u.as_str()) + .map(|v| format!("/{}", v)) + .unwrap_or_default(); + let url = format!( + "{}/proxy/pixiv{}?{}", + base, + name, + serde_urlencoded::to_string(map)? + ); Ok(url) } diff --git a/src/server/push/task/pixiv_send_message.rs b/src/server/push/task/pixiv_send_message.rs index 52343b2b..d07bd0a6 100644 --- a/src/server/push/task/pixiv_send_message.rs +++ b/src/server/push/task/pixiv_send_message.rs @@ -1,12 +1,14 @@ use super::super::super::preclude::*; -use crate::db::push_task::{AuthorLocation, EveryPushConfig, PushConfig}; +use crate::db::push_task::{AuthorLocation, EveryPushConfig, PushConfig, PushDeerConfig}; use crate::error::PixivDownloaderError; use crate::opt::author_name_filter::AuthorFiler; use crate::parser::description::DescriptionParser; use crate::pixivapp::illust::PixivAppIllust; use crate::push::every_push::{EveryPushClient, EveryPushTextType}; +use crate::push::pushdeer::PushdeerClient; use crate::{get_helper, gettext}; use json::JsonValue; +use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; struct RunContext<'a> { ctx: Arc, @@ -99,6 +101,7 @@ impl<'a> RunContext<'a> { pub fn filter_author(&self) -> bool { match self.cfg { PushConfig::EveryPush(e) => e.filter_author, + PushConfig::PushDeer(e) => e.filter_author, } } @@ -344,9 +347,190 @@ impl<'a> RunContext<'a> { } Ok(()) } + + pub async fn send_push_deer(&self, cfg: &PushDeerConfig) -> Result<(), PixivDownloaderError> { + let client = PushdeerClient::new(&cfg.push_server); + match &cfg.typ { + EveryPushTextType::Text => { + let mut title = self.title().map(|s| { + if cfg.add_link_to_title { + if let Some(id) = self.id() { + format!("[{}](https://www.pixiv.net/artworks/{})", s, id) + } else { + s.to_owned() + } + } else { + s.to_owned() + } + }); + let author = self.author(); + if cfg.author_locations.contains(&AuthorLocation::Title) { + if let Some(t) = &title { + if let Some(a) = &author { + let au = if let Some(uid) = self.user_id() { + format!("[{}](https://www.pixiv.net/users/{})", a, uid) + } else { + a.to_owned() + }; + title = Some(format!("{} - {}", t, au)); + } + } + } + let mut text = String::new(); + if let Some(t) = &title { + text.push_str(t); + text.push_str("\n\n"); + } + if cfg.author_locations.contains(&AuthorLocation::Top) { + if let Some(a) = &author { + self.add_author(&mut text, a); + } + } + if cfg.add_link { + if let Some(id) = self.id() { + let link = format!("https://www.pixiv.net/artworks/{}", id); + text.push_str(&format!("[{}]({}) \n", link, link)); + } + } + if let Some(desc) = self.desc() { + let mut p = DescriptionParser::new(true); + p.parse(desc)?; + while !text.ends_with("\n\n") { + text.push_str("\n"); + } + text.push_str(&p.data); + if !p.data.ends_with("\n\n") { + text.push_str("\n\n"); + } + } + if cfg.add_tags { + if cfg.add_ai_tag && self.is_ai() { + text.push_str("#"); + text.push_str(gettext("AI generated")); + text.push_str(" "); + } + if let Some(i) = self.illust { + for tag in i.tags() { + if let Some(name) = tag.name() { + let encoded = percent_encode(name.as_bytes(), NON_ALPHANUMERIC); + text.push_str(&format!( + "[#{}](https://www.pixiv.net/tags/{}) ", + name, &encoded + )); + if cfg.add_translated_tag { + if let Some(t) = tag.translated_name() { + text.push_str(&format!( + "[#{}](https://www.pixiv.net/tags/{}) ", + t, &encoded + )); + } + } + } + } + } + text.push_str(" \n"); + } + if cfg.author_locations.contains(&AuthorLocation::Bottom) { + if let Some(a) = &author { + self.add_author(&mut text, a); + } + } + client.push_text_message(&cfg.pushkey, &text).await?; + } + EveryPushTextType::Markdown => { + let mut title = self.title().map(|s| s.to_owned()); + let author = self.author(); + if cfg.author_locations.contains(&AuthorLocation::Title) { + if let Some(t) = &title { + if let Some(a) = &author { + title = Some(format!("{} - {}", t, a)); + } + } + } + let title = title.ok_or("title not found.")?; + let mut text = String::new(); + let len = self.len().unwrap_or(1); + for i in 0..len { + if let Some(url) = self.get_image_url(i).await? { + if cfg.add_link_to_image { + text.push_str("["); + } + text.push_str(&format!("![​]({})", url)); + if cfg.add_link_to_image { + text.push_str(&format!("]({})", url)); + } + } + } + if cfg.author_locations.contains(&AuthorLocation::Top) { + if let Some(a) = &author { + self.add_author(&mut text, a); + } + } + if cfg.add_link { + if let Some(id) = self.id() { + let link = format!("https://www.pixiv.net/artworks/{}", id); + text.push_str(&format!("[{}]({}) \n", link, link)); + } + } + if let Some(desc) = self.desc() { + let mut p = DescriptionParser::new(true); + p.parse(desc)?; + while !text.ends_with("\n\n") { + text.push_str("\n"); + } + text.push_str(&p.data); + if !p.data.ends_with("\n\n") { + text.push_str("\n\n"); + } + } + if cfg.add_tags { + if cfg.add_ai_tag && self.is_ai() { + text.push_str("#"); + text.push_str(gettext("AI generated")); + text.push_str(" "); + } + if let Some(i) = self.illust { + for tag in i.tags() { + if let Some(name) = tag.name() { + let encoded = percent_encode(name.as_bytes(), NON_ALPHANUMERIC); + text.push_str(&format!( + "[#{}](https://www.pixiv.net/tags/{}) ", + name, &encoded + )); + if cfg.add_translated_tag { + if let Some(t) = tag.translated_name() { + text.push_str(&format!( + "[#{}](https://www.pixiv.net/tags/{}) ", + t, &encoded + )); + } + } + } + } + } + text.push_str(" \n"); + } + if cfg.author_locations.contains(&AuthorLocation::Bottom) { + if let Some(a) = &author { + self.add_author(&mut text, a); + } + } + client + .push_markdown_message(&cfg.pushkey, &title, &text) + .await?; + } + EveryPushTextType::Image => { + let url = self.get_image_url(0).await?.ok_or("image url not found.")?; + client.push_image(&cfg.pushkey, &url).await?; + } + } + Ok(()) + } + pub async fn run(&self) -> Result<(), PixivDownloaderError> { match self.cfg { PushConfig::EveryPush(e) => self.send_every_push(e).await, + PushConfig::PushDeer(e) => self.send_push_deer(e).await, } } }