add author-name-filters settings

This commit is contained in:
2022-03-10 22:38:04 +08:00
parent 54fdca5b15
commit bf17c29ea1
12 changed files with 330 additions and 46 deletions

156
src/author_name_filter.rs Normal file
View 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<お仕事募集中"));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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