diff --git a/crates/bili_sync/src/utils/rule.rs b/crates/bili_sync/src/utils/rule.rs index c0f4cc4..3bda70e 100644 --- a/crates/bili_sync/src/utils/rule.rs +++ b/crates/bili_sync/src/utils/rule.rs @@ -37,13 +37,22 @@ impl Evaluatable for Condition { } } -impl Evaluatable<&NaiveDateTime> for Condition { - fn evaluate(&self, value: &NaiveDateTime) -> bool { +impl Evaluatable for Condition { + fn evaluate(&self, value: NaiveDateTime) -> bool { match self { - Condition::Equals(expected) => expected == value, - Condition::GreaterThan(threshold) => value > threshold, - Condition::LessThan(threshold) => value < threshold, - Condition::Between(start, end) => value > start && value < end, + Condition::Equals(expected) => *expected == value, + Condition::GreaterThan(threshold) => value > *threshold, + Condition::LessThan(threshold) => value < *threshold, + Condition::Between(start, end) => value > *start && value < *end, + _ => false, + } + } +} + +impl Evaluatable for Condition { + fn evaluate(&self, value: bool) -> bool { + match self { + Condition::Equals(expected) => *expected == value, _ => false, } } @@ -65,13 +74,20 @@ impl FieldEvaluatable for RuleTarget { .favtime .try_as_ref() .map(|fav_time| fav_time.and_utc().with_timezone(&Local).naive_local()) // 数据库中保存的一律是 utc 时间,转换为 local 时间再比较 - .is_some_and(|fav_time| cond.evaluate(&fav_time)), + .is_some_and(|fav_time| cond.evaluate(fav_time)), RuleTarget::PubTime(cond) => video .pubtime .try_as_ref() .map(|pub_time| pub_time.and_utc().with_timezone(&Local).naive_local()) - .is_some_and(|pub_time| cond.evaluate(&pub_time)), + .is_some_and(|pub_time| cond.evaluate(pub_time)), RuleTarget::PageCount(cond) => cond.evaluate(pages.len()), + RuleTarget::SumVideoLength(cond) => pages + .iter() + .try_fold(0usize, |acc, page| { + page.duration.try_as_ref().map(|d| acc + *d as usize).ok_or(()) + }) + .is_ok_and(|total_length| cond.evaluate(total_length)), + RuleTarget::MultiUpper(cond) => cond.evaluate(video.staff.as_ref().is_some()), RuleTarget::Not(inner) => !inner.evaluate(video, pages), } } @@ -86,9 +102,13 @@ impl FieldEvaluatable for RuleTarget { .tags .as_ref() .is_some_and(|tags| tags.0.iter().any(|tag| cond.evaluate(tag))), - RuleTarget::FavTime(cond) => cond.evaluate(&video.favtime.and_utc().with_timezone(&Local).naive_local()), - RuleTarget::PubTime(cond) => cond.evaluate(&video.pubtime.and_utc().with_timezone(&Local).naive_local()), + RuleTarget::FavTime(cond) => cond.evaluate(video.favtime.and_utc().with_timezone(&Local).naive_local()), + RuleTarget::PubTime(cond) => cond.evaluate(video.pubtime.and_utc().with_timezone(&Local).naive_local()), RuleTarget::PageCount(cond) => cond.evaluate(pages.len()), + RuleTarget::SumVideoLength(cond) => { + cond.evaluate(pages.iter().fold(0usize, |acc, page| acc + page.duration as usize)) + } + RuleTarget::MultiUpper(cond) => cond.evaluate(video.staff.is_some()), RuleTarget::Not(inner) => !inner.evaluate_model(video, pages), } } diff --git a/crates/bili_sync_entity/src/custom_type/rule.rs b/crates/bili_sync_entity/src/custom_type/rule.rs index eee745c..2d4cb16 100644 --- a/crates/bili_sync_entity/src/custom_type/rule.rs +++ b/crates/bili_sync_entity/src/custom_type/rule.rs @@ -30,6 +30,8 @@ pub enum RuleTarget { FavTime(Condition), PubTime(Condition), PageCount(Condition), + SumVideoLength(Condition), + MultiUpper(Condition), Not(Box), } @@ -63,6 +65,8 @@ impl Display for RuleTarget { RuleTarget::FavTime(_) => "收藏时间", RuleTarget::PubTime(_) => "发布时间", RuleTarget::PageCount(_) => "视频分页数量", + RuleTarget::SumVideoLength(_) => "视频总时长", + RuleTarget::MultiUpper(_) => "联合投稿", RuleTarget::Not(inner) => { if depth == 0 { get_field_name(inner, depth + 1) @@ -79,14 +83,16 @@ impl Display for RuleTarget { RuleTarget::FavTime(cond) | RuleTarget::PubTime(cond) => { write!(f, "{}不{}", field_name, cond) } - RuleTarget::PageCount(cond) => write!(f, "{}不{}", field_name, cond), + RuleTarget::PageCount(cond) | RuleTarget::SumVideoLength(cond) => write!(f, "{}不{}", field_name, cond), + RuleTarget::MultiUpper(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), + RuleTarget::PageCount(cond) | RuleTarget::SumVideoLength(cond) => write!(f, "{}{}", field_name, cond), + RuleTarget::MultiUpper(cond) => write!(f, "{}{}", field_name, cond), } } } diff --git a/web/src/lib/components/rule-editor.svelte b/web/src/lib/components/rule-editor.svelte index 17521b6..b8d4324 100644 --- a/web/src/lib/components/rule-editor.svelte +++ b/web/src/lib/components/rule-editor.svelte @@ -5,6 +5,7 @@ import { Checkbox } from '$lib/components/ui/checkbox/index.js'; import * as Card from '$lib/components/ui/card/index.js'; import { Badge } from '$lib/components/ui/badge/index.js'; + import * as Select from '$lib/components/ui/select/index.js'; import { PlusIcon, MinusIcon, XIcon } from '@lucide/svelte/icons'; import type { Rule, RuleTarget, Condition } from '$lib/types'; import { onMount } from 'svelte'; @@ -21,7 +22,9 @@ { value: 'tags', label: '标签' }, { value: 'favTime', label: '收藏时间' }, { value: 'pubTime', label: '发布时间' }, - { value: 'pageCount', label: '视频分页数量' } + { value: 'pageCount', label: '视频分页数量' }, + { value: 'sumVideoLength', label: '视频总时长' }, + { value: 'multiUpper', label: '联合投稿' } ]; const getOperatorOptions = (field: string) => { @@ -37,6 +40,7 @@ { value: 'matchesRegex', label: '匹配正则' } ]; case 'pageCount': + case 'sumVideoLength': return [ { value: 'equals', label: '等于' }, { value: 'greaterThan', label: '大于' }, @@ -51,6 +55,8 @@ { value: 'lessThan', label: '早于' }, { value: 'between', label: '时间范围' } ]; + case 'multiUpper': + return [{ value: 'equals', label: '等于' }]; default: return []; } @@ -80,7 +86,9 @@ } }); - function convertRuleTargetToLocal(target: RuleTarget): LocalCondition { + function convertRuleTargetToLocal( + target: RuleTarget + ): LocalCondition { if (typeof target.rule === 'object' && 'field' in target.rule) { // 嵌套的 not const innerCondition = convertRuleTargetToLocal(target.rule); @@ -93,10 +101,10 @@ let value = ''; let value2 = ''; if (Array.isArray(condition.value)) { - value = String(condition.value[0] || ''); - value2 = String(condition.value[1] || ''); + value = String(condition.value[0] ?? ''); + value2 = String(condition.value[1] ?? ''); } else { - value = String(condition.value || ''); + value = String(condition.value ?? ''); } return { field: target.field, @@ -111,8 +119,8 @@ if (localRule.length === 0) return null; return localRule.map((andGroup) => andGroup.conditions.map((condition) => { - let value: string | number | Date | (string | number | Date)[]; - if (condition.field === 'pageCount') { + let value: string | number | boolean | Date | (string | number | boolean | Date)[]; + if (condition.field === 'pageCount' || condition.field === 'sumVideoLength') { if (condition.operator === 'between') { value = [parseInt(condition.value) || 0, parseInt(condition.value2 || '0') || 0]; } else { @@ -124,6 +132,8 @@ } else { value = condition.value; } + } else if (condition.field === 'multiUpper') { + value = condition.value === 'true'; } else { if (condition.operator === 'between') { value = [condition.value, condition.value2 || '']; @@ -131,12 +141,12 @@ value = condition.value; } } - const conditionObj: Condition = { + const conditionObj: Condition = { operator: condition.operator, value }; - let target: RuleTarget = { + let target: RuleTarget = { field: condition.field, rule: conditionObj }; @@ -187,7 +197,7 @@ condition.field = value; const operators = getOperatorOptions(value); condition.operator = operators[0]?.value || 'equals'; - condition.value = ''; + condition.value = value === 'multiUpper' ? 'false' : ''; condition.value2 = ''; } else if (field === 'operator') { condition.operator = value; @@ -290,36 +300,43 @@
- + + {FIELD_OPTIONS.find((o) => o.value === condition.field)?.label ?? + condition.field} + + + {#each FIELD_OPTIONS as option (option.value)} + + {/each} + +
- + + {getOperatorOptions(condition.field).find( + (o) => o.value === condition.operator + )?.label ?? condition.operator} + + + {#each getOperatorOptions(condition.field) as option (option.value)} + + {/each} + +
@@ -328,7 +345,7 @@ {#if condition.operator === 'between'}
- {#if condition.field === 'pageCount'} + {#if condition.field === 'pageCount' || condition.field === 'sumVideoLength'} {/if}
- {:else if condition.field === 'pageCount'} + {:else if condition.field === 'pageCount' || condition.field === 'sumVideoLength'} + {:else if condition.field === 'multiUpper'} + updateCondition(groupIndex, conditionIndex, 'value', v)} + > + + {condition.value === 'true' ? 'true' : 'false'} + + + + + + {:else} { rule: Condition | RuleTarget; } -export type AndGroup = RuleTarget[]; +export type AndGroup = RuleTarget[]; export type Rule = AndGroup[]; export interface VideoSourceDetail {