From 289be3a1f08a3e0967af9d251145a9b25b444dfc Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sat, 28 Oct 2023 09:13:32 +0000 Subject: [PATCH] Update push --- src/db/push_task.rs | 3 + src/parser/description.rs | 146 +++++++++++++++--- src/server/push/task/pixiv_send_message.rs | 169 ++++++++++++++++++++- 3 files changed, 296 insertions(+), 22 deletions(-) diff --git a/src/db/push_task.rs b/src/db/push_task.rs index 2273781..bb124f7 100644 --- a/src/db/push_task.rs +++ b/src/db/push_task.rs @@ -76,6 +76,9 @@ pub struct EveryPushConfig { #[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, } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/parser/description.rs b/src/parser/description.rs index 258023d..00d9f90 100644 --- a/src/parser/description.rs +++ b/src/parser/description.rs @@ -1,3 +1,4 @@ +use crate::error::PixivDownloaderError; use crate::gettext; use crate::pixiv_link::remove_track; use html_parser::Dom; @@ -31,7 +32,23 @@ impl DescriptionNode { self.attrs.insert(String::from(k), String::from(v)) } - pub fn is_link(&self) -> bool { + pub fn is_em(&self) -> bool { + self.tag == "em" || self.tag == "i" + } + + pub fn is_headline(&self) -> bool { + match self.tag.as_str() { + "h1" => true, + "h2" => true, + "h3" => true, + "h4" => true, + "h5" => true, + "h6" => true, + _ => false, + } + } + + pub fn is_link(&self, md_mode: bool) -> bool { if self.tag != "a" { return false; } @@ -39,12 +56,36 @@ impl DescriptionNode { return false; } let href = self.attrs.get("href").unwrap(); - if href.as_str() == self.data.as_str() { + if !md_mode && href.as_str() == self.data.as_str() { return false; } true } + pub fn is_paragraph(&self) -> bool { + self.tag == "p" || self.tag == "paragraph" + } + + pub fn is_strong(&self) -> bool { + self.tag == "strong" || self.tag == "b" + } + + pub fn to_em(&self) -> String { + format!("*{}*", self.data.as_str()) + } + + pub fn to_headline(&self) -> String { + let mut s = String::from("#"); + let level = self.tag.chars().last().unwrap().to_digit(10).unwrap(); + for _ in 1..level { + s += "#"; + } + s += " "; + s += self.data.as_str(); + s += "\n"; + s + } + pub fn to_link(&self) -> String { format!( "[{}]({})", @@ -52,6 +93,16 @@ impl DescriptionNode { self.attrs.get("href").unwrap() ) } + + pub fn to_paragraph(&self) -> String { + let mut s = self.data.clone(); + s += "\n\n"; + s + } + + pub fn to_strong(&self) -> String { + format!("**{}**", self.data.as_str()) + } } /// A simple HTML parser to parse description HTML @@ -60,13 +111,16 @@ pub struct DescriptionParser { nodes: Vec, /// Output pub data: String, + /// Markdown mode + md_mode: bool, } impl DescriptionParser { - pub fn new() -> Self { + pub fn new(md_mode: bool) -> Self { Self { nodes: Vec::new(), data: String::from(""), + md_mode, } } @@ -85,10 +139,11 @@ impl DescriptionParser { if tag == "script" || tag == "style" { return; } else if tag == "br" { + let br = if self.md_mode { " \n" } else { "\n" }; if self.nodes.len() == 0 { - self.data += "\n"; + self.data += br; } else { - self.nodes.last_mut().unwrap().data += "\n"; + self.nodes.last_mut().unwrap().data += br; } return; } @@ -109,56 +164,86 @@ impl DescriptionParser { self.iter(n); } let node = self.nodes.pop().unwrap(); - let s = if node.is_link() { + let mut is_paragraph = false; + let s = if node.is_link(self.md_mode) { node.to_link() + } else if self.md_mode && node.is_headline() { + node.to_headline() + } else if self.md_mode && node.is_paragraph() { + is_paragraph = true; + node.to_paragraph() + } else if self.md_mode && node.is_strong() { + node.to_strong() + } else if self.md_mode && node.is_em() { + node.to_em() } else { node.data }; if self.nodes.len() == 0 { + while self.md_mode && is_paragraph && !self.data.ends_with("\n\n") { + self.data += "\n"; + } self.data += s.as_str(); } else { - self.nodes.last_mut().unwrap().data += s.as_str(); + let n = self.nodes.last_mut().unwrap(); + while self.md_mode && is_paragraph && !n.data.ends_with("\n\n") { + n.data += "\n"; + } + n.data += s.as_str(); } } } } - pub fn parse(&mut self, desc: &str) -> Result<(), ()> { - let r = Dom::parse(desc); + pub fn parse + ?Sized>(&mut self, desc: &S) -> Result<(), PixivDownloaderError> { + let r = Dom::parse(desc.as_ref()); if r.is_err() { - println!("{} {}", gettext("Failed to parse HTML:"), r.unwrap_err()); - return Err(()); + return Err(format!("{} {}", gettext("Failed to parse HTML:"), r.unwrap_err()).into()); } let dom = r.unwrap(); if dom.errors.len() > 0 { - println!("{}", gettext("Some errors occured during parsing:")); + let mut s = String::from(gettext("Some errors occured during parsing:")); for i in dom.errors.iter() { - println!("{}", i); + s += "\n"; + s += i; } + return Err(s.into()); } for node in dom.children.iter() { self.iter(node) } if self.nodes.len() != 0 { - println!( + println!(); + return Err(format!( "{} {:?}", gettext("There are some nodes still in stack:"), self.nodes - ); - return Err(()); + ) + .into()); } Ok(()) } } -pub fn parse_description(desc: &str) -> Option { - let mut p = DescriptionParser::new(); +pub fn parse_description + ?Sized>(desc: &S) -> Option { + let mut p = DescriptionParser::new(false); match p.parse(desc) { Ok(_) => Some(p.data), - Err(_) => None, + Err(e) => { + println!("{}", e); + None + } } } +pub fn convert_description_to_md + ?Sized>( + desc: &S, +) -> Result { + let mut p = DescriptionParser::new(true); + p.parse(desc)?; + Ok(p.data) +} + #[test] fn test_parse_description() { assert_eq!( @@ -178,3 +263,26 @@ fn test_parse_description() { parse_description("https://a.com") ) } + +#[test] +fn test_convert_description_to_md() { + assert_eq!( + String::from("test \n[https://a.com](https://a.com)"), + convert_description_to_md( + "test
https://a.com" + ) + .unwrap() + ); + assert_eq!( + String::from("# He\n## Be\ntest"), + convert_description_to_md("

He

Be

test").unwrap() + ); + assert_eq!( + String::from("D\n\nHe\n\nBe\n\ntest"), + convert_description_to_md("D

He

Be

test").unwrap() + ); + assert_eq!( + String::from("# Head\nD\n\nHe\n\nBe\n\nt***e**s*t\n\n[Link](https://a.com)\n\n"), + convert_description_to_md("

Head

D

He

Be

test

Link

").unwrap() + ); +} diff --git a/src/server/push/task/pixiv_send_message.rs b/src/server/push/task/pixiv_send_message.rs index edd88bd..075f687 100644 --- a/src/server/push/task/pixiv_send_message.rs +++ b/src/server/push/task/pixiv_send_message.rs @@ -1,10 +1,11 @@ use super::super::super::preclude::*; use crate::db::push_task::{AuthorLocation, EveryPushConfig, PushConfig}; use crate::error::PixivDownloaderError; -use crate::get_helper; 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::{get_helper, gettext}; use json::JsonValue; struct RunContext<'a> { @@ -29,6 +30,25 @@ impl<'a> RunContext<'a> { } } + pub fn id(&self) -> Option { + match self.illust { + Some(i) => match i.id() { + Some(i) => return Some(i), + None => {} + }, + None => {} + } + match self.data { + Some(d) => d["illustId"] + .as_u64() + .or_else(|| match d["illustId"].as_str() { + Some(s) => s.parse().ok(), + None => None, + }), + None => None, + } + } + pub fn _author(&self) -> Option<&str> { match self.illust { Some(i) => match i.user_name() { @@ -58,6 +78,23 @@ impl<'a> RunContext<'a> { } } + pub fn user_id(&self) -> Option { + match self.illust { + Some(i) => match i.user_id() { + Some(u) => return Some(u), + None => {} + }, + None => {} + } + match self.data { + Some(d) => d["userId"].as_u64().or_else(|| match d["userId"].as_str() { + Some(s) => s.parse().ok(), + None => None, + }), + None => None, + } + } + /// Whether to filter author name pub fn filter_author(&self) -> bool { match self.cfg { @@ -104,11 +141,137 @@ impl<'a> RunContext<'a> { } } + pub fn add_author + ?Sized>(&self, text: &mut String, author: &S) { + text.push_str(gettext("by ")); + let author = author.as_ref(); + if let Some(uid) = self.user_id() { + text.push_str(&format!( + "[{}](https://www.pixiv.net/users/{})", + author, uid + )); + } else { + text.push_str(author); + } + text.push_str(" \n"); + } + + pub fn desc(&self) -> Option<&str> { + match self.illust { + Some(i) => { + if !i.caption_is_empty() { + return i.caption(); + } + } + None => {} + } + match self.data { + Some(d) => d["description"] + .as_str() + .or_else(|| d["illustComment"].as_str()), + None => None, + } + } + pub async fn send_every_push(&self, cfg: &EveryPushConfig) -> Result<(), PixivDownloaderError> { let client = EveryPushClient::new(&cfg.push_server); match cfg.typ { - EveryPushTextType::Text => {} - EveryPushTextType::Markdown => {} + EveryPushTextType::Text => { + 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 mut text = String::new(); + if cfg.author_locations.contains(&AuthorLocation::Top) { + if let Some(a) = &author { + text.push_str(gettext("by ")); + text.push_str(a); + text.push_str("\n"); + } + } + if cfg.add_link { + if let Some(id) = self.id() { + text.push_str(&format!("https://www.pixiv.net/artworks/{}\n", id)); + } + } + if let Some(desc) = self.desc() { + let mut p = DescriptionParser::new(false); + p.parse(desc)?; + text.push_str(&p.data); + text.push_str("\n"); + } + if cfg.author_locations.contains(&AuthorLocation::Bottom) { + if let Some(a) = &author { + text.push_str(gettext("by ")); + text.push_str(a); + text.push_str("\n"); + } + } + client + .push_message( + &cfg.push_token, + &text, + title.as_ref(), + cfg.topic_id.as_ref(), + Some(EveryPushTextType::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 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? { + 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)?; + text.push_str(&p.data); + if !p.data.ends_with("\n\n") { + text.push_str("\n\n"); + } + } + if cfg.author_locations.contains(&AuthorLocation::Bottom) { + if let Some(a) = &author { + self.add_author(&mut text, a); + } + } + client + .push_message( + &cfg.push_token, + &text, + title.as_ref(), + cfg.topic_id.as_ref(), + Some(EveryPushTextType::Markdown), + ) + .await?; + } EveryPushTextType::Image => { let mut title = self.title().map(|s| s.to_owned()); if cfg.author_locations.contains(&AuthorLocation::Title) {