feat: 实现视频的筛选规则 (#457)

This commit is contained in:
ᴀᴍᴛᴏᴀᴇʀ
2025-09-24 00:42:27 +08:00
committed by GitHub
parent 6c7d295fe6
commit 210c94398a
39 changed files with 1345 additions and 181 deletions

View File

@@ -0,0 +1,2 @@
pub mod rule;
pub mod string_vec;

View File

@@ -0,0 +1,120 @@
use std::fmt::Display;
use derivative::Derivative;
use sea_orm::FromJsonQueryResult;
use sea_orm::prelude::DateTime;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, Debug, Serialize, Deserialize, Derivative)]
#[derivative(PartialEq, Eq)]
#[serde(rename_all = "camelCase", tag = "operator", content = "value")]
pub enum Condition<T: Serialize + Display> {
Equals(T),
Contains(T),
#[serde(deserialize_with = "deserialize_regex", serialize_with = "serialize_regex")]
MatchesRegex(String, #[derivative(PartialEq = "ignore")] regex::Regex),
Prefix(T),
Suffix(T),
GreaterThan(T),
LessThan(T),
Between(T, T),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
#[serde(rename_all = "camelCase", tag = "field", content = "rule")]
pub enum RuleTarget {
Title(Condition<String>),
Tags(Condition<String>),
FavTime(Condition<DateTime>),
PubTime(Condition<DateTime>),
PageCount(Condition<usize>),
Not(Box<RuleTarget>),
}
pub type AndGroup = Vec<RuleTarget>;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct Rule(pub Vec<AndGroup>);
impl<T: Serialize + Display> Display for Condition<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Condition::Equals(v) => write!(f, "等于“{}”", v),
Condition::Contains(v) => write!(f, "包含“{}”", v),
Condition::MatchesRegex(pat, _) => write!(f, "匹配“{}”", pat),
Condition::Prefix(v) => write!(f, "以“{}”开头", v),
Condition::Suffix(v) => write!(f, "以“{}”结尾", v),
Condition::GreaterThan(v) => write!(f, "大于“{}”", v),
Condition::LessThan(v) => write!(f, "小于“{}”", v),
Condition::Between(start, end) => write!(f, "在“{}”和“{}”之间", start, end),
}
}
}
impl Display for RuleTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn get_field_name(rt: &RuleTarget, depth: usize) -> &'static str {
match rt {
RuleTarget::Title(_) => "标题",
RuleTarget::Tags(_) => "标签",
RuleTarget::FavTime(_) => "收藏时间",
RuleTarget::PubTime(_) => "发布时间",
RuleTarget::PageCount(_) => "视频分页数量",
RuleTarget::Not(inner) => {
if depth == 0 {
get_field_name(inner, depth + 1)
} else {
"格式化失败"
}
}
}
}
let field_name = get_field_name(self, 0);
match self {
RuleTarget::Not(inner) => match inner.as_ref() {
RuleTarget::Title(cond) | RuleTarget::Tags(cond) => write!(f, "{}不{}", field_name, cond),
RuleTarget::FavTime(cond) | RuleTarget::PubTime(cond) => {
write!(f, "{}不{}", field_name, cond)
}
RuleTarget::PageCount(cond) => write!(f, "{}不{}", field_name, cond),
RuleTarget::Not(_) => write!(f, "格式化失败"),
},
RuleTarget::Title(cond) | RuleTarget::Tags(cond) => write!(f, "{}{}", field_name, cond),
RuleTarget::FavTime(cond) | RuleTarget::PubTime(cond) => {
write!(f, "{}{}", field_name, cond)
}
RuleTarget::PageCount(cond) => write!(f, "{}{}", field_name, cond),
}
}
}
impl Display for Rule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let groups: Vec<String> = self
.0
.iter()
.map(|group| {
let conditions: Vec<String> = group.iter().map(|target| format!("({})", target)).collect();
format!("{}", conditions.join(""))
})
.collect();
write!(f, "{}", groups.join(""))
}
}
fn deserialize_regex<'de, D>(deserializer: D) -> Result<(String, regex::Regex), D::Error>
where
D: Deserializer<'de>,
{
let pattern = String::deserialize(deserializer)?;
// 反序列化时预编译 regex优化性能
let regex = regex::Regex::new(&pattern).map_err(|e| serde::de::Error::custom(e))?;
Ok((pattern, regex))
}
fn serialize_regex<S>(pattern: &String, _regex: &regex::Regex, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(pattern)
}

View File

@@ -0,0 +1,20 @@
use sea_orm::FromJsonQueryResult;
use serde::{Deserialize, Serialize};
// reference: https://www.sea-ql.org/SeaORM/docs/generate-entity/column-types/#json-column
// 在 entity 中使用裸 Vec 仅在 postgres 中支持sea-orm 会将其映射为 postgres array
// 如果需要实现跨数据库的 array必须将其包裹在 wrapper type 中
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct StringVec(pub Vec<String>);
impl From<Vec<String>> for StringVec {
fn from(value: Vec<String>) -> Self {
Self(value)
}
}
impl From<StringVec> for Vec<String> {
fn from(value: StringVec) -> Self {
value.0
}
}

View File

@@ -2,6 +2,8 @@
use sea_orm::entity::prelude::*;
use crate::rule::Rule;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "collection")]
pub struct Model {
@@ -14,6 +16,7 @@ pub struct Model {
pub path: String,
pub created_at: String,
pub latest_row_at: DateTime,
pub rule: Option<Rule>,
pub enabled: bool,
}

View File

@@ -2,6 +2,8 @@
use sea_orm::entity::prelude::*;
use crate::rule::Rule;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "favorite")]
pub struct Model {
@@ -13,6 +15,7 @@ pub struct Model {
pub path: String,
pub created_at: String,
pub latest_row_at: DateTime,
pub rule: Option<Rule>,
pub enabled: bool,
}

View File

@@ -2,6 +2,8 @@
use sea_orm::entity::prelude::*;
use crate::rule::Rule;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "submission")]
pub struct Model {
@@ -12,6 +14,7 @@ pub struct Model {
pub path: String,
pub created_at: String,
pub latest_row_at: DateTime,
pub rule: Option<Rule>,
pub enabled: bool,
}

View File

@@ -2,6 +2,8 @@
use sea_orm::entity::prelude::*;
use crate::string_vec::StringVec;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)]
#[sea_orm(table_name = "video")]
pub struct Model {
@@ -25,7 +27,8 @@ pub struct Model {
pub favtime: DateTime,
pub download_status: u32,
pub valid: bool,
pub tags: Option<serde_json::Value>,
pub should_download: bool,
pub tags: Option<StringVec>,
pub single_page: Option<bool>,
pub created_at: String,
}

View File

@@ -2,6 +2,8 @@
use sea_orm::entity::prelude::*;
use crate::rule::Rule;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "watch_later")]
pub struct Model {
@@ -10,6 +12,7 @@ pub struct Model {
pub path: String,
pub created_at: String,
pub latest_row_at: DateTime,
pub rule: Option<Rule>,
pub enabled: bool,
}

View File

@@ -1,2 +1,5 @@
mod custom_type;
mod entities;
pub use custom_type::*;
pub use entities::*;