Fix IPTV categories timing out under per-category stream fetches.

Return all live categories immediately; only wait briefly for bulk counts and keep a single Alle zenders entry.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jos Vooges | STH 2026-09-07 23:21:14 +02:00
parent 318f613b40
commit 1834d42bd5
2 changed files with 16 additions and 36 deletions

View file

@ -205,7 +205,8 @@ export class XtreamClient {
}
getLiveCategories() {
return this.getJson<XtreamCategory[]>({ action: "get_live_categories" }, 5 * 60_000);
// Korte TTL: na IPTV-wijzigingen snel weer alle planken zichtbaar
return this.getJson<XtreamCategory[]>({ action: "get_live_categories" }, 60_000);
}
getLiveStreams(categoryId?: string) {

View file

@ -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<null>((resolve) => setTimeout(() => resolve(null), 6_000)),
]);
const streams = Array.isArray(streamsRaw) ? streamsRaw : [];
const counts = new Map<string, number>();
@ -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<T>(items: T[], concurrency: number, fn: (item: T) => Promise<void>) {
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));
}