Update push

This commit is contained in:
2023-10-28 06:57:02 +00:00
committed by GitHub
parent 48ba176ae7
commit acb2280e1c
17 changed files with 607 additions and 19 deletions

2
Cargo.lock generated
View File

@@ -1469,11 +1469,13 @@ dependencies = [
"openssl",
"parse-size",
"proc_macros",
"rand",
"regex",
"reqwest",
"rusqlite",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"url",
"urlparse",

View File

@@ -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]

View File

@@ -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,
}
}
}

View File

@@ -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 {}

View File

@@ -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 {

View File

@@ -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
View 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,
})
}
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,
}
}

View File

@@ -1,3 +1,4 @@
pub mod index;
pub mod task;
pub use index::{PushContext, PushRoute};

View 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(()),
},
}
}

View 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
}

View 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
}

View File

@@ -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(),
}

View File

@@ -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(),
]
}