Skip to content

Commit 4783020

Browse files
authored
Merge pull request #15 from varthe/require-filter
Add "require" option and beta image publish workflow
2 parents b671a16 + cce0606 commit 4783020

File tree

5 files changed

+158
-100
lines changed

5 files changed

+158
-100
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Publish beta image to Docker Hub
2+
3+
on:
4+
release:
5+
types: [prereleased]
6+
workflow_dispatch:
7+
8+
jobs:
9+
publish_beta_image:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: checkout
13+
uses: actions/checkout@v3
14+
15+
- name: Set up Docker Buildx
16+
uses: docker/setup-buildx-action@v2
17+
18+
- name: Log in to Docker Hub
19+
run: |
20+
echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u varthe --password-stdin
21+
22+
- name: Build and push multi-arch beta image
23+
run: |
24+
docker buildx create --use
25+
docker buildx build --platform linux/amd64,linux/arm64 -t varthe/redirecterr:beta --push .

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ config/
1616
# Configuration files
1717
eslint.config.mjs
1818
.prettierrc
19-
*.yaml
19+
.prettierignore
20+
config.yaml

README.md

Lines changed: 99 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
21
# Redirecterr
2+
33
Filter and redirect Overseerr/Jellyseerr requests based on requester, keywords, age ratings, and more. Supports routing to multiple instances simultaneously.
4+
45
## Getting Started
56

67
### Docker Compose
@@ -9,17 +10,17 @@ Use the following `docker-compose.yaml` to deploy the Redirecterr service:
910

1011
```yaml
1112
services:
12-
redirecterr:
13-
image: varthe/redirecterr:latest
14-
container_name: redirecterr
15-
hostname: redirecterr
16-
ports:
17-
- 8481:8481
18-
volumes:
19-
- /path/to/config:/config
20-
- /path/to/logs:/logs
21-
environment:
22-
- LOG_LEVEL=info
13+
redirecterr:
14+
image: varthe/redirecterr:latest
15+
container_name: redirecterr
16+
hostname: redirecterr
17+
ports:
18+
- 8481:8481
19+
volumes:
20+
- /path/to/config:/config
21+
- /path/to/logs:/logs
22+
environment:
23+
- LOG_LEVEL=info
2324
```
2425
2526
### Webhook setup
@@ -28,70 +29,74 @@ In order for Redirecterr to work you need to disable automatic request approval
2829
2930
Then, in your seerr navigate to **Settings -> Notifications -> Webhook** and configure the following:
3031
31-
- **Enable Agent**: Enabled
32-
- **Webhook URL**: `http://redirecterr:8481/webhook`
33-
- **JSON Payload**:
34-
```json
35-
{
36-
"notification_type": "{{notification_type}}",
37-
"media": {
38-
"media_type": "{{media_type}}",
39-
"tmdbId": "{{media_tmdbid}}",
40-
"tvdbId": "{{media_tvdbid}}",
41-
"status": "{{media_status}}",
42-
"status4k": "{{media_status4k}}"
43-
},
44-
"request": {
45-
"request_id": "{{request_id}}",
46-
"requestedBy_email": "{{requestedBy_email}}",
47-
"requestedBy_username": "{{requestedBy_username}}",
48-
"requestedBy_avatar": "{{requestedBy_avatar}}"
49-
},
50-
"{{extra}}": []
51-
}
52-
```
53-
- **Notification Types**: Select **Request Pending Approval**
32+
- **Enable Agent**: Enabled
33+
- **Webhook URL**: `http://redirecterr:8481/webhook`
34+
- **JSON Payload**:
35+
```json
36+
{
37+
"notification_type": "{{notification_type}}",
38+
"media": {
39+
"media_type": "{{media_type}}",
40+
"tmdbId": "{{media_tmdbid}}",
41+
"tvdbId": "{{media_tvdbid}}",
42+
"status": "{{media_status}}",
43+
"status4k": "{{media_status4k}}"
44+
},
45+
"request": {
46+
"request_id": "{{request_id}}",
47+
"requestedBy_email": "{{requestedBy_email}}",
48+
"requestedBy_username": "{{requestedBy_username}}",
49+
"requestedBy_avatar": "{{requestedBy_avatar}}"
50+
},
51+
"{{extra}}": []
52+
}
53+
```
54+
- **Notification Types**: Select **Request Pending Approval**
5455

5556
## Configuration Overview
5657

5758
The configuration for Redirecterr is defined in `config.yaml`. Below is a breakdown of the required and optional settings.
5859

5960
### Required Settings
6061

61-
- **`overseerr_url`**: The base URL of your Overseerr instance.
62-
- **`overseerr_api_token`**: The API token for your Overseerr instance.
62+
- **`overseerr_url`**: The base URL of your Overseerr instance.
63+
- **`overseerr_api_token`**: The API token for your Overseerr instance.
6364

6465
### Fallback Settings
65-
- **`approve_on_no_match`**: When no filters match, the request is approved automatically and handled by Overseerr according to its default settings. (Recommended)
66+
67+
- **`approve_on_no_match`**: When no filters match, the request is approved automatically and handled by Overseerr according to its default settings. (Recommended)
68+
6669
### Instances
6770

6871
Define your Radarr and Sonarr instances in this section. You can name the instances as needed.
6972

70-
- **`server_id`** (Required): The ID of the instance as shown in **Settings -> Services** in Overseerr. IDs start at 0 and increment sequentially from left to right (see image below).
71-
- **`root_folder`** (Required): The path to the root folder for the instance, as configured in its settings.
72-
- **`quality_profile_id`** (Optional): Overrides the default quality profile set in Overseerr. If not provided, the default profile will be used. To find the profile ID, open your browser and use the following URL, replacing `<url>` with your arr instance's URL and `<api-key>` with its API key:
73-
```
74-
http://<url>/api/v3/qualityProfile?apiKey=<api-key>
75-
```
76-
This returns a JSON response listing all available quality profiles and their IDs. The ID can be found at the very bottom of the response.
77-
- **`approve`** (Optional): Automatically approves requests by default. To disable, set this flag to `False` in the configuration.
78-
73+
- **`server_id`** (Required): The ID of the instance as shown in **Settings -> Services** in Overseerr. IDs start at 0 and increment sequentially from left to right (see image below).
74+
- **`root_folder`** (Required): The path to the root folder for the instance, as configured in its settings.
75+
- **`quality_profile_id`** (Optional): Overrides the default quality profile set in Overseerr. If not provided, the default profile will be used. To find the profile ID, open your browser and use the following URL, replacing `<url>` with your arr instance's URL and `<api-key>` with its API key:
76+
```
77+
http://<url>/api/v3/qualityProfile?apiKey=<api-key>
78+
```
79+
This returns a JSON response listing all available quality profiles and their IDs. The ID can be found at the very bottom of the response.
80+
- **`approve`** (Optional): Automatically approves requests by default. To disable, set this flag to `False` in the configuration.
81+
7982
![arrs](https://github.com/user-attachments/assets/a7a60d91-0f24-42a9-bbe1-ea4f1c945e6a)
8083

8184
### Filters
8285

8386
Define your request filters in this section.
8487

85-
- **`media_type`**: Specifies the type of media, either `"movie"` or `"tv"`.
86-
- **`is_not_4k`**: Should only apply to non-4k requests
87-
- **`is_4k`**: Should only apply to 4k requests
88-
- **`conditions`**: A set of fields and values used to filter requests. Refer to [testData.js](https://github.com/varthe/Redirecterr/blob/main/testData.js) for examples of request objects. Each field within `conditions` can be:
89-
- A **single value**: Matches if the value is present in the request.
90-
- A **list of values**: Matches if any value in the list is present in the request.
91-
- An **`exclude`** object: Used to exclude specific values. The `exclude`` object can contain either a single value or a list of values. The filter will match if none of the specified values are present in the request.
88+
- **`media_type`**: Specifies the type of media, either `"movie"` or `"tv"`.
89+
- **`is_not_4k`**: Should only apply to non-4k requests
90+
- **`is_4k`**: Should only apply to 4k requests
91+
- **`conditions`**: A set of fields and values used to filter requests. Refer to [testData.js](https://github.com/varthe/Redirecterr/blob/main/testData.js) for examples of request objects. Each field within `conditions` can be:
92+
93+
- A **single value**: Matches if the value is present in the request.
94+
- A **list of values**: Matches if any value in the list is present in the request.
95+
- A **`require`** object: Used to require specific values. The `require` object can contain either a single value or a list of values. The filter will match if all specified values are present in the request.
96+
- An **`exclude`** object: Used to exclude specific values. The `exclude` object can contain either a single value or a list of values. The filter will match if none of the specified values are present in the request.
97+
98+
- **`apply`**: A list of instance names (defined in the **Instances** section) to which the request will be sent.
9299

93-
- **`apply`**: A list of instance names (defined in the **Instances** section) to which the request will be sent.
94-
95100
Redirecterr processes filters sequentially and will apply the first matching filter it encounters. Make sure to order your filters appropriately to get the desired behavior.
96101

97102
### Sample `config.yaml`
@@ -103,44 +108,44 @@ overseerr_api_token: "YOUR_API_TOKEN"
103108
approve_on_no_match: True
104109
105110
instances:
106-
radarr: # Custom instance name
107-
server_id: 0
108-
root_folder: "/mnt/plex/Movies"
109-
radarr4k: # Custom instance name
110-
server_id: 1
111-
root_folder: "/mnt/plex/Movies - 4K"
112-
radarr_anime: # Custom instance name
113-
server_id: 2
114-
root_folder: "/mnt/plex/Movies - Anime"
115-
quality_profile_id: 2 # Optional
116-
approve: false # Optional
117-
sonarr: # Custom instance name
118-
server_id: 0
119-
root_folder: "/mnt/plex/Shows"
111+
radarr: # Custom instance name
112+
server_id: 0
113+
root_folder: "/mnt/plex/Movies"
114+
radarr4k: # Custom instance name
115+
server_id: 1
116+
root_folder: "/mnt/plex/Movies - 4K"
117+
radarr_anime: # Custom instance name
118+
server_id: 2
119+
root_folder: "/mnt/plex/Movies - Anime"
120+
quality_profile_id: 2 # Optional
121+
approve: false # Optional
122+
sonarr: # Custom instance name
123+
server_id: 0
124+
root_folder: "/mnt/plex/Shows"
120125
121126
filters:
122-
- media_type: movie
123-
conditions:
124-
keywords: anime # Match if keyword "anime" is present
125-
requestedBy_username: varthe # Match if requested by "varthe"
126-
# requestedBy_email: ""
127-
apply: radarr_anime # Send request to "radarr_anime"
128-
- media_type: movie
129-
conditions:
130-
keywords: # Exclude requests with keywords "anime" or "animation"
131-
exclude:
132-
- anime
133-
- animation
134-
apply: # Send requests to "radarr" and "radarr4k"
135-
- radarr
136-
- radarr4k
137-
- media_type: tv
138-
conditions:
139-
genres: # Match if genre is "adventure" or "comedy"
140-
- adventure
141-
- comedy
142-
contentRatings: # Match if content rating is "12" or "16"
143-
- 12
144-
- 16
145-
apply: sonarr # Send request to "sonarr"
127+
- media_type: movie
128+
conditions:
129+
keywords: anime # Match if keyword "anime" is present
130+
requestedBy_username: varthe # Match if requested by "varthe"
131+
# requestedBy_email: ""
132+
apply: radarr_anime # Send request to "radarr_anime"
133+
- media_type: movie
134+
conditions:
135+
keywords: # Exclude requests with keywords "anime" or "animation"
136+
exclude:
137+
- anime
138+
- animation
139+
apply: # Send requests to "radarr" and "radarr4k"
140+
- radarr
141+
- radarr4k
142+
- media_type: tv
143+
conditions:
144+
genres: # Match if genre is "adventure" or "comedy"
145+
- adventure
146+
- comedy
147+
contentRatings: # Match if content rating is "12" or "16"
148+
- 12
149+
- 16
150+
apply: sonarr # Send request to "sonarr"
146151
```

configBuilder.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ const schema = {
8181
required: ["exclude"],
8282
additionalProperties: false,
8383
},
84+
{
85+
type: "object",
86+
properties: {
87+
require: {
88+
anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
89+
},
90+
},
91+
required: ["require"],
92+
additionalProperties: false,
93+
},
8494
],
8595
},
8696
},

main.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const buildDebugLogMessage = (message, details = {}) => {
5454
}
5555

5656
// Main functions
57-
const matchValue = (filterValue, dataValue) => {
57+
const matchValue = (filterValue, dataValue, required = false) => {
5858
const arrayValues = normalizeToArray(filterValue)
5959

6060
if (isObject(dataValue)) {
@@ -63,14 +63,22 @@ const matchValue = (filterValue, dataValue) => {
6363
if (
6464
value.some((item) =>
6565
arrayValues.some((filterVal) =>
66-
Object.values(item).some((field) => String(field).toLowerCase().includes(filterVal))
66+
Object.values(item).some((field) => {
67+
if (required) return String(field).toLowerCase() === filterVal
68+
return String(field).toLowerCase().includes(filterVal)
69+
})
6770
)
6871
)
6972
) {
7073
return true
7174
}
7275
} else {
73-
if (arrayValues.some((filterVal) => String(value).toLowerCase().includes(filterVal))) {
76+
if (
77+
arrayValues.some((filterVal) => {
78+
if (required) return String(value).toLowerCase() === filterVal
79+
return String(value).toLowerCase().includes(filterVal)
80+
})
81+
) {
7482
return true
7583
}
7684
}
@@ -80,7 +88,10 @@ const matchValue = (filterValue, dataValue) => {
8088
if (isObjectArray(dataValue)) {
8189
return dataValue.some((item) =>
8290
arrayValues.some((value) =>
83-
Object.values(item).some((field) => String(field).toLowerCase().includes(value))
91+
Object.values(item).some((field) => {
92+
if (required) return String(field).toLowerCase() === value
93+
return String(field).toLowerCase().includes(value)
94+
})
8495
)
8596
)
8697
}
@@ -113,7 +124,13 @@ const findMatchingInstances = (webhook, data, filters) => {
113124
)
114125
}
115126

116-
if (value.exclude ? matchValue(value.exclude, requestValue) : !matchValue(value, requestValue)) {
127+
if (value.require && !matchValue(value.require, requestValue, true)) {
128+
logger.debug(`Filter check for required key "${key}" did not match.`)
129+
return false
130+
} else if (value.exclude && matchValue(value.exclude, requestValue)) {
131+
logger.debug(`Filter check for excluded key "${key}" did not match.`)
132+
return false
133+
} else if (!matchValue(value, requestValue)) {
117134
logger.debug(`Filter check for key "${key}" did not match.`)
118135
return false
119136
}

0 commit comments

Comments
 (0)