Improve admin metadata linking with full TMDB search and ID support.
Merge NL/EN TMDB hits and accept TMDB ids/URLs so titles missing on IMDb can still be linked.
This commit is contained in:
parent
23c81f2b83
commit
e8d1c4bfee
4 changed files with 209 additions and 49 deletions
|
|
@ -280,9 +280,42 @@ export default function MoviesPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyImdbDirect() {
|
async function applyImdbDirect() {
|
||||||
const imdb = linkQuery.trim();
|
const raw = linkQuery.trim();
|
||||||
if (!/^tt\d+$/i.test(imdb) || !linkMovieId) {
|
if (!linkMovieId) return;
|
||||||
setLinkMsg("Vul een IMDb-id in (bijv. tt1375666) of zoek eerst op titel.");
|
|
||||||
|
const tmdbFromUrl = raw.match(/themoviedb\.org\/(?:movie|tv)\/(\d+)/i)?.[1];
|
||||||
|
const tmdbId = tmdbFromUrl
|
||||||
|
? parseInt(tmdbFromUrl, 10)
|
||||||
|
: /^\d{1,8}$/.test(raw)
|
||||||
|
? parseInt(raw, 10)
|
||||||
|
: null;
|
||||||
|
if (tmdbId != null && Number.isFinite(tmdbId)) {
|
||||||
|
setLinkBusy(true);
|
||||||
|
setLinkMsg("Koppelen via TMDB…");
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/v1/admin/movies/${linkMovieId}/link`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ tmdbId }),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
setLinkMsg(data.error?.message ?? "Opslaan mislukt");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLinkMsg("Gekoppeld via TMDB. Metadata vernieuwd.");
|
||||||
|
setLinkMovieId(null);
|
||||||
|
setExpanded(data.movieId ?? linkMovieId);
|
||||||
|
load();
|
||||||
|
} finally {
|
||||||
|
setLinkBusy(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^tt\d+$/i.test(raw)) {
|
||||||
|
setLinkMsg("Vul een titel, TMDB-id of IMDb-id (tt…) in.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLinkBusy(true);
|
setLinkBusy(true);
|
||||||
|
|
@ -292,7 +325,7 @@ export default function MoviesPage() {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ imdbId: imdb }),
|
body: JSON.stringify({ imdbId: raw }),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
|
@ -348,20 +381,29 @@ export default function MoviesPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyFolderImdbAsSeries() {
|
async function applyFolderImdbAsSeries() {
|
||||||
const imdb = folderQuery.trim();
|
const raw = folderQuery.trim();
|
||||||
if (!/^tt\d+$/i.test(imdb) || !folderRelink) {
|
if (!folderRelink) return;
|
||||||
setFolderMsg("Vul een IMDb-id van de serie in (tt…) of zoek op titel.");
|
|
||||||
|
const tmdbFromUrl = raw.match(/themoviedb\.org\/(?:movie|tv)\/(\d+)/i)?.[1];
|
||||||
|
const tmdbId = tmdbFromUrl
|
||||||
|
? parseInt(tmdbFromUrl, 10)
|
||||||
|
: /^\d{1,8}$/.test(raw)
|
||||||
|
? parseInt(raw, 10)
|
||||||
|
: null;
|
||||||
|
const idLabel = tmdbId != null ? `TMDB ${tmdbId}` : raw;
|
||||||
|
if (tmdbId == null && !/^tt\d+$/i.test(raw)) {
|
||||||
|
setFolderMsg("Vul een serietitel, TMDB-id of IMDb-id (tt…) in.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!confirm(
|
!confirm(
|
||||||
`Alle ${folderRelink.fileCount} bestanden in\n${folderRelink.folder}\nals SERIE herkoppelen via ${imdb}?`
|
`Alle ${folderRelink.fileCount} bestanden in\n${folderRelink.folder}\nals SERIE herkoppelen via ${idLabel}?`
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setFolderBusy(true);
|
setFolderBusy(true);
|
||||||
setFolderMsg("Map herkoppelen via IMDb…");
|
setFolderMsg(tmdbId != null ? "Map herkoppelen via TMDB…" : "Map herkoppelen via IMDb…");
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/v1/admin/library/relink-folder`, {
|
const res = await fetch(`/api/v1/admin/library/relink-folder`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -370,7 +412,7 @@ export default function MoviesPage() {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
nodeId: folderRelink.nodeId,
|
nodeId: folderRelink.nodeId,
|
||||||
folder: folderRelink.folder,
|
folder: folderRelink.folder,
|
||||||
imdbId: imdb,
|
...(tmdbId != null ? { tmdbId } : { imdbId: raw }),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
@ -683,7 +725,7 @@ export default function MoviesPage() {
|
||||||
<input
|
<input
|
||||||
value={linkQuery}
|
value={linkQuery}
|
||||||
onChange={(e) => setLinkQuery(e.target.value)}
|
onChange={(e) => setLinkQuery(e.target.value)}
|
||||||
placeholder="Titel of IMDb-id (tt…)"
|
placeholder="Titel, TMDB-id of IMDb-id (tt…)"
|
||||||
style={{ flex: 1, minWidth: 200, marginBottom: 0 }}
|
style={{ flex: 1, minWidth: 200, marginBottom: 0 }}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
@ -691,7 +733,7 @@ export default function MoviesPage() {
|
||||||
Zoek
|
Zoek
|
||||||
</button>
|
</button>
|
||||||
<button type="button" disabled={linkBusy} onClick={() => void applyImdbDirect()}>
|
<button type="button" disabled={linkBusy} onClick={() => void applyImdbDirect()}>
|
||||||
Koppel IMDb-id
|
Koppel id
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{linkMsg && (
|
{linkMsg && (
|
||||||
|
|
@ -798,7 +840,7 @@ export default function MoviesPage() {
|
||||||
<input
|
<input
|
||||||
value={folderQuery}
|
value={folderQuery}
|
||||||
onChange={(e) => setFolderQuery(e.target.value)}
|
onChange={(e) => setFolderQuery(e.target.value)}
|
||||||
placeholder="Serietitel of IMDb-id (tt…)"
|
placeholder="Serietitel, TMDB-id of IMDb-id (tt…)"
|
||||||
style={{ flex: 1, minWidth: 200, marginBottom: 0 }}
|
style={{ flex: 1, minWidth: 200, marginBottom: 0 }}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
@ -806,7 +848,7 @@ export default function MoviesPage() {
|
||||||
Zoek serie
|
Zoek serie
|
||||||
</button>
|
</button>
|
||||||
<button type="button" disabled={folderBusy} onClick={() => void applyFolderImdbAsSeries()}>
|
<button type="button" disabled={folderBusy} onClick={() => void applyFolderImdbAsSeries()}>
|
||||||
Koppel IMDb-id
|
Koppel id
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{folderMsg && (
|
{folderMsg && (
|
||||||
|
|
|
||||||
|
|
@ -118,9 +118,45 @@ export default function PendingSeriesPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyImdb() {
|
async function applyImdb() {
|
||||||
const imdb = query.trim();
|
const raw = query.trim();
|
||||||
if (!/^tt\d+$/i.test(imdb) || !link) {
|
if (!link) return;
|
||||||
setLinkMsg("Vul een IMDb-id in (tt…) of zoek op titel.");
|
|
||||||
|
const tmdbFromUrl = raw.match(/themoviedb\.org\/(?:movie|tv)\/(\d+)/i)?.[1];
|
||||||
|
const tmdbId = tmdbFromUrl
|
||||||
|
? parseInt(tmdbFromUrl, 10)
|
||||||
|
: /^\d{1,8}$/.test(raw)
|
||||||
|
? parseInt(raw, 10)
|
||||||
|
: null;
|
||||||
|
if (tmdbId != null && Number.isFinite(tmdbId)) {
|
||||||
|
setBusy(true);
|
||||||
|
setLinkMsg("Koppelen via TMDB…");
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/v1/admin/library/relink-folder", {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
nodeId: link.nodeId,
|
||||||
|
folder: link.folder,
|
||||||
|
tmdbId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
setLinkMsg(data.error?.message ?? "Koppelen mislukt");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMsg(`Gekoppeld → ${data.seriesTitle}: ${data.moved}/${data.total}`);
|
||||||
|
setLink(null);
|
||||||
|
load();
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^tt\d+$/i.test(raw)) {
|
||||||
|
setLinkMsg("Vul een titel, TMDB-id of IMDb-id (tt…) in.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
|
|
@ -133,7 +169,7 @@ export default function PendingSeriesPage() {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
nodeId: link.nodeId,
|
nodeId: link.nodeId,
|
||||||
folder: link.folder,
|
folder: link.folder,
|
||||||
imdbId: imdb,
|
imdbId: raw,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
@ -247,7 +283,7 @@ export default function PendingSeriesPage() {
|
||||||
<input
|
<input
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
placeholder="Titel of IMDb-id (tt…)"
|
placeholder="Titel, TMDB-id of IMDb-id (tt…)"
|
||||||
style={{ flex: 1, minWidth: 200, marginBottom: 0 }}
|
style={{ flex: 1, minWidth: 200, marginBottom: 0 }}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
@ -255,7 +291,7 @@ export default function PendingSeriesPage() {
|
||||||
Zoek
|
Zoek
|
||||||
</button>
|
</button>
|
||||||
<button type="button" disabled={busy} onClick={() => void applyImdb()}>
|
<button type="button" disabled={busy} onClick={() => void applyImdb()}>
|
||||||
Koppel IMDb-id
|
Koppel id
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{linkMsg && (
|
{linkMsg && (
|
||||||
|
|
|
||||||
|
|
@ -283,20 +283,31 @@ export default function SeriesPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyFolderImdbDirect() {
|
async function applyFolderImdbDirect() {
|
||||||
const imdb = folderQuery.trim();
|
const raw = folderQuery.trim();
|
||||||
if (!/^tt\d+$/i.test(imdb) || !folderRelink) {
|
if (!folderRelink) return;
|
||||||
setFolderMsg("Vul een IMDb-id in (bijv. tt14424422) of zoek eerst op titel.");
|
|
||||||
|
const tmdbFromUrl = raw.match(/themoviedb\.org\/(?:movie|tv)\/(\d+)/i)?.[1];
|
||||||
|
const tmdbId = tmdbFromUrl
|
||||||
|
? parseInt(tmdbFromUrl, 10)
|
||||||
|
: /^\d{1,8}$/.test(raw)
|
||||||
|
? parseInt(raw, 10)
|
||||||
|
: null;
|
||||||
|
const idLabel = tmdbId != null ? `TMDB ${tmdbId}` : raw;
|
||||||
|
if (tmdbId == null && !/^tt\d+$/i.test(raw)) {
|
||||||
|
setFolderMsg("Vul een titel, TMDB-id of IMDb-id (tt…) in.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!confirm(
|
!confirm(
|
||||||
`Alle ${folderRelink.fileCount} bestanden in\n${folderRelink.folder}\nherkoppelen via ${imdb}?`
|
`Alle ${folderRelink.fileCount} bestanden in\n${folderRelink.folder}\nherkoppelen via ${idLabel}?`
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setFolderBusy(true);
|
setFolderBusy(true);
|
||||||
setFolderMsg("Hele map herkoppelen via IMDb…");
|
setFolderMsg(
|
||||||
|
tmdbId != null ? "Hele map herkoppelen via TMDB…" : "Hele map herkoppelen via IMDb…"
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/v1/admin/library/relink-folder`, {
|
const res = await fetch(`/api/v1/admin/library/relink-folder`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -305,7 +316,7 @@ export default function SeriesPage() {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
nodeId: folderRelink.nodeId,
|
nodeId: folderRelink.nodeId,
|
||||||
folder: folderRelink.folder,
|
folder: folderRelink.folder,
|
||||||
imdbId: imdb,
|
...(tmdbId != null ? { tmdbId } : { imdbId: raw }),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
@ -381,9 +392,42 @@ export default function SeriesPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyImdbDirect() {
|
async function applyImdbDirect() {
|
||||||
const imdb = linkQuery.trim();
|
const raw = linkQuery.trim();
|
||||||
if (!/^tt\d+$/i.test(imdb) || !linkSeriesId) {
|
if (!linkSeriesId) return;
|
||||||
setLinkMsg("Vul een IMDb-id in (bijv. tt0903747) of zoek eerst op titel.");
|
|
||||||
|
const tmdbFromUrl = raw.match(/themoviedb\.org\/(?:movie|tv)\/(\d+)/i)?.[1];
|
||||||
|
const tmdbId = tmdbFromUrl
|
||||||
|
? parseInt(tmdbFromUrl, 10)
|
||||||
|
: /^\d{1,8}$/.test(raw)
|
||||||
|
? parseInt(raw, 10)
|
||||||
|
: null;
|
||||||
|
if (tmdbId != null && Number.isFinite(tmdbId)) {
|
||||||
|
setLinkBusy(true);
|
||||||
|
setLinkMsg("Koppelen via TMDB…");
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/v1/admin/series/${linkSeriesId}/link`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ tmdbId }),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
setLinkMsg(data.error?.message ?? "Opslaan mislukt");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLinkMsg("Gekoppeld via TMDB. Metadata vernieuwd.");
|
||||||
|
setLinkSeriesId(null);
|
||||||
|
setExpanded(data.seriesId ?? linkSeriesId);
|
||||||
|
load();
|
||||||
|
} finally {
|
||||||
|
setLinkBusy(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^tt\d+$/i.test(raw)) {
|
||||||
|
setLinkMsg("Vul een titel, TMDB-id of IMDb-id (tt…) in.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLinkBusy(true);
|
setLinkBusy(true);
|
||||||
|
|
@ -393,7 +437,7 @@ export default function SeriesPage() {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ imdbId: imdb }),
|
body: JSON.stringify({ imdbId: raw }),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
|
@ -746,7 +790,7 @@ export default function SeriesPage() {
|
||||||
<input
|
<input
|
||||||
value={linkQuery}
|
value={linkQuery}
|
||||||
onChange={(e) => setLinkQuery(e.target.value)}
|
onChange={(e) => setLinkQuery(e.target.value)}
|
||||||
placeholder="Titel of IMDb-id (tt…)"
|
placeholder="Titel, TMDB-id of IMDb-id (tt…)"
|
||||||
style={{ flex: 1, minWidth: 200, marginBottom: 0 }}
|
style={{ flex: 1, minWidth: 200, marginBottom: 0 }}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
@ -754,7 +798,7 @@ export default function SeriesPage() {
|
||||||
Zoek
|
Zoek
|
||||||
</button>
|
</button>
|
||||||
<button type="button" disabled={linkBusy} onClick={() => void applyImdbDirect()}>
|
<button type="button" disabled={linkBusy} onClick={() => void applyImdbDirect()}>
|
||||||
Koppel IMDb-id
|
Koppel id
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{linkMsg && (
|
{linkMsg && (
|
||||||
|
|
@ -876,7 +920,7 @@ export default function SeriesPage() {
|
||||||
<input
|
<input
|
||||||
value={folderQuery}
|
value={folderQuery}
|
||||||
onChange={(e) => setFolderQuery(e.target.value)}
|
onChange={(e) => setFolderQuery(e.target.value)}
|
||||||
placeholder="Titel of IMDb-id (tt…)"
|
placeholder="Titel, TMDB-id of IMDb-id (tt…)"
|
||||||
style={{ flex: 1, minWidth: 200, marginBottom: 0 }}
|
style={{ flex: 1, minWidth: 200, marginBottom: 0 }}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
@ -884,7 +928,7 @@ export default function SeriesPage() {
|
||||||
Zoek
|
Zoek
|
||||||
</button>
|
</button>
|
||||||
<button type="button" disabled={folderBusy} onClick={() => void applyFolderImdbDirect()}>
|
<button type="button" disabled={folderBusy} onClick={() => void applyFolderImdbDirect()}>
|
||||||
Koppel IMDb-id
|
Koppel id
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{folderMsg && (
|
{folderMsg && (
|
||||||
|
|
|
||||||
|
|
@ -1434,7 +1434,7 @@ export class MetadataService {
|
||||||
return this.deleteMediaFilesSafely(stale.map((f) => f.id));
|
return this.deleteMediaFilesSafely(stale.map((f) => f.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Admin: search TMDB by title (or resolve a single IMDb id). */
|
/** Admin: search TMDB by title, TMDB id, or resolve a single IMDb id. */
|
||||||
async searchCatalog(
|
async searchCatalog(
|
||||||
type: "movie" | "series",
|
type: "movie" | "series",
|
||||||
query: string
|
query: string
|
||||||
|
|
@ -1458,6 +1458,28 @@ export class MetadataService {
|
||||||
return found ? [found] : [];
|
return found ? [found] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Direct TMDB id or themoviedb.org URL
|
||||||
|
const tmdbIdFromUrl = q.match(/themoviedb\.org\/(?:movie|tv)\/(\d+)/i)?.[1];
|
||||||
|
const tmdbIdRaw = tmdbIdFromUrl || (/^\d{1,8}$/.test(q) ? q : null);
|
||||||
|
if (tmdbIdRaw) {
|
||||||
|
const tmdbId = parseInt(tmdbIdRaw, 10);
|
||||||
|
const meta =
|
||||||
|
type === "movie"
|
||||||
|
? await this.getTmdbMovieDetails(tmdbId)
|
||||||
|
: await this.getTmdbSeriesDetails(tmdbId);
|
||||||
|
if (!meta) return [];
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
tmdbId: meta.id,
|
||||||
|
title: meta.title,
|
||||||
|
year: meta.year ?? null,
|
||||||
|
overview: meta.overview || null,
|
||||||
|
posterUrl: meta.posterUrl || null,
|
||||||
|
imdbId: meta.imdbId || null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const path = type === "movie" ? "search/movie" : "search/tv";
|
const path = type === "movie" ? "search/movie" : "search/tv";
|
||||||
const base = {
|
const base = {
|
||||||
api_key: this.tmdbApiKey,
|
api_key: this.tmdbApiKey,
|
||||||
|
|
@ -1478,32 +1500,48 @@ export class MetadataService {
|
||||||
})}`
|
})}`
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
if (!nlRes.ok) return [];
|
const nlData = nlRes.ok ? ((await nlRes.json()) as TmdbSearchResult) : { results: [] };
|
||||||
const data = (await nlRes.json()) as TmdbSearchResult;
|
const enData = enRes.ok ? ((await enRes.json()) as TmdbSearchResult) : { results: [] };
|
||||||
const enById = new Map<number, TmdbSearchResult["results"][number]>();
|
const nlList = nlData.results ?? [];
|
||||||
if (enRes.ok) {
|
const enList = enData.results ?? [];
|
||||||
const enData = (await enRes.json()) as TmdbSearchResult;
|
if (!nlList.length && !enList.length) return [];
|
||||||
for (const hit of enData.results ?? []) enById.set(hit.id, hit);
|
|
||||||
|
const enById = new Map(enList.map((hit) => [hit.id, hit]));
|
||||||
|
const nlById = new Map(nlList.map((hit) => [hit.id, hit]));
|
||||||
|
const orderedIds: number[] = [];
|
||||||
|
const seen = new Set<number>();
|
||||||
|
for (const hit of [...nlList, ...enList]) {
|
||||||
|
if (seen.has(hit.id)) continue;
|
||||||
|
seen.add(hit.id);
|
||||||
|
orderedIds.push(hit.id);
|
||||||
}
|
}
|
||||||
return (data.results ?? []).slice(0, 12).map((item) => {
|
|
||||||
const en = enById.get(item.id);
|
return orderedIds.slice(0, 12).map((id) => {
|
||||||
|
const nl = nlById.get(id);
|
||||||
|
const en = enById.get(id);
|
||||||
|
const item = nl ?? en!;
|
||||||
const date = type === "movie" ? item.release_date : item.first_air_date;
|
const date = type === "movie" ? item.release_date : item.first_air_date;
|
||||||
const year = date ? parseInt(date.slice(0, 4), 10) : null;
|
const year = date ? parseInt(date.slice(0, 4), 10) : null;
|
||||||
const localized = type === "movie" ? item.title : item.name;
|
const localized = type === "movie" ? nl?.title ?? item.title : nl?.name ?? item.name;
|
||||||
const original = type === "movie" ? item.original_title : item.original_name;
|
const original =
|
||||||
|
type === "movie"
|
||||||
|
? item.original_title ?? en?.original_title
|
||||||
|
: item.original_name ?? en?.original_name;
|
||||||
const english = type === "movie" ? en?.title : en?.name;
|
const english = type === "movie" ? en?.title : en?.name;
|
||||||
const overview = item.overview?.trim() || en?.overview?.trim() || null;
|
const overview =
|
||||||
|
(nl?.overview || item.overview)?.trim() || en?.overview?.trim() || null;
|
||||||
|
const posterPath = item.poster_path || en?.poster_path || nl?.poster_path;
|
||||||
return {
|
return {
|
||||||
tmdbId: item.id,
|
tmdbId: id,
|
||||||
title: preferNlOrEnglishTitle({
|
title: preferNlOrEnglishTitle({
|
||||||
localizedTitle: localized,
|
localizedTitle: localized,
|
||||||
originalTitle: original,
|
originalTitle: original,
|
||||||
originalLanguage: item.original_language,
|
originalLanguage: item.original_language ?? en?.original_language,
|
||||||
englishTitle: english,
|
englishTitle: english,
|
||||||
}),
|
}),
|
||||||
year: Number.isFinite(year) ? year : null,
|
year: Number.isFinite(year) ? year : null,
|
||||||
overview,
|
overview,
|
||||||
posterUrl: item.poster_path ? `${TMDB_IMAGE_BASE}${item.poster_path}` : null,
|
posterUrl: posterPath ? `${TMDB_IMAGE_BASE}${posterPath}` : null,
|
||||||
imdbId: null,
|
imdbId: null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue