Usuniecie skryptów i zabezpieczenie tokenem api do aktualizacji danych z jambox
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -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(`
|
||||
@@ -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
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
// scripts/importJamboxChannelLists.js
|
||||
import Database from "better-sqlite3";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
|
||||
const DB_PATH =
|
||||
process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
|
||||
|
||||
// źródła: URL + docelowy pakiet (source + slug)
|
||||
const FEEDS = [
|
||||
{
|
||||
url: "https://www.jambox.pl/xml/listakanalow-smart.xml",
|
||||
source: "EVIO",
|
||||
slug: "smart",
|
||||
},
|
||||
{
|
||||
url: "https://www.jambox.pl/xml/listakanalow-optimum.xml",
|
||||
source: "EVIO",
|
||||
slug: "optimum",
|
||||
},
|
||||
{
|
||||
url: "https://www.jambox.pl/xml/listakanalow-platinum.xml",
|
||||
source: "EVIO",
|
||||
slug: "platinum",
|
||||
},
|
||||
{
|
||||
url: "https://www.jambox.pl/xml/listakanalow-pluspodstawowy.xml",
|
||||
source: "PLUS",
|
||||
slug: "podstawowy",
|
||||
},
|
||||
{
|
||||
url: "https://www.jambox.pl/xml/listakanalow-pluskorzystny.xml",
|
||||
source: "PLUS",
|
||||
slug: "korzystny",
|
||||
},
|
||||
{
|
||||
url: "https://www.jambox.pl/xml/listakanalow-plusbogaty.xml",
|
||||
source: "PLUS",
|
||||
slug: "bogaty",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Helper: pobranie XML-a
|
||||
*/
|
||||
async function fetchXml(url) {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Błąd pobierania XML z ${url}: ${res.status} ${res.statusText}`
|
||||
);
|
||||
}
|
||||
return await res.text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parser XML -> tablica node'ów z <xml><node>...</node></xml>
|
||||
*/
|
||||
function parseNodes(xmlText) {
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "@_",
|
||||
});
|
||||
|
||||
const json = parser.parse(xmlText);
|
||||
let nodes = json.xml?.node ?? json.node ?? [];
|
||||
|
||||
if (!nodes) return [];
|
||||
if (!Array.isArray(nodes)) nodes = [nodes];
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prosta dekodacja encji HTML (– etc.)
|
||||
* – nie bawimy się w pełen parser HTML, tylko najczęstsze kody
|
||||
*/
|
||||
function decodeHtmlEntities(str) {
|
||||
if (!str) return "";
|
||||
return String(str)
|
||||
.replace(/–/g, "–")
|
||||
.replace(/ /g, " ")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
/**
|
||||
* Z node'a zrobimy obiekt kanału
|
||||
*/
|
||||
function mapChannelNode(node) {
|
||||
const number = Number(node.nr);
|
||||
const name = node.nazwa_kanalu ?? node.nazwa ?? null;
|
||||
|
||||
if (!number || !name) {
|
||||
console.warn("⚠ Pomijam node bez numeru / nazwy:", node);
|
||||
return null;
|
||||
}
|
||||
|
||||
// opis – chcemy HTML, ale z poprawionymi encjami
|
||||
let descriptionHtml = "";
|
||||
|
||||
if (typeof node.opis === "string") {
|
||||
descriptionHtml = node.opis;
|
||||
} else if (node.opis && node.opis.p) {
|
||||
// <opis><p>...</p></opis>
|
||||
if (typeof node.opis.p === "string") {
|
||||
descriptionHtml = `<p>${node.opis.p}</p>`;
|
||||
} else if (Array.isArray(node.opis.p)) {
|
||||
// kilka <p>...</p>
|
||||
descriptionHtml = node.opis.p
|
||||
.map((p) =>
|
||||
typeof p === "string" ? `<p>${p}</p>` : ""
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
}
|
||||
|
||||
descriptionHtml = decodeHtmlEntities(descriptionHtml.trim());
|
||||
|
||||
// logo – interesuje nas przede wszystkim src
|
||||
let logoUrl = null;
|
||||
const logoNode = node.field_logo_fid;
|
||||
|
||||
if (typeof logoNode === "string") {
|
||||
// próbujemy wyciągnąć src="..."
|
||||
const m = logoNode.match(/src="([^"]+)"/);
|
||||
if (m) logoUrl = m[1];
|
||||
} else if (logoNode && logoNode.img) {
|
||||
// fast-xml-parser: <field_logo_fid><img ... /></field_logo_fid>
|
||||
const img = logoNode.img;
|
||||
if (img["@_src"]) {
|
||||
logoUrl = img["@_src"];
|
||||
}
|
||||
}
|
||||
|
||||
// gwarantowany
|
||||
const guaranteed = String(node.gwarantowany || "")
|
||||
.toLowerCase()
|
||||
.includes("gwarantowany")
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
return {
|
||||
number,
|
||||
name: String(name).trim(),
|
||||
description: descriptionHtml || null,
|
||||
logo_url: logoUrl || null,
|
||||
guaranteed,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Upewniamy się, że tabela docelowa istnieje
|
||||
*/
|
||||
function ensureSchema(db) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS jambox_package_channels (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
package_id INTEGER NOT NULL,
|
||||
number INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
logo_url TEXT,
|
||||
guaranteed INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TEXT NOT NULL,
|
||||
UNIQUE (package_id, number),
|
||||
FOREIGN KEY (package_id)
|
||||
REFERENCES jambox_base_packages (id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Znajdowanie id pakietu po (source, slug)
|
||||
*/
|
||||
function getPackageId(db, source, slug) {
|
||||
const row = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT id
|
||||
FROM jambox_base_packages
|
||||
WHERE source = ?
|
||||
AND slug = ?;
|
||||
`
|
||||
)
|
||||
.get(source, slug);
|
||||
|
||||
return row?.id ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert kanałów dla danego pakietu
|
||||
*/
|
||||
function upsertChannelsForPackage(db, packageId, channels) {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO jambox_package_channels
|
||||
(package_id, number, name, description, logo_url, guaranteed, updated_at)
|
||||
VALUES
|
||||
(@package_id, @number, @name, @description, @logo_url, @guaranteed, @updated_at)
|
||||
ON CONFLICT(package_id, number) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
description = excluded.description,
|
||||
logo_url = excluded.logo_url,
|
||||
guaranteed = excluded.guaranteed,
|
||||
updated_at = excluded.updated_at;
|
||||
`);
|
||||
|
||||
const tx = db.transaction((rows) => {
|
||||
let count = 0;
|
||||
for (const ch of rows) {
|
||||
stmt.run({
|
||||
package_id: packageId,
|
||||
number: ch.number,
|
||||
name: ch.name,
|
||||
description: ch.description,
|
||||
logo_url: ch.logo_url,
|
||||
guaranteed: ch.guaranteed,
|
||||
updated_at: now,
|
||||
});
|
||||
count++;
|
||||
}
|
||||
console.log(` → zapisano/zmieniono ${count} kanałów dla package_id=${packageId}`);
|
||||
});
|
||||
|
||||
tx(channels);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`Używam bazy: ${DB_PATH}`);
|
||||
const db = new Database(DB_PATH);
|
||||
|
||||
ensureSchema(db);
|
||||
|
||||
for (const feed of FEEDS) {
|
||||
console.log(
|
||||
`\n=== Przetwarzam ${feed.url} (source=${feed.source}, slug=${feed.slug}) ===`
|
||||
);
|
||||
|
||||
const packageId = getPackageId(db, feed.source, feed.slug);
|
||||
if (!packageId) {
|
||||
console.warn(
|
||||
`⚠ Brak pakietu w jambox_base_packages dla source=${feed.source}, slug="${feed.slug}". Pomijam ten feed.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const xml = await fetchXml(feed.url);
|
||||
const nodes = parseNodes(xml);
|
||||
|
||||
console.log(`📦 Znaleziono ${nodes.length} node'ów (kanałów) w XML-u.`);
|
||||
|
||||
const channels = [];
|
||||
for (const node of nodes) {
|
||||
const mapped = mapChannelNode(node);
|
||||
if (!mapped) continue;
|
||||
channels.push(mapped);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`🧮 Po zmapowaniu pozostaje ${channels.length} poprawnych kanałów.`
|
||||
);
|
||||
|
||||
upsertChannelsForPackage(db, packageId, channels);
|
||||
} catch (err) {
|
||||
console.error(`❌ Błąd przy przetwarzaniu ${feed.url}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
console.log("\n🎉 Import list kanałów JAMBOX zakończony.");
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("❌ Krytyczny błąd:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,127 +0,0 @@
|
||||
import Database from "better-sqlite3";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
|
||||
const DB_PATH = process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
|
||||
|
||||
const SOURCES = [
|
||||
{
|
||||
kind: "premium",
|
||||
url: "https://www.jambox.pl/xml/slownik-pakietypremium.xml",
|
||||
},
|
||||
{
|
||||
kind: "tematyczne_plus",
|
||||
url: "https://www.jambox.pl/xml/slownik-pakietytematyczneplus.xml",
|
||||
},
|
||||
];
|
||||
|
||||
async function fetchXml(url) {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Błąd pobierania XML z ${url}: ${res.status} ${res.statusText}`
|
||||
);
|
||||
}
|
||||
return await res.text();
|
||||
}
|
||||
|
||||
function parseNodesFromXml(xmlText) {
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "@_",
|
||||
});
|
||||
|
||||
const json = parser.parse(xmlText);
|
||||
let nodes = json.xml?.node ?? json.node ?? [];
|
||||
|
||||
if (!nodes) return [];
|
||||
if (!Array.isArray(nodes)) nodes = [nodes];
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function ensureSchema(db) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS jambox_tv_addons (
|
||||
tid INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
function upsertTvAddons(db, kind, nodes) {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const upsert = db.prepare(`
|
||||
INSERT INTO jambox_tv_addons (tid, name, kind, is_active, updated_at)
|
||||
VALUES (@tid, @name, @kind, 1, @updated_at)
|
||||
ON CONFLICT(tid) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
kind = excluded.kind,
|
||||
is_active = excluded.is_active,
|
||||
updated_at = excluded.updated_at;
|
||||
`);
|
||||
|
||||
const tx = db.transaction((rows) => {
|
||||
let ok = 0;
|
||||
|
||||
for (const node of rows) {
|
||||
const name = node.name ?? node.nazwa ?? node["#text"] ?? "";
|
||||
const tidRaw = node.tid ?? node.id ?? null;
|
||||
|
||||
if (!tidRaw || !name) {
|
||||
console.warn(`⚠ Pomijam node bez tid/name (kind=${kind}):`, node);
|
||||
continue;
|
||||
}
|
||||
|
||||
const tid = Number(tidRaw);
|
||||
if (!Number.isFinite(tid) || tid <= 0) {
|
||||
console.warn(`⚠ Pomijam node z niepoprawnym tid (kind=${kind}):`, node);
|
||||
continue;
|
||||
}
|
||||
|
||||
upsert.run({
|
||||
tid,
|
||||
name: String(name).trim(),
|
||||
kind,
|
||||
updated_at: now,
|
||||
});
|
||||
|
||||
ok++;
|
||||
}
|
||||
|
||||
console.log(`✅ ${kind}: zapisano/zmieniono ${ok} rekordów.`);
|
||||
});
|
||||
|
||||
tx(nodes);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`Używam bazy: ${DB_PATH}`);
|
||||
const db = new Database(DB_PATH);
|
||||
|
||||
ensureSchema(db);
|
||||
|
||||
for (const { kind, url } of SOURCES) {
|
||||
console.log(`\n=== Przetwarzam ${kind} (${url}) ===`);
|
||||
|
||||
try {
|
||||
const xml = await fetchXml(url);
|
||||
const nodes = parseNodesFromXml(xml);
|
||||
|
||||
console.log(`📦 Znaleziono ${nodes.length} node'ów (kind=${kind})`);
|
||||
upsertTvAddons(db, kind, nodes);
|
||||
} catch (err) {
|
||||
console.error(`❌ Błąd przy imporcie ${kind}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
console.log("\n🎉 Import słowników pakietów dodatkowych zakończony.");
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("❌ Krytyczny błąd:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,134 +0,0 @@
|
||||
import Database from "better-sqlite3";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
|
||||
const DB_PATH = "./src/data/ServicesRange.db";
|
||||
|
||||
const SOURCES = [
|
||||
{
|
||||
source: "EVIO",
|
||||
url: "https://www.jambox.pl/xml/slownik-pakietypodstawoweevio.xml",
|
||||
},
|
||||
{
|
||||
source: "PLUS",
|
||||
url: "https://www.jambox.pl/xml/slownik-pakietypodstawoweplus.xml",
|
||||
},
|
||||
];
|
||||
|
||||
function slugify(str) {
|
||||
if (!str) return null;
|
||||
return str
|
||||
.toString()
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
async function fetchXml(url) {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Błąd pobierania XML z ${url}: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
return await res.text();
|
||||
}
|
||||
|
||||
function parseNodesFromXml(xmlText) {
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "@_",
|
||||
});
|
||||
|
||||
const json = parser.parse(xmlText);
|
||||
const nodes = json.xml?.node ?? json.node ?? [];
|
||||
|
||||
if (Array.isArray(nodes)) return nodes;
|
||||
if (!nodes) return [];
|
||||
return [nodes];
|
||||
}
|
||||
|
||||
function ensureSchema(db) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS jambox_base_packages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
source TEXT NOT NULL,
|
||||
tid INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT,
|
||||
sort_order INTEGER,
|
||||
updated_at TEXT NOT NULL,
|
||||
UNIQUE(source, tid)
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
function upsertBasePackages(db, source, nodes) {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const upsert = db.prepare(`
|
||||
INSERT INTO jambox_base_packages (source, tid, name, slug, sort_order, updated_at)
|
||||
VALUES (@source, @tid, @name, @slug, @sort_order, @updated_at)
|
||||
ON CONFLICT(source, tid) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
slug = excluded.slug,
|
||||
sort_order = excluded.sort_order,
|
||||
updated_at = excluded.updated_at;
|
||||
`);
|
||||
|
||||
const tx = db.transaction((rows) => {
|
||||
rows.forEach((node, index) => {
|
||||
const name = node.name ?? node["#text"] ?? "";
|
||||
const tidRaw = node.tid ?? node.id ?? null;
|
||||
|
||||
if (!tidRaw || !name) {
|
||||
console.warn(`⚠ Pomijam node bez tid/name (source=${source}):`, node);
|
||||
return;
|
||||
}
|
||||
|
||||
const tid = Number(tidRaw);
|
||||
|
||||
const record = {
|
||||
source,
|
||||
tid,
|
||||
name: String(name).trim(),
|
||||
slug: slugify(String(name)),
|
||||
sort_order: index + 1,
|
||||
updated_at: now,
|
||||
};
|
||||
|
||||
upsert.run(record);
|
||||
});
|
||||
});
|
||||
|
||||
tx(nodes);
|
||||
|
||||
console.log(`✅ ${source}: zapisano/zmieniono ${nodes.length} pakietów.`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`Używam bazy: ${DB_PATH}`);
|
||||
const db = new Database(DB_PATH);
|
||||
ensureSchema(db);
|
||||
|
||||
for (const { source, url } of SOURCES) {
|
||||
console.log(`\n=== Przetwarzam ${source} (${url}) ===`);
|
||||
try {
|
||||
const xml = await fetchXml(url);
|
||||
const nodes = parseNodesFromXml(xml);
|
||||
|
||||
console.log(`📦 Znaleziono ${nodes.length} node'ów dla ${source}`);
|
||||
upsertBasePackages(db, source, nodes);
|
||||
} catch (err) {
|
||||
console.error(`❌ Błąd przy źródle ${source}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
console.log("\n🎉 Import pakietów podstawowych zakończony.");
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("❌ Krytyczny błąd:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,239 +0,0 @@
|
||||
// scripts/updateJamboxChannels.js
|
||||
import Database from "better-sqlite3";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
|
||||
const DB_PATH =
|
||||
process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db";
|
||||
|
||||
const JAMBOX_NUMBERS_URL = "https://www.jambox.pl/xml/jamboxwliczbach.xml";
|
||||
|
||||
/**
|
||||
* Prosty helper do pobrania XML-a
|
||||
*/
|
||||
async function fetchXml(url) {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Błąd pobierania XML z ${url}: ${res.status} ${res.statusText}`
|
||||
);
|
||||
}
|
||||
return await res.text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsowanie XML -> pojedynczy node z licznikami
|
||||
*/
|
||||
function parseCountsNode(xmlText) {
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: "@_",
|
||||
});
|
||||
|
||||
const json = parser.parse(xmlText);
|
||||
|
||||
// dokładnie jak w podglądzie: <xml><node>...</node></xml>
|
||||
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);
|
||||
if (!Number.isFinite(n)) {
|
||||
console.warn(`⚠ Pole ${key} ma dziwną wartość:`, raw);
|
||||
return 0;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobierz ID cech "Liczba Kanałów" i "Liczba Kanałów HD"
|
||||
* żeby nie polegać na tym, że to zawsze 7 i 8.
|
||||
*/
|
||||
function getChannelFeatureIds(db) {
|
||||
const rows = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT id, label
|
||||
FROM internet_features
|
||||
WHERE label IN ('Liczba Kanałów', 'Liczba Kanałów HD');
|
||||
`
|
||||
)
|
||||
.all();
|
||||
|
||||
let channelsId = null;
|
||||
let channelsHdId = null;
|
||||
|
||||
for (const row of rows) {
|
||||
if (row.label === "Liczba Kanałów") channelsId = row.id;
|
||||
if (row.label === "Liczba Kanałów HD") channelsHdId = row.id;
|
||||
}
|
||||
|
||||
if (!channelsId || !channelsHdId) {
|
||||
throw new Error(
|
||||
`Brak wymaganych cech w internet_features (Liczba Kanałów / Liczba Kanałów HD).`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`ℹ Feature IDs: "Liczba Kanałów" = ${channelsId}, "Liczba Kanałów HD" = ${channelsHdId}`
|
||||
);
|
||||
|
||||
return { channelsId, channelsHdId };
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca mapę (source, slug) -> package_id z jambox_base_packages
|
||||
*/
|
||||
function getPackageIdLookup(db) {
|
||||
const rows = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT id, source, slug
|
||||
FROM jambox_base_packages;
|
||||
`
|
||||
)
|
||||
.all();
|
||||
|
||||
const map = new Map();
|
||||
|
||||
for (const row of rows) {
|
||||
if (!row.source || !row.slug) continue;
|
||||
const key = `${row.source}::${row.slug}`;
|
||||
map.set(key, row.id);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualizacja rekordów w jambox_package_feature_values
|
||||
* dla "Liczba Kanałów" i "Liczba Kanałów HD"
|
||||
*/
|
||||
function updateChannelFeatureValues(db, packages, featureIds, packageIdMap) {
|
||||
const { channelsId, channelsHdId } = featureIds;
|
||||
|
||||
const upsertStmt = db.prepare(`
|
||||
INSERT INTO jambox_package_feature_values (package_id, feature_id, value)
|
||||
VALUES (@package_id, @feature_id, @value)
|
||||
ON CONFLICT(package_id, feature_id) DO UPDATE SET
|
||||
value = excluded.value;
|
||||
`);
|
||||
|
||||
const tx = db.transaction((rows) => {
|
||||
let total = 0;
|
||||
|
||||
for (const pkg of rows) {
|
||||
const key = `${pkg.source}::${pkg.slug}`;
|
||||
const packageId = packageIdMap.get(key);
|
||||
|
||||
if (!packageId) {
|
||||
console.warn(
|
||||
`⚠ Brak pakietu w jambox_base_packages dla: source=${pkg.source}, slug="${pkg.slug}"`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Liczba Kanałów
|
||||
upsertStmt.run({
|
||||
package_id: packageId,
|
||||
feature_id: channelsId,
|
||||
value: String(pkg.canals),
|
||||
});
|
||||
|
||||
// Liczba Kanałów HD
|
||||
upsertStmt.run({
|
||||
package_id: packageId,
|
||||
feature_id: channelsHdId,
|
||||
value: String(pkg.canalshd),
|
||||
});
|
||||
|
||||
total += 2;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`✅ Zaktualizowano / wstawiono ${total} wartości cech w jambox_package_feature_values.`
|
||||
);
|
||||
});
|
||||
|
||||
tx(packages);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`Używam bazy: ${DB_PATH}`);
|
||||
const db = new Database(DB_PATH); // tu normalne R/W połączenie
|
||||
|
||||
console.log(`\n=== Pobieram JAMBOX "w liczbach" ===`);
|
||||
const xml = await fetchXml(JAMBOX_NUMBERS_URL);
|
||||
const node = parseCountsNode(xml);
|
||||
|
||||
// 🔗 Ręczne mapowanie z ich XML-a na nasze pakiety (source + slug)
|
||||
const packages = [
|
||||
// EVIO
|
||||
{
|
||||
source: "EVIO",
|
||||
slug: "smart",
|
||||
canals: getInt(node, "ilosc_kanalow_eviosmart"),
|
||||
canalshd: getInt(node, "ilosc_kanalow_hd_eviosmart"),
|
||||
},
|
||||
{
|
||||
source: "EVIO",
|
||||
slug: "optimum",
|
||||
canals: getInt(node, "ilosc_kanalow_eviooptimum"),
|
||||
canalshd: getInt(node, "ilosc_kanalow_hd_eviooptimum"),
|
||||
},
|
||||
{
|
||||
source: "EVIO",
|
||||
slug: "platinum",
|
||||
canals: getInt(node, "ilosc_kanalow_evioplatinum"),
|
||||
canalshd: getInt(node, "ilosc_kanalow_hd_evioplatinum"),
|
||||
},
|
||||
|
||||
// PLUS
|
||||
{
|
||||
source: "PLUS",
|
||||
slug: "podstawowy",
|
||||
canals: getInt(node, "ilosc_kanalow_podstawowy"),
|
||||
canalshd: getInt(node, "ilosc_kanalow_hd_podstawowy"),
|
||||
},
|
||||
{
|
||||
source: "PLUS",
|
||||
slug: "korzystny",
|
||||
canals: getInt(node, "ilosc_kanalow_korzystny"),
|
||||
canalshd: getInt(node, "ilosc_kanalow_hd_korzystny"),
|
||||
},
|
||||
{
|
||||
source: "PLUS",
|
||||
slug: "bogaty",
|
||||
canals: getInt(node, "ilosc_kanalow_bogaty"),
|
||||
canalshd: getInt(node, "ilosc_kanalow_hd_bogaty"),
|
||||
},
|
||||
];
|
||||
|
||||
console.log("🧮 Pakiety do aktualizacji:");
|
||||
packages.forEach((p) => {
|
||||
console.log(
|
||||
` - ${p.source} / ${p.slug}: ${p.canals} kanałów, ${p.canalshd} HD`
|
||||
);
|
||||
});
|
||||
|
||||
// 🔹 Pobierz ID cech i mapę pakietów
|
||||
const featureIds = getChannelFeatureIds(db);
|
||||
const packageIdMap = getPackageIdLookup(db);
|
||||
|
||||
// 🔹 Aktualizacja tabeli jambox_package_feature_values
|
||||
updateChannelFeatureValues(db, packages, featureIds, packageIdMap);
|
||||
|
||||
db.close();
|
||||
console.log("\n🎉 Aktualizacja liczby kanałów zakończona.");
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("❌ Krytyczny błąd:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user