mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-07-07 10:32:00 +08:00
add author-name-filters settings
This commit is contained in:
156
src/author_name_filter.rs
Normal file
156
src/author_name_filter.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use crate::data::json::ToJson;
|
||||
use crate::gettext;
|
||||
use crate::stdext::TryErr;
|
||||
use json::JsonValue;
|
||||
use regex::Regex;
|
||||
use std::cmp::PartialEq;
|
||||
use std::convert::From;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug, derive_more::From, PartialEq)]
|
||||
pub enum AuthorNameFilterError {
|
||||
String(String),
|
||||
Regex(regex::Error),
|
||||
}
|
||||
|
||||
impl Display for AuthorNameFilterError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::String(s) => { f.write_str(s) }
|
||||
Self::Regex(r) => { f.write_fmt(format_args!("{} {}", gettext("Failed to parse regex:"), r)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for AuthorNameFilterError {
|
||||
fn from(s: &str) -> Self {
|
||||
Self::String(String::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, derive_more::From)]
|
||||
pub enum AuthorNameFilter {
|
||||
Simple(String),
|
||||
Regex(Regex),
|
||||
}
|
||||
|
||||
/// Used to filter the author name
|
||||
pub trait AuthorFiler {
|
||||
/// Used to filter the author name
|
||||
fn filter(&self, author: &str) -> String;
|
||||
}
|
||||
|
||||
impl AuthorNameFilter {
|
||||
pub fn from_json<T: ToJson>(v: T) -> Result<Vec<Self>, AuthorNameFilterError> {
|
||||
let v = v.to_json().try_err(gettext("Failed to get JSON object."))?;
|
||||
if !v.is_array() {
|
||||
Err(gettext("Unsupported JSON type."))?;
|
||||
}
|
||||
let mut re = Vec::new();
|
||||
for j in v.members() {
|
||||
re.push(Self::try_from(j)?);
|
||||
}
|
||||
Ok(re)
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthorFiler for AuthorNameFilter {
|
||||
fn filter(&self, author: &str) -> String {
|
||||
match self {
|
||||
Self::Simple(s) => {
|
||||
match author.find(s) {
|
||||
Some(i) => { String::from(&author[..i]) }
|
||||
None => { String::from(author) }
|
||||
}
|
||||
}
|
||||
Self::Regex(r) => {
|
||||
r.replace(author, "").to_owned().to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AuthorFiler> AuthorFiler for Vec<T> {
|
||||
fn filter(&self, author: &str) -> String {
|
||||
let ori = String::from(author);
|
||||
for i in self {
|
||||
let r = i.filter(author);
|
||||
if r != ori {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return ori;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for AuthorNameFilter {
|
||||
fn from(s: &str) -> Self {
|
||||
Self::Simple(String::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AuthorNameFilter {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match self {
|
||||
Self::Simple(s) => {
|
||||
match other {
|
||||
Self::Regex(_) => { false }
|
||||
Self::Simple(t) => { s == t }
|
||||
}
|
||||
}
|
||||
Self::Regex(r) => {
|
||||
match other {
|
||||
Self::Simple(_) => { false }
|
||||
Self::Regex(s) => {
|
||||
r.as_str() == s.as_str()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&JsonValue> for AuthorNameFilter {
|
||||
type Error = AuthorNameFilterError;
|
||||
fn try_from(j: &JsonValue) -> Result<Self, Self::Error> {
|
||||
if j.is_string() {
|
||||
return Ok(Self::from(j.as_str().unwrap()));
|
||||
} else if j.is_object() {
|
||||
let t = (&j["type"]).as_str().try_err(gettext("Failed to get filter's type."))?.to_lowercase();
|
||||
let rule = (&j["rule"]).as_str().try_err(gettext("Failed to get filter's rule."))?;
|
||||
if t == "simple" {
|
||||
return Ok(Self::from(rule));
|
||||
} else if t == "regex" {
|
||||
return Ok(Self::from(Regex::new(rule)?));
|
||||
} else {
|
||||
Err(format!("{} {}", gettext("Unknown filter's type:"), t.as_str()))?;
|
||||
}
|
||||
} else {
|
||||
Err(gettext("Unsupported JSON type."))?;
|
||||
};
|
||||
return Err(Self::Error::from(""));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_author_name_filters(v: &JsonValue) -> bool {
|
||||
let r = AuthorNameFilter::from_json(v);
|
||||
if r.is_err() {
|
||||
println!("{} {}", gettext("Failed parse author name filters:"), r.as_ref().unwrap_err());
|
||||
}
|
||||
r.is_ok()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_author_name_filter() {
|
||||
assert!(AuthorNameFilter::from("s") == AuthorNameFilter::from("s"));
|
||||
assert!(AuthorNameFilter::from(Regex::new("s").unwrap()) == AuthorNameFilter::from(Regex::new("s").unwrap()));
|
||||
let l = AuthorNameFilter::from_json(json::array!["🌸"]).unwrap();
|
||||
assert_eq!(l, vec![AuthorNameFilter::from("🌸")]);
|
||||
assert_eq!(l[0].filter("moco🌸お仕事募集中"), String::from("moco"));
|
||||
let r = AuthorNameFilter::from(Regex::new(".?お仕事募集中").unwrap());
|
||||
assert_eq!(r.filter("moco🌸お仕事募集中"), String::from("moco"));
|
||||
let l = AuthorNameFilter::from_json(json::array![{"type": "simple", "rule": "🌸"}, {"type": "regex", "rule": ".?お仕事募集中"}]).unwrap();
|
||||
assert_eq!(l, vec![AuthorNameFilter::from("🌸"), AuthorNameFilter::from(r)]);
|
||||
assert_eq!(l.filter("moco<お仕事募集中🌸お仕事募集中"), String::from("moco<お仕事募集中"));
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::author_name_filter::AuthorFiler;
|
||||
use crate::gettext;
|
||||
use crate::opthelper::OptHelper;
|
||||
use crate::pixiv_link::ToPixivID;
|
||||
use crate::pixiv_link::PixivID;
|
||||
use json::JsonValue;
|
||||
@@ -6,7 +8,7 @@ use std::convert::TryInto;
|
||||
use xml::unescape;
|
||||
|
||||
/// Pixiv's basic data
|
||||
pub struct PixivData {
|
||||
pub struct PixivData<'a> {
|
||||
/// ID
|
||||
pub id: PixivID,
|
||||
/// The title
|
||||
@@ -14,10 +16,11 @@ pub struct PixivData {
|
||||
/// The author
|
||||
pub author: Option<String>,
|
||||
pub description: Option<String>,
|
||||
helper: OptHelper<'a>,
|
||||
}
|
||||
|
||||
impl PixivData {
|
||||
pub fn new<T: ToPixivID>(id: T) -> Option<Self> {
|
||||
impl<'a> PixivData<'a> {
|
||||
pub fn new<T: ToPixivID>(id: T, helper: OptHelper<'a>) -> Option<Self> {
|
||||
let i = id.to_pixiv_id();
|
||||
if i.is_none() {
|
||||
return None;
|
||||
@@ -27,6 +30,7 @@ impl PixivData {
|
||||
title: None,
|
||||
author: None,
|
||||
description: None,
|
||||
helper: helper,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,7 +58,11 @@ impl PixivData {
|
||||
if self.author.is_none() || allow_overwrite {
|
||||
let author = value["userName"].as_str();
|
||||
if author.is_some() {
|
||||
self.author = Some(String::from(author.unwrap()));
|
||||
let au = author.unwrap();
|
||||
match self.helper.author_name_filters() {
|
||||
Some(l) => { self.author = Some(l.filter(au)) }
|
||||
None => { self.author = Some(String::from(author.unwrap())); }
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.description.is_none() || allow_overwrite {
|
||||
|
||||
@@ -74,13 +74,13 @@ impl JSONDataFile {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PixivData> for JSONDataFile {
|
||||
impl<'a> From<PixivData<'a>> for JSONDataFile {
|
||||
fn from(p: PixivData) -> Self {
|
||||
JSONDataFile::from(&p)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PixivData> for JSONDataFile {
|
||||
impl<'a> From<&'a PixivData<'a>> for JSONDataFile {
|
||||
fn from(p: &PixivData) -> Self {
|
||||
let mut f = Self {
|
||||
id: p.id.clone(),
|
||||
|
||||
@@ -74,7 +74,7 @@ impl Main {
|
||||
}
|
||||
let base = PathBuf::from(".");
|
||||
let json_file = base.join(format!("{}.json", id));
|
||||
let mut datas = PixivData::new(id).unwrap();
|
||||
let mut datas = PixivData::new(id, pw.helper.clone()).unwrap();
|
||||
if ajax_ver {
|
||||
datas.from_web_page_ajax_data(&re, true);
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
extern crate c_fixed_string;
|
||||
extern crate chrono;
|
||||
extern crate dateparser;
|
||||
extern crate derive_more;
|
||||
extern crate futures_util;
|
||||
extern crate json;
|
||||
#[cfg(feature = "int-enum")]
|
||||
@@ -21,6 +22,7 @@ extern crate xml;
|
||||
#[cfg(feature = "exif")]
|
||||
#[doc(hidden)]
|
||||
mod _exif;
|
||||
mod author_name_filter;
|
||||
mod cookies;
|
||||
mod data;
|
||||
mod download;
|
||||
@@ -37,6 +39,7 @@ mod pixiv_web;
|
||||
mod retry_interval;
|
||||
mod settings;
|
||||
mod settings_list;
|
||||
mod stdext;
|
||||
mod utils;
|
||||
mod webclient;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::author_name_filter::AuthorNameFilter;
|
||||
use crate::opts::CommandOpts;
|
||||
use crate::list::NonTailList;
|
||||
use crate::retry_interval::parse_retry_interval_from_json;
|
||||
@@ -12,9 +13,17 @@ pub struct OptHelper<'a> {
|
||||
/// Settings
|
||||
settings: &'a SettingStore,
|
||||
default_retry_interval: NonTailList<Duration>,
|
||||
_author_name_filters: Option<Vec<AuthorNameFilter>>,
|
||||
}
|
||||
|
||||
impl<'a> OptHelper<'a> {
|
||||
pub fn author_name_filters(&self) -> Option<&Vec<AuthorNameFilter>> {
|
||||
if self.settings.have("author-name-filters") {
|
||||
return self._author_name_filters.as_ref();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// return cookies location, no any check
|
||||
pub fn cookies(&self) -> Option<String> {
|
||||
if self.opt.cookies.is_some() {
|
||||
@@ -40,10 +49,16 @@ impl<'a> OptHelper<'a> {
|
||||
pub fn new(opt: &'a CommandOpts, settings: &'a SettingStore) -> Self {
|
||||
let mut l = NonTailList::default();
|
||||
l += Duration::new(3, 0);
|
||||
let _author_name_filters = if settings.have("author-name-filters") {
|
||||
Some(AuthorNameFilter::from_json(settings.get("author-name-filters").unwrap()).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self {
|
||||
opt,
|
||||
settings,
|
||||
default_retry_interval: l,
|
||||
_author_name_filters: _author_name_filters,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::author_name_filter::check_author_name_filters;
|
||||
use crate::gettext;
|
||||
use crate::retry_interval::check_retry_interval;
|
||||
use crate::settings::SettingDes;
|
||||
@@ -12,6 +13,7 @@ pub fn get_settings_list() -> Vec<SettingDes> {
|
||||
SettingDes::new("retry", gettext("Max retry count if request failed."), JsonValueType::Number, Some(check_u64)).unwrap(),
|
||||
SettingDes::new("retry-interval", gettext("The interval (in seconds) between two retries."), JsonValueType::Multiple, Some(check_retry_interval)).unwrap(),
|
||||
SettingDes::new("use-webpage", gettext("Use data from webpage first."), JsonValueType::Boolean, None).unwrap(),
|
||||
SettingDes::new("author-name-filters", gettext("Remove the part which after these parttens."), JsonValueType::Array, Some(check_author_name_filters)).unwrap(),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
14
src/stdext.rs
Normal file
14
src/stdext.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
/// Try with custom error message
|
||||
pub trait TryErr<T, E> {
|
||||
/// try with custom error message
|
||||
fn try_err(&self, err: E) -> Result<T, E>;
|
||||
}
|
||||
|
||||
impl<T: ToOwned + ToOwned<Owned = T>, E> TryErr<T, E> for Option<T> {
|
||||
fn try_err(&self, v: E) -> Result<T, E> {
|
||||
match self {
|
||||
Some(r) => { Ok(r.to_owned()) }
|
||||
None => { Err(v) }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user