pixiv push now support web api

This commit is contained in:
2023-11-04 15:20:43 +00:00
committed by GitHub
parent 135b2498e7
commit 64ec82eb7d
6 changed files with 314 additions and 94 deletions

View File

@@ -3,17 +3,53 @@ use crate::push::every_push::EveryPushTextType;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub enum PixivMode {
/// All artworks
All,
/// R18 artworks
R18,
}
impl PixivMode {
pub fn is_r18(&self) -> bool {
matches!(self, Self::R18)
}
}
fn default_restrict() -> PixivRestrictType {
PixivRestrictType::All
}
fn default_mode() -> PixivMode {
PixivMode::All
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum PushTaskPixivAction {
Follow {
#[serde(default = "default_restrict")]
/// Follower's type
///
/// Only supported when using Pixiv APP API.
restrict: PixivRestrictType,
#[serde(default = "default_mode")]
/// Illust's type
///
/// Only supported when using Pixiv Web API.
mode: PixivMode,
},
Bookmarks {
#[serde(default = "default_restrict")]
/// Bookmarks' type
restrict: PixivRestrictType,
/// User ID
uid: u64,
},
Illusts {
/// User ID
uid: u64,
},
}
@@ -71,6 +107,7 @@ pub struct EveryPushConfig {
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")]
@@ -104,6 +141,7 @@ pub struct PushDeerConfig {
pub typ: EveryPushTextType,
#[serde(default = "defualt_author_locations")]
/// Author locations
///
/// Not supported when type is `Image`.
pub author_locations: Vec<AuthorLocation>,
#[serde(default = "default_true")]
@@ -114,6 +152,7 @@ pub struct PushDeerConfig {
pub add_link: bool,
#[serde(default = "default_true")]
/// Whether to add artwork link to title
///
/// Supported when type is `Text`.
pub add_link_to_title: bool,
#[serde(default = "default_true")]
@@ -130,6 +169,7 @@ pub struct PushDeerConfig {
pub add_translated_tag: bool,
#[serde(default = "default_true")]
/// Whether to add image link
///
/// Supported when type is `Text`.
pub add_image_link: bool,
}

View File

@@ -290,6 +290,35 @@ impl PixivWebClient {
v
}
pub async fn get_follow(&self, page: u64, r18: bool) -> Option<JsonValue> {
self.auto_init();
let mut params = self.get_params().unwrap_or_else(|| json::object! {});
params["p"] = page.into();
params["mode"] = if r18 { "r18" } else { "all" }.into();
let r = self
.client
.get_with_param(
"https://www.pixiv.net/ajax/follow_latest/illust",
Some(params),
None,
)
.await;
if r.is_none() {
return None;
}
let r = r.unwrap();
let v = self.deal_json(r).await;
if v.is_some() {
log::debug!(
target: "pixiv_web",
"{} {}",
gettext("Follower's new illusts: "),
v.as_ref().unwrap().pretty(2)
);
}
v
}
pub fn logined(&self) -> bool {
let data = self.data.get_ref();
if data.is_none() {

View File

@@ -57,8 +57,8 @@ pub async fn run_push_task(
) -> 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
PushTaskPixivAction::Follow { restrict, mode } => {
pixiv_follow::run_push_task(ctx, task, config, restrict, mode, send_mode).await
}
_ => Ok(()),
},

View File

@@ -1,25 +1,24 @@
use super::super::super::preclude::*;
use super::pixiv_send_message::send_message;
use super::TestSendMode;
use crate::db::push_task::PushTaskPixivConfig;
use crate::db::push_task::{PixivMode, 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 crate::utils::parse_pixiv_id;
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()),
}
}
@@ -38,6 +37,7 @@ struct RunContext<'a> {
task: &'a PushTask,
config: &'a PushTaskPixivConfig,
restrict: &'a PixivRestrictType,
mode: &'a PixivMode,
send_mode: Option<&'a TestSendMode>,
data: Arc<PixivFollowData>,
use_app_api: bool,
@@ -51,6 +51,7 @@ impl<'a> RunContext<'a> {
task: &'a PushTask,
config: &'a PushTaskPixivConfig,
restrict: &'a PixivRestrictType,
mode: &'a PixivMode,
send_mode: Option<&'a TestSendMode>,
) -> Self {
let helper = get_helper();
@@ -59,6 +60,7 @@ impl<'a> RunContext<'a> {
task,
config,
restrict,
mode,
send_mode,
data: Arc::new(PixivFollowData::new()),
use_app_api: config.use_app_api.unwrap_or(helper.use_app_api()),
@@ -72,6 +74,77 @@ impl<'a> RunContext<'a> {
pub async fn run(&self) -> Result<(), PixivDownloaderError> {
if self.use_app_api {
self.app_run().await?;
} else {
self.web_run().await?;
}
Ok(())
}
pub async fn web_run(&self) -> Result<(), PixivDownloaderError> {
let pw = self.ctx.pixiv_web_client().await;
let data = pw
.get_follow(1, self.mode.is_r18())
.await
.ok_or("Failed to get follow.")?;
let illusts = &data["thumbnails"]["illust"];
match self.send_mode {
Some(m) => {
if m.is_all() {
for i in illusts.members() {
self.web_illust(i, &data).await?;
}
} else {
let index = m.to_index(illusts.len());
if let Some(index) = index {
self.web_illust(&illusts[index], &data).await?;
}
}
}
None => {}
}
Ok(())
}
pub async fn web_illust(
&self,
illust: &JsonValue,
data: &JsonValue,
) -> Result<(), PixivDownloaderError> {
let id = parse_pixiv_id(&illust["id"]).ok_or("illust id is none")?;
let wdata = match self.data.get_web_data(id) {
Some(d) => d,
None => {
let pw = self.ctx.pixiv_web_client().await;
let wdata = pw
.get_artwork_ajax(id)
.await
.ok_or("Failed to get artwork ajax.")?;
self.data.set_web_data(id, wdata.clone());
wdata
}
};
let len = illust["pageCount"].as_u64().unwrap_or(1);
let pdata = if len != 1 {
let pw = self.ctx.pixiv_web_client().await;
let pdata = pw
.get_illust_pages(id)
.await
.ok_or("Failed to get illust pages.")?;
Some(pdata)
} else {
None
};
for i in self.task.push_configs.iter() {
send_message(
self.ctx.clone(),
None,
Some(&wdata),
pdata.as_ref(),
Some(illust),
Some(&data["tagTranslation"]),
i,
)
.await?;
}
Ok(())
}
@@ -117,7 +190,16 @@ impl<'a> RunContext<'a> {
}
};
for i in self.task.push_configs.iter() {
send_message(self.ctx.clone(), Some(&illust), data.as_ref(), i).await?;
send_message(
self.ctx.clone(),
Some(&illust),
data.as_ref(),
None,
None,
None,
i,
)
.await?;
}
Ok(())
}
@@ -128,8 +210,9 @@ pub async fn run_push_task(
task: &PushTask,
config: &PushTaskPixivConfig,
restrict: &PixivRestrictType,
mode: &PixivMode,
send_mode: Option<&TestSendMode>,
) -> Result<(), PixivDownloaderError> {
let ctx = RunContext::new(ctx, task, config, restrict, send_mode);
let ctx = RunContext::new(ctx, task, config, restrict, mode, send_mode);
ctx.run().await
}

View File

@@ -6,7 +6,7 @@ use crate::parser::description::DescriptionParser;
use crate::pixivapp::illust::PixivAppIllust;
use crate::push::every_push::{EveryPushClient, EveryPushTextType};
use crate::push::pushdeer::PushdeerClient;
use crate::utils::get_file_name_from_url;
use crate::utils::{get_file_name_from_url, parse_pixiv_id};
use crate::{get_helper, gettext};
use json::JsonValue;
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
@@ -15,6 +15,9 @@ struct RunContext<'a> {
ctx: Arc<ServerContext>,
illust: Option<&'a PixivAppIllust>,
data: Option<&'a JsonValue>,
pdata: Option<&'a JsonValue>,
tdata: Option<&'a JsonValue>,
translated_table: Option<&'a JsonValue>,
cfg: &'a PushConfig,
}
@@ -28,7 +31,11 @@ impl<'a> RunContext<'a> {
None => {}
}
match self.data {
Some(d) => d["illustTitle"].as_str(),
Some(d) => return d["title"].as_str().or_else(|| d["illustTitle"].as_str()),
None => {}
}
match self.tdata {
Some(d) => d["title"].as_str(),
None => None,
}
}
@@ -42,12 +49,11 @@ impl<'a> RunContext<'a> {
None => {}
}
match self.data {
Some(d) => d["illustId"]
.as_u64()
.or_else(|| match d["illustId"].as_str() {
Some(s) => s.parse().ok(),
None => None,
}),
Some(d) => return parse_pixiv_id(&d["id"]).or_else(|| parse_pixiv_id(&d["illustId"])),
None => {}
}
match self.tdata {
Some(d) => parse_pixiv_id(&d["id"]),
None => None,
}
}
@@ -61,6 +67,10 @@ impl<'a> RunContext<'a> {
None => {}
}
match self.data {
Some(d) => return d["userName"].as_str(),
None => {}
}
match self.tdata {
Some(d) => d["userName"].as_str(),
None => None,
}
@@ -90,10 +100,11 @@ impl<'a> RunContext<'a> {
None => {}
}
match self.data {
Some(d) => d["userId"].as_u64().or_else(|| match d["userId"].as_str() {
Some(s) => s.parse().ok(),
None => None,
}),
Some(d) => return parse_pixiv_id(&d["userId"]),
None => {}
}
match self.tdata {
Some(d) => parse_pixiv_id(&d["userId"]),
None => None,
}
}
@@ -112,7 +123,15 @@ impl<'a> RunContext<'a> {
None => {}
}
match self.data {
Some(d) => d["pageCount"].as_u64(),
Some(d) => return d["pageCount"].as_u64(),
None => {}
}
match self.tdata {
Some(d) => return d["pageCount"].as_u64(),
None => {}
}
match self.pdata {
Some(d) => Some(d.len() as u64),
None => None,
}
}
@@ -135,6 +154,20 @@ impl<'a> RunContext<'a> {
},
None => {}
}
match self.pdata {
Some(d) => {
return d[index as usize]["urls"]["original"]
.as_str()
.map(|s| s.to_owned())
}
None => {}
}
if index == 0 {
match self.data {
Some(d) => return d["urls"]["original"].as_str().map(|s| s.to_owned()),
None => {}
}
}
None
}
@@ -187,9 +220,101 @@ impl<'a> RunContext<'a> {
}
None => {}
}
match self.data {
Some(d) => {
if let Some(id) = d["aiType"].as_u64() {
if id == 2 {
return true;
}
}
}
None => {}
}
match self.tdata {
Some(d) => {
if let Some(id) = d["aiType"].as_u64() {
if id == 2 {
return true;
}
}
}
None => {}
}
false
}
pub fn add_ai_tag(&self) -> bool {
match self.cfg {
PushConfig::EveryPush(e) => e.add_ai_tag,
PushConfig::PushDeer(e) => e.add_ai_tag,
}
}
pub fn add_translated_tag(&self) -> bool {
match self.cfg {
PushConfig::EveryPush(e) => e.add_translated_tag,
PushConfig::PushDeer(e) => e.add_translated_tag,
}
}
pub fn add_tags_md(&self, text: &mut String, ensure_ascii: bool) {
if self.add_ai_tag() && self.is_ai() {
text.push_str("#");
text.push_str(gettext("AI generated"));
text.push_str(" ");
}
if let Some(i) = self.illust {
for tag in i.tags() {
if let Some(name) = tag.name() {
let encoded = if ensure_ascii {
percent_encode(name.as_bytes(), NON_ALPHANUMERIC).to_string()
} else {
name.to_owned()
};
text.push_str(&format!(
"[#{}](https://www.pixiv.net/tags/{}) ",
name, &encoded
));
if self.add_translated_tag() {
if let Some(t) = tag.translated_name() {
text.push_str(&format!(
"[#{}](https://www.pixiv.net/tags/{}) ",
t, &encoded
));
}
}
}
}
text.push_str(" \n");
return;
}
if let Some(d) = self.data {
for tag in d["tags"]["tags"].members() {
if let Some(name) = &tag["tag"].as_str() {
let encoded = if ensure_ascii {
percent_encode(name.as_bytes(), NON_ALPHANUMERIC).to_string()
} else {
name.to_string()
};
text.push_str(&format!(
"[#{}](https://www.pixiv.net/tags/{}) ",
name, &encoded
));
if self.add_translated_tag() {
if let Some(t) = &tag["translation"]["en"].as_str() {
text.push_str(&format!(
"[#{}](https://www.pixiv.net/tags/{}) ",
t, &encoded
));
}
}
}
}
text.push_str(" \n");
return;
}
}
pub async fn send_every_push(&self, cfg: &EveryPushConfig) -> Result<(), PixivDownloaderError> {
let client = EveryPushClient::new(&cfg.push_server);
match cfg.typ {
@@ -285,30 +410,7 @@ impl<'a> RunContext<'a> {
}
}
if cfg.add_tags {
if cfg.add_ai_tag && self.is_ai() {
text.push_str("#");
text.push_str(gettext("AI generated"));
text.push_str(" ");
}
if let Some(i) = self.illust {
for tag in i.tags() {
if let Some(name) = tag.name() {
text.push_str(&format!(
"[#{}](https://www.pixiv.net/tags/{}) ",
name, name
));
if cfg.add_translated_tag {
if let Some(t) = tag.translated_name() {
text.push_str(&format!(
"[#{}](https://www.pixiv.net/tags/{}) ",
t, name
));
}
}
}
}
}
text.push_str(" \n");
self.add_tags_md(&mut text, false);
}
if cfg.author_locations.contains(&AuthorLocation::Bottom) {
if let Some(a) = &author {
@@ -415,31 +517,7 @@ impl<'a> RunContext<'a> {
}
}
if cfg.add_tags {
if cfg.add_ai_tag && self.is_ai() {
text.push_str("#");
text.push_str(gettext("AI generated"));
text.push_str(" ");
}
if let Some(i) = self.illust {
for tag in i.tags() {
if let Some(name) = tag.name() {
let encoded = percent_encode(name.as_bytes(), NON_ALPHANUMERIC);
text.push_str(&format!(
"[#{}](https://www.pixiv.net/tags/{}) ",
name, &encoded
));
if cfg.add_translated_tag {
if let Some(t) = tag.translated_name() {
text.push_str(&format!(
"[#{}](https://www.pixiv.net/tags/{}) ",
t, &encoded
));
}
}
}
}
}
text.push_str(" \n");
self.add_tags_md(&mut text, true);
}
if cfg.author_locations.contains(&AuthorLocation::Bottom) {
if let Some(a) = &author {
@@ -495,31 +573,7 @@ impl<'a> RunContext<'a> {
}
}
if cfg.add_tags {
if cfg.add_ai_tag && self.is_ai() {
text.push_str("#");
text.push_str(gettext("AI generated"));
text.push_str(" ");
}
if let Some(i) = self.illust {
for tag in i.tags() {
if let Some(name) = tag.name() {
let encoded = percent_encode(name.as_bytes(), NON_ALPHANUMERIC);
text.push_str(&format!(
"[#{}](https://www.pixiv.net/tags/{}) ",
name, &encoded
));
if cfg.add_translated_tag {
if let Some(t) = tag.translated_name() {
text.push_str(&format!(
"[#{}](https://www.pixiv.net/tags/{}) ",
t, &encoded
));
}
}
}
}
}
text.push_str(" \n");
self.add_tags_md(&mut text, true);
}
if cfg.author_locations.contains(&AuthorLocation::Bottom) {
if let Some(a) = &author {
@@ -550,12 +604,18 @@ pub async fn send_message(
ctx: Arc<ServerContext>,
illust: Option<&PixivAppIllust>,
data: Option<&JsonValue>,
pdata: Option<&JsonValue>,
tdata: Option<&JsonValue>,
translated_table: Option<&JsonValue>,
cfg: &PushConfig,
) -> Result<(), PixivDownloaderError> {
let ctx = RunContext {
ctx,
illust,
data,
pdata,
tdata,
translated_table,
cfg,
};
ctx.run().await

View File

@@ -1,4 +1,5 @@
use crate::gettext;
use json::JsonValue;
use reqwest::IntoUrl;
use std::env;
use std::io::Write;
@@ -69,3 +70,10 @@ pub fn get_file_name_from_url<U: IntoUrl>(url: U) -> Option<String> {
}
Some(String::from(r.unwrap()))
}
pub fn parse_pixiv_id(id: &JsonValue) -> Option<u64> {
id.as_u64().or_else(|| match id.as_str() {
Some(s) => s.parse::<u64>().ok(),
None => None,
})
}