Usuniecie skryptów i zabezpieczenie tokenem api do aktualizacji danych z jambox
This commit is contained in:
@@ -12,8 +12,14 @@ const FEEDS = [
|
||||
];
|
||||
|
||||
const DB_PATH =
|
||||
process.env.FUZ_DB_PATH ||
|
||||
path.join(process.cwd(), "src", "data", "ServicesRange.db");
|
||||
process.env.FUZ_DB_PATH || path.join(process.cwd(), "src", "data", "ServicesRange.db");
|
||||
|
||||
function isAuthorized(request) {
|
||||
const expected = import.meta.env.JAMBOX_ADMIN_TOKEN;
|
||||
if (!expected) return false;
|
||||
const token = request.headers.get("x-admin-token");
|
||||
return token === expected;
|
||||
}
|
||||
|
||||
function getDb() {
|
||||
const db = new Database(DB_PATH);
|
||||
@@ -22,9 +28,7 @@ function getDb() {
|
||||
}
|
||||
|
||||
async function fetchXml(url) {
|
||||
const res = await fetch(url, {
|
||||
headers: { accept: "application/xml,text/xml,*/*" },
|
||||
});
|
||||
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();
|
||||
}
|
||||
@@ -50,14 +54,8 @@ function decodeEntities(input) {
|
||||
.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))
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -68,8 +66,7 @@ function htmlToMarkdown(input) {
|
||||
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 if (Array.isArray(input.p)) html = input.p.map((p) => `<p>${p}</p>`).join("");
|
||||
} else html = String(input);
|
||||
|
||||
let s = decodeEntities(html);
|
||||
@@ -131,18 +128,23 @@ 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() {
|
||||
export async function POST({ request }) {
|
||||
if (!isAuthorized(request)) {
|
||||
return new Response(JSON.stringify({ ok: false, error: "Unauthorized" }), {
|
||||
status: 401,
|
||||
headers: { "content-type": "application/json; charset=utf-8" },
|
||||
});
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
|
||||
const upsert = db.prepare(`
|
||||
@@ -153,7 +155,7 @@ export async function POST() {
|
||||
opis = COALESCE(excluded.opis, jambox_channels.opis)
|
||||
`);
|
||||
|
||||
const logoCache = new Map();
|
||||
const logoCache = new Map();
|
||||
const rows = [];
|
||||
|
||||
try {
|
||||
@@ -190,20 +192,15 @@ export async function POST() {
|
||||
|
||||
trx(rows);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
ok: true,
|
||||
rows: rows.length,
|
||||
db: DB_PATH,
|
||||
}),
|
||||
{ headers: { "content-type": "application/json; charset=utf-8" } }
|
||||
);
|
||||
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 }
|
||||
);
|
||||
return new Response(JSON.stringify({ ok: false, error: String(e?.message || e) }), {
|
||||
status: 500,
|
||||
headers: { "content-type": "application/json; charset=utf-8" },
|
||||
});
|
||||
} finally {
|
||||
try { db.close(); } catch {}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@ import fs from "node:fs/promises";
|
||||
|
||||
const URL = "https://www.jambox.pl/xml/mozliwosci.xml";
|
||||
|
||||
function isAuthorized(request) {
|
||||
const expected = import.meta.env.JAMBOX_ADMIN_TOKEN;
|
||||
if (!expected) return false;
|
||||
const token = request.headers.get("x-admin-token");
|
||||
return token === expected;
|
||||
}
|
||||
|
||||
function toArray(v) {
|
||||
if (!v) return [];
|
||||
return Array.isArray(v) ? v : [v];
|
||||
@@ -22,7 +29,7 @@ function decodeEntities(input) {
|
||||
|
||||
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))
|
||||
String.fromCodePoint(parseInt(h, 16)),
|
||||
);
|
||||
|
||||
return s;
|
||||
@@ -171,13 +178,23 @@ function toYaml(sections) {
|
||||
return out.join("\n").trimEnd() + "\n";
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
export async function POST({ request }) {
|
||||
if (!isAuthorized(request)) {
|
||||
return new Response(JSON.stringify({ ok: false, error: "Unauthorized" }), {
|
||||
status: 401,
|
||||
headers: { "content-type": "application/json; charset=utf-8" },
|
||||
});
|
||||
}
|
||||
|
||||
const res = await fetch(URL, {
|
||||
headers: { accept: "application/xml,text/xml,*/*" },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return new Response(`JAMBOX XML: HTTP ${res.status}`, { status: 502 });
|
||||
return new Response(JSON.stringify({ ok: false, error: `JAMBOX XML: HTTP ${res.status}` }), {
|
||||
status: 502,
|
||||
headers: { "content-type": "application/json; charset=utf-8" },
|
||||
});
|
||||
}
|
||||
|
||||
const xml = await res.text();
|
||||
@@ -195,10 +212,7 @@ export async function POST() {
|
||||
|
||||
if (!title) return null;
|
||||
|
||||
const blocks = [
|
||||
...parseHtmlContent(n?.teaser),
|
||||
...parseHtmlContent(n?.description),
|
||||
];
|
||||
const blocks = [...parseHtmlContent(n?.teaser), ...parseHtmlContent(n?.description)];
|
||||
|
||||
const content = blocksToMarkdown(blocks);
|
||||
if (!content) return null;
|
||||
@@ -216,7 +230,7 @@ export async function POST() {
|
||||
await fs.mkdir(outDir, { recursive: true });
|
||||
await fs.writeFile(outFile, toYaml(sections), "utf8");
|
||||
|
||||
return new Response(JSON.stringify({ ok: true, count: sections.length }), {
|
||||
headers: { "content-type": "application/json" },
|
||||
return new Response(JSON.stringify({ ok: true, count: sections.length, file: outFile }), {
|
||||
headers: { "content-type": "application/json; charset=utf-8" },
|
||||
});
|
||||
}
|
||||
|
||||
205
src/pages/api/jambox/import-jambox-package-channels.js
Normal file
205
src/pages/api/jambox/import-jambox-package-channels.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import yaml from "js-yaml";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
|
||||
const JAMBOX_NUMBERS_URL = "https://www.jambox.pl/xml/jamboxwliczbach.xml";
|
||||
|
||||
/**
|
||||
* Proste auth, żeby nikt z internetu nie mógł Ci pisać po YAML.
|
||||
* .env: JAMBOX_ADMIN_TOKEN="..."
|
||||
*/
|
||||
function isAuthorized(request) {
|
||||
const expected = import.meta.env.JAMBOX_ADMIN_TOKEN;
|
||||
if (!expected) return false;
|
||||
const token = request.headers.get("x-admin-token");
|
||||
return token === expected;
|
||||
}
|
||||
|
||||
async function fetchXml(url) {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Błąd pobierania XML: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
return await res.text();
|
||||
}
|
||||
|
||||
function parseCountsNode(xmlText) {
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "@_",
|
||||
});
|
||||
|
||||
const json = parser.parse(xmlText);
|
||||
const node = json.xml?.node ?? json.node ?? null;
|
||||
|
||||
if (!node) {
|
||||
throw new Error("Nie znaleziono <node> w jamboxwliczbach.xml");
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function getInt(node, key) {
|
||||
const raw = node?.[key];
|
||||
const n = Number(raw);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapowanie kluczy XML na source+slug (tak jak w Twoim starym skrypcie)
|
||||
*/
|
||||
function xmlKeyFor(source, slug, isHd) {
|
||||
const s = String(source || "").toUpperCase();
|
||||
const sl = String(slug || "").toLowerCase();
|
||||
|
||||
// EVIO: ilosc_kanalow_eviooptimum / ilosc_kanalow_hd_eviooptimum
|
||||
if (s === "EVIO") {
|
||||
return isHd ? `ilosc_kanalow_hd_evio${sl}` : `ilosc_kanalow_evio${sl}`;
|
||||
}
|
||||
|
||||
// PLUS: ilosc_kanalow_korzystny / ilosc_kanalow_hd_korzystny
|
||||
if (s === "PLUS") {
|
||||
return isHd ? `ilosc_kanalow_hd_${sl}` : `ilosc_kanalow_${sl}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function findParamIndex(params, key) {
|
||||
return params.findIndex(
|
||||
(p) => String(p?.klucz ?? "").toLowerCase() === key.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
export async function POST({ request }) {
|
||||
try {
|
||||
if (!isAuthorized(request)) {
|
||||
return new Response(JSON.stringify({ ok: false, error: "Unauthorized" }), {
|
||||
status: 401,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const body = await request.json().catch(() => ({}));
|
||||
const dryRun = body?.dryRun === true;
|
||||
|
||||
|
||||
const YAML_PATH =
|
||||
import.meta.env.JAMBOX_TV_YAML_PATH ||
|
||||
path.join(process.cwd(), "src", "content", "internet-telewizja", "cards.yaml");
|
||||
|
||||
// 1) XML
|
||||
const xml = await fetchXml(JAMBOX_NUMBERS_URL);
|
||||
const node = parseCountsNode(xml);
|
||||
|
||||
// 2) YAML
|
||||
const rawYaml = await fs.readFile(YAML_PATH, "utf8");
|
||||
const doc = yaml.load(rawYaml);
|
||||
|
||||
if (!doc || !Array.isArray(doc.cards)) {
|
||||
return new Response(JSON.stringify({ ok: false, error: "YAML: brak doc.cards" }), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const changedItems = [];
|
||||
const unchangedItems = [];
|
||||
const skippedItems = [];
|
||||
|
||||
for (const card of doc.cards) {
|
||||
const id = card?.id ?? null;
|
||||
const source = String(card?.source ?? "");
|
||||
const slug = String(card?.slug ?? "");
|
||||
|
||||
if (!source || !slug) {
|
||||
skippedItems.push({ id, reason: "brak source/slug" });
|
||||
continue;
|
||||
}
|
||||
|
||||
const params = Array.isArray(card?.parametry) ? card.parametry : [];
|
||||
const iCanals = findParamIndex(params, "canals");
|
||||
const iHd = findParamIndex(params, "canalshd");
|
||||
|
||||
// nie dopisujemy na siłę, tylko aktualizujemy to co istnieje
|
||||
if (iCanals === -1 && iHd === -1) {
|
||||
skippedItems.push({ id, source, slug, reason: "brak canals/canalshd w parametry" });
|
||||
continue;
|
||||
}
|
||||
|
||||
const keyCanals = xmlKeyFor(source, slug, false);
|
||||
const keyHd = xmlKeyFor(source, slug, true);
|
||||
|
||||
const xmlCanals = keyCanals ? getInt(node, keyCanals) : null;
|
||||
const xmlHd = keyHd ? getInt(node, keyHd) : null;
|
||||
|
||||
if (xmlCanals == null && xmlHd == null) {
|
||||
skippedItems.push({
|
||||
id,
|
||||
source,
|
||||
slug,
|
||||
reason: "brak wartości w XML",
|
||||
keys: { keyCanals, keyHd },
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const before = {
|
||||
canals: iCanals !== -1 ? Number(params[iCanals]?.value) : null,
|
||||
canalshd: iHd !== -1 ? Number(params[iHd]?.value) : null,
|
||||
};
|
||||
|
||||
const after = {
|
||||
canals: xmlCanals ?? before.canals,
|
||||
canalshd: xmlHd ?? before.canalshd,
|
||||
};
|
||||
|
||||
const willChange =
|
||||
(after.canals != null && before.canals !== after.canals) ||
|
||||
(after.canalshd != null && before.canalshd !== after.canalshd);
|
||||
|
||||
if (!willChange) {
|
||||
unchangedItems.push({ id, source, slug, before });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
if (iCanals !== -1 && after.canals != null) params[iCanals].value = after.canals;
|
||||
if (iHd !== -1 && after.canalshd != null) params[iHd].value = after.canalshd;
|
||||
card.parametry = params;
|
||||
}
|
||||
|
||||
changedItems.push({ id, source, slug, before, after, keys: { keyCanals, keyHd } });
|
||||
}
|
||||
|
||||
if (!dryRun && changedItems.length > 0) {
|
||||
const newYaml = yaml.dump(doc, {
|
||||
lineWidth: -1,
|
||||
noRefs: true,
|
||||
sortKeys: false,
|
||||
});
|
||||
await fs.writeFile(YAML_PATH, newYaml, "utf8");
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
ok: true,
|
||||
dryRun,
|
||||
yamlPath: YAML_PATH,
|
||||
changed: changedItems.length,
|
||||
unchanged: unchangedItems.length,
|
||||
skipped: skippedItems.length,
|
||||
changedItems,
|
||||
skippedItems,
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("update-channels error:", err);
|
||||
return new Response(JSON.stringify({ ok: false, error: String(err?.message ?? err) }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ export async function GET() {
|
||||
return url;
|
||||
});
|
||||
|
||||
// Budowa XML
|
||||
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${urls
|
||||
|
||||
Reference in New Issue
Block a user