mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 02:39:18 -05:00
Compare commits
5 Commits
preview-de
...
preview-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fb9bda1c6 | ||
|
|
9e737576de | ||
|
|
cd479d0d17 | ||
|
|
e9f2f4490f | ||
|
|
d5bf17574f |
@@ -9,7 +9,11 @@ cypress/config/settings.cypress.json
|
||||
# assets
|
||||
src/assets/
|
||||
public/
|
||||
!public/sw.js
|
||||
docs/
|
||||
!/public/
|
||||
/public/*
|
||||
!/public/sw.js
|
||||
|
||||
# helm charts
|
||||
**/charts
|
||||
|
||||
@@ -5198,6 +5198,12 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
example: 1,2
|
||||
- in: query
|
||||
name: excludeKeywords
|
||||
schema:
|
||||
type: string
|
||||
example: 3,4
|
||||
description: Comma-separated list of keyword IDs to exclude from results
|
||||
- in: query
|
||||
name: sortBy
|
||||
schema:
|
||||
@@ -5518,6 +5524,12 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
example: 1,2
|
||||
- in: query
|
||||
name: excludeKeywords
|
||||
schema:
|
||||
type: string
|
||||
example: 3,4
|
||||
description: Comma-separated list of keyword IDs to exclude from results
|
||||
- in: query
|
||||
name: sortBy
|
||||
schema:
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"cronstrue": "2.23.0",
|
||||
"date-fns": "2.29.3",
|
||||
"dayjs": "1.11.7",
|
||||
"dns-caching": "^0.2.5",
|
||||
"dns-caching": "^0.2.7",
|
||||
"email-templates": "12.0.1",
|
||||
"email-validator": "2.0.4",
|
||||
"express": "4.21.2",
|
||||
|
||||
536
pnpm-lock.yaml
generated
536
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -113,7 +113,7 @@ interface MetadataResponse {
|
||||
ratingKey: string;
|
||||
type: 'movie' | 'show';
|
||||
title: string;
|
||||
Guid: {
|
||||
Guid?: {
|
||||
id: `imdb://tt${number}` | `tmdb://${number}` | `tvdb://${number}`;
|
||||
}[];
|
||||
}[];
|
||||
@@ -312,19 +312,32 @@ class PlexTvAPI extends ExternalAPI {
|
||||
const watchlistDetails = await Promise.all(
|
||||
(cachedWatchlist?.response.MediaContainer.Metadata ?? []).map(
|
||||
async (watchlistItem) => {
|
||||
const detailedResponse = await this.getRolling<MetadataResponse>(
|
||||
`/library/metadata/${watchlistItem.ratingKey}`,
|
||||
{
|
||||
baseURL: 'https://discover.provider.plex.tv',
|
||||
let detailedResponse: MetadataResponse;
|
||||
try {
|
||||
detailedResponse = await this.getRolling<MetadataResponse>(
|
||||
`/library/metadata/${watchlistItem.ratingKey}`,
|
||||
{
|
||||
baseURL: 'https://discover.provider.plex.tv',
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
if (e.response?.status === 404) {
|
||||
logger.warn(
|
||||
`Item with ratingKey ${watchlistItem.ratingKey} not found, it may have been removed from the server.`,
|
||||
{ label: 'Plex.TV Metadata API' }
|
||||
);
|
||||
return null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const metadata = detailedResponse.MediaContainer.Metadata[0];
|
||||
|
||||
const tmdbString = metadata.Guid.find((guid) =>
|
||||
const tmdbString = metadata.Guid?.find((guid) =>
|
||||
guid.id.startsWith('tmdb')
|
||||
);
|
||||
const tvdbString = metadata.Guid.find((guid) =>
|
||||
const tvdbString = metadata.Guid?.find((guid) =>
|
||||
guid.id.startsWith('tvdb')
|
||||
);
|
||||
|
||||
@@ -343,7 +356,9 @@ class PlexTvAPI extends ExternalAPI {
|
||||
)
|
||||
);
|
||||
|
||||
const filteredList = watchlistDetails.filter((detail) => detail.tmdbId);
|
||||
const filteredList = watchlistDetails.filter(
|
||||
(detail) => detail?.tmdbId
|
||||
) as PlexWatchlistItem[];
|
||||
|
||||
return {
|
||||
offset,
|
||||
|
||||
@@ -86,6 +86,7 @@ interface DiscoverMovieOptions {
|
||||
genre?: string;
|
||||
studio?: string;
|
||||
keywords?: string;
|
||||
excludeKeywords?: string;
|
||||
sortBy?: SortOptions;
|
||||
watchRegion?: string;
|
||||
watchProviders?: string;
|
||||
@@ -111,6 +112,7 @@ interface DiscoverTvOptions {
|
||||
genre?: string;
|
||||
network?: number;
|
||||
keywords?: string;
|
||||
excludeKeywords?: string;
|
||||
sortBy?: SortOptions;
|
||||
watchRegion?: string;
|
||||
watchProviders?: string;
|
||||
@@ -495,6 +497,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
|
||||
genre,
|
||||
studio,
|
||||
keywords,
|
||||
excludeKeywords,
|
||||
withRuntimeGte,
|
||||
withRuntimeLte,
|
||||
voteAverageGte,
|
||||
@@ -545,6 +548,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
|
||||
with_genres: genre,
|
||||
with_companies: studio,
|
||||
with_keywords: keywords,
|
||||
without_keywords: excludeKeywords,
|
||||
'with_runtime.gte': withRuntimeGte,
|
||||
'with_runtime.lte': withRuntimeLte,
|
||||
'vote_average.gte': voteAverageGte,
|
||||
@@ -577,6 +581,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
|
||||
genre,
|
||||
network,
|
||||
keywords,
|
||||
excludeKeywords,
|
||||
withRuntimeGte,
|
||||
withRuntimeLte,
|
||||
voteAverageGte,
|
||||
@@ -628,6 +633,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
|
||||
with_genres: genre,
|
||||
with_networks: network,
|
||||
with_keywords: keywords,
|
||||
without_keywords: excludeKeywords,
|
||||
'with_runtime.gte': withRuntimeGte,
|
||||
'with_runtime.lte': withRuntimeLte,
|
||||
'vote_average.gte': voteAverageGte,
|
||||
|
||||
@@ -82,7 +82,7 @@ app
|
||||
}
|
||||
|
||||
// Add DNS caching
|
||||
if (settings.network.dnsCache) {
|
||||
if (settings.network.dnsCache?.enabled) {
|
||||
initializeDnsCache({
|
||||
forceMinTtl: settings.network.dnsCache.forceMinTtl,
|
||||
forceMaxTtl: settings.network.dnsCache.forceMaxTtl,
|
||||
|
||||
@@ -61,6 +61,7 @@ const QueryFilterOptions = z.object({
|
||||
studio: z.coerce.string().optional(),
|
||||
genre: z.coerce.string().optional(),
|
||||
keywords: z.coerce.string().optional(),
|
||||
excludeKeywords: z.coerce.string().optional(),
|
||||
language: z.coerce.string().optional(),
|
||||
withRuntimeGte: z.coerce.string().optional(),
|
||||
withRuntimeLte: z.coerce.string().optional(),
|
||||
@@ -90,6 +91,7 @@ discoverRoutes.get('/movies', async (req, res, next) => {
|
||||
try {
|
||||
const query = ApiQuerySchema.parse(req.query);
|
||||
const keywords = query.keywords;
|
||||
const excludeKeywords = query.excludeKeywords;
|
||||
|
||||
const data = await tmdb.getDiscoverMovies({
|
||||
page: Number(query.page),
|
||||
@@ -105,6 +107,7 @@ discoverRoutes.get('/movies', async (req, res, next) => {
|
||||
? new Date(query.primaryReleaseDateGte).toISOString().split('T')[0]
|
||||
: undefined,
|
||||
keywords,
|
||||
excludeKeywords,
|
||||
withRuntimeGte: query.withRuntimeGte,
|
||||
withRuntimeLte: query.withRuntimeLte,
|
||||
voteAverageGte: query.voteAverageGte,
|
||||
@@ -381,6 +384,7 @@ discoverRoutes.get('/tv', async (req, res, next) => {
|
||||
try {
|
||||
const query = ApiQuerySchema.parse(req.query);
|
||||
const keywords = query.keywords;
|
||||
const excludeKeywords = query.excludeKeywords;
|
||||
const data = await tmdb.getDiscoverTv({
|
||||
page: Number(query.page),
|
||||
sortBy: query.sortBy as SortOptions,
|
||||
@@ -395,6 +399,7 @@ discoverRoutes.get('/tv', async (req, res, next) => {
|
||||
: undefined,
|
||||
originalLanguage: query.language,
|
||||
keywords,
|
||||
excludeKeywords,
|
||||
withRuntimeGte: query.withRuntimeGte,
|
||||
withRuntimeLte: query.withRuntimeLte,
|
||||
voteAverageGte: query.voteAverageGte,
|
||||
|
||||
@@ -33,6 +33,7 @@ const messages = defineMessages('components.Discover.FilterSlideover', {
|
||||
studio: 'Studio',
|
||||
genres: 'Genres',
|
||||
keywords: 'Keywords',
|
||||
excludeKeywords: 'Exclude Keywords',
|
||||
originalLanguage: 'Original Language',
|
||||
runtimeText: '{minValue}-{maxValue} minute runtime',
|
||||
ratingText: 'Ratings between {minValue} and {maxValue}',
|
||||
@@ -181,6 +182,19 @@ const FilterSlideover = ({
|
||||
updateQueryParams('keywords', value?.map((v) => v.value).join(','));
|
||||
}}
|
||||
/>
|
||||
<span className="text-lg font-semibold">
|
||||
{intl.formatMessage(messages.excludeKeywords)}
|
||||
</span>
|
||||
<KeywordSelector
|
||||
defaultValue={currentFilters.excludeKeywords}
|
||||
isMulti
|
||||
onChange={(value) => {
|
||||
updateQueryParams(
|
||||
'excludeKeywords',
|
||||
value?.map((v) => v.value).join(',')
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<span className="text-lg font-semibold">
|
||||
{intl.formatMessage(messages.originalLanguage)}
|
||||
</span>
|
||||
|
||||
@@ -99,6 +99,7 @@ export const QueryFilterOptions = z.object({
|
||||
studio: z.string().optional(),
|
||||
genre: z.string().optional(),
|
||||
keywords: z.string().optional(),
|
||||
excludeKeywords: z.string().optional(),
|
||||
language: z.string().optional(),
|
||||
withRuntimeGte: z.string().optional(),
|
||||
withRuntimeLte: z.string().optional(),
|
||||
@@ -161,6 +162,10 @@ export const prepareFilterValues = (
|
||||
filterValues.keywords = values.keywords;
|
||||
}
|
||||
|
||||
if (values.excludeKeywords) {
|
||||
filterValues.excludeKeywords = values.excludeKeywords;
|
||||
}
|
||||
|
||||
if (values.language) {
|
||||
filterValues.language = values.language;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
"components.Discover.FilterSlideover.activefilters": "{count, plural, one {# Active Filter} other {# Active Filters}}",
|
||||
"components.Discover.FilterSlideover.certification": "Content Rating",
|
||||
"components.Discover.FilterSlideover.clearfilters": "Clear Active Filters",
|
||||
"components.Discover.FilterSlideover.excludeKeywords": "Exclude Keywords",
|
||||
"components.Discover.FilterSlideover.filters": "Filters",
|
||||
"components.Discover.FilterSlideover.firstAirDate": "First Air Date",
|
||||
"components.Discover.FilterSlideover.from": "From",
|
||||
|
||||
Reference in New Issue
Block a user