Include custom live lists in admin TV category order.

Lists appear alongside XUI categories and respect the saved order in apps.
This commit is contained in:
Jos Vooges | STH 2026-09-18 20:08:00 +02:00
parent c97374c9cb
commit 94bf98ded7
2 changed files with 84 additions and 59 deletions

View file

@ -919,9 +919,10 @@ export default function SettingsPage() {
Categorie-volgorde
</h3>
<p className="muted" style={{ marginBottom: "0.75rem" }}>
Volgorde van links naar rechts in de TV-apps. Nieuwe XUI-categorieën komen onderaan.
Volgorde van links naar rechts in de TV-apps. XUI-categorieën én Live-lijsten
(ClearKey/DASH). Nieuwe items komen onderaan.
{tvCategories.length === 0
? " Geen actieve IPTV-lijn gevonden — koppel eerst een kijker."
? " Geen categorieën — koppel een IPTV-lijn en/of maak Live-lijsten."
: ""}
</p>
<div style={{ display: "flex", flexDirection: "column", gap: "0.35rem" }}>

View file

@ -84,24 +84,27 @@ export async function getViewerIptvCategories(viewerId: string, config: Config)
throw new AppError("FORBIDDEN", "Geen TV-toegang", 403);
}
const prefs = await getIptvSettings(config);
let categories: Array<{
id: string;
name: string;
rawName: string;
channelCount: number;
}> = [];
let streamCountForAll = 0;
if (xt?.line.enableLive) {
const [catsRaw, streamsRaw, iptvPrefs] = await Promise.all([
const [catsRaw, streamsRaw] = await Promise.all([
xt.client.getLiveCategories().catch(() => [] as Awaited<ReturnType<typeof xt.client.getLiveCategories>>),
Promise.race([
xt.client.getLiveStreams().catch(() => [] as XtreamLiveStream[]),
new Promise<XtreamLiveStream[]>((resolve) => setTimeout(() => resolve([]), 6_000)),
]),
getIptvSettings(config),
]);
const cats = Array.isArray(catsRaw) ? catsRaw : [];
const streams: XtreamLiveStream[] = Array.isArray(streamsRaw) ? streamsRaw : [];
streamCountForAll = streams.length;
const counts = new Map<string, number>();
for (const s of streams) {
@ -131,18 +134,6 @@ export async function getViewerIptvCategories(viewerId: string, config: Config)
channelCount,
}));
}
const prefs = iptvPrefs ?? (await getIptvSettings(config));
categories = applyIptvCategoryOrder(categories, prefs.categoryOrder);
if (prefs.showAlleZenders) {
categories.unshift({
id: "__all__",
name: "Alle zenders",
rawName: "Alle zenders",
channelCount: streams.length,
});
}
}
const customLists = await listAssignedLiveListsForViewer(viewerId);
@ -155,61 +146,94 @@ export async function getViewerIptvCategories(viewerId: string, config: Config)
});
}
categories = applyIptvCategoryOrder(categories, prefs.categoryOrder);
if (prefs.showAlleZenders && xt?.line.enableLive) {
categories.unshift({
id: "__all__",
name: "Alle zenders",
rawName: "Alle zenders",
channelCount: streamCountForAll,
});
}
return { categories };
}
/** Admin: categorieën uit een actieve IPTV-lijn voor volgorde-UI. */
/** Admin: XUI-categorieën + custom live-lijsten voor volgorde-UI. */
export async function listIptvCategoriesForAdmin(config: Config) {
const iptvPrefs = await getIptvSettings(config);
let categories: Array<{
id: string;
name: string;
rawName: string;
channelCount: number;
}> = [];
const line = await prisma.iptvLine.findFirst({
where: { enableLive: true },
orderBy: { updatedAt: "desc" },
select: { id: true },
});
if (!line) return [] as Array<{ id: string; name: string; channelCount: number }>;
const xt = await loadXtreamByLineId(line.id, config);
if (!xt?.line.enableLive) {
return [] as Array<{ id: string; name: string; channelCount: number }>;
if (line) {
const xt = await loadXtreamByLineId(line.id, config);
if (xt?.line.enableLive) {
const [catsRaw, streamsRaw] = await Promise.all([
xt.client.getLiveCategories().catch(() => []),
Promise.race([
xt.client.getLiveStreams().catch(() => [] as XtreamLiveStream[]),
new Promise<XtreamLiveStream[]>((resolve) => setTimeout(() => resolve([]), 8_000)),
]),
]);
const cats = Array.isArray(catsRaw) ? catsRaw : [];
const streams = Array.isArray(streamsRaw) ? streamsRaw : [];
const counts = new Map<string, number>();
for (const s of streams) {
const key = normalizeIptvCategoryId(s.category_id);
if (!key) continue;
counts.set(key, (counts.get(key) ?? 0) + 1);
}
categories = cats
.map((c) => {
const id = normalizeIptvCategoryId(c.category_id);
const rawName = String(c.category_name ?? "");
return {
id,
name: friendlyCategoryName(rawName),
rawName,
channelCount: counts.get(id) ?? 0,
};
})
.filter((c) => c.id.length > 0 && !isAggregateCategoryName(c.rawName));
if (categories.length === 0 && counts.size > 0) {
categories = [...counts.entries()].map(([id, channelCount]) => ({
id,
name: `Groep ${id}`,
rawName: `Groep ${id}`,
channelCount,
}));
}
}
}
const [catsRaw, streamsRaw, iptvPrefs] = await Promise.all([
xt.client.getLiveCategories().catch(() => []),
Promise.race([
xt.client.getLiveStreams().catch(() => [] as XtreamLiveStream[]),
new Promise<XtreamLiveStream[]>((resolve) => setTimeout(() => resolve([]), 8_000)),
]),
getIptvSettings(config),
]);
const cats = Array.isArray(catsRaw) ? catsRaw : [];
const streams = Array.isArray(streamsRaw) ? streamsRaw : [];
const counts = new Map<string, number>();
for (const s of streams) {
const key = normalizeIptvCategoryId(s.category_id);
if (!key) continue;
counts.set(key, (counts.get(key) ?? 0) + 1);
}
let categories = cats
.map((c) => {
const id = normalizeIptvCategoryId(c.category_id);
const rawName = String(c.category_name ?? "");
return {
id,
name: friendlyCategoryName(rawName),
rawName,
channelCount: counts.get(id) ?? 0,
};
})
.filter((c) => c.id.length > 0 && !isAggregateCategoryName(c.rawName));
if (categories.length === 0 && counts.size > 0) {
categories = [...counts.entries()].map(([id, channelCount]) => ({
id,
name: `Groep ${id}`,
rawName: `Groep ${id}`,
channelCount,
}));
const customLists = await prisma.liveChannelList.findMany({
where: { enabled: true },
orderBy: [{ sortOrder: "asc" }, { name: "asc" }],
include: { _count: { select: { channels: { where: { enabled: true } } } } },
});
const { customCategoryId } = await import("./live-channels");
for (const list of customLists) {
categories.push({
id: customCategoryId(list.id),
name: list.name,
rawName: list.name,
channelCount: list._count.channels,
});
}
categories = applyIptvCategoryOrder(categories, iptvPrefs.categoryOrder);