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 run: npm run lint
- name: Format - name: Format
run: npm run format run: npm run format
- name: Docs format
run: npm run format-docs
- name: Build - name: Build
run: npm run build run: npm run build
- name: Tests - name: Tests

View File

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

View File

@@ -1,4 +1,3 @@
# Schema # Schema
The JSON Schema is available here: https://github.com/psyko-gh/overcrawlrr/blob/master/schema/schema.json 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 ### Overseerr authentication
Overcrawlrr need to authenticate to Overseerr to fetch movie information and create movie requests. 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. 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: Doing so, you can have fine-grained control on its permissions:
- Allow/deny automatic requests validation, - Allow/deny automatic requests validation,
- Allow/deny to request in 4K, - Allow/deny to request in 4K,
- Specify the languages and the region when discovering movies, - Specify the languages and the region when discovering movies,
- And more... - And more...
Once the user is created, you can fill these value in Overcrawlrr `settings.yaml`: Once the user is created, you can fill these value in Overcrawlrr `settings.yaml`:
```yaml title="settings.yaml" ```yaml title="settings.yaml"
config: config:
overseerr: overseerr:
apiUrl: xxx # required apiUrl: xxx # required
user: xxx # required user: xxx # required
password: xxx # required password: xxx # required
dryRun: true # Optional - dryRun will not send requests to Overseerr dryRun: true # Optional - dryRun will not send requests to Overseerr
# allowing you to test your rules # 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 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`: To read data from your Plex library, you have to specify your Plex URL and token in `settings.yaml`:
```yaml title="settings.yaml" ```yaml title="settings.yaml"
config: config:
# Only required when using Smart recommendations job # Only required when using Smart recommendations job
plex: plex:
apiUrl: xxx apiUrl: xxx
plexToken: xxx plexToken: xxx
``` ```
--- ---
### Using environment variables ### Using environment variables
You can refer to environment variables using the `{{ key }}` syntax. You can refer to environment variables using the `{{ key }}` syntax.
```yaml title="settings.yaml" ```yaml title="settings.yaml"
config: config:
overseerr: overseerr:
apiUrl: xxx apiUrl: xxx
user: '{{ OVERSEERR_USER }}' user: '{{ OVERSEERR_USER }}'
password: '{{ OVERSEERR_PASSWORD }}' password: '{{ OVERSEERR_PASSWORD }}'
``` ```
These variables can be defined in the `docker-compose.yaml` These variables can be defined in the `docker-compose.yaml`
```yaml title="docker-compose.yaml" ```yaml title="docker-compose.yaml"
services: services:
overcrawlrr: overcrawlrr:
# ... # ...
environment: environment:
- OVERSEERR_USER= - OVERSEERR_USER=
- OVERSEERR_PASSWORD= - OVERSEERR_PASSWORD=
``` ```
--- ---

View File

