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;
|
: 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:{…}`).
|
* Electron/Playwright geeft cookies decoded (`s:j:{…}`).
|
||||||
* In de Cookie-header moeten die percent-encoded — anders parst Viaplay
|
* In de Cookie-header moeten die percent-encoded — anders parst Viaplay
|
||||||
|
|
@ -541,6 +594,40 @@ export async function fetchSportHighlights(
|
||||||
return extractProducts(res.json);
|
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). */
|
/** Catalog-item van de Viaplay sport-sectie (plank/rail). */
|
||||||
export type ViaplaySportRailItem = {
|
export type ViaplaySportRailItem = {
|
||||||
guid: string;
|
guid: string;
|
||||||
|
|
@ -671,9 +758,9 @@ function productToRailItem(node: Record<string, unknown>): ViaplaySportRailItem
|
||||||
|
|
||||||
const title = productTitle(content, guid);
|
const title = productTitle(content, guid);
|
||||||
const subtitle =
|
const subtitle =
|
||||||
String(
|
contentSynopsis(content) ||
|
||||||
content?.originalTitle ?? content?.synopsis ?? content?.description ?? ""
|
viaplayPlainText(content?.originalTitle) ||
|
||||||
).trim() || null;
|
viaplayPlainText(content?.subtitle);
|
||||||
|
|
||||||
rememberStreamHref(guid, href);
|
rememberStreamHref(guid, href);
|
||||||
|
|
||||||
|
|
@ -819,8 +906,11 @@ function extractProducts(json: unknown): ViaplayEpgProduct[] {
|
||||||
const sport = sportFromPageUrl(page);
|
const sport = sportFromPageUrl(page);
|
||||||
out.push({
|
out.push({
|
||||||
guid,
|
guid,
|
||||||
title: String(content?.title ?? system?.guid ?? guid).trim(),
|
title: viaplayPlainText(content?.title) || String(system?.guid ?? guid).trim(),
|
||||||
subtitle: String(content?.subtitle ?? content?.description ?? "").trim() || null,
|
subtitle:
|
||||||
|
contentSynopsis(content) ||
|
||||||
|
viaplayPlainText(content?.subtitle) ||
|
||||||
|
viaplayPlainText(content?.originalTitle),
|
||||||
startMs,
|
startMs,
|
||||||
endMs,
|
endMs,
|
||||||
imageUrl: pickImage(content),
|
imageUrl: pickImage(content),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import type { ScheduleEventPublic } from "../viewer/schedule-events";
|
import type { ScheduleEventPublic } from "../viewer/schedule-events";
|
||||||
import {
|
import {
|
||||||
|
fetchProductSynopsis,
|
||||||
fetchSportRails,
|
fetchSportRails,
|
||||||
|
viaplayPlainText,
|
||||||
type ViaplaySportRail,
|
type ViaplaySportRail,
|
||||||
type ViaplaySportRailItem,
|
type ViaplaySportRailItem,
|
||||||
} from "./client";
|
} from "./client";
|
||||||
import {
|
import {
|
||||||
loadViaplayConfigOnly,
|
loadViaplayConfigOnly,
|
||||||
|
patchViaplayEventCache,
|
||||||
} from "./settings";
|
} from "./settings";
|
||||||
import type { ViaplayEventSnapshot } from "./tweaks";
|
import type { ViaplayEventSnapshot } from "./tweaks";
|
||||||
import { toMediaGuid } from "./accounts";
|
import { toMediaGuid } from "./accounts";
|
||||||
|
|
@ -30,10 +33,12 @@ export function eventToPublic(
|
||||||
nowMs: number
|
nowMs: number
|
||||||
): ScheduleEventPublic {
|
): ScheduleEventPublic {
|
||||||
const phase = phaseFor(event.startMs, event.endMs, nowMs);
|
const phase = phaseFor(event.startMs, event.endMs, nowMs);
|
||||||
|
const description = viaplayPlainText(event.subtitle);
|
||||||
return {
|
return {
|
||||||
id: toEventId(event.mediaGuid),
|
id: toEventId(event.mediaGuid),
|
||||||
name: event.title,
|
name: event.title,
|
||||||
description: event.subtitle,
|
description:
|
||||||
|
description && description !== event.title ? description : null,
|
||||||
provider: "viaplay",
|
provider: "viaplay",
|
||||||
category: event.category || "sport",
|
category: event.category || "sport",
|
||||||
eventType: "live",
|
eventType: "live",
|
||||||
|
|
@ -92,8 +97,20 @@ export async function getViaplayScheduleEvent(
|
||||||
e.mediaGuid === `${mediaGuid}-NL` ||
|
e.mediaGuid === `${mediaGuid}-NL` ||
|
||||||
e.mediaGuid.replace(/-NL$/i, "") === mediaGuid.replace(/-NL$/i, "")
|
e.mediaGuid.replace(/-NL$/i, "") === mediaGuid.replace(/-NL$/i, "")
|
||||||
);
|
);
|
||||||
if (!event) return null;
|
if (!event || !config) return null;
|
||||||
return eventToPublic(event, nowMs);
|
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 = {
|
export type ViaplaySportChip = {
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,10 @@ function parseEvents(raw: unknown): ViaplayEventSnapshot[] {
|
||||||
guid,
|
guid,
|
||||||
mediaGuid,
|
mediaGuid,
|
||||||
title,
|
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,
|
category: typeof row.category === "string" ? row.category : null,
|
||||||
sportLabel: typeof row.sportLabel === "string" ? row.sportLabel : null,
|
sportLabel: typeof row.sportLabel === "string" ? row.sportLabel : null,
|
||||||
sportBucket: typeof row.sportBucket === "string" ? row.sportBucket : null,
|
sportBucket: typeof row.sportBucket === "string" ? row.sportBucket : null,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue