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}`); } }