diff --git a/apps/master-api/src/iptv/xtream.ts b/apps/master-api/src/iptv/xtream.ts index 91bb554..1d6320d 100644 --- a/apps/master-api/src/iptv/xtream.ts +++ b/apps/master-api/src/iptv/xtream.ts @@ -205,7 +205,8 @@ export class XtreamClient { } getLiveCategories() { - return this.getJson({ action: "get_live_categories" }, 5 * 60_000); + // Korte TTL: na IPTV-wijzigingen snel weer alle planken zichtbaar + return this.getJson({ action: "get_live_categories" }, 60_000); } getLiveStreams(categoryId?: string) { diff --git a/apps/master-api/src/viewer/iptv-client.ts b/apps/master-api/src/viewer/iptv-client.ts index d4e3ab3..2c3db21 100644 --- a/apps/master-api/src/viewer/iptv-client.ts +++ b/apps/master-api/src/viewer/iptv-client.ts @@ -82,9 +82,14 @@ export async function getViewerIptvCategories(viewerId: string, config: Config) const xt = await loadXtreamForToken(tokenId, config); if (!xt?.line.enableLive) throw new AppError("FORBIDDEN", "Live TV niet beschikbaar", 403); + // Categorieën zijn leidend — nooit blokkeren op de (soms trage/incomplete) bulk-zenderlijst const catsRaw = await xt.client.getLiveCategories().catch(() => null); - const streamsRaw = await xt.client.getLiveStreams().catch(() => null); const cats = Array.isArray(catsRaw) ? catsRaw : []; + + const streamsRaw = await Promise.race([ + xt.client.getLiveStreams().catch(() => null), + new Promise((resolve) => setTimeout(() => resolve(null), 6_000)), + ]); const streams = Array.isArray(streamsRaw) ? streamsRaw : []; const counts = new Map(); @@ -94,23 +99,10 @@ export async function getViewerIptvCategories(viewerId: string, config: Config) counts.set(key, (counts.get(key) ?? 0) + 1); } - // Als de bulk-lijst incompleet is: per categorie bijtellen (gecached in XtreamClient) - const idsNeedingCount = cats - .map((c) => normalizeIptvCategoryId(c.category_id)) - .filter((id) => id && (counts.get(id) ?? 0) === 0); - if (idsNeedingCount.length > 0) { - await mapPool(idsNeedingCount, 8, async (id) => { - const list = await xt.client.getLiveStreams(id).catch(() => null); - if (Array.isArray(list) && list.length > 0) { - counts.set(id, list.length); - } - }); - } - const categories = cats .map((c) => { const id = normalizeIptvCategoryId(c.category_id); - const rawName = c.category_name ?? ""; + const rawName = String(c.category_name ?? ""); return { id, name: friendlyCategoryName(rawName), @@ -118,26 +110,25 @@ export async function getViewerIptvCategories(viewerId: string, config: Config) channelCount: counts.get(id) ?? 0, }; }) - // Alleen lege ids + provider-verzamelcategorieën weg (niet echte genre-planken) + // Toon ALLE echte planken; alleen pure verzamelbakken van de provider weg .filter((c) => c.id.length > 0 && !isAggregateCategoryName(c.rawName)) .sort((a, b) => a.name.localeCompare(b.name, "nl")); - // Één verzamelknop van ons — niet van de IPTV-provider - const totalChannels = - streams.length > 0 - ? streams.length - : [...counts.values()].reduce((a, b) => a + b, 0); + // Één eigen verzamelknop categories.unshift({ id: "__all__", name: "Alle zenders", rawName: "Alle zenders", - channelCount: totalChannels, + channelCount: streams.length, }); return { categories }; } -/** Alleen echte verzamelbakken (Alles / All / Alle zenders), geen Sport/Films/etc. */ +/** + * Alleen pure verzamelbakken (Alles / All), géén "All Sports", "Alle films", enz. + * Exacte match ná friendly-name (landcode-prefix al gestript). + */ export function isAggregateCategoryName(raw: string): boolean { const n = friendlyCategoryName(raw).toLowerCase().replace(/\s+/g, " ").trim(); return ( @@ -162,18 +153,6 @@ export function normalizeIptvCategoryId(raw: unknown): string { return s; } -async function mapPool(items: T[], concurrency: number, fn: (item: T) => Promise) { - const queue = [...items]; - const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => { - while (queue.length > 0) { - const item = queue.shift(); - if (item === undefined) return; - await fn(item); - } - }); - await Promise.all(workers); -} - function sortChannels(streams: XtreamLiveStream[]): XtreamLiveStream[] { return [...streams].sort((a, b) => (a.num ?? a.stream_id) - (b.num ?? b.stream_id)); }