Merge pull request #10 from psyko-gh/feature-voice-cast

Added voice option in cast predicate + Docs lint
This commit is contained in:
psyko-gh
2024-08-25 16:22:43 +02:00
committed by GitHub
13 changed files with 302 additions and 203 deletions

View File

@@ -24,6 +24,8 @@ jobs:
run: npm run lint
- name: Format
run: npm run format
- name: Docs format
run: npm run format-docs
- name: Build
run: npm run build
- name: Tests

View File

@@ -67,21 +67,23 @@ config:
- name: Accept favorites actors
whenMatch:
- cast:
- Jenna Ortega
- Michael Keaton
- Denzel Washington
- Keanu Reeves
- Jessica Alba
- Halle Berry
- Sydney Sweeney
- Tom Hanks
- Samuel L. Jackson
- Morgan Freeman
- Anthony Hopkins
- Gal Gadot
- Tom Cruise
- Brad Pitt
- Matt Damon
voice: exclude
names:
- Jenna Ortega
- Michael Keaton
- Denzel Washington
- Keanu Reeves
- Jessica Alba
- Halle Berry
- Sydney Sweeney
- Tom Hanks
- Samuel L. Jackson
- Morgan Freeman
- Anthony Hopkins
- Gal Gadot
- Tom Cruise
- Brad Pitt
- Matt Damon
action: accept
- name: Reject declining actors
whenMatch:

View File

@@ -1,4 +1,3 @@
# Schema
The JSON Schema is available here: https://github.com/psyko-gh/overcrawlrr/blob/master/schema/schema.json

View File

@@ -9,25 +9,27 @@ Overcrawlrr configuration is defined in the `settings.yaml` searched by default
---
### Overseerr authentication
Overcrawlrr need to authenticate to Overseerr to fetch movie information and create movie requests.
It is recommended to create a [local user in Overseerr](https://docs.overseerr.dev/using-overseerr/users#creating-local-users) dedicated to Overcrawlrr.
Doing so, you can have fine-grained control on its permissions:
- Allow/deny automatic requests validation,
- Allow/deny to request in 4K,
- Specify the languages and the region when discovering movies,
- And more...
- Allow/deny automatic requests validation,
- Allow/deny to request in 4K,
- Specify the languages and the region when discovering movies,
- And more...
Once the user is created, you can fill these value in Overcrawlrr `settings.yaml`:
```yaml title="settings.yaml"
config:
config:
overseerr:
apiUrl: xxx # required
user: xxx # required
password: xxx # required
dryRun: true # Optional - dryRun will not send requests to Overseerr
# allowing you to test your rules
apiUrl: xxx # required
user: xxx # required
password: xxx # required
dryRun: true # Optional - dryRun will not send requests to Overseerr
# allowing you to test your rules
```
---
@@ -39,37 +41,38 @@ Once the user is created, you can fill these value in Overcrawlrr `settings.yaml
This authentication is optional and only required if you're using the Smart Recommendations job
To read data from your Plex library, you have to specify your Plex URL and token in `settings.yaml`:
```yaml title="settings.yaml"
config:
config:
# Only required when using Smart recommendations job
plex:
apiUrl: xxx
plexToken: xxx
```
---
### Using environment variables
You can refer to environment variables using the `{{ key }}` syntax.
```yaml title="settings.yaml"
config:
config:
overseerr:
apiUrl: xxx
user: '{{ OVERSEERR_USER }}'
password: '{{ OVERSEERR_PASSWORD }}'
apiUrl: xxx
user: '{{ OVERSEERR_USER }}'
password: '{{ OVERSEERR_PASSWORD }}'
```
These variables can be defined in the `docker-compose.yaml`
```yaml title="docker-compose.yaml"
services:
overcrawlrr:
# ...
environment:
- OVERSEERR_USER=
- OVERSEERR_PASSWORD=
overcrawlrr:
# ...
environment:
- OVERSEERR_USER=
- OVERSEERR_PASSWORD=
```
---

View File

@@ -4,23 +4,21 @@
Using Overcrawlrr requires you to have a running instance of Overseerr
With docker-compose
```yaml
services:
overcrawlrr:
image: ghcr.io/psyko-gh/overcrawlrr:latest
container_name: overcrawlrr
restart: unless-stopped
ports:
- 5056:5056
environment:
- OVERSEERR_USER=
- OVERSEERR_PASSWORD=
volumes:
- /path/to/config:/config
overcrawlrr:
image: ghcr.io/psyko-gh/overcrawlrr:latest
container_name: overcrawlrr
restart: unless-stopped
ports:
- 5056:5056
environment:
- OVERSEERR_USER=
- OVERSEERR_PASSWORD=
volumes:
- /path/to/config:/config
```
With docker cli

View File

@@ -10,19 +10,19 @@ It will fetch movies from Overseerr _(from the upcoming/popular/trending section
```yaml title="settings.yaml"
config:
overseer:
# ....
discovery:
# Required
cron: '30 3 * * *'
# Required - the overseer streams to search through
# Possible values: upcoming, popular, trending
streams:
- upcoming
- popular
- trending
# Required - the name of the ruleset used to evaluate movies
ruleset: Ruleset name
overseer:
# ....
discovery:
# Required
cron: '30 3 * * *'
# Required - the overseer streams to search through
# Possible values: upcoming, popular, trending
streams:
- upcoming
- popular
- trending
# Required - the name of the ruleset used to evaluate movies
ruleset: Ruleset name
```
## Smart recommendations
@@ -37,19 +37,38 @@ Considering the movie you liked, it will crawl their recommended movies and appl
```yaml title="settings.yaml"
config:
plex:
# ....
smartRecommendations:
# Required
cron: '1 14 * * *'
# The name of the plex library to use as a seed
plexLibrary: Films
# Your minimum personal rating to consider
minimumRating: 6.9
ruleset: Ruleset name
plex:
# ....
smartRecommendations:
# Required
cron: '1 14 * * *'
# The name of the plex library to use as a seed
plexLibrary: Films
# Your minimum personal rating to consider
minimumRating: 6.9
ruleset: Ruleset name
```
## Checking output
## Testing jobs
### Evaluating a ruleset
You can also evaluate a ruleset against a specific movie, using the following URL:
```
http://localhost:5056/api/movies/<id_movie>/evaluate/<ruleset_name>
```
Simply replace `<id_movie>` and `<rulest_name>` by the values matching your needs.
To find the ID of a movie, navigate to the movie in Overseerr. The ID is the number is the URL.
### Manually triggering jobs
You can manually trigger the job by accessing the following URL (assuming Overcrawlrr is accessible at `http://localhost:5056`):
- Discover job: `http://localhost:5056/api/discover`
- Smart recommendations job: `http://localhost:5056/api/smartRecommendations`
Evaluation of the jobs are displayed in the container log. If you named your service `overcrawlrr`, you can see it with the following command:

View File

@@ -5,9 +5,9 @@
Filters on the adult status of the movie.
```yaml
- adult: yes
# or
- adult: no
- adult: yes
# or
- adult: no
```
---
@@ -19,25 +19,27 @@ Filters on the age of the movie.
See [Duration expressions](#duration-expressions) for more details
```yaml
- age: less than 2 years
# or
- age: more than 6 months
- age: less than 2 years
# or
- age: more than 6 months
```
---
### `and`
Predicate that will match if all of its predicate matches
```yaml
# Will match if the movie is less than 2 years old AND if the movie genre is 'animation'
- and:
- age: less than 2 years
- genre:
# Will match if the movie is less than 2 years old AND if the movie genre is 'animation'
- and:
- age: less than 2 years
- genre:
- animation
```
---
### `cast`
Filters based on the cast of the movie. Will match when one or more of the listed name matches.
@@ -45,12 +47,22 @@ Filters based on the cast of the movie. Will match when one or more of the liste
**Case insensitive**
```yaml
- cast:
- Denzel Washington
- Jessica Alba
- cast:
- Denzel Washington
- Jessica Alba
```
It is also possible to exclude an cast when it's performing voice only (in animation movies for example)
```yaml
- cast:
voice: exclude
names:
- Scarlett Johansson
```
---
### `crew`
Filters based on the crew of the movie. Will match when one or more of the listed name matches.
@@ -58,21 +70,23 @@ Filters based on the crew of the movie. Will match when one or more of the liste
**Case insensitive**
```yaml
- crew:
- James Cameron
- Hans Zimmer
- crew:
- James Cameron
- Hans Zimmer
```
It is also possible to specify the job
```yaml
- crew:
job: director
names:
- crew:
job: director
names:
- James Cameron
- Steven Spielberg
```
---
### `genre`
Filters on the genre of the movie. Will match when one or more of the listed genres matches the genre of the movie.
@@ -80,25 +94,27 @@ Filters on the genre of the movie. Will match when one or more of the listed gen
**Case insensitive**
```yaml
- genre: musical
# or with an array of values
- genre:
- animation
- romance
- genre: musical
# or with an array of values
- genre:
- animation
- romance
```
---
### `not`
Predicate that invert the result of its child predicate
```yaml
- not:
- genre:
- not:
- genre:
- animation
```
---
### `originalLanguage`
Filters on the original language of the movie
@@ -106,27 +122,29 @@ Filters on the original language of the movie
**Case insensitive**
```yaml
# ISO 639-1 format of the language (de, au, us, fr...)
- originalLanguage: en
# or with an array of values
- originalLanguage:
- en
- fr
# ISO 639-1 format of the language (de, au, us, fr...)
- originalLanguage: en
# or with an array of values
- originalLanguage:
- en
- fr
```
---
### `or`
Predicate that will match if any of its predicate matches
```yaml
# Will match if the movie is less than 2 years old OR if the movie score is above 8
- or:
- age: less than 2 years
- score: above 8
# Will match if the movie is less than 2 years old OR if the movie score is above 8
- or:
- age: less than 2 years
- score: above 8
```
---
### `productionCompany`
Filters based on the production companies of the movie. Will match when one or more of the listed company matches.
@@ -134,24 +152,26 @@ Filters based on the production companies of the movie. Will match when one or m
**Case insensitive**
```yaml
- productionCompany:
- 20th Century Fox
- Warner Bros. Pictures
- Twisted Pictures
- productionCompany:
- 20th Century Fox
- Warner Bros. Pictures
- Twisted Pictures
```
---
### `released`
Filters on the released status of the movie.
```yaml
- released: yes
# or
- released: no
- released: yes
# or
- released: no
```
---
### `runtime`
Filters on the runtime _(duration)_ of the movie.
@@ -159,12 +179,13 @@ 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
- runtime: less than 2.5 hours
# or
- runtime: more than 120 minutes
```
---
### `score`
Filters on the score of the movie.
@@ -172,12 +193,13 @@ Filters on the score of the movie.
Overseerr score is expressed between 0 to 10, but to make things clear, it is possible to pass the score as a fractional number.
```yaml
- score: above 6.5
# or
- score: below 75/100 # Would be the same as 7.5, 7.5/10 or even 750/1000
- score: above 6.5
# or
- score: below 75/100 # Would be the same as 7.5, 7.5/10 or even 750/1000
```
---
### `status`
Filters on the status of the movie.
@@ -187,24 +209,24 @@ The possible values are the one provided by [TMDB](https://www.themoviedb.org/):
**Case insensitive**
```yaml
- status: released
# or with an array of values
- status:
- released
- post production
- planned
- status: released
# or with an array of values
- status:
- released
- post production
- planned
```
---
### `voteCount`
Filters on the vote count of the movie.
```yaml
- voteCount: above 1000
# or
- voteCount: below 100
- voteCount: above 1000
# or
- voteCount: below 100
```
---
@@ -216,11 +238,11 @@ Filters based on the available Streaming/VOD platforms. Will match when one or m
**Case insensitive**
```yaml
# This predicate will match when the movie is available in Germany on Netflix or Amazon Prime
- watchProviders:
# ISO 3166-1 alpha-2 format of the region (de, au, us, fr...)
- region: de
- names:
# This predicate will match when the movie is available in Germany on Netflix or Amazon Prime
- watchProviders:
# ISO 3166-1 alpha-2 format of the region (de, au, us, fr...)
- region: de
- names:
- Netflix
- Amazon Prime
```
@@ -231,12 +253,12 @@ Filters based on the available Streaming/VOD platforms. Will match when one or m
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`
- 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`
The following expressions are valid:
- `less than 1 hour`/`less than 1 hours`/`less than 60 minutes`
- `less than 3 hours`/`less than 3 hour`/`less than 180 minute`
- `more than 6 month`
- `less than 1 hour`/`less than 1 hours`/`less than 60 minutes`
- `less than 3 hours`/`less than 3 hour`/`less than 180 minute`
- `more than 6 month`

View File

@@ -8,24 +8,24 @@ A ruleset is a named group of rules.
```yaml title="settings.yaml"
config:
# ...
rulesets:
# Required - the name of the ruleset
- name: Ruleset name
# Optional - the name of another ruleset.
# This ruleset will apply all the rules in the extended ruleset
# before applying its own rules
extends: Another ruleset
# Required - An array defining the rules of the ruleset
rules:
- # rule 1
- # rule 2
- # rule ...
# ...
rulesets:
# Required - the name of the ruleset
- name: Ruleset name
# Optional - the name of another ruleset.
# This ruleset will apply all the rules in the extended ruleset
# before applying its own rules
extends: Another ruleset
# Required - An array defining the rules of the ruleset
rules:
- # rule 1
- # rule 2
- # rule ...
```
- The rules are applied in the declared order,
- When a rule matches, the rule's `action` is applied, and the evaluation of the ruleset stops.
- If a rule doesn't match, it is ignored, and the next rule is evaluated.
- The rules are applied in the declared order,
- When a rule matches, the rule's `action` is applied, and the evaluation of the ruleset stops.
- If a rule doesn't match, it is ignored, and the next rule is evaluated.
---
@@ -36,25 +36,25 @@ A rule is a named group of predicates.
A predicate is a simple operation allowing to test the property of a movie.
```yaml title="settings.yaml"
# Required - the name of the rule
- name: The rule name
# Required - an array defining the predicates used in the rule
whenMatch:
- # predicate 1
- # predicate 2
- # ...
- # predicate n
# Required - the action to apply when the rule matches
# Possible values: accept or reject
action: accept
# Required - the name of the rule
- name: The rule name
# Required - an array defining the predicates used in the rule
whenMatch:
- # predicate 1
- # predicate 2
- # ...
- # predicate n
# Required - the action to apply when the rule matches
# Possible values: accept or reject
action: accept
```
The rule will match if **all the predicates** in the `whenMatch` match.
When matching:
- If `action: accept`, a request is sent to Overseerr to add the movie,
- If `action: reject`, the movie is ignored, and the evaluation of the ruleset stops for this movie.
- If `action: accept`, a request is sent to Overseerr to add the movie,
- If `action: reject`, the movie is ignored, and the evaluation of the ruleset stops for this movie.
---
@@ -66,25 +66,26 @@ This is particularly useful for common rejection rules.
```yaml title="settings.yaml"
config:
# ...
rulesets:
- name: exclude-netflix
rules:
- name: Exclude netflix movies
watchProviders:
- region: us
- names:
- Netflix
action: reject
- name: filter-movies
extends: exclude-netflix # movie will be evaluated against exclude-netflix rules first
rules:
- name: High score
whenMatch:
- age: less than 1 year
- score: above 9/10
action: accept
# ...
rulesets:
- name: exclude-netflix
rules:
- name: Exclude netflix movies
watchProviders:
- region: us
- names:
- Netflix
action: reject
- name: filter-movies
extends: exclude-netflix # movie will be evaluated against exclude-netflix rules first
rules:
- name: High score
whenMatch:
- age: less than 1 year
- score: above 9/10
action: accept
```
## Ruleset example
The following ruleset will:
@@ -93,18 +94,18 @@ The following ruleset will:
2. Request all movies _(that went through rule 1)_ released in the past year, with a score above 7
```yaml title="settings.yaml"
rulesets:
- name: New great movie
rules:
rulesets:
- name: New great movie
rules:
- name: Reject less wanted genres
whenMatch:
- genre:
- animation
- romance
- genre:
- animation
- romance
action: reject
- name: New great movies
whenMatch:
- age: less than 1 years
- score: above 7
- age: less than 1 years
- score: above 7
action: accept
```

View File

@@ -13,6 +13,8 @@
"lint": "eslint \"./**/*.ts\"",
"format:fix": "prettier ./src --write --cache",
"format": "prettier ./src --check --cache",
"format-docs": "prettier ./docs --check --cache",
"format-docs:fix": "prettier ./docs --write --cache",
"start": "tsx src/index.ts",
"test": "jest"
},

View File

@@ -218,7 +218,18 @@
"type": "object",
"required": ["cast"],
"properties": {
"cast": { "type": "array", "items": {"type": "string" }}
"cast": {
"oneOf": [
{ "type": "array", "items": { "type": "string" }},
{
"type": "object",
"properties": {
"voice": { "type": "string", "enum": ["include", "exclude"]},
"names": { "type": "array", "items": {"type": "string" }}
}
}
]
}
}
},

View File

@@ -157,6 +157,36 @@ describe('castPredicate', () => {
const rule = testRule(new CastPredicate({ cast: ['Jessica Alba'] }));
assertRuleDoesntMatch(rule, movie);
});
it('should match with voice field', async () => {
const ruleIncludeVoice = testRule(
new CastPredicate({
cast: {
voice: 'include',
names: ['Bob Sherman'],
},
})
);
const ruleExcludeVoice = testRule(
new CastPredicate({
cast: {
voice: 'exclude',
names: ['Bob Sherman'],
},
})
);
const ruleExcludeVoice2 = testRule(
new CastPredicate({
cast: {
voice: 'exclude',
names: ['Bob Sherman', 'Sigourney Weaver'],
},
})
);
assertRuleMatches(ruleIncludeVoice, movie);
assertRuleMatches(ruleExcludeVoice2, movie);
assertRuleDoesntMatch(ruleExcludeVoice, movie);
});
});
describe('crewPredicate', () => {

View File

@@ -52,7 +52,12 @@ export type AgeOptions = {
};
export type CastOptions = {
cast: string[];
cast: string[] | CastWithVoiceOptions;
};
export type CastWithVoiceOptions = {
voice: 'include' | 'exclude';
names: string[];
};
export type GenreOptions = {

View File

@@ -4,10 +4,13 @@ import { PredicateBuilder } from '@core/lib/rules';
import { CastOptions } from '@core/lib/rules/interfaces';
export class CastPredicate extends TagsPredicate {
private excludeVoice: boolean = false;
constructor(options: CastOptions) {
super({
terms: options.cast,
terms: Array.isArray(options.cast) ? options.cast : options.cast.names,
});
this.excludeVoice = Array.isArray(options.cast) ? false : options.cast.voice?.toLowerCase() === 'exclude';
}
getTags(movie: MovieDetails): string[] {
@@ -16,7 +19,9 @@ export class CastPredicate extends TagsPredicate {
}
const tags: string[] = [];
for (const cast of movie.credits.cast) {
tags.push(cast.name);
if (!this.excludeVoice || !cast.character || !cast.character.toLowerCase().includes('(voice)')) {
tags.push(cast.name);
}
}
return tags;
}