Files
2026-06-08 21:58:16 +12:00

174 lines
4.5 KiB
JavaScript

import { error, json } from '@sveltejs/kit';
import { normalizeLookupItem } from '../../../lib/collection-tools.js';
import {
buildSingleGenreUpdate,
normalizeGenreNames,
pickSuggestedGenre
} from '../../../lib/genre-cleanup.js';
import { fetchEmby, fetchEmbyJson, normalizeEmbyGuid } from '../../../lib/server/emby-api.js';
import { fetchTmdbDetails, searchTmdbByTitle } from '../../../lib/server/tmdb-api.js';
function getItemTmdbId(item) {
const providerIds = item?.ProviderIds || item?.providerIds || {};
return String(providerIds.Tmdb || providerIds.TMDB || providerIds.tmdb || '').trim();
}
function getTmdbMediaType(item) {
return item?.Type === 'Series' || item?.type === 'Series' ? 'tv' : 'movie';
}
function normalizeTitle(value) {
return String(value || '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '');
}
async function fetchFullItem(userId, itemId) {
const candidates = [
() => fetchEmbyJson(`/Users/${encodeURIComponent(userId)}/Items/${encodeURIComponent(itemId)}`),
() =>
fetchEmbyJson(`/Items/${encodeURIComponent(itemId)}`, {
params: { UserId: userId }
})
];
let lastError = null;
for (const candidate of candidates) {
try {
return await candidate();
} catch (err) {
lastError = err;
}
}
throw lastError || new Error('Could not fetch item details from Emby');
}
async function resolveTmdbMatch(item) {
const directTmdbId = getItemTmdbId(item);
if (directTmdbId) {
return {
tmdbId: Number(directTmdbId),
mediaType: getTmdbMediaType(item),
source: 'providerId'
};
}
const matches = await searchTmdbByTitle({
mediaType: getTmdbMediaType(item),
name: item?.Name || item?.name,
year: item?.ProductionYear || item?.year
});
const exactTitle = normalizeTitle(item?.Name || item?.name);
const bestMatch =
matches.find(
(candidate) =>
normalizeTitle(candidate?.name) === exactTitle &&
(!item?.ProductionYear || !candidate?.year || candidate.year === item.ProductionYear)
) || matches[0];
if (!bestMatch?.tmdbId) return null;
return {
tmdbId: Number(bestMatch.tmdbId),
mediaType: bestMatch.mediaType,
source: 'search'
};
}
async function inspectItemGenres(userId, itemId) {
const fullItem = await fetchFullItem(userId, itemId);
const normalizedItem = normalizeLookupItem(fullItem);
const currentGenres = normalizeGenreNames([
...(fullItem?.Genres || []),
...((fullItem?.GenreItems || []).map((entry) => entry?.Name))
]);
const tmdbMatch = await resolveTmdbMatch(fullItem);
if (!tmdbMatch?.tmdbId) {
return {
item: normalizedItem,
currentGenres,
tmdb: null,
suggestedGenre: ''
};
}
const details = await fetchTmdbDetails(tmdbMatch);
const tmdbGenres = normalizeGenreNames((details?.genres || []).map((genre) => genre?.name));
return {
item: normalizedItem,
currentGenres,
tmdb: {
tmdbId: tmdbMatch.tmdbId,
mediaType: tmdbMatch.mediaType,
source: tmdbMatch.source,
genres: tmdbGenres
},
suggestedGenre: pickSuggestedGenre(tmdbGenres, currentGenres)
};
}
export async function GET({ url }) {
const userId = normalizeEmbyGuid(url.searchParams.get('userId'));
const itemId = String(url.searchParams.get('itemId') || '').trim();
if (!userId) {
throw error(400, 'Missing userId');
}
if (!itemId) {
throw error(400, 'Missing itemId');
}
try {
return json(await inspectItemGenres(userId, itemId));
} catch (err) {
if (String(err.message || '').includes('not configured')) {
throw error(400, err.message);
}
throw error(502, `Could not inspect item genres: ${err.message}`);
}
}
export async function POST({ request }) {
const body = await request.json();
const userId = normalizeEmbyGuid(body?.userId);
const itemId = String(body?.itemId || '').trim();
const genreName = String(body?.genreName || '').trim();
if (!userId) {
throw error(400, 'Missing userId');
}
if (!itemId) {
throw error(400, 'Missing itemId');
}
if (!genreName) {
throw error(400, 'Missing genreName');
}
try {
const fullItem = await fetchFullItem(userId, itemId);
const updatedItem = buildSingleGenreUpdate(fullItem, genreName);
await fetchEmby(`/Items/${encodeURIComponent(itemId)}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedItem)
});
return json(await inspectItemGenres(userId, itemId));
} catch (err) {
if (err?.status) throw err;
if (String(err.message || '').includes('not configured')) {
throw error(400, err.message);
}
throw error(502, `Could not update item genres: ${err.message}`);
}
}