Show Viaplay event synopsis instead of [object Object] on the detail page.
This commit is contained in:
parent
84657acbc9
commit
97e76c3551
3 changed files with 119 additions and 9 deletions
|
|
@ -25,6 +25,59 @@ function asRecord(v: unknown): Record<string, unknown> | null {
|
|||
: null;
|
||||
}
|
||||
|
||||
/** Viaplay zet synopsis/description vaak als `{ value, formatted }`, niet als string. */
|
||||
export function viaplayPlainText(raw: unknown, depth = 0): string | null {
|
||||
if (raw == null) return null;
|
||||
if (typeof raw === "number" && Number.isFinite(raw)) return String(raw);
|
||||
if (typeof raw === "string") {
|
||||
const t = raw
|
||||
.replace(/<[^>]+>/g, " ")
|
||||
.replace(/ /gi, " ")
|
||||
.replace(/&/gi, "&")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
if (!t || /^\[object \w+]$/i.test(t)) return null;
|
||||
return t;
|
||||
}
|
||||
if (depth > 4) return null;
|
||||
if (Array.isArray(raw)) {
|
||||
for (const item of raw) {
|
||||
const t = viaplayPlainText(item, depth + 1);
|
||||
if (t) return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const rec = asRecord(raw);
|
||||
if (!rec) return null;
|
||||
for (const key of [
|
||||
"value",
|
||||
"formatted",
|
||||
"text",
|
||||
"synopsis",
|
||||
"long",
|
||||
"short",
|
||||
"description",
|
||||
"nl",
|
||||
"en",
|
||||
]) {
|
||||
const t = viaplayPlainText(rec[key], depth + 1);
|
||||
if (t) return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function contentSynopsis(content: Record<string, unknown> | null): string | null {
|
||||
if (!content) return null;
|
||||
return (
|
||||
viaplayPlainText(content.synopsis) ||
|
||||
viaplayPlainText(content.description) ||
|
||||
viaplayPlainText(content.shortDescription) ||
|
||||
viaplayPlainText(content.longDescription) ||
|
||||
viaplayPlainText(content.editorial) ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Electron/Playwright geeft cookies decoded (`s:j:{…}`).
|
||||
* In de Cookie-header moeten die percent-encoded — anders parst Viaplay
|
||||
|
|
@ -541,6 +594,40 @@ export async function fetchSportHighlights(
|
|||
return extractProducts(res.json);
|
||||
}
|
||||
|
||||
/** Productpagina → echte synopsis (niet `String(object)`). */
|
||||
export async function fetchProductSynopsis(
|
||||
pageUrl: string,
|
||||
timeoutMs: number
|
||||
): Promise<string | null> {
|
||||
const raw = String(pageUrl || "").trim();
|
||||
if (!raw) return null;
|
||||
let url = raw;
|
||||
try {
|
||||
const u = new URL(raw);
|
||||
u.searchParams.set("partial", "true");
|
||||
u.searchParams.set("language", u.searchParams.get("language") || "nl");
|
||||
url = u.toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const res = await fetchJson(url, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"User-Agent": VIAPLAY_USER_AGENT,
|
||||
Referer: "https://viaplay.com/",
|
||||
},
|
||||
timeoutMs,
|
||||
});
|
||||
if (res.status !== 200) return null;
|
||||
const root = asRecord(res.json);
|
||||
const embedded = asRecord(root?._embedded);
|
||||
const product =
|
||||
asRecord(embedded?.["viaplay:product"]) ||
|
||||
asRecord(root?.product) ||
|
||||
root;
|
||||
return contentSynopsis(asRecord(product?.content));
|
||||
}
|
||||
|
||||
/** Catalog-item van de Viaplay sport-sectie (plank/rail). */
|
||||
export type ViaplaySportRailItem = {
|
||||
guid: string;
|
||||
|
|
@ -671,9 +758,9 @@ function productToRailItem(node: Record<string, unknown>): ViaplaySportRailItem
|
|||
|
||||
const title = productTitle(content, guid);
|
||||
const subtitle =
|
||||
String(
|
||||
content?.originalTitle ?? content?.synopsis ?? content?.description ?? ""
|
||||
).trim() || null;
|
||||
contentSynopsis(content) ||
|
||||
viaplayPlainText(content?.originalTitle) ||
|
||||
viaplayPlainText(content?.subtitle);
|
||||
|
||||
rememberStreamHref(guid, href);
|
||||
|
||||
|
|
@ -819,8 +906,11 @@ function extractProducts(json: unknown): ViaplayEpgProduct[] {
|
|||
const sport = sportFromPageUrl(page);
|
||||
out.push({
|
||||
guid,
|
||||
title: String(content?.title ?? system?.guid ?? guid).trim(),
|
||||
subtitle: String(content?.subtitle ?? content?.description ?? "").trim() || null,
|
||||
title: viaplayPlainText(content?.title) || String(system?.guid ?? guid).trim(),
|
||||
subtitle:
|
||||
contentSynopsis(content) ||
|
||||
viaplayPlainText(content?.subtitle) ||
|
||||
viaplayPlainText(content?.originalTitle),
|
||||
startMs,
|
||||
endMs,
|
||||
imageUrl: pickImage(content),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import type { ScheduleEventPublic } from "../viewer/schedule-events";
|
||||
import {
|
||||
fetchProductSynopsis,
|
||||
fetchSportRails,
|
||||
viaplayPlainText,
|
||||
type ViaplaySportRail,
|
||||
type ViaplaySportRailItem,
|
||||
} from "./client";
|
||||
import {
|
||||
loadViaplayConfigOnly,
|
||||
patchViaplayEventCache,
|
||||
} from "./settings";
|
||||
import type { ViaplayEventSnapshot } from "./tweaks";
|
||||
import { toMediaGuid } from "./accounts";
|
||||
|
|
@ -30,10 +33,12 @@ export function eventToPublic(
|
|||
nowMs: number
|
||||
): ScheduleEventPublic {
|
||||
const phase = phaseFor(event.startMs, event.endMs, nowMs);
|
||||
const description = viaplayPlainText(event.subtitle);
|
||||
return {
|
||||
id: toEventId(event.mediaGuid),
|
||||
name: event.title,
|
||||
description: event.subtitle,
|
||||
description:
|
||||
description && description !== event.title ? description : null,
|
||||
provider: "viaplay",
|
||||
category: event.category || "sport",
|
||||
eventType: "live",
|
||||
|
|
@ -92,8 +97,20 @@ export async function getViaplayScheduleEvent(
|
|||
e.mediaGuid === `${mediaGuid}-NL` ||
|
||||
e.mediaGuid.replace(/-NL$/i, "") === mediaGuid.replace(/-NL$/i, "")
|
||||
);
|
||||
if (!event) return null;
|
||||
return eventToPublic(event, nowMs);
|
||||
if (!event || !config) return null;
|
||||
const pub = eventToPublic(event, nowMs);
|
||||
if (pub.description || !event.pageUrl) return pub;
|
||||
try {
|
||||
const text = await fetchProductSynopsis(
|
||||
event.pageUrl,
|
||||
config.httpTimeoutMs || 15_000
|
||||
);
|
||||
if (!text || text === event.title) return pub;
|
||||
void patchViaplayEventCache(event.mediaGuid, { subtitle: text });
|
||||
return { ...pub, description: text };
|
||||
} catch {
|
||||
return pub;
|
||||
}
|
||||
}
|
||||
|
||||
export type ViaplaySportChip = {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,10 @@ function parseEvents(raw: unknown): ViaplayEventSnapshot[] {
|
|||
guid,
|
||||
mediaGuid,
|
||||
title,
|
||||
subtitle: typeof row.subtitle === "string" ? row.subtitle : null,
|
||||
subtitle:
|
||||
typeof row.subtitle === "string" && !/^\[object \w+]$/i.test(row.subtitle.trim())
|
||||
? row.subtitle
|
||||
: null,
|
||||
category: typeof row.category === "string" ? row.category : null,
|
||||
sportLabel: typeof row.sportLabel === "string" ? row.sportLabel : null,
|
||||
sportBucket: typeof row.sportBucket === "string" ? row.sportBucket : null,
|
||||
|
|
|
|||
Loading…
Reference in a new issue