Kolejne zmiany,
This commit is contained in:
210
src/pages/api/jambox/import-jambox-channels.js
Normal file
210
src/pages/api/jambox/import-jambox-channels.js
Normal file
@@ -0,0 +1,210 @@
|
||||
import path from "node:path";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
import Database from "better-sqlite3";
|
||||
|
||||
const FEEDS = [
|
||||
{ url: "https://www.jambox.pl/xml/listakanalow-smart.xml", name: "Smart" },
|
||||
{ url: "https://www.jambox.pl/xml/listakanalow-optimum.xml", name: "Optimum" },
|
||||
{ url: "https://www.jambox.pl/xml/listakanalow-platinum.xml", name: "Platinum" },
|
||||
{ url: "https://www.jambox.pl/xml/listakanalow-pluspodstawowy.xml", name: "Podstawowy" },
|
||||
{ url: "https://www.jambox.pl/xml/listakanalow-pluskorzystny.xml", name: "Korzystny" },
|
||||
{ url: "https://www.jambox.pl/xml/listakanalow-plusbogaty.xml", name: "Bogaty" },
|
||||
];
|
||||
|
||||
const DB_PATH =
|
||||
process.env.FUZ_DB_PATH ||
|
||||
path.join(process.cwd(), "src", "data", "ServicesRange.db");
|
||||
|
||||
function getDb() {
|
||||
const db = new Database(DB_PATH);
|
||||
db.pragma("journal_mode = WAL");
|
||||
return db;
|
||||
}
|
||||
|
||||
async function fetchXml(url) {
|
||||
const res = await fetch(url, {
|
||||
headers: { accept: "application/xml,text/xml,*/*" },
|
||||
});
|
||||
if (!res.ok) throw new Error(`XML HTTP ${res.status} for ${url}`);
|
||||
return await res.text();
|
||||
}
|
||||
|
||||
function parseNodes(xmlText) {
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "@_",
|
||||
});
|
||||
const json = parser.parse(xmlText);
|
||||
const nodes = json?.xml?.node ?? json?.node ?? [];
|
||||
return Array.isArray(nodes) ? nodes : [nodes];
|
||||
}
|
||||
|
||||
function decodeEntities(input) {
|
||||
if (!input) return "";
|
||||
let s = String(input)
|
||||
.replace(/ /g, " ")
|
||||
.replace(/–/g, "–")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
|
||||
s = s.replace(/&#(\d+);/g, (_, d) =>
|
||||
String.fromCodePoint(Number(d))
|
||||
);
|
||||
s = s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) =>
|
||||
String.fromCodePoint(parseInt(h, 16))
|
||||
);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
function htmlToMarkdown(input) {
|
||||
if (!input) return "";
|
||||
|
||||
let html = "";
|
||||
if (typeof input === "string") html = input;
|
||||
else if (input?.p) {
|
||||
if (typeof input.p === "string") html = `<p>${input.p}</p>`;
|
||||
else if (Array.isArray(input.p))
|
||||
html = input.p.map((p) => `<p>${p}</p>`).join("");
|
||||
} else html = String(input);
|
||||
|
||||
let s = decodeEntities(html);
|
||||
|
||||
s = s
|
||||
.replace(/<\s*(ul|ol)[^>]*>/gi, "\n__LIST_START__\n")
|
||||
.replace(/<\/\s*(ul|ol)\s*>/gi, "\n__LIST_END__\n")
|
||||
.replace(/<\s*li[^>]*>/gi, "__LI__")
|
||||
.replace(/<\/\s*li\s*>/gi, "\n")
|
||||
.replace(/<\s*br\s*\/?\s*>/gi, "\n")
|
||||
.replace(/<\/\s*(p|div)\s*>/gi, "\n")
|
||||
.replace(/<\s*(p|div)[^>]*>/gi, "")
|
||||
.replace(/<[^>]+>/g, "")
|
||||
.replace(/\r/g, "")
|
||||
.replace(/[ \t]+\n/g, "\n")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trim();
|
||||
|
||||
const lines = s.split("\n").map((x) => x.trim());
|
||||
const out = [];
|
||||
let inList = false;
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line) {
|
||||
if (!inList) out.push("");
|
||||
continue;
|
||||
}
|
||||
if (line === "__LIST_START__") {
|
||||
inList = true;
|
||||
continue;
|
||||
}
|
||||
if (line === "__LIST_END__") {
|
||||
inList = false;
|
||||
out.push("");
|
||||
continue;
|
||||
}
|
||||
if (inList && line.startsWith("__LI__")) {
|
||||
out.push(`- ${line.replace("__LI__", "").trim()}`);
|
||||
continue;
|
||||
}
|
||||
out.push(line);
|
||||
}
|
||||
|
||||
return out.join("\n").trim();
|
||||
}
|
||||
|
||||
function extractLogoUrl(node) {
|
||||
const logo = node?.field_logo_fid;
|
||||
if (!logo) return null;
|
||||
if (typeof logo === "string") {
|
||||
const m = logo.match(/src="([^"]+)"/);
|
||||
return m?.[1] ?? null;
|
||||
}
|
||||
if (logo?.img?.["@_src"]) return logo.img["@_src"];
|
||||
return null;
|
||||
}
|
||||
|
||||
async function downloadLogoAsBase64(url) {
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) return null;
|
||||
|
||||
const ct = res.headers.get("content-type") || "image/png";
|
||||
const buf = Buffer.from(await res.arrayBuffer());
|
||||
if (!buf.length) return null;
|
||||
|
||||
return `data:${ct};base64,${buf.toString("base64")}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
const db = getDb();
|
||||
|
||||
const upsert = db.prepare(`
|
||||
INSERT INTO jambox_channels (nazwa, pckg_name, image, opis)
|
||||
VALUES (@nazwa, @pckg_name, @image, @opis)
|
||||
ON CONFLICT(nazwa, pckg_name) DO UPDATE SET
|
||||
image = COALESCE(excluded.image, jambox_channels.image),
|
||||
opis = COALESCE(excluded.opis, jambox_channels.opis)
|
||||
`);
|
||||
|
||||
const logoCache = new Map();
|
||||
const rows = [];
|
||||
|
||||
try {
|
||||
for (const feed of FEEDS) {
|
||||
const xml = await fetchXml(feed.url);
|
||||
const nodes = parseNodes(xml);
|
||||
|
||||
for (const node of nodes) {
|
||||
const name = (node?.nazwa_kanalu ?? node?.nazwa ?? "").trim();
|
||||
if (!name) continue;
|
||||
|
||||
const opis = htmlToMarkdown(node?.opis) || null;
|
||||
|
||||
const key = name.toLowerCase();
|
||||
let img = logoCache.get(key);
|
||||
if (img === undefined) {
|
||||
const logoUrl = extractLogoUrl(node);
|
||||
img = logoUrl ? await downloadLogoAsBase64(logoUrl) : null;
|
||||
logoCache.set(key, img);
|
||||
}
|
||||
|
||||
rows.push({
|
||||
nazwa: name,
|
||||
pckg_name: feed.name,
|
||||
image: img,
|
||||
opis,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const trx = db.transaction((data) => {
|
||||
for (const r of data) upsert.run(r);
|
||||
});
|
||||
|
||||
trx(rows);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
ok: true,
|
||||
rows: rows.length,
|
||||
db: DB_PATH,
|
||||
}),
|
||||
{ headers: { "content-type": "application/json; charset=utf-8" } }
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("import jambox_channels:", e);
|
||||
return new Response(
|
||||
JSON.stringify({ ok: false, error: String(e.message || e) }),
|
||||
{ status: 500 }
|
||||
);
|
||||
} finally {
|
||||
try { db.close(); } catch {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user