@@ -4,23 +4,21 @@
Using Overcrawlrr requires you to have a running instance of Overseerr Using Overcrawlrr requires you to have a running instance of Overseerr
With docker-compose With docker-compose
```yaml ```yaml
services: services:
overcrawlrr: overcrawlrr:
image: ghcr.io/psyko-gh/overcrawlrr:latest image: ghcr.io/psyko-gh/overcrawlrr:latest
container_name: overcrawlrr container_name: overcrawlrr
restart: unless-stopped restart: unless-stopped
ports: ports:
- 5056:5056 - 5056:5056
environment: environment:
- OVERSEERR_USER= - OVERSEERR_USER=
- OVERSEERR_PASSWORD= - OVERSEERR_PASSWORD=
volumes: volumes:
- /path/to/config:/config - /path/to/config:/config
``` ```
With docker cli 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" ```yaml title="settings.yaml"
config: config:
overseer: overseer:
# .... # ....
discovery: discovery:
# Required # Required
cron: '30 3 * * *' cron: '30 3 * * *'
# Required - the overseer streams to search through # Required - the overseer streams to search through
# Possible values: upcoming, popular, trending # Possible values: upcoming, popular, trending
streams: streams:
- upcoming - upcoming
- popular - popular
- trending - trending
# Required - the name of the ruleset used to evaluate movies # Required - the name of the ruleset used to evaluate movies
ruleset: Ruleset name ruleset: Ruleset name
``` ```
## Smart recommendations ## Smart recommendations
@@ -37,19 +37,38 @@ Considering the movie you liked, it will crawl their recommended movies and appl
```yaml title="settings.yaml" ```yaml title="settings.yaml"
config: config:
plex: plex:
# .... # ....
smartRecommendations: smartRecommendations:
# Required # Required
cron: '1 14 * * *' cron: '1 14 * * *'
# The name of the plex library to use as a seed # The name of the plex library to use as a seed
plexLibrary: Films plexLibrary: Films
# Your minimum personal rating to consider # Your minimum personal rating to consider
minimumRating: 6.9 minimumRating: 6.9
ruleset: Ruleset name 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: 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. Filters on the adult status of the movie.
```yaml ```yaml
- adult: yes - adult: yes
# or # or
- adult: no - adult: no
``` ```
--- ---
@@ -19,25 +19,27 @@ Filters on the age of the movie.
See [Duration expressions](#duration-expressions) for more details See [Duration expressions](#duration-expressions) for more details
```yaml ```yaml
- age: less than 2 years - age: less than 2 years
# or # or
- age: more than 6 months - age: more than 6 months
``` ```
--- ---
### `and` ### `and`
Predicate that will match if all of its predicate matches Predicate that will match if all of its predicate matches
```yaml ```yaml
# Will match if the movie is less than 2 years old AND if the movie genre is 'animation' # Will match if the movie is less than 2 years old AND if the movie genre is 'animation'
- and: - and:
- age: less than 2 years - age: less than 2 years
- genre: - genre:
- animation - animation
``` ```
--- ---
### `cast` ### `cast`
Filters based on the cast of the movie. Will match when one or more of the listed name matches. 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** **Case insensitive**
```yaml ```yaml
- cast: - cast:
- Denzel Washington - Denzel Washington
- Jessica Alba - 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` ### `crew`
Filters based on the crew of the movie. Will match when one or more of the listed name matches. 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** **Case insensitive**
```yaml ```yaml
- crew: - crew:
- James Cameron - James Cameron
- Hans Zimmer - Hans Zimmer
``` ```
It is also possible to specify the job It is also possible to specify the job
```yaml ```yaml
- crew: - crew:
job: director job: director
names: names:
- James Cameron - James Cameron
- Steven Spielberg - Steven Spielberg
``` ```
--- ---
### `genre` ### `genre`
Filters on the genre of the movie. Will match when one or more of the listed genres matches the genre of the movie. 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** **Case insensitive**
```yaml ```yaml
- genre: musical - genre: musical
# or with an array of values # or with an array of values
- genre: - genre:
- animation - animation
- romance - romance
``` ```
--- ---
### `not` ### `not`
Predicate that invert the result of its child predicate Predicate that invert the result of its child predicate
```yaml ```yaml
- not: - not:
- genre: - genre:
- animation - animation
``` ```
--- ---
### `originalLanguage` ### `originalLanguage`
Filters on the original language of the movie Filters on the original language of the movie
@@ -106,27 +122,29 @@ Filters on the original language of the movie
**Case insensitive** **Case insensitive**
```yaml ```yaml
# ISO 639-1 format of the language (de, au, us, fr...) # ISO 639-1 format of the language (de, au, us, fr...)
- originalLanguage: en - originalLanguage: en
# or with an array of values # or with an array of values
- originalLanguage: - originalLanguage:
- en - en
- fr - fr
``` ```
--- ---
### `or` ### `or`
Predicate that will match if any of its predicate matches Predicate that will match if any of its predicate matches
```yaml ```yaml
# Will match if the movie is less than 2 years old OR if the movie score is above 8 # Will match if the movie is less than 2 years old OR if the movie score is above 8
- or: - or:
- age: less than 2 years - age: less than 2 years
- score: above 8 - score: above 8
``` ```
--- ---
### `productionCompany` ### `productionCompany`
Filters based on the production companies of the movie. Will match when one or more of the listed company matches. 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** **Case insensitive**
```yaml ```yaml
- productionCompany: - productionCompany:
- 20th Century Fox - 20th Century Fox
- Warner Bros. Pictures - Warner Bros. Pictures
- Twisted Pictures - Twisted Pictures
``` ```
--- ---
### `released` ### `released`
Filters on the released status of the movie. Filters on the released status of the movie.
```yaml ```yaml
- released: yes - released: yes
# or # or
- released: no - released: no
``` ```
--- ---
### `runtime` ### `runtime`
Filters on the runtime _(duration)_ of the movie. 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 See [Duration expressions](#duration-expressions) for more details
```yaml ```yaml
- runtime: less than 2.5 hours - runtime: less than 2.5 hours
# or # or
- runtime: more than 120 minutes - runtime: more than 120 minutes
``` ```
--- ---
### `score` ### `score`
Filters on the score of the movie. 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. 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 ```yaml
- score: above 6.5 - score: above 6.5
# or # or
- score: below 75/100 # Would be the same as 7.5, 7.5/10 or even 750/1000 - score: below 75/100 # Would be the same as 7.5, 7.5/10 or even 750/1000
``` ```
--- ---
### `status` ### `status`
Filters on the status of the movie. 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** **Case insensitive**
```yaml ```yaml
- status: released - status: released
# or with an array of values # or with an array of values
- status: - status:
- released - released
- post production - post production
- planned - planned
``` ```
--- ---
### `voteCount` ### `voteCount`
Filters on the vote count of the movie. Filters on the vote count of the movie.
```yaml ```yaml
- voteCount: above 1000 - voteCount: above 1000
# or # or
- voteCount: below 100 - voteCount: below 100
``` ```
--- ---
@@ -216,11 +238,11 @@ Filters based on the available Streaming/VOD platforms. Will match when one or m
**Case insensitive** **Case insensitive**
```yaml ```yaml
# This predicate will match when the movie is available in Germany on Netflix or Amazon Prime # This predicate will match when the movie is available in Germany on Netflix or Amazon Prime
- watchProviders: - watchProviders:
# ISO 3166-1 alpha-2 format of the region (de, au, us, fr...) # ISO 3166-1 alpha-2 format of the region (de, au, us, fr...)
- region: de - region: de
- names: - names:
- Netflix - Netflix
- Amazon Prime - 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: 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` - an **operator**: `less than` or `more than`
- a integer or decimal **number**: `2` or `2.5` - 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` - 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: The following expressions are valid:
- `less than 1 hour`/`less than 1 hours`/`less than 60 minutes` - `less than 1 hour`/`less than 1 hours`/`less than 60 minutes`
- `less than 3 hours`/`less than 3 hour`/`less than 180 minute` - `less than 3 hours`/`less than 3 hour`/`less than 180 minute`
- `more than 6 month` - `more than 6 month`

View File

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

View File

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

View File

@@ -218,7 +218,18 @@
"type": "object", "type": "object",
"required": ["cast"], "required": ["cast"],
"properties": { "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'] })); const rule = testRule(new CastPredicate({ cast: ['Jessica Alba'] }));
assertRuleDoesntMatch(rule, movie); 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', () => { describe('crewPredicate', () => {

View File

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

View File

@@ -4,10 +4,13 @@ import { PredicateBuilder } from '@core/lib/rules';
import { CastOptions } from '@core/lib/rules/interfaces'; import { CastOptions } from '@core/lib/rules/interfaces';
export class CastPredicate extends TagsPredicate { export class CastPredicate extends TagsPredicate {
private excludeVoice: boolean = false;
constructor(options: CastOptions) { constructor(options: CastOptions) {
super({ 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[] { getTags(movie: MovieDetails): string[] {
@@ -16,7 +19,9 @@ export class CastPredicate extends TagsPredicate {
} }
const tags: string[] = []; const tags: string[] = [];
for (const cast of movie.credits.cast) { 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; return tags;
} }