feat(predicates): added runtime predicate
This commit is contained in:
22
README.md
22
README.md
@@ -161,6 +161,8 @@ Filters on the released status of the movie.
|
||||
|
||||
Filters on the age of the movie.
|
||||
|
||||
See [Duration expressions](#duration-expressions) for more details
|
||||
|
||||
```yaml
|
||||
- age: less than 2 years
|
||||
# or
|
||||
@@ -178,6 +180,19 @@ Filters on the score of the movie.
|
||||
- score: below 5.5
|
||||
```
|
||||
|
||||
---
|
||||
### `runtime`
|
||||
|
||||
Filters on the runtime _(duration)_ of the movie.
|
||||
|
||||
See [Duration expressions](#duration-expressions) for more details
|
||||
|
||||
```yaml
|
||||
- runtime: less than 2.5 hours
|
||||
# or
|
||||
- runtime: more than 120 minutes
|
||||
```
|
||||
|
||||
---
|
||||
### `voteCount`
|
||||
|
||||
@@ -316,3 +331,10 @@ Predicate that invert the result of its child predicate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Duration expressions
|
||||
|
||||
Duration expressions, like the one used in the `age` or `runtime` predicate can be expressed in the following way:
|
||||
- an **operator**: `less than` or `more than`
|
||||
- a integer or decimal **number**: `2` or `2.5`
|
||||
- a **unit**: one of the following `year`, `month`, `week`, `day`, `hour`, `minute`. Singular or plural doesn't matter, so `hour` is the same as `hours`
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
{ "$ref": "#/definitions/castPredicate" },
|
||||
{ "$ref": "#/definitions/crewPredicate" },
|
||||
{ "$ref": "#/definitions/releasedPredicate" },
|
||||
{ "$ref": "#/definitions/runtimePredicate" },
|
||||
{ "$ref": "#/definitions/productionCompanyPredicate" }
|
||||
]
|
||||
},
|
||||
@@ -245,6 +246,17 @@
|
||||
"properties": {
|
||||
"productionCompany": { "type": "array", "items": {"type": "string" }}
|
||||
}
|
||||
},
|
||||
|
||||
"runtimePredicate": {
|
||||
"$id": "#/definitions/runtimePredicate",
|
||||
"type": "object",
|
||||
"required": ["runtime"],
|
||||
"properties": {
|
||||
"runtime": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
// End of definitions
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { AgePredicate } from '@core/lib/rules/predicate/age';
|
||||
import * as movieJson from './movie.json';
|
||||
import { MovieDetails } from '@core/api/overseerr/interfaces';
|
||||
import { AdultPredicate } from '@core/lib/rules/predicate/adult';
|
||||
import { RuntimePredicate } from '@core/lib/rules/predicate/runtime';
|
||||
|
||||
const movie = movieJson as MovieDetails;
|
||||
const testRule = (predicate: Predicate | Predicate[]) => new Rule('test rule', Array.isArray(predicate) ? predicate : [predicate], 'accept');
|
||||
@@ -257,3 +258,18 @@ describe('adult predicate', () => {
|
||||
assertRuleDoesntMatch(yesRule, movie);
|
||||
});
|
||||
});
|
||||
|
||||
describe('runtime predicate', () => {
|
||||
it('should match', async () => {
|
||||
assertRuleMatches(testRule(new RuntimePredicate({ runtime: 'less than 3 hours' })), movie);
|
||||
assertRuleMatches(testRule(new RuntimePredicate({ runtime: 'less than 2.5 hours' })), movie);
|
||||
assertRuleMatches(testRule(new RuntimePredicate({ runtime: 'more than 2 hour' })), movie);
|
||||
assertRuleMatches(testRule(new RuntimePredicate({ runtime: 'more than 2.25 hours' })), movie);
|
||||
});
|
||||
|
||||
it('should not match', async () => {
|
||||
assertRuleDoesntMatch(testRule(new RuntimePredicate({ runtime: 'more than 3 hours' })), movie);
|
||||
assertRuleDoesntMatch(testRule(new RuntimePredicate({ runtime: 'more than 2.5 weeks' })), movie);
|
||||
assertRuleDoesntMatch(testRule(new RuntimePredicate({ runtime: 'less than 2 minutes' })), movie);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ export type PredicateOption =
|
||||
| KeywordOptions
|
||||
| ProductionCompanyOptions
|
||||
| ReleasedOptions
|
||||
| RuntimeOptions
|
||||
| ScoreOptions
|
||||
| VoteCountOptions
|
||||
| WatchProvidersOptions;
|
||||
@@ -93,3 +94,7 @@ export type CrewJobNamesOptions = {
|
||||
job: string;
|
||||
names: string[];
|
||||
};
|
||||
|
||||
export type RuntimeOptions = {
|
||||
runtime: string;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PredicateBuilder } from '@core/lib/rules';
|
||||
import { MovieDetails } from '@core/api/overseerr/interfaces';
|
||||
import { BooleanPredicate } from '@core/lib/rules/predicate/boolean';
|
||||
import { AdultOptions, ReleasedOptions } from '@core/lib/rules/interfaces';
|
||||
import { AdultOptions } from '@core/lib/rules/interfaces';
|
||||
|
||||
export class AdultPredicate extends BooleanPredicate {
|
||||
constructor(options: AdultOptions) {
|
||||
|
||||
22
src/lib/rules/predicate/runtime.ts
Normal file
22
src/lib/rules/predicate/runtime.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { MovieDetails } from '@core/api/overseerr/interfaces';
|
||||
import { PredicateBuilder } from '@core/lib/rules';
|
||||
import { fromHumanReadableDuration, TimePredicate } from '@core/lib/rules/predicate/time';
|
||||
import { RuntimeOptions } from '@core/lib/rules/interfaces';
|
||||
|
||||
export class RuntimePredicate extends TimePredicate {
|
||||
constructor(options: RuntimeOptions) {
|
||||
super(fromHumanReadableDuration(options.runtime), runtimeMetric);
|
||||
}
|
||||
}
|
||||
|
||||
const runtimeMetric = (movie: MovieDetails): number => {
|
||||
if (!movie.runtime) {
|
||||
return 0;
|
||||
}
|
||||
return Number(movie.runtime) * 60;
|
||||
};
|
||||
|
||||
export const RuntimePredicateBuilder: PredicateBuilder = {
|
||||
key: 'runtime',
|
||||
build: (data: RuntimeOptions) => new RuntimePredicate(data),
|
||||
};
|
||||
@@ -13,13 +13,14 @@ export class TimePredicate extends NumberPredicate {
|
||||
}
|
||||
}
|
||||
|
||||
export const fromHumanReadableDuration = (str: string): { threshold: number; operator: 'lt' | 'gt' } => {
|
||||
const regex = /(less than|more than) (\d+(\.\d+)?) (years|year|days|day|month|months|weeks|week)/gm;
|
||||
export const fromHumanReadableDuration = (str: string): NumberPredicateOptions => {
|
||||
const regex = /(less than|more than) (\d+(\.\d+)?) (year|month|week|day|hour|minute)[s]?/gm;
|
||||
const groups = [...str.matchAll(regex)][0];
|
||||
const operator = groups[1];
|
||||
const threshold: number = Number(groups[2]);
|
||||
const units = groups[4].endsWith('s') ? groups[4] : `${groups[4]}s`;
|
||||
const scale: Record<string, number> = {
|
||||
minutes: 60,
|
||||
hours: 60 * 60,
|
||||
days: 24 * 60 * 60,
|
||||
weeks: 7 * 24 * 60 * 60,
|
||||
|
||||
Reference in New Issue
Block a user