mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-06-06 05:49:01 +08:00
Update
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -286,6 +286,7 @@ dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
@@ -1472,6 +1473,7 @@ dependencies = [
|
||||
"reqwest",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"url",
|
||||
"urlparse",
|
||||
|
||||
@@ -12,7 +12,7 @@ base64 = { version = "0.21", optional = true }
|
||||
bytes = { version = "1.4", optional = true }
|
||||
c_fixed_string = { version = "0.2", optional = true }
|
||||
cfg-if = "1"
|
||||
chrono = "0.4"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
dateparser = "0.2.0"
|
||||
derive_more = "0.99"
|
||||
fancy-regex = "0.11"
|
||||
@@ -42,6 +42,7 @@ reqwest = { version = "0.11", features = ["brotli", "deflate", "gzip", "socks",
|
||||
rusqlite = { version = "0.29", features = ["bundled", "chrono"], optional = true }
|
||||
RustyXML = "0.3"
|
||||
serde = "1"
|
||||
serde_json = { version = "1", optional = true }
|
||||
tokio = { version = "1.27", features = ["rt", "macros", "rt-multi-thread", "time"] }
|
||||
url = "2.3"
|
||||
urlparse = "0.7"
|
||||
@@ -59,7 +60,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"]
|
||||
server = ["async-trait", "base64", "db", "hex", "hyper", "multipart", "openssl", "serde_json"]
|
||||
ugoira = ["avdict", "bindgen", "cmake", "link-cplusplus"]
|
||||
|
||||
[patch.crates-io]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod config;
|
||||
pub mod pixiv_artworks;
|
||||
#[cfg(feature = "server")]
|
||||
pub mod push_task;
|
||||
#[cfg(feature = "db_sqlite")]
|
||||
pub mod sqlite;
|
||||
#[cfg(feature = "server")]
|
||||
@@ -13,6 +15,8 @@ pub use config::PixivDownloaderDbConfig;
|
||||
#[cfg(feature = "db_sqlite")]
|
||||
pub use config::PixivDownloaderSqliteConfig;
|
||||
pub use pixiv_artworks::{PixivArtwork, PixivArtworkLock};
|
||||
#[cfg(feature = "server")]
|
||||
pub use push_task::{PushConfig, PushTask, PushTaskConfig};
|
||||
#[cfg(feature = "db_sqlite")]
|
||||
pub use sqlite::{PixivDownloaderSqlite, SqliteError};
|
||||
#[cfg(feature = "server")]
|
||||
@@ -22,15 +26,15 @@ pub use traits::PixivDownloaderDb;
|
||||
pub use user::User;
|
||||
|
||||
#[derive(Debug, derive_more::Display)]
|
||||
pub struct PixivDownloaderDbError {
|
||||
e: anyhow::Error,
|
||||
pub enum PixivDownloaderDbError {
|
||||
AnyHow(anyhow::Error),
|
||||
#[cfg(feature = "db_sqlite")]
|
||||
Sqlite(SqliteError),
|
||||
}
|
||||
|
||||
impl PixivDownloaderDbError {
|
||||
pub fn msg<S: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static>(msg: S) -> Self {
|
||||
Self {
|
||||
e: anyhow::Error::msg(msg),
|
||||
}
|
||||
Self::AnyHow(anyhow::Error::msg(msg))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +43,7 @@ where
|
||||
T: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(e: T) -> Self {
|
||||
Self { e: e.into() }
|
||||
Self::AnyHow(e.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +52,31 @@ use crate::gettext;
|
||||
#[cfg(feature = "db_sqlite")]
|
||||
impl From<SqliteError> for PixivDownloaderDbError {
|
||||
fn from(e: SqliteError) -> Self {
|
||||
PixivDownloaderDbError::msg(e)
|
||||
PixivDownloaderDbError::Sqlite(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "db_sqlite", feature = "server"))]
|
||||
pub trait Optional2Extension<T> {
|
||||
fn optional2(self) -> Result<Option<T>, PixivDownloaderDbError>;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "db_sqlite", feature = "server"))]
|
||||
impl<T> Optional2Extension<T> for Result<T, PixivDownloaderDbError> {
|
||||
fn optional2(self) -> Result<Option<T>, PixivDownloaderDbError> {
|
||||
match self {
|
||||
Ok(v) => Ok(Some(v)),
|
||||
Err(e) => match e {
|
||||
PixivDownloaderDbError::Sqlite(e) => match e {
|
||||
SqliteError::DbError(e) => match e {
|
||||
rusqlite::Error::QueryReturnedNoRows => Ok(None),
|
||||
_ => Err(PixivDownloaderDbError::Sqlite(SqliteError::DbError(e))),
|
||||
},
|
||||
_ => Err(PixivDownloaderDbError::Sqlite(e)),
|
||||
},
|
||||
_ => Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
76
src/db/push_task.rs
Normal file
76
src/db/push_task.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
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,
|
||||
},
|
||||
Bookmarks {
|
||||
restrict: PushTaskPixivRestrictType,
|
||||
uid: u64,
|
||||
},
|
||||
Illusts {
|
||||
uid: u64,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PushTaskPixivConfig {
|
||||
pub act: PushTaskPixivAction,
|
||||
/// Tag translation language
|
||||
pub lang: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum PushTaskConfig {
|
||||
Pixiv(PushTaskPixivConfig),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EveryPushConfig {
|
||||
/// Push server
|
||||
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>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum PushConfig {
|
||||
EveryPush(EveryPushConfig),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PushTask {
|
||||
/// The task ID
|
||||
pub id: u64,
|
||||
/// Configurations of the task
|
||||
pub config: PushTaskConfig,
|
||||
/// Push configurations
|
||||
pub push_configs: Vec<PushConfig>,
|
||||
#[serde(with = "chrono::serde::ts_seconds")]
|
||||
/// Last updated time
|
||||
pub last_updated: DateTime<Utc>,
|
||||
/// Update interval
|
||||
pub ttl: u64,
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
#[cfg(feature = "server")]
|
||||
use super::super::Optional2Extension;
|
||||
use super::super::{PixivArtwork, PixivArtworkLock};
|
||||
use super::super::{
|
||||
PixivDownloaderDb, PixivDownloaderDbConfig, PixivDownloaderDbError, PixivDownloaderSqliteConfig,
|
||||
};
|
||||
#[cfg(feature = "server")]
|
||||
use super::super::{PushConfig, PushTask, PushTaskConfig};
|
||||
#[cfg(feature = "server")]
|
||||
use super::super::{Token, User};
|
||||
use super::SqliteError;
|
||||
use bytes::BytesMut;
|
||||
@@ -51,6 +55,13 @@ id INT,
|
||||
file_id INT,
|
||||
page INT
|
||||
);";
|
||||
const PUSH_TASK_TABLE: &'static str = "CREATE TABLE push_task (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
config TEXT,
|
||||
push_configs TEXT,
|
||||
last_updated DATETIME,
|
||||
ttl INT
|
||||
);";
|
||||
const TAGS_TABLE: &'static str = "CREATE TABLE tags (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT
|
||||
@@ -82,7 +93,7 @@ v3 INT,
|
||||
v4 INT,
|
||||
PRIMARY KEY (id)
|
||||
);";
|
||||
const VERSION: [u8; 4] = [1, 0, 0, 6];
|
||||
const VERSION: [u8; 4] = [1, 0, 0, 7];
|
||||
|
||||
pub struct PixivDownloaderSqlite {
|
||||
db: Mutex<Connection>,
|
||||
@@ -116,6 +127,29 @@ impl PixivDownloaderSqlite {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn _add_push_task(
|
||||
ts: &Transaction,
|
||||
config: &PushTaskConfig,
|
||||
push_configs: &[PushConfig],
|
||||
ttl: u64,
|
||||
) -> Result<u64, PixivDownloaderDbError> {
|
||||
ts.execute(
|
||||
"INSERT INTO push_task (config, push_configs, last_updated, ttl) VALUES (?, ?, ?, ?);",
|
||||
(
|
||||
serde_json::to_string(config)?,
|
||||
serde_json::to_string(push_configs)?,
|
||||
DateTime::UNIX_EPOCH,
|
||||
ttl,
|
||||
),
|
||||
)?;
|
||||
Ok(ts.query_row(
|
||||
"SELECT seq FROM sqlite_sequence WHERE name = 'push_task';",
|
||||
[],
|
||||
|row| Ok(row.get(0)?),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn _add_root_user(
|
||||
&self,
|
||||
@@ -215,6 +249,9 @@ impl PixivDownloaderSqlite {
|
||||
if db_version < [1, 0, 0, 6] {
|
||||
tx.execute(CONFIG_TABLE, [])?;
|
||||
}
|
||||
if db_version < [1, 0, 0, 7] {
|
||||
tx.execute(PUSH_TASK_TABLE, [])?;
|
||||
}
|
||||
self._write_version(&tx)?;
|
||||
tx.commit()?;
|
||||
}
|
||||
@@ -247,6 +284,9 @@ impl PixivDownloaderSqlite {
|
||||
if !tables.contains_key("pixiv_files") {
|
||||
t.execute(PIXIV_FILES_TABLE, [])?;
|
||||
}
|
||||
if !tables.contains_key("push_task") {
|
||||
t.execute(PUSH_TASK_TABLE, [])?;
|
||||
}
|
||||
if !tables.contains_key("tags") {
|
||||
t.execute(TAGS_TABLE, [])?;
|
||||
}
|
||||
@@ -342,6 +382,29 @@ impl PixivDownloaderSqlite {
|
||||
.optional()?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn get_push_task(&self, id: u64) -> Result<Option<PushTask>, PixivDownloaderDbError> {
|
||||
let con = self.db.lock().await;
|
||||
con.query_row_and_then::<PushTask, PixivDownloaderDbError, _, _>(
|
||||
"SELECT * FROM push_task WHERE id = ?;",
|
||||
[id],
|
||||
|row| {
|
||||
let config: String = row.get(1)?;
|
||||
let config: PushTaskConfig = serde_json::from_str(&config)?;
|
||||
let push_configs: String = row.get(2)?;
|
||||
let push_configs: Vec<PushConfig> = serde_json::from_str(&push_configs)?;
|
||||
Ok(PushTask {
|
||||
id: row.get(0)?,
|
||||
config,
|
||||
push_configs,
|
||||
last_updated: row.get(3)?,
|
||||
ttl: row.get(4)?,
|
||||
})
|
||||
},
|
||||
)
|
||||
.optional2()
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn get_token(&self, id: u64) -> Result<Option<Token>, SqliteError> {
|
||||
let con = self.db.lock().await;
|
||||
@@ -554,6 +617,56 @@ impl PixivDownloaderSqlite {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn _update_push_task(
|
||||
tx: &Transaction,
|
||||
id: u64,
|
||||
config: Option<&PushTaskConfig>,
|
||||
push_configs: Option<&[PushConfig]>,
|
||||
ttl: Option<u64>,
|
||||
) -> Result<(), PixivDownloaderDbError> {
|
||||
match config {
|
||||
Some(config) => {
|
||||
let config = serde_json::to_string(config)?;
|
||||
tx.execute(
|
||||
"UPDATE push_task SET config = ? WHERE id = ?;",
|
||||
(config, id),
|
||||
)?;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
match push_configs {
|
||||
Some(push_configs) => {
|
||||
let push_configs = serde_json::to_string(push_configs)?;
|
||||
tx.execute(
|
||||
"UPDATE push_task SET push_configs = ? WHERE id = ?;",
|
||||
(push_configs, id),
|
||||
)?;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
match ttl {
|
||||
Some(ttl) => {
|
||||
tx.execute("UPDATE push_task SET ttl = ? WHERE id = ?;", (ttl, id))?;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn _update_push_task_last_updated(
|
||||
tx: &Transaction,
|
||||
id: u64,
|
||||
last_updated: &DateTime<Utc>,
|
||||
) -> Result<(), PixivDownloaderDbError> {
|
||||
tx.execute(
|
||||
"UPDATE push_task SET last_updated = ? WHERE id = ?;",
|
||||
(last_updated, id),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn _update_user(
|
||||
&self,
|
||||
@@ -677,6 +790,23 @@ impl PixivDownloaderDb for PixivDownloaderSqlite {
|
||||
Ok(self.get_pixiv_artwork(id).await?.expect("User not found:"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn add_push_task(
|
||||
&self,
|
||||
config: &PushTaskConfig,
|
||||
push_configs: &[PushConfig],
|
||||
ttl: u64,
|
||||
) -> Result<PushTask, PixivDownloaderDbError> {
|
||||
let task = {
|
||||
let mut db = self.db.lock().await;
|
||||
let tx = db.transaction()?;
|
||||
let task = Self::_add_push_task(&tx, config, push_configs, ttl)?;
|
||||
tx.commit()?;
|
||||
task
|
||||
};
|
||||
Ok(self.get_push_task(task).await?.expect("Task not found:"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn add_root_user(
|
||||
&self,
|
||||
@@ -770,6 +900,32 @@ impl PixivDownloaderDb for PixivDownloaderSqlite {
|
||||
Ok(self.get_config(key).await?)
|
||||
}
|
||||
|
||||
async fn get_config_or_set_default(
|
||||
&self,
|
||||
key: &str,
|
||||
default: fn() -> Result<String, PixivDownloaderDbError>,
|
||||
) -> Result<String, PixivDownloaderDbError> {
|
||||
let mut db = self.db.lock().await;
|
||||
let tx = db.transaction()?;
|
||||
let value = tx
|
||||
.query_row("SELECT value FROM config WHERE key = ?;", [key], |row| {
|
||||
row.get(0)
|
||||
})
|
||||
.optional()?;
|
||||
match value {
|
||||
Some(v) => {
|
||||
tx.commit()?;
|
||||
Ok(v)
|
||||
}
|
||||
None => {
|
||||
let v = default()?;
|
||||
Self::_set_config(&tx, key, &v)?;
|
||||
tx.commit()?;
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_pixiv_artwork(
|
||||
&self,
|
||||
id: u64,
|
||||
@@ -777,6 +933,11 @@ impl PixivDownloaderDb for PixivDownloaderSqlite {
|
||||
Ok(self.get_pixiv_artwork(id).await?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn get_push_task(&self, id: u64) -> Result<Option<PushTask>, PixivDownloaderDbError> {
|
||||
Ok(self.get_push_task(id).await?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn get_token(&self, id: u64) -> Result<Option<Token>, PixivDownloaderDbError> {
|
||||
Ok(self.get_token(id).await?)
|
||||
@@ -851,6 +1012,38 @@ impl PixivDownloaderDb for PixivDownloaderSqlite {
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn update_push_task(
|
||||
&self,
|
||||
id: u64,
|
||||
config: Option<&PushTaskConfig>,
|
||||
push_configs: Option<&[PushConfig]>,
|
||||
ttl: Option<u64>,
|
||||
) -> Result<PushTask, PixivDownloaderDbError> {
|
||||
{
|
||||
let mut db = self.db.lock().await;
|
||||
let tx = db.transaction()?;
|
||||
Self::_update_push_task(&tx, id, config, push_configs, ttl)?;
|
||||
tx.commit()?;
|
||||
}
|
||||
Ok(self.get_push_task(id).await?.expect("Task not found:"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn update_push_task_last_updated(
|
||||
&self,
|
||||
id: u64,
|
||||
last_updated: &DateTime<Utc>,
|
||||
) -> Result<(), PixivDownloaderDbError> {
|
||||
{
|
||||
let mut db = self.db.lock().await;
|
||||
let mut tx = db.transaction()?;
|
||||
Self::_update_push_task_last_updated(&mut tx, id, last_updated)?;
|
||||
tx.commit()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn update_user(
|
||||
&self,
|
||||
|
||||
@@ -2,6 +2,8 @@ use super::PixivDownloaderDbConfig;
|
||||
use super::PixivDownloaderDbError;
|
||||
use super::{PixivArtwork, PixivArtworkLock};
|
||||
#[cfg(feature = "server")]
|
||||
use super::{PushConfig, PushTask, PushTaskConfig};
|
||||
#[cfg(feature = "server")]
|
||||
use super::{Token, User};
|
||||
use chrono::{DateTime, Utc};
|
||||
use flagset::FlagSet;
|
||||
@@ -36,6 +38,17 @@ pub trait PixivDownloaderDb {
|
||||
lock: &FlagSet<PixivArtworkLock>,
|
||||
) -> Result<PixivArtwork, PixivDownloaderDbError>;
|
||||
#[cfg(feature = "server")]
|
||||
/// Add a push task
|
||||
/// * `config` - The task's config
|
||||
/// * `push_configs` - The task's push configurations
|
||||
/// * `ttl` - The task's update interval
|
||||
async fn add_push_task(
|
||||
&self,
|
||||
config: &PushTaskConfig,
|
||||
push_configs: &[PushConfig],
|
||||
ttl: u64,
|
||||
) -> Result<PushTask, PixivDownloaderDbError>;
|
||||
#[cfg(feature = "server")]
|
||||
/// Add root user to database.
|
||||
/// * `name` - User name
|
||||
/// * `username` - Unique user name
|
||||
@@ -98,6 +111,14 @@ pub trait PixivDownloaderDb {
|
||||
/// Get a config from database
|
||||
/// * `key` - The config key
|
||||
async fn get_config(&self, key: &str) -> Result<Option<String>, PixivDownloaderDbError>;
|
||||
/// Get a config from database or set a default value to database and return
|
||||
/// * `key` - The config key
|
||||
/// * `default` - The function to return default value
|
||||
async fn get_config_or_set_default(
|
||||
&self,
|
||||
key: &str,
|
||||
default: fn() -> Result<String, PixivDownloaderDbError>,
|
||||
) -> Result<String, PixivDownloaderDbError>;
|
||||
/// Get an artwork from database
|
||||
/// * `id` - The artwork ID
|
||||
async fn get_pixiv_artwork(
|
||||
@@ -107,19 +128,21 @@ pub trait PixivDownloaderDb {
|
||||
#[cfg(feature = "server")]
|
||||
/// Get proxy pixiv secrets
|
||||
async fn get_proxy_pixiv_secrets(&self) -> Result<String, PixivDownloaderDbError> {
|
||||
let key = "proxy_pixiv_secrets";
|
||||
match self.get_config(key).await? {
|
||||
Some(v) => Ok(v),
|
||||
None => {
|
||||
let mut buf = [0; 32];
|
||||
openssl::rand::rand_bytes(&mut buf)?;
|
||||
let v = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &buf);
|
||||
self.set_config(key, &v).await?;
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
self.get_config_or_set_default("proxy_pixiv_secrets", || {
|
||||
let mut buf = [0; 32];
|
||||
openssl::rand::rand_bytes(&mut buf)?;
|
||||
Ok(base64::Engine::encode(
|
||||
&base64::engine::general_purpose::STANDARD,
|
||||
&buf,
|
||||
))
|
||||
})
|
||||
.await
|
||||
}
|
||||
#[cfg(feature = "server")]
|
||||
/// Get a push task by ID
|
||||
/// * `id` - The task's ID
|
||||
async fn get_push_task(&self, id: u64) -> Result<Option<PushTask>, PixivDownloaderDbError>;
|
||||
#[cfg(feature = "server")]
|
||||
/// Get token by ID
|
||||
/// * `id` - The token ID
|
||||
async fn get_token(&self, id: u64) -> Result<Option<Token>, PixivDownloaderDbError>;
|
||||
@@ -181,6 +204,28 @@ pub trait PixivDownloaderDb {
|
||||
is_admin: bool,
|
||||
) -> Result<User, PixivDownloaderDbError>;
|
||||
#[cfg(feature = "server")]
|
||||
/// Update a push task
|
||||
/// * `id`: The task's ID
|
||||
/// * `config`: The task's config
|
||||
/// * `push_configs`: The task's push configurations
|
||||
/// * `ttl`: The task's update interval
|
||||
async fn update_push_task(
|
||||
&self,
|
||||
id: u64,
|
||||
config: Option<&PushTaskConfig>,
|
||||
push_configs: Option<&[PushConfig]>,
|
||||
ttl: Option<u64>,
|
||||
) -> Result<PushTask, PixivDownloaderDbError>;
|
||||
#[cfg(feature = "server")]
|
||||
/// Update a push task's last updated time
|
||||
/// * `id`: The task's ID
|
||||
/// * `last_updated`: The task's last updated time
|
||||
async fn update_push_task_last_updated(
|
||||
&self,
|
||||
id: u64,
|
||||
last_updated: &DateTime<Utc>,
|
||||
) -> Result<(), PixivDownloaderDbError>;
|
||||
#[cfg(feature = "server")]
|
||||
/// Update a user's information
|
||||
/// * `id`: The user's ID
|
||||
/// * `name`: The user's name
|
||||
|
||||
@@ -29,6 +29,8 @@ pub enum PixivDownloaderError {
|
||||
ParseIntError(std::num::ParseIntError),
|
||||
ReqwestError(reqwest::Error),
|
||||
PixivAppError(crate::pixivapp::error::PixivAppError),
|
||||
#[cfg(feature = "serde_json")]
|
||||
SerdeJsonError(serde_json::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for PixivDownloaderError {}
|
||||
|
||||
@@ -53,7 +53,7 @@ impl<T: ToJson> ToJson for &T {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToJson2> ToJson2 for &T {
|
||||
impl<T: ToJson2 + ?Sized> ToJson2 for &T {
|
||||
fn to_json2(&self) -> JsonValue {
|
||||
(*self).to_json2()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::webclient::WebClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
/// Text type
|
||||
pub enum EveryPushTextType {
|
||||
Text,
|
||||
@@ -42,6 +44,7 @@ impl EveryPushClient {
|
||||
/// * `push_token` - push token
|
||||
/// * `text` - text
|
||||
/// * `title` - title
|
||||
/// * `topic_id` - topic id
|
||||
/// * `typ` - text type
|
||||
///
|
||||
/// For more information, see [API document](https://github.com/PeanutMelonSeedBigAlmond/EveryPush.Server/blob/main/api.md#推送消息)
|
||||
@@ -49,11 +52,13 @@ impl EveryPushClient {
|
||||
P: AsRef<str> + ?Sized,
|
||||
T: AsRef<str> + ?Sized,
|
||||
I: AsRef<str> + ?Sized,
|
||||
O: AsRef<str> + ?Sized,
|
||||
>(
|
||||
&self,
|
||||
push_token: &P,
|
||||
text: &T,
|
||||
title: Option<&I>,
|
||||
topic_id: Option<&O>,
|
||||
typ: Option<EveryPushTextType>,
|
||||
) -> Result<(), String> {
|
||||
let mut params = HashMap::new();
|
||||
@@ -63,6 +68,10 @@ impl EveryPushClient {
|
||||
Some(t) => params.insert("title", t.as_ref()),
|
||||
None => None,
|
||||
};
|
||||
match topic_id {
|
||||
Some(t) => params.insert("topicId", t.as_ref()),
|
||||
None => None,
|
||||
};
|
||||
match &typ {
|
||||
Some(t) => params.insert("type", t.as_ref()),
|
||||
None => None,
|
||||
@@ -105,6 +114,7 @@ async fn test_every_push_push() {
|
||||
&token,
|
||||
"Push Test",
|
||||
Some("Push"),
|
||||
Some("test"),
|
||||
Some(EveryPushTextType::Text),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use super::auth::RSAKey;
|
||||
use super::body::hyper::HyperBody;
|
||||
use super::cors::CorsContext;
|
||||
use super::params::RequestParams;
|
||||
use super::result::JSONResult;
|
||||
use super::preclude::HttpBodyType;
|
||||
use super::result::{JSONResult, SerdeJSONResult, SerdeJSONResult2};
|
||||
use crate::db::{open_and_init_database, PixivDownloaderDb, Token, User};
|
||||
use crate::error::PixivDownloaderError;
|
||||
use crate::ext::json::ToJson2;
|
||||
@@ -11,6 +13,7 @@ use futures_util::lock::Mutex;
|
||||
use hyper::{http::response::Builder, Body, Request, Response};
|
||||
use json::JsonValue;
|
||||
use std::collections::BTreeMap;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct ServerContext {
|
||||
@@ -53,6 +56,33 @@ impl ServerContext {
|
||||
Ok(builder.body(re.to_json2())?)
|
||||
}
|
||||
|
||||
pub fn response_serde_json_result(
|
||||
&self,
|
||||
builder: Builder,
|
||||
re: SerdeJSONResult,
|
||||
) -> Result<Response<Pin<Box<HttpBodyType>>>, PixivDownloaderError> {
|
||||
let builder = match &re {
|
||||
Ok(_) => builder,
|
||||
Err(err) => {
|
||||
if err.code <= -400 && err.code >= -600 {
|
||||
builder.status((-err.code) as u16)
|
||||
} else if err.code < 0 {
|
||||
builder.status(500)
|
||||
} else if err.code > 0 {
|
||||
builder.status(400)
|
||||
} else {
|
||||
builder
|
||||
}
|
||||
}
|
||||
};
|
||||
let s = SerdeJSONResult2::new(re);
|
||||
Ok(
|
||||
builder.body::<Pin<Box<HttpBodyType>>>(Box::pin(HyperBody::from(
|
||||
serde_json::to_string(&s)?,
|
||||
)))?,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn verify_token2(
|
||||
&self,
|
||||
req: &Request<Body>,
|
||||
|
||||
@@ -11,6 +11,8 @@ pub mod params;
|
||||
pub mod preclude;
|
||||
/// Routes about proxy
|
||||
pub mod proxy;
|
||||
/// Push tasks management
|
||||
pub mod push;
|
||||
/// Base result type for JSON response
|
||||
pub mod result;
|
||||
/// Routes
|
||||
|
||||
@@ -2,7 +2,7 @@ pub use super::body::hyper::HyperBody;
|
||||
pub use super::body::response::ResponseBody;
|
||||
pub use super::context::ServerContext;
|
||||
pub use super::params::RequestParams;
|
||||
pub use super::result::JSONResult;
|
||||
pub use super::result::{JSONResult, SerdeJSONResult};
|
||||
pub use super::route::ResponseForType;
|
||||
pub use super::traits::{GetRequestParams, MatchRoute, ResponseFor, ResponseJsonFor};
|
||||
pub use crate::error::PixivDownloaderError;
|
||||
|
||||
221
src/server/push/index.rs
Normal file
221
src/server/push/index.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
use super::super::preclude::*;
|
||||
use crate::db::{PushConfig, PushTaskConfig};
|
||||
use crate::ext::try_err::TryErr3;
|
||||
|
||||
/// Push task manage action
|
||||
pub enum PushAction {
|
||||
/// Add a new push task
|
||||
Add,
|
||||
/// Change a exist push task
|
||||
Change,
|
||||
/// Get a exist push task
|
||||
Get,
|
||||
}
|
||||
|
||||
pub struct PushContext {
|
||||
ctx: Arc<ServerContext>,
|
||||
action: Option<PushAction>,
|
||||
is_restful: bool,
|
||||
}
|
||||
|
||||
impl PushContext {
|
||||
pub fn new(ctx: Arc<ServerContext>, action: Option<PushAction>, is_restful: bool) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
action,
|
||||
is_restful,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle(&self, mut req: Request<Body>) -> SerdeJSONResult {
|
||||
let params = req
|
||||
.get_params()
|
||||
.await
|
||||
.try_err3(400, "Failed to get parameters:")?;
|
||||
let t = self
|
||||
.ctx
|
||||
.verify(&req, ¶ms)
|
||||
.await
|
||||
.try_err3(401, "Unauthorized")?;
|
||||
if t.is_some_and(|t| !t.is_admin) {
|
||||
return Err((403, "Permission denied.").into());
|
||||
}
|
||||
match &self.action {
|
||||
Some(a) => match a {
|
||||
PushAction::Add => {
|
||||
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 ttl = params
|
||||
.get_u64("ttl")
|
||||
.try_err3(400, "Bad ttl.")?
|
||||
.unwrap_or(300);
|
||||
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 re = self
|
||||
.ctx
|
||||
.db
|
||||
.add_push_task(&config, &push_configs, ttl)
|
||||
.await
|
||||
.try_err3(500, "Failed to add push task:")?;
|
||||
Ok(serde_json::to_value(re).try_err3(500, "Failed to serialize result:")?)
|
||||
}
|
||||
PushAction::Change => {
|
||||
let id = params
|
||||
.get_u64("id")
|
||||
.try_err3(400, "Bad id.")?
|
||||
.try_err3(400, "Missing id.")?;
|
||||
let config = match params.get("config") {
|
||||
Some(v) => Some(
|
||||
serde_json::from_str::<PushTaskConfig>(v)
|
||||
.try_err3(400, "Failed to parse config:")?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let ttl = params.get_u64("ttl").try_err3(400, "Bad ttl")?;
|
||||
let push_configs = match params.get("push_configs") {
|
||||
Some(v) => Some(
|
||||
serde_json::from_str::<Vec<PushConfig>>(v)
|
||||
.try_err3(400, "Failed to parse push_configs:")?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let push_configs = match &push_configs {
|
||||
Some(v) => Some(v.as_ref()),
|
||||
None => None,
|
||||
};
|
||||
let re = self
|
||||
.ctx
|
||||
.db
|
||||
.update_push_task(id, config.as_ref(), push_configs, ttl)
|
||||
.await
|
||||
.try_err3(500, "Failed to change push task:")?;
|
||||
Ok(serde_json::to_value(re).try_err3(500, "Failed to serialize result:")?)
|
||||
}
|
||||
PushAction::Get => {
|
||||
let id = params
|
||||
.get_u64("id")
|
||||
.try_err3(400, "Bad id.")?
|
||||
.try_err3(400, "Missing id.")?;
|
||||
let re = self
|
||||
.ctx
|
||||
.db
|
||||
.get_push_task(id)
|
||||
.await
|
||||
.try_err3(500, "Failed to get push task:")?;
|
||||
Ok(serde_json::to_value(re).try_err3(500, "Failed to serialize result:")?)
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!("PushContext::handle: action is None")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResponseFor<Body, Pin<Box<HttpBodyType>>> for PushContext {
|
||||
async fn response(
|
||||
&self,
|
||||
req: Request<Body>,
|
||||
) -> Result<Response<Pin<Box<HttpBodyType>>>, PixivDownloaderError> {
|
||||
let builder = if self.is_restful {
|
||||
filter_http_methods!(
|
||||
req,
|
||||
Box::pin(HyperBody::empty()),
|
||||
true,
|
||||
self.ctx,
|
||||
allow_headers = [CONTENT_TYPE, X_SIGN, X_TOKEN_ID],
|
||||
typ_def = Pin<Box<HttpBodyType>>,
|
||||
GET,
|
||||
OPTIONS,
|
||||
PATCH,
|
||||
PUT,
|
||||
);
|
||||
builder
|
||||
} else {
|
||||
filter_http_methods!(
|
||||
req,
|
||||
Box::pin(HyperBody::empty()),
|
||||
true,
|
||||
self.ctx,
|
||||
allow_headers = [CONTENT_TYPE, X_SIGN, X_TOKEN_ID],
|
||||
typ_def = Pin<Box<HttpBodyType>>,
|
||||
GET,
|
||||
OPTIONS,
|
||||
POST,
|
||||
);
|
||||
builder
|
||||
};
|
||||
let re = self.handle(req).await;
|
||||
self.ctx.response_serde_json_result(builder, re)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PushRoute {
|
||||
regex: Regex,
|
||||
}
|
||||
|
||||
impl PushRoute {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
regex: Regex::new(r"^(/+api)?/+push(/+(add|change|get))?$").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchRoute<Body, Pin<Box<HttpBodyType>>> for PushRoute {
|
||||
fn match_route(
|
||||
&self,
|
||||
ctx: &Arc<ServerContext>,
|
||||
req: &http::Request<Body>,
|
||||
) -> Option<Box<ResponseForType>> {
|
||||
let path = req.uri().path();
|
||||
let pat = self.regex.captures(path);
|
||||
match pat {
|
||||
Some(cap) => {
|
||||
if req.method() == Method::OPTIONS {
|
||||
return Some(Box::new(PushContext::new(
|
||||
Arc::clone(ctx),
|
||||
None,
|
||||
cap.get(2).is_none(),
|
||||
)));
|
||||
}
|
||||
let cap2 = cap.get(2);
|
||||
let is_restful = cap2.is_none();
|
||||
let action = match cap2 {
|
||||
Some(m) => {
|
||||
let m = m.as_str().trim_start_matches("/");
|
||||
match m {
|
||||
"add" => Some(PushAction::Add),
|
||||
"change" => Some(PushAction::Change),
|
||||
"get" => Some(PushAction::Get),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let m = req.method();
|
||||
if m == Method::PUT {
|
||||
Some(PushAction::Add)
|
||||
} else if m == Method::GET {
|
||||
Some(PushAction::Get)
|
||||
} else if m == Method::PATCH {
|
||||
Some(PushAction::Change)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(Box::new(PushContext::new(
|
||||
Arc::clone(ctx),
|
||||
action,
|
||||
is_restful,
|
||||
)))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/server/push/mod.rs
Normal file
3
src/server/push/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod index;
|
||||
|
||||
pub use index::{PushContext, PushRoute};
|
||||
@@ -2,6 +2,8 @@ use crate::ext::json::ToJson2;
|
||||
#[cfg(test)]
|
||||
use crate::ext::json::{FromJson, ToJson};
|
||||
use json::JsonValue;
|
||||
use serde::ser::SerializeMap;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Error information of a request
|
||||
@@ -85,6 +87,7 @@ impl From<(i32, String, String)> for JSONError {
|
||||
}
|
||||
|
||||
pub type JSONResult = Result<JsonValue, JSONError>;
|
||||
pub type SerdeJSONResult = Result<serde_json::Value, JSONError>;
|
||||
|
||||
impl ToJson2 for JSONResult {
|
||||
fn to_json2(&self) -> JsonValue {
|
||||
@@ -104,6 +107,38 @@ impl ToJson2 for JSONResult {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SerdeJSONResult2 {
|
||||
r: SerdeJSONResult,
|
||||
}
|
||||
|
||||
impl SerdeJSONResult2 {
|
||||
pub fn new(r: SerdeJSONResult) -> Self {
|
||||
Self { r }
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SerdeJSONResult2 {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match &self.r {
|
||||
Ok(v) => {
|
||||
let mut s = serializer.serialize_map(Some(3))?;
|
||||
s.serialize_entry("ok", &true)?;
|
||||
s.serialize_entry("code", &0)?;
|
||||
s.serialize_entry("result", v)?;
|
||||
s.end()
|
||||
}
|
||||
Err(e) => {
|
||||
let mut s = serializer.serialize_map(Some(4))?;
|
||||
s.serialize_entry("ok", &false)?;
|
||||
s.serialize_entry("code", &e.code)?;
|
||||
s.serialize_entry("msg", &e.msg)?;
|
||||
s.serialize_entry::<_, Option<String>>("debug_msg", &None)?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl FromJson for JSONResult {
|
||||
type Err = crate::error::PixivDownloaderError;
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::auth::*;
|
||||
use super::context::ServerContext;
|
||||
use super::preclude::HttpBodyType;
|
||||
use super::proxy::*;
|
||||
use super::push::*;
|
||||
use super::traits::MatchRoute;
|
||||
use super::traits::ResponseFor;
|
||||
use super::version::VersionRoute;
|
||||
@@ -26,6 +27,7 @@ impl ServerRoutes {
|
||||
routes.push(Box::new(AuthPubkeyRoute::new()));
|
||||
routes.push(Box::new(AuthTokenRoute::new()));
|
||||
routes.push(Box::new(ProxyPixivRoute::new()));
|
||||
routes.push(Box::new(PushRoute::new()));
|
||||
Self { routes }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user