feat(search): search by id (#2082)
* feat(search): search by id This adds the ability to search by ID (starting with TMDb ID). Since there doesn't seem to be way of searching across movies, tv and persons, I have to search through all 3 and use the first one in the order: movie -> tv -> person Searching by ID is triggered using a 'prefix' just like in the *arrs. * fix: missed some refactoring * feat(search): use locale language * feat(search): search using imdb id * feat(search): search using tvdb id * fix: alias type import * fix: missed some refactoring * fix(search): account for id being a string * feat(search): account for movies/tvs/persons with the same id * feat(search): remove non-null assertion Co-authored-by: Ryan Cohen <ryan@sct.dev>pull/2419/head
parent
e0b6abe479
commit
b31cdbf074
@ -0,0 +1,169 @@
|
||||
import TheMovieDb from '../api/themoviedb';
|
||||
import {
|
||||
TmdbMovieDetails,
|
||||
TmdbMovieResult,
|
||||
TmdbPersonDetails,
|
||||
TmdbPersonResult,
|
||||
TmdbSearchMultiResponse,
|
||||
TmdbTvDetails,
|
||||
TmdbTvResult,
|
||||
} from '../api/themoviedb/interfaces';
|
||||
import {
|
||||
mapMovieDetailsToResult,
|
||||
mapPersonDetailsToResult,
|
||||
mapTvDetailsToResult,
|
||||
} from '../models/Search';
|
||||
import { isMovieDetails, isTvDetails } from '../utils/typeHelpers';
|
||||
|
||||
type SearchProviderId = 'TMDb' | 'IMDb' | 'TVDB';
|
||||
|
||||
interface SearchProvider {
|
||||
id: SearchProviderId;
|
||||
pattern: RegExp;
|
||||
search: (id: string, language?: string) => Promise<TmdbSearchMultiResponse>;
|
||||
}
|
||||
|
||||
const searchProviders: SearchProvider[] = [];
|
||||
|
||||
export const findSearchProvider = (
|
||||
query: string
|
||||
): SearchProvider | undefined => {
|
||||
return searchProviders.find((provider) => provider.pattern.test(query));
|
||||
};
|
||||
|
||||
searchProviders.push({
|
||||
id: 'TMDb',
|
||||
pattern: new RegExp(/(?<=tmdb:)\d+/),
|
||||
search: async (
|
||||
id: string,
|
||||
language?: string
|
||||
): Promise<TmdbSearchMultiResponse> => {
|
||||
const tmdb = new TheMovieDb();
|
||||
|
||||
const moviePromise = tmdb.getMovie({ movieId: parseInt(id), language });
|
||||
const tvShowPromise = tmdb.getTvShow({ tvId: parseInt(id), language });
|
||||
const personPromise = tmdb.getPerson({ personId: parseInt(id), language });
|
||||
|
||||
const responses = await Promise.allSettled([
|
||||
moviePromise,
|
||||
tvShowPromise,
|
||||
personPromise,
|
||||
]);
|
||||
|
||||
const successfulResponses = responses.filter(
|
||||
(r) => r.status === 'fulfilled'
|
||||
) as
|
||||
| (
|
||||
| PromiseFulfilledResult<TmdbMovieDetails>
|
||||
| PromiseFulfilledResult<TmdbTvDetails>
|
||||
| PromiseFulfilledResult<TmdbPersonDetails>
|
||||
)[];
|
||||
|
||||
const results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[] = [];
|
||||
|
||||
if (successfulResponses.length) {
|
||||
results.push(
|
||||
...successfulResponses.map((r) => {
|
||||
if (isMovieDetails(r.value)) {
|
||||
return mapMovieDetailsToResult(r.value);
|
||||
} else if (isTvDetails(r.value)) {
|
||||
return mapTvDetailsToResult(r.value);
|
||||
} else {
|
||||
return mapPersonDetailsToResult(r.value);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
page: 1,
|
||||
total_pages: 1,
|
||||
total_results: results.length,
|
||||
results,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
searchProviders.push({
|
||||
id: 'IMDb',
|
||||
pattern: new RegExp(/(?<=imdb:)(tt|nm)\d+/),
|
||||
search: async (
|
||||
id: string,
|
||||
language?: string
|
||||
): Promise<TmdbSearchMultiResponse> => {
|
||||
const tmdb = new TheMovieDb();
|
||||
|
||||
const responses = await tmdb.getByExternalId({
|
||||
externalId: id,
|
||||
type: 'imdb',
|
||||
language,
|
||||
});
|
||||
|
||||
const results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[] = [];
|
||||
|
||||
// set the media_type here since searching by external id doesn't return it
|
||||
results.push(
|
||||
...(responses.movie_results.map((movie) => ({
|
||||
...movie,
|
||||
media_type: 'movie',
|
||||
})) as TmdbMovieResult[]),
|
||||
...(responses.tv_results.map((tv) => ({
|
||||
...tv,
|
||||
media_type: 'tv',
|
||||
})) as TmdbTvResult[]),
|
||||
...(responses.person_results.map((person) => ({
|
||||
...person,
|
||||
media_type: 'person',
|
||||
})) as TmdbPersonResult[])
|
||||
);
|
||||
|
||||
return {
|
||||
page: 1,
|
||||
total_pages: 1,
|
||||
total_results: results.length,
|
||||
results,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
searchProviders.push({
|
||||
id: 'TVDB',
|
||||
pattern: new RegExp(/(?<=tvdb:)\d+/),
|
||||
search: async (
|
||||
id: string,
|
||||
language?: string
|
||||
): Promise<TmdbSearchMultiResponse> => {
|
||||
const tmdb = new TheMovieDb();
|
||||
|
||||
const responses = await tmdb.getByExternalId({
|
||||
externalId: parseInt(id),
|
||||
type: 'tvdb',
|
||||
language,
|
||||
});
|
||||
|
||||
const results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[] = [];
|
||||
|
||||
// set the media_type here since searching by external id doesn't return it
|
||||
results.push(
|
||||
...(responses.movie_results.map((movie) => ({
|
||||
...movie,
|
||||
media_type: 'movie',
|
||||
})) as TmdbMovieResult[]),
|
||||
...(responses.tv_results.map((tv) => ({
|
||||
...tv,
|
||||
media_type: 'tv',
|
||||
})) as TmdbTvResult[]),
|
||||
...(responses.person_results.map((person) => ({
|
||||
...person,
|
||||
media_type: 'person',
|
||||
})) as TmdbPersonResult[])
|
||||
);
|
||||
|
||||
return {
|
||||
page: 1,
|
||||
total_pages: 1,
|
||||
total_results: results.length,
|
||||
results,
|
||||
};
|
||||
},
|
||||
});
|
Loading…
Reference in new issue