Dodana strona możliwości dekodera
This commit is contained in:
117
src/lib/mozliwosci.ts
Normal file
117
src/lib/mozliwosci.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
|
||||
const URL = "https://www.jambox.pl/xml/mozliwosci.xml";
|
||||
|
||||
// mały cache w pamięci procesu (SSR)
|
||||
let cache: { ts: number; items: Feature[] } | null = null;
|
||||
|
||||
export type Feature = {
|
||||
id: string;
|
||||
title: string;
|
||||
icon?: string;
|
||||
teaser?: string;
|
||||
description?: string;
|
||||
screens: string[];
|
||||
};
|
||||
|
||||
function toArray<T>(v: T | T[] | undefined | null): T[] {
|
||||
if (!v) return [];
|
||||
return Array.isArray(v) ? v : [v];
|
||||
}
|
||||
|
||||
// minimalne “od-HTML-owanie” dla itd.
|
||||
function cleanHtmlText(s?: string): string {
|
||||
if (!s) return "";
|
||||
return String(s)
|
||||
.replace(/ /g, " ")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function slugify(s: string) {
|
||||
return cleanHtmlText(s)
|
||||
.toLowerCase()
|
||||
.replace(/[\u0105]/g, "a")
|
||||
.replace(/[\u0107]/g, "c")
|
||||
.replace(/[\u0119]/g, "e")
|
||||
.replace(/[\u0142]/g, "l")
|
||||
.replace(/[\u0144]/g, "n")
|
||||
.replace(/[\u00f3]/g, "o")
|
||||
.replace(/[\u015b]/g, "s")
|
||||
.replace(/[\u017a\u017c]/g, "z")
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/(^-|-$)/g, "");
|
||||
}
|
||||
|
||||
function extractUrlsFromString(s: string): string[] {
|
||||
// wyciąga URL-e zarówno z czystego tekstu, jak i z HTML (<div>url</div>)
|
||||
const urls = s.match(/https?:\/\/[^\s<"]+/g) ?? [];
|
||||
return urls.map((u) => u.trim());
|
||||
}
|
||||
|
||||
function extractScreens(screen: any): string[] {
|
||||
if (!screen) return [];
|
||||
|
||||
// przypadek: <screen>https://...png</screen>
|
||||
if (typeof screen === "string") return extractUrlsFromString(screen);
|
||||
|
||||
// przypadek: <screen><div class="field-item">URL</div>...</screen>
|
||||
// fast-xml-parser zrobi np. screen.div albo screen["div"]
|
||||
const divs = (screen as any)?.div;
|
||||
if (divs) {
|
||||
return toArray(divs)
|
||||
.map((d) => {
|
||||
if (typeof d === "string") return d;
|
||||
// czasem parser daje obiekt z #text
|
||||
return d?.["#text"] ?? "";
|
||||
})
|
||||
.flatMap((x) => extractUrlsFromString(String(x)))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
// fallback: spróbuj stringifikacji i regex na URL
|
||||
return extractUrlsFromString(JSON.stringify(screen));
|
||||
}
|
||||
|
||||
export async function fetchMozliwosci(ttlMs = 60_000): Promise<Feature[]> {
|
||||
const now = Date.now();
|
||||
if (cache && now - cache.ts < ttlMs) return cache.items;
|
||||
|
||||
const res = await fetch(URL, {
|
||||
headers: { accept: "application/xml,text/xml,*/*" },
|
||||
});
|
||||
if (!res.ok) throw new Error(`JAMBOX XML: HTTP ${res.status}`);
|
||||
|
||||
const xml = await res.text();
|
||||
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "@_",
|
||||
trimValues: true,
|
||||
});
|
||||
|
||||
const parsed = parser.parse(xml);
|
||||
|
||||
const nodes = toArray(parsed?.xml?.node ?? parsed?.node);
|
||||
|
||||
const items: Feature[] = nodes
|
||||
.map((n: any) => {
|
||||
const title = cleanHtmlText(n?.title ?? "");
|
||||
const teaser = cleanHtmlText(n?.teaser ?? "");
|
||||
const description = cleanHtmlText(n?.description ?? "");
|
||||
|
||||
const icon = typeof n?.icon === "string" ? n.icon.trim() : undefined;
|
||||
const screens = extractScreens(n?.screen);
|
||||
|
||||
const id = slugify(title || "feature");
|
||||
|
||||
return { id, title, icon, teaser, description, screens };
|
||||
})
|
||||
.filter((x) => x.title);
|
||||
|
||||
cache = { ts: now, items };
|
||||
return items;
|
||||
}
|
||||
Reference in New Issue
Block a user