mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-06-06 05:49:01 +08:00
Update push
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1469,11 +1469,13 @@ dependencies = [
|
||||
"openssl",
|
||||
"parse-size",
|
||||
"proc_macros",
|
||||
"rand",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"url",
|
||||
"urlparse",
|
||||
|
||||
@@ -37,12 +37,14 @@ multipart = { features = ["server"], git = 'https://github.com/lifegpc/multipart
|
||||
openssl = { version = "0.10", optional = true }
|
||||
parse-size = "1"
|
||||
proc_macros = { path = "proc_macros" }
|
||||
rand = { version = "0", optional = true }
|
||||
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"
|
||||
serde_json = { version = "1", optional = true }
|
||||
serde_urlencoded = { version = "*", optional = true }
|
||||
tokio = { version = "1.27", features = ["rt", "macros", "rt-multi-thread", "time"] }
|
||||
url = "2.3"
|
||||
urlparse = "0.7"
|
||||
@@ -60,7 +62,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"]
|
||||
server = ["async-trait", "base64", "db", "hex", "hyper", "multipart", "openssl", "serde_json", "rand", "serde_urlencoded"]
|
||||
ugoira = ["avdict", "bindgen", "cmake", "link-cplusplus"]
|
||||
|
||||
[patch.crates-io]
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
use crate::pixiv_app::PixivRestrictType;
|
||||
use crate::push::every_push::EveryPushTextType;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum PushTaskPixivRestrictType {
|
||||
Public,
|
||||
Private,
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum PushTaskPixivAction {
|
||||
Follow {
|
||||
restrict: PushTaskPixivRestrictType,
|
||||
restrict: PixivRestrictType,
|
||||
},
|
||||
Bookmarks {
|
||||
restrict: PushTaskPixivRestrictType,
|
||||
restrict: PixivRestrictType,
|
||||
uid: u64,
|
||||
},
|
||||
Illusts {
|
||||
@@ -31,6 +24,12 @@ pub struct PushTaskPixivConfig {
|
||||
pub act: PushTaskPixivAction,
|
||||
/// Tag translation language
|
||||
pub lang: Option<String>,
|
||||
/// Whether to use description from Web API when description from APP API is empty.
|
||||
pub use_web_description: Option<bool>,
|
||||
/// Whether to use Pixiv APP API first.
|
||||
pub use_app_api: Option<bool>,
|
||||
/// Use data from webpage first.
|
||||
pub use_webpage: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -39,6 +38,26 @@ pub enum PushTaskConfig {
|
||||
Pixiv(PushTaskPixivConfig),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Author location
|
||||
pub enum AuthorLocation {
|
||||
/// add author name to title
|
||||
Title,
|
||||
/// add author name to the top of description
|
||||
Top,
|
||||
/// add author name to the bottom of description
|
||||
Bottom,
|
||||
}
|
||||
|
||||
fn defualt_author_locations() -> Vec<AuthorLocation> {
|
||||
vec![AuthorLocation::Top]
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EveryPushConfig {
|
||||
@@ -46,11 +65,17 @@ pub struct EveryPushConfig {
|
||||
pub push_server: String,
|
||||
/// Push token
|
||||
pub push_token: String,
|
||||
#[serde(rename = "type")]
|
||||
/// Push type
|
||||
pub typ: EveryPushTextType,
|
||||
/// Push topic ID
|
||||
pub topic_id: Option<String>,
|
||||
#[serde(default = "defualt_author_locations")]
|
||||
/// Author locations
|
||||
/// If type is `Image`, this field only support [AuthorLocation::Title].
|
||||
pub author_locations: Vec<AuthorLocation>,
|
||||
#[serde(default = "default_true")]
|
||||
/// Whether to filter author name
|
||||
pub filter_author: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -74,3 +99,15 @@ pub struct PushTask {
|
||||
/// Update interval
|
||||
pub ttl: u64,
|
||||
}
|
||||
|
||||
impl PushTask {
|
||||
pub fn new(config: PushTaskConfig, push_configs: Vec<PushConfig>) -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
config,
|
||||
push_configs,
|
||||
last_updated: DateTime::UNIX_EPOCH,
|
||||
ttl: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ pub enum PixivDownloaderError {
|
||||
PixivAppError(crate::pixivapp::error::PixivAppError),
|
||||
#[cfg(feature = "serde_json")]
|
||||
SerdeJsonError(serde_json::Error),
|
||||
#[cfg(feature = "serde_urlencoded")]
|
||||
SerdeUrlencodedError(serde_urlencoded::ser::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for PixivDownloaderError {}
|
||||
|
||||
@@ -573,6 +573,12 @@ impl OptHelper {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
/// The server's host name. Used in some proxy.
|
||||
pub fn server_base(&self) -> Option<String> {
|
||||
self.settings.get_ref().get_str("server-base")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OptHelper {
|
||||
|
||||
@@ -7,17 +7,37 @@ use crate::ext::rw_lock::GetRwLock;
|
||||
use crate::opthelper::OptHelper;
|
||||
use crate::pixivapp::error::handle_error;
|
||||
use crate::pixivapp::illust::PixivAppIllust;
|
||||
use crate::pixivapp::illusts::PixivAppIllusts;
|
||||
use crate::webclient::{ReqMiddleware, WebClient};
|
||||
use crate::{get_helper, gettext};
|
||||
use chrono::{DateTime, Local, SecondsFormat, Utc};
|
||||
use json::JsonValue;
|
||||
use reqwest::{Client, Request, RequestBuilder};
|
||||
use reqwest::{Client, IntoUrl, Request, RequestBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum PixivRestrictType {
|
||||
Public,
|
||||
Private,
|
||||
All,
|
||||
}
|
||||
|
||||
impl ToString for PixivRestrictType {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
PixivRestrictType::Public => String::from("public"),
|
||||
PixivRestrictType::Private => String::from("private"),
|
||||
PixivRestrictType::All => String::from("all"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PixivAppMiddleware {
|
||||
internal: Arc<PixivAppClientInternal>,
|
||||
helper: Arc<OptHelper>,
|
||||
@@ -239,6 +259,31 @@ impl PixivAppClientInternal {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_follow(
|
||||
&self,
|
||||
restrict: &PixivRestrictType,
|
||||
) -> Result<JsonValue, PixivDownloaderError> {
|
||||
self.auto_handle().await?;
|
||||
let re = self
|
||||
.client
|
||||
.get_with_param(
|
||||
"https://app-api.pixiv.net/v2/illust/follow",
|
||||
json::object! {"restrict": restrict.to_string()},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.ok_or(gettext("Failed to get follow."))?;
|
||||
let obj = handle_error(re).await?;
|
||||
if get_helper().verbose() {
|
||||
println!(
|
||||
"{}{}",
|
||||
gettext("Follower's new illusts: "),
|
||||
obj.pretty(2).as_str()
|
||||
);
|
||||
}
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
pub async fn get_illust_details(&self, id: u64) -> Result<JsonValue, PixivDownloaderError> {
|
||||
self.auto_handle().await?;
|
||||
let re = self
|
||||
@@ -275,6 +320,24 @@ impl PixivAppClientInternal {
|
||||
handle_error(re).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_url<U: IntoUrl + Clone, E, I: std::fmt::Display + ?Sized>(
|
||||
&self,
|
||||
url: U,
|
||||
err: E,
|
||||
info: &I,
|
||||
) -> Result<JsonValue, PixivDownloaderError>
|
||||
where
|
||||
PixivDownloaderError: From<E>,
|
||||
{
|
||||
self.auto_handle().await?;
|
||||
let re = self.client.get(url, None).await.ok_or(err)?;
|
||||
let obj = handle_error(re).await?;
|
||||
if get_helper().verbose() {
|
||||
println!("{}{}", info, obj.pretty(2).as_str());
|
||||
}
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -314,6 +377,14 @@ impl PixivAppClient {
|
||||
let obj = self.internal.get_illust_details(id).await?;
|
||||
Ok(PixivAppIllust::new(obj["illust"].clone()))
|
||||
}
|
||||
|
||||
pub async fn get_follow(
|
||||
&self,
|
||||
restrict: &PixivRestrictType,
|
||||
) -> Result<PixivAppIllusts, PixivDownloaderError> {
|
||||
let obj = self.internal.get_follow(restrict).await?;
|
||||
PixivAppIllusts::new(self.internal.clone(), obj)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<PixivAppClientInternal> for PixivAppClient {
|
||||
|
||||
62
src/pixivapp/illusts.rs
Normal file
62
src/pixivapp/illusts.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use super::illust::PixivAppIllust;
|
||||
use crate::error::PixivDownloaderError;
|
||||
use crate::gettext;
|
||||
use crate::pixiv_app::PixivAppClientInternal;
|
||||
use json::JsonValue;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct PixivAppIllusts {
|
||||
pub illusts: Vec<PixivAppIllust>,
|
||||
next_url: Option<String>,
|
||||
client: Arc<PixivAppClientInternal>,
|
||||
}
|
||||
|
||||
impl PixivAppIllusts {
|
||||
#[allow(dead_code)]
|
||||
/// Get next page.
|
||||
/// # Note
|
||||
/// If no next page presented, will return a error.
|
||||
pub async fn get_next_page(&self) -> Result<Self, PixivDownloaderError> {
|
||||
match &self.next_url {
|
||||
Some(url) => Self::new(
|
||||
self.client.clone(),
|
||||
self.client
|
||||
.get_url(
|
||||
url,
|
||||
gettext("Failed to get next page of the illusts."),
|
||||
gettext("Illusts data:"),
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
None => Err("No next url, can not get next page.".into()),
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
/// Returns true if next page is presented.
|
||||
pub fn has_next_page(&self) -> bool {
|
||||
self.next_url.is_some()
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
client: Arc<PixivAppClientInternal>,
|
||||
value: JsonValue,
|
||||
) -> Result<Self, PixivDownloaderError> {
|
||||
let oillusts = &value["illusts"];
|
||||
if !oillusts.is_array() {
|
||||
return Err(gettext("Failed to parse illust list.").into());
|
||||
}
|
||||
let mut illusts = Vec::new();
|
||||
for item in oillusts.members() {
|
||||
illusts.push(PixivAppIllust::new(item.clone()));
|
||||
}
|
||||
let next_url = match value["nextUrl"].as_str() {
|
||||
Some(next_url) => Some(next_url.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
Ok(Self {
|
||||
illusts,
|
||||
next_url,
|
||||
client,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,6 @@ pub mod check;
|
||||
/// Error handling for pixiv app api
|
||||
pub mod error;
|
||||
pub mod illust;
|
||||
pub mod illusts;
|
||||
pub mod image_urls;
|
||||
pub mod tag;
|
||||
|
||||
@@ -33,10 +33,10 @@ pub struct EveryPushClient {
|
||||
}
|
||||
|
||||
impl EveryPushClient {
|
||||
pub fn new(server: String) -> Self {
|
||||
pub fn new<S: AsRef<str> + ?Sized>(server: &S) -> Self {
|
||||
Self {
|
||||
client: WebClient::default(),
|
||||
server,
|
||||
server: server.as_ref().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ async fn test_every_push_push() {
|
||||
match std::env::var("EVERY_PUSH_SERVER") {
|
||||
Ok(server) => match std::env::var("EVERY_PUSH_TOKEN") {
|
||||
Ok(token) => {
|
||||
let client = EveryPushClient::new(server);
|
||||
let client = EveryPushClient::new(&server);
|
||||
match client
|
||||
.push_message(
|
||||
&token,
|
||||
|
||||
@@ -9,10 +9,13 @@ use crate::error::PixivDownloaderError;
|
||||
use crate::ext::json::ToJson2;
|
||||
use crate::get_helper;
|
||||
use crate::gettext;
|
||||
use crate::pixiv_app::PixivAppClient;
|
||||
use crate::pixiv_web::PixivWebClient;
|
||||
use futures_util::lock::Mutex;
|
||||
use hyper::{http::response::Builder, Body, Request, Response};
|
||||
use json::JsonValue;
|
||||
use std::collections::BTreeMap;
|
||||
use reqwest::IntoUrl;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -20,6 +23,8 @@ pub struct ServerContext {
|
||||
pub cors: CorsContext,
|
||||
pub db: Arc<Box<dyn PixivDownloaderDb + Send + Sync>>,
|
||||
pub rsa_key: Mutex<Option<RSAKey>>,
|
||||
pub _pixiv_app_client: Mutex<Option<PixivAppClient>>,
|
||||
pub _pixiv_web_client: Mutex<Option<Arc<PixivWebClient>>>,
|
||||
}
|
||||
|
||||
impl ServerContext {
|
||||
@@ -31,9 +36,53 @@ impl ServerContext {
|
||||
Err(e) => panic!("{} {}", gettext("Failed to open database:"), e),
|
||||
},
|
||||
rsa_key: Mutex::new(None),
|
||||
_pixiv_app_client: Mutex::new(None),
|
||||
_pixiv_web_client: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn generate_pixiv_proxy_url<U: IntoUrl>(
|
||||
&self,
|
||||
u: U,
|
||||
) -> Result<String, PixivDownloaderError> {
|
||||
let u = u.into_url()?;
|
||||
let host = u.host_str().ok_or("Host not found.")?;
|
||||
if !host.ends_with(".pximg.net") {
|
||||
return Err("Host not match.".into());
|
||||
}
|
||||
let helper = get_helper();
|
||||
let base = helper
|
||||
.server_base()
|
||||
.unwrap_or(format!("http://{}", helper.server()));
|
||||
let mut map = HashMap::new();
|
||||
map.insert("url", u.as_str());
|
||||
let secret = self.db.get_proxy_pixiv_secrets().await?;
|
||||
let mut sha512 = openssl::sha::Sha512::new();
|
||||
sha512.update(secret.as_bytes());
|
||||
sha512.update("url".as_bytes());
|
||||
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)?);
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
pub async fn pixiv_app_client(&self) -> PixivAppClient {
|
||||
let mut pixiv_app_client = self._pixiv_app_client.lock().await;
|
||||
if pixiv_app_client.is_none() {
|
||||
pixiv_app_client.replace(PixivAppClient::with_db(Some(self.db.clone())));
|
||||
}
|
||||
pixiv_app_client.as_ref().unwrap().clone()
|
||||
}
|
||||
|
||||
pub async fn pixiv_web_client(&self) -> Arc<PixivWebClient> {
|
||||
let mut pixiv_web_client = self._pixiv_web_client.lock().await;
|
||||
if pixiv_web_client.is_none() {
|
||||
pixiv_web_client.replace(Arc::new(PixivWebClient::new()));
|
||||
}
|
||||
pixiv_web_client.as_ref().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn response_json_result(
|
||||
&self,
|
||||
builder: Builder,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::super::preclude::*;
|
||||
use crate::db::{PushConfig, PushTaskConfig};
|
||||
use super::task::{run_push_task, TestSendMode};
|
||||
use crate::db::{PushConfig, PushTask, PushTaskConfig};
|
||||
use crate::ext::try_err::TryErr3;
|
||||
|
||||
/// Push task manage action
|
||||
@@ -10,6 +11,8 @@ pub enum PushAction {
|
||||
Change,
|
||||
/// Get a exist push task
|
||||
Get,
|
||||
/// Test a push task
|
||||
Test,
|
||||
}
|
||||
|
||||
pub struct PushContext {
|
||||
@@ -109,6 +112,26 @@ impl PushContext {
|
||||
.try_err3(404, "Push task not found.")?;
|
||||
Ok(serde_json::to_value(re).try_err3(500, "Failed to serialize result:")?)
|
||||
}
|
||||
PushAction::Test => {
|
||||
let config = params.get("config").ok_or((400, "Missing config."))?;
|
||||
let config: PushTaskConfig =
|
||||
serde_json::from_str(config).try_err3(400, "Failed to parse config:")?;
|
||||
let push_configs = params
|
||||
.get("push_configs")
|
||||
.ok_or((400, "Missing push_configs."))?;
|
||||
let push_configs: Vec<PushConfig> = serde_json::from_str(push_configs)
|
||||
.try_err3(400, "Failed to parse push_configs:")?;
|
||||
let task = PushTask::new(config, push_configs);
|
||||
let test_send_mode = params
|
||||
.get("test_send_mode")
|
||||
.ok_or((400, "Missing test_send_mode."))?;
|
||||
let test_send_mode: TestSendMode = serde_json::from_str(test_send_mode)
|
||||
.try_err3(400, "Failed to parse test_send_mode:")?;
|
||||
run_push_task(self.ctx.clone(), &task, Some(&test_send_mode))
|
||||
.await
|
||||
.try_err3(1, "Failed to test push task:")?;
|
||||
Ok(serde_json::to_value(true).try_err3(500, "Failed to serialize result:")?)
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!("PushContext::handle: action is None")
|
||||
@@ -163,7 +186,7 @@ pub struct PushRoute {
|
||||
impl PushRoute {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
regex: Regex::new(r"^(/+api)?/+push(/+(add|change|get))?$").unwrap(),
|
||||
regex: Regex::new(r"^(/+api)?/+push(/+(add|change|get|test))?$").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,6 +217,7 @@ impl MatchRoute<Body, Pin<Box<HttpBodyType>>> for PushRoute {
|
||||
"add" => Some(PushAction::Add),
|
||||
"change" => Some(PushAction::Change),
|
||||
"get" => Some(PushAction::Get),
|
||||
"test" => Some(PushAction::Test),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod index;
|
||||
pub mod task;
|
||||
|
||||
pub use index::{PushContext, PushRoute};
|
||||
|
||||
66
src/server/push/task/mod.rs
Normal file
66
src/server/push/task/mod.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
pub mod pixiv_follow;
|
||||
pub mod pixiv_send_message;
|
||||
|
||||
use super::super::preclude::*;
|
||||
use crate::db::push_task::PushTaskPixivAction;
|
||||
use crate::db::{PushTask, PushTaskConfig};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum TestSendMode {
|
||||
/// Send first message
|
||||
First,
|
||||
/// Send last message
|
||||
Last,
|
||||
/// Send random message
|
||||
Random,
|
||||
/// Send all messages
|
||||
All,
|
||||
#[serde(untagged)]
|
||||
/// Send specified message
|
||||
Fixed(u64),
|
||||
}
|
||||
|
||||
impl TestSendMode {
|
||||
pub fn is_all(&self) -> bool {
|
||||
matches!(self, TestSendMode::All)
|
||||
}
|
||||
|
||||
pub fn to_index(&self, len: usize) -> Option<usize> {
|
||||
match self {
|
||||
TestSendMode::First => Some(0),
|
||||
TestSendMode::Last => Some(len - 1),
|
||||
TestSendMode::Random => {
|
||||
if len == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(rand::random::<usize>() % len)
|
||||
}
|
||||
}
|
||||
TestSendMode::All => None,
|
||||
TestSendMode::Fixed(index) => {
|
||||
if *index >= len as u64 {
|
||||
None
|
||||
} else {
|
||||
Some(*index as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_push_task(
|
||||
ctx: Arc<ServerContext>,
|
||||
task: &PushTask,
|
||||
send_mode: Option<&TestSendMode>,
|
||||
) -> Result<(), PixivDownloaderError> {
|
||||
match &task.config {
|
||||
PushTaskConfig::Pixiv(config) => match &config.act {
|
||||
PushTaskPixivAction::Follow { restrict } => {
|
||||
pixiv_follow::run_push_task(ctx, task, config, restrict, send_mode).await
|
||||
}
|
||||
_ => Ok(()),
|
||||
},
|
||||
}
|
||||
}
|
||||
135
src/server/push/task/pixiv_follow.rs
Normal file
135
src/server/push/task/pixiv_follow.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use super::super::super::preclude::*;
|
||||
use super::pixiv_send_message::send_message;
|
||||
use super::TestSendMode;
|
||||
use crate::db::push_task::PushTaskPixivConfig;
|
||||
use crate::db::PushTask;
|
||||
use crate::ext::rw_lock::GetRwLock;
|
||||
use crate::get_helper;
|
||||
use crate::pixiv_app::PixivRestrictType;
|
||||
use crate::pixivapp::illust::PixivAppIllust;
|
||||
use json::JsonValue;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
|
||||
struct PixivFollowData {
|
||||
web_list: RwLock<Option<JsonValue>>,
|
||||
web_data: RwLock<HashMap<u64, JsonValue>>,
|
||||
}
|
||||
|
||||
impl PixivFollowData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
web_list: RwLock::new(None),
|
||||
web_data: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_web_data(&self, id: u64) -> Option<JsonValue> {
|
||||
self.web_data.get_ref().get(&id).cloned()
|
||||
}
|
||||
|
||||
pub fn set_web_data(&self, id: u64, data: JsonValue) {
|
||||
self.web_data.get_mut().insert(id, data);
|
||||
}
|
||||
}
|
||||
|
||||
struct RunContext<'a> {
|
||||
ctx: Arc<ServerContext>,
|
||||
task: &'a PushTask,
|
||||
config: &'a PushTaskPixivConfig,
|
||||
restrict: &'a PixivRestrictType,
|
||||
send_mode: Option<&'a TestSendMode>,
|
||||
data: Arc<PixivFollowData>,
|
||||
use_app_api: bool,
|
||||
use_web_description: bool,
|
||||
use_webpage: bool,
|
||||
}
|
||||
|
||||
impl<'a> RunContext<'a> {
|
||||
pub fn new(
|
||||
ctx: Arc<ServerContext>,
|
||||
task: &'a PushTask,
|
||||
config: &'a PushTaskPixivConfig,
|
||||
restrict: &'a PixivRestrictType,
|
||||
send_mode: Option<&'a TestSendMode>,
|
||||
) -> Self {
|
||||
let helper = get_helper();
|
||||
Self {
|
||||
ctx,
|
||||
task,
|
||||
config,
|
||||
restrict,
|
||||
send_mode,
|
||||
data: Arc::new(PixivFollowData::new()),
|
||||
use_app_api: config.use_app_api.unwrap_or(helper.use_app_api()),
|
||||
use_web_description: config
|
||||
.use_web_description
|
||||
.unwrap_or(helper.use_web_description()),
|
||||
use_webpage: config.use_webpage.unwrap_or(helper.use_webpage()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&self) -> Result<(), PixivDownloaderError> {
|
||||
if self.use_app_api {
|
||||
self.app_run().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn app_run(&self) -> Result<(), PixivDownloaderError> {
|
||||
let app = self.ctx.pixiv_app_client().await;
|
||||
let app_data = app.get_follow(self.restrict).await?;
|
||||
match self.send_mode {
|
||||
Some(m) => {
|
||||
if m.is_all() {
|
||||
for i in app_data.illusts.iter() {
|
||||
self.app_illust(i).await?;
|
||||
}
|
||||
} else {
|
||||
let index = m.to_index(app_data.illusts.len());
|
||||
if let Some(index) = index {
|
||||
self.app_illust(&app_data.illusts[index]).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn app_illust(&self, illust: &PixivAppIllust) -> Result<(), PixivDownloaderError> {
|
||||
let id = illust.id().ok_or("illust id is none")?;
|
||||
let data = match self.data.get_web_data(id) {
|
||||
Some(d) => Some(d),
|
||||
None => {
|
||||
if self.use_web_description && illust.caption_is_empty() {
|
||||
let pw = self.ctx.pixiv_web_client().await;
|
||||
match pw.get_artwork_ajax(id).await {
|
||||
Some(data) => {
|
||||
self.data.set_web_data(id, data.clone());
|
||||
Some(data)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
for i in self.task.push_configs.iter() {
|
||||
send_message(self.ctx.clone(), Some(&illust), data.as_ref(), i).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_push_task(
|
||||
ctx: Arc<ServerContext>,
|
||||
task: &PushTask,
|
||||
config: &PushTaskPixivConfig,
|
||||
restrict: &PixivRestrictType,
|
||||
send_mode: Option<&TestSendMode>,
|
||||
) -> Result<(), PixivDownloaderError> {
|
||||
let ctx = RunContext::new(ctx, task, config, restrict, send_mode);
|
||||
ctx.run().await
|
||||
}
|
||||
126
src/server/push/task/pixiv_send_message.rs
Normal file
126
src/server/push/task/pixiv_send_message.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
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::pixivapp::illust::PixivAppIllust;
|
||||
use crate::push::every_push::{EveryPushClient, EveryPushTextType};
|
||||
use json::JsonValue;
|
||||
|
||||
struct RunContext<'a> {
|
||||
ctx: Arc<ServerContext>,
|
||||
illust: Option<&'a PixivAppIllust>,
|
||||
data: Option<&'a JsonValue>,
|
||||
cfg: &'a PushConfig,
|
||||
}
|
||||
|
||||
impl<'a> RunContext<'a> {
|
||||
pub fn title(&self) -> Option<&str> {
|
||||
match self.illust {
|
||||
Some(i) => match i.title() {
|
||||
Some(t) => return Some(t),
|
||||
None => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
match self.data {
|
||||
Some(d) => d["illustTitle"].as_str(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn _author(&self) -> Option<&str> {
|
||||
match self.illust {
|
||||
Some(i) => match i.user_name() {
|
||||
Some(u) => return Some(u),
|
||||
None => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
match self.data {
|
||||
Some(d) => d["userName"].as_str(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn author(&self) -> Option<String> {
|
||||
match self._author() {
|
||||
Some(a) => {
|
||||
if self.filter_author() {
|
||||
match get_helper().author_name_filters() {
|
||||
Some(l) => return Some(l.filter(a)),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
Some(a.to_owned())
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to filter author name
|
||||
pub fn filter_author(&self) -> bool {
|
||||
match self.cfg {
|
||||
PushConfig::EveryPush(e) => e.filter_author,
|
||||
}
|
||||
}
|
||||
|
||||
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::Image => {
|
||||
let mut title = self.title().map(|s| s.to_owned());
|
||||
if cfg.author_locations.contains(&AuthorLocation::Title) {
|
||||
if let Some(t) = &title {
|
||||
if let Some(a) = self.author() {
|
||||
title = Some(format!("{} - {}", t, a));
|
||||
}
|
||||
}
|
||||
}
|
||||
let url = match self.illust {
|
||||
Some(i) => i.original_image_url().map(|s| s.to_owned()).or_else(|| {
|
||||
match i.meta_pages().get(0) {
|
||||
Some(p) => p.original().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
}
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
let url = url.ok_or("image url not found.")?;
|
||||
let url = self.ctx.generate_pixiv_proxy_url(url).await?;
|
||||
client
|
||||
.push_message(
|
||||
&cfg.push_token,
|
||||
&url,
|
||||
title.as_ref(),
|
||||
cfg.topic_id.as_ref(),
|
||||
Some(EveryPushTextType::Image),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn run(&self) -> Result<(), PixivDownloaderError> {
|
||||
match self.cfg {
|
||||
PushConfig::EveryPush(e) => self.send_every_push(e).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_message(
|
||||
ctx: Arc<ServerContext>,
|
||||
illust: Option<&PixivAppIllust>,
|
||||
data: Option<&JsonValue>,
|
||||
cfg: &PushConfig,
|
||||
) -> Result<(), PixivDownloaderError> {
|
||||
let ctx = RunContext {
|
||||
ctx,
|
||||
illust,
|
||||
data,
|
||||
cfg,
|
||||
};
|
||||
ctx.run().await
|
||||
}
|
||||
@@ -40,6 +40,8 @@ impl UnitTestContext {
|
||||
.unwrap(),
|
||||
),
|
||||
rsa_key: Mutex::new(None),
|
||||
_pixiv_app_client: Mutex::new(None),
|
||||
_pixiv_web_client: Mutex::new(None),
|
||||
}),
|
||||
routes: ServerRoutes::new(),
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@ pub fn get_settings_list() -> Vec<SettingDes> {
|
||||
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(),
|
||||
#[cfg(feature = "server")]
|
||||
SettingDes::new("server-base", gettext("The server's host name. Used in some proxy."), JsonValueType::Str, None).unwrap(),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user