Skip to content

Commit 2f07c38

Browse files
committed
chore: prepare 2.7.3 release
2 parents 697a081 + 17d4f13 commit 2f07c38

File tree

15 files changed

+359
-155
lines changed

15 files changed

+359
-155
lines changed

jellyseerr-api.yml

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,83 @@ components:
141141
UserSettings:
142142
type: object
143143
properties:
144+
username:
145+
type: string
146+
nullable: true
147+
example: 'Mr User'
148+
email:
149+
type: string
150+
example: '[email protected]'
151+
discordId:
152+
type: string
153+
nullable: true
154+
example: '123456789'
144155
locale:
145156
type: string
157+
nullable: true
158+
example: 'en'
146159
discoverRegion:
147160
type: string
148-
originalLanguage:
149-
type: string
161+
nullable: true
162+
example: 'US'
150163
streamingRegion:
151164
type: string
165+
nullable: true
166+
example: 'US'
167+
originalLanguage:
168+
type: string
169+
nullable: true
170+
example: 'en'
171+
movieQuotaLimit:
172+
type: number
173+
nullable: true
174+
description: 'Maximum number of movie requests allowed'
175+
example: 10
176+
movieQuotaDays:
177+
type: number
178+
nullable: true
179+
description: 'Time period in days for movie quota'
180+
example: 30
181+
tvQuotaLimit:
182+
type: number
183+
nullable: true
184+
description: 'Maximum number of TV requests allowed'
185+
example: 5
186+
tvQuotaDays:
187+
type: number
188+
nullable: true
189+
description: 'Time period in days for TV quota'
190+
example: 14
191+
globalMovieQuotaDays:
192+
type: number
193+
nullable: true
194+
description: 'Global movie quota days setting'
195+
example: 30
196+
globalMovieQuotaLimit:
197+
type: number
198+
nullable: true
199+
description: 'Global movie quota limit setting'
200+
example: 10
201+
globalTvQuotaLimit:
202+
type: number
203+
nullable: true
204+
description: 'Global TV quota limit setting'
205+
example: 5
206+
globalTvQuotaDays:
207+
type: number
208+
nullable: true
209+
description: 'Global TV quota days setting'
210+
example: 14
211+
watchlistSyncMovies:
212+
type: boolean
213+
nullable: true
214+
description: 'Enable watchlist sync for movies'
215+
example: true
216+
watchlistSyncTv:
217+
type: boolean
218+
nullable: true
219+
description: 'Enable watchlist sync for TV'
220+
example: false
152221
MainSettings:
153222
type: object
154223
properties:
@@ -4469,11 +4538,7 @@ paths:
44694538
content:
44704539
application/json:
44714540
schema:
4472-
type: object
4473-
properties:
4474-
username:
4475-
type: string
4476-
example: 'Mr User'
4541+
$ref: '#/components/schemas/UserSettings'
44774542
post:
44784543
summary: Update general settings for a user
44794544
description: Updates and returns general settings for a specific user. Requires `MANAGE_USERS` permission if editing other users.
@@ -4490,22 +4555,14 @@ paths:
44904555
content:
44914556
application/json:
44924557
schema:
4493-
type: object
4494-
properties:
4495-
username:
4496-
type: string
4497-
nullable: true
4558+
$ref: '#/components/schemas/UserSettings'
44984559
responses:
44994560
'200':
45004561
description: Updated user general settings returned
45014562
content:
45024563
application/json:
45034564
schema:
4504-
type: object
4505-
properties:
4506-
username:
4507-
type: string
4508-
example: 'Mr User'
4565+
$ref: '#/components/schemas/UserSettings'
45094566
/user/{userId}/settings/password:
45104567
get:
45114568
summary: Get password page informatiom
@@ -6599,9 +6656,16 @@ paths:
65996656
example: '1'
66006657
schema:
66016658
type: string
6659+
- in: query
6660+
name: is4k
6661+
description: Whether to remove from 4K service instance (true) or regular service instance (false)
6662+
required: false
6663+
example: false
6664+
schema:
6665+
type: boolean
66026666
responses:
66036667
'204':
6604-
description: Succesfully removed media item
6668+
description: Successfully removed media item
66056669
/media/{mediaId}/{status}:
66066670
post:
66076671
summary: Update media status
@@ -7268,11 +7332,22 @@ paths:
72687332
example: 1
72697333
responses:
72707334
'200':
7271-
description: Keyword returned
7335+
description: Keyword returned (null if not found)
72727336
content:
72737337
application/json:
72747338
schema:
7339+
nullable: true
72757340
$ref: '#/components/schemas/Keyword'
7341+
'500':
7342+
description: Internal server error
7343+
content:
7344+
application/json:
7345+
schema:
7346+
type: object
7347+
properties:
7348+
message:
7349+
type: string
7350+
example: 'Unable to retrieve keyword data.'
72767351
/watchproviders/regions:
72777352
get:
72787353
summary: Get watch provider regions

server/api/plextv.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ class PlexTvAPI extends ExternalAPI {
291291
headers: {
292292
'If-None-Match': cachedWatchlist?.etag,
293293
},
294-
baseURL: 'https://metadata.provider.plex.tv',
294+
baseURL: 'https://discover.provider.plex.tv',
295295
validateStatus: (status) => status < 400, // Allow HTTP 304 to return without error
296296
}
297297
);
@@ -315,7 +315,7 @@ class PlexTvAPI extends ExternalAPI {
315315
const detailedResponse = await this.getRolling<MetadataResponse>(
316316
`/library/metadata/${watchlistItem.ratingKey}`,
317317
{
318-
baseURL: 'https://metadata.provider.plex.tv',
318+
baseURL: 'https://discover.provider.plex.tv',
319319
}
320320
);
321321

server/api/themoviedb/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,7 @@ class TheMovieDb extends ExternalAPI {
10541054
keywordId,
10551055
}: {
10561056
keywordId: number;
1057-
}): Promise<TmdbKeyword> {
1057+
}): Promise<TmdbKeyword | null> {
10581058
try {
10591059
const data = await this.get<TmdbKeyword>(
10601060
`/keyword/${keywordId}`,
@@ -1064,6 +1064,9 @@ class TheMovieDb extends ExternalAPI {
10641064

10651065
return data;
10661066
} catch (e) {
1067+
if (e.response?.status === 404) {
1068+
return null;
1069+
}
10671070
throw new Error(`[TMDB] Failed to fetch keyword: ${e.message}`);
10681071
}
10691072
}

server/job/blacklistedTagsProcessor.ts

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class BlacklistedTagProcessor implements RunnableScanner<StatusBase> {
7272
const blacklistedTagsArr = blacklistedTags.split(',');
7373

7474
const pageLimit = settings.main.blacklistedTagsLimit;
75+
const invalidKeywords = new Set<string>();
7576

7677
if (blacklistedTags.length === 0) {
7778
return;
@@ -87,6 +88,19 @@ class BlacklistedTagProcessor implements RunnableScanner<StatusBase> {
8788

8889
// Iterate for each tag
8990
for (const tag of blacklistedTagsArr) {
91+
const keywordDetails = await tmdb.getKeywordDetails({
92+
keywordId: Number(tag),
93+
});
94+
95+
if (keywordDetails === null) {
96+
logger.warn('Skipping invalid keyword in blacklisted tags', {
97+
label: 'Blacklisted Tags Processor',
98+
keywordId: tag,
99+
});
100+
invalidKeywords.add(tag);
101+
continue;
102+
}
103+
90104
let queryMax = pageLimit * SortOptionsIterable.length;
91105
let fixedSortMode = false; // Set to true when the page limit allows for getting every page of tag
92106

@@ -102,24 +116,51 @@ class BlacklistedTagProcessor implements RunnableScanner<StatusBase> {
102116
throw new AbortTransaction();
103117
}
104118

105-
const response = await getDiscover({
106-
page,
107-
sortBy,
108-
keywords: tag,
109-
});
110-
await this.processResults(response, tag, type, em);
111-
await new Promise((res) => setTimeout(res, TMDB_API_DELAY_MS));
112-
113-
this.progress++;
114-
if (page === 1 && response.total_pages <= queryMax) {
115-
// We will finish the tag with less queries than expected, move progress accordingly
116-
this.progress += queryMax - response.total_pages;
117-
fixedSortMode = true;
118-
queryMax = response.total_pages;
119+
try {
120+
const response = await getDiscover({
121+
page,
122+
sortBy,
123+
keywords: tag,
124+
});
125+
126+
await this.processResults(response, tag, type, em);
127+
await new Promise((res) => setTimeout(res, TMDB_API_DELAY_MS));
128+
129+
this.progress++;
130+
if (page === 1 && response.total_pages <= queryMax) {
131+
// We will finish the tag with less queries than expected, move progress accordingly
132+
this.progress += queryMax - response.total_pages;
133+
fixedSortMode = true;
134+
queryMax = response.total_pages;
135+
}
136+
} catch (error) {
137+
logger.error('Error processing keyword in blacklisted tags', {
138+
label: 'Blacklisted Tags Processor',
139+
keywordId: tag,
140+
errorMessage: error.message,
141+
});
119142
}
120143
}
121144
}
122145
}
146+
147+
if (invalidKeywords.size > 0) {
148+
const currentTags = blacklistedTagsArr.filter(
149+
(tag) => !invalidKeywords.has(tag)
150+
);
151+
const cleanedTags = currentTags.join(',');
152+
153+
if (cleanedTags !== blacklistedTags) {
154+
settings.main.blacklistedTags = cleanedTags;
155+
await settings.save();
156+
157+
logger.info('Cleaned up invalid keywords from settings', {
158+
label: 'Blacklisted Tags Processor',
159+
removedKeywords: Array.from(invalidKeywords),
160+
newBlacklistedTags: cleanedTags,
161+
});
162+
}
163+
}
123164
}
124165

125166
private async processResults(

server/routes/discover.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,15 @@ discoverRoutes.get('/movies', async (req, res, next) => {
128128
if (keywords) {
129129
const splitKeywords = keywords.split(',');
130130

131-
keywordData = await Promise.all(
131+
const keywordResults = await Promise.all(
132132
splitKeywords.map(async (keywordId) => {
133133
return await tmdb.getKeywordDetails({ keywordId: Number(keywordId) });
134134
})
135135
);
136+
137+
keywordData = keywordResults.filter(
138+
(keyword): keyword is TmdbKeyword => keyword !== null
139+
);
136140
}
137141

138142
return res.status(200).json({
@@ -415,11 +419,15 @@ discoverRoutes.get('/tv', async (req, res, next) => {
415419
if (keywords) {
416420
const splitKeywords = keywords.split(',');
417421

418-
keywordData = await Promise.all(
422+
const keywordResults = await Promise.all(
419423
splitKeywords.map(async (keywordId) => {
420424
return await tmdb.getKeywordDetails({ keywordId: Number(keywordId) });
421425
})
422426
);
427+
428+
keywordData = keywordResults.filter(
429+
(keyword): keyword is TmdbKeyword => keyword !== null
430+
);
423431
}
424432

425433
return res.status(200).json({

server/routes/imageproxy.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,40 @@ import { Router } from 'express';
44

55
const router = Router();
66

7-
const tmdbImageProxy = new ImageProxy('tmdb', 'https://image.tmdb.org', {
8-
rateLimitOptions: {
9-
maxRequests: 20,
10-
maxRPS: 50,
11-
},
12-
});
13-
const tvdbImageProxy = new ImageProxy('tvdb', 'https://artworks.thetvdb.com', {
14-
rateLimitOptions: {
15-
maxRequests: 20,
16-
maxRPS: 50,
17-
},
18-
});
7+
// Delay the initialization of ImageProxy instances until the proxy (if any) is properly configured
8+
let _tmdbImageProxy: ImageProxy;
9+
function initTmdbImageProxy() {
10+
if (!_tmdbImageProxy) {
11+
_tmdbImageProxy = new ImageProxy('tmdb', 'https://image.tmdb.org', {
12+
rateLimitOptions: {
13+
maxRequests: 20,
14+
maxRPS: 50,
15+
},
16+
});
17+
}
18+
return _tmdbImageProxy;
19+
}
20+
let _tvdbImageProxy: ImageProxy;
21+
function initTvdbImageProxy() {
22+
if (!_tvdbImageProxy) {
23+
_tvdbImageProxy = new ImageProxy('tvdb', 'https://artworks.thetvdb.com', {
24+
rateLimitOptions: {
25+
maxRequests: 20,
26+
maxRPS: 50,
27+
},
28+
});
29+
}
30+
return _tvdbImageProxy;
31+
}
1932

2033
router.get('/:type/*', async (req, res) => {
2134
const imagePath = req.path.replace(/^\/\w+/, '');
2235
try {
2336
let imageData;
2437
if (req.params.type === 'tmdb') {
25-
imageData = await tmdbImageProxy.getImage(imagePath);
38+
imageData = await initTmdbImageProxy().getImage(imagePath);
2639
} else if (req.params.type === 'tvdb') {
27-
imageData = await tvdbImageProxy.getImage(imagePath);
40+
imageData = await initTvdbImageProxy().getImage(imagePath);
2841
} else {
2942
logger.error('Unsupported image type', {
3043
imagePath,

0 commit comments

Comments
 (0)