From 142d289be9a56562302c3a3e377937b890fc227a Mon Sep 17 00:00:00 2001 From: dm Date: Tue, 16 Dec 2025 08:44:16 +0100 Subject: [PATCH] =?UTF-8?q?Usuniecie=20skrypt=C3=B3w=20i=20zabezpieczenie?= =?UTF-8?q?=20tokenem=20api=20do=20aktualizacji=20danych=20z=20jambox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/data/ServicesRange.db | Bin 6742016 -> 6742016 bytes src/data/ServicesRange.db-shm | Bin 32768 -> 0 bytes src/data/ServicesRange.db-wal | 0 .../api/jambox/import-jambox-channels.js | 59 ++-- .../api/jambox/import-jambox-mozliwosci.js | 32 +- .../jambox/import-jambox-package-channels.js | 205 +++++++++++++ src/pages/sitemap.xml.js | 1 - src/scripts/importJamboxChannelLists.js | 280 ------------------ src/scripts/importJamboxTvAddons.js | 127 -------- src/scripts/update-jambox-base.js | 134 --------- src/scripts/updateJamboxChannels.js | 239 --------------- 11 files changed, 256 insertions(+), 821 deletions(-) delete mode 100644 src/data/ServicesRange.db-shm delete mode 100644 src/data/ServicesRange.db-wal create mode 100644 src/pages/api/jambox/import-jambox-package-channels.js delete mode 100644 src/scripts/importJamboxChannelLists.js delete mode 100644 src/scripts/importJamboxTvAddons.js delete mode 100644 src/scripts/update-jambox-base.js delete mode 100644 src/scripts/updateJamboxChannels.js diff --git a/src/data/ServicesRange.db b/src/data/ServicesRange.db index 098177588f184fcca57aff75df2669b695939b9a..da07d75a3a75846bd0f82d4a7a7611af588bd8e8 100644 GIT binary patch delta 327 zcmWO2wN*m_06@|62@>2j;iJLb-2x%FyFGM{P`n;$8kV4E6ILO`UEpSZzap8ek5~|X z#*;z#js%h;5y_Q2$(I6&N}&`kNT(#EOS+{;dZkbLWk3dHNQPxZMrBOKWkM!pN~UE-W@S$1WkD8YNtR_rR%K0+ zvMw8vl1)j=mTb$8?8=_(%YhuqksQm3oXVM;%Y|IZm0Zh>+{&HY%Y$Tp9_2}%DhQ2N4)|=>Px# delta 327 zcmWO2wN*m_06@|63GM_Z9|`X68WMuLI}e>B6t9Pxh9&6PgjGm!7r2?69+%mLBPqKIxYM8I&OzmJu11F&UQ$nUpD+mKm9qIhmIQS(GJNmK90Jsw5>P zYm%0A*^o`yl5N?MUD=a;Igmp+l4CiMQ#q4!xsXe_l54q"); - - 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 = `

${input.p}

`; - else if (Array.isArray(input.p)) - html = input.p.map((p) => `

${p}

`).join(""); + else if (Array.isArray(input.p)) html = input.p.map((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 {} } diff --git a/src/pages/api/jambox/import-jambox-mozliwosci.js b/src/pages/api/jambox/import-jambox-mozliwosci.js index c66cfe2..6d41162 100644 --- a/src/pages/api/jambox/import-jambox-mozliwosci.js +++ b/src/pages/api/jambox/import-jambox-mozliwosci.js @@ -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" }, }); } diff --git a/src/pages/api/jambox/import-jambox-package-channels.js b/src/pages/api/jambox/import-jambox-package-channels.js new file mode 100644 index 0000000..0b42c1e --- /dev/null +++ b/src/pages/api/jambox/import-jambox-package-channels.js @@ -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 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" }, + }); + } +} diff --git a/src/pages/sitemap.xml.js b/src/pages/sitemap.xml.js index 65c20c5..498e07e 100644 --- a/src/pages/sitemap.xml.js +++ b/src/pages/sitemap.xml.js @@ -27,7 +27,6 @@ export async function GET() { return url; }); - // Budowa XML const body = ` ${urls diff --git a/src/scripts/importJamboxChannelLists.js b/src/scripts/importJamboxChannelLists.js deleted file mode 100644 index d10d8f6..0000000 --- a/src/scripts/importJamboxChannelLists.js +++ /dev/null @@ -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 ... - */ -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) { - //

...

- if (typeof node.opis.p === "string") { - descriptionHtml = `

${node.opis.p}

`; - } else if (Array.isArray(node.opis.p)) { - // kilka

...

- descriptionHtml = node.opis.p - .map((p) => - typeof p === "string" ? `

${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: - 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); -}); diff --git a/src/scripts/importJamboxTvAddons.js b/src/scripts/importJamboxTvAddons.js deleted file mode 100644 index 2b2ff3c..0000000 --- a/src/scripts/importJamboxTvAddons.js +++ /dev/null @@ -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); -}); diff --git a/src/scripts/update-jambox-base.js b/src/scripts/update-jambox-base.js deleted file mode 100644 index b77b205..0000000 --- a/src/scripts/update-jambox-base.js +++ /dev/null @@ -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); -}); diff --git a/src/scripts/updateJamboxChannels.js b/src/scripts/updateJamboxChannels.js deleted file mode 100644 index 5f27442..0000000 --- a/src/scripts/updateJamboxChannels.js +++ /dev/null @@ -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: ... - const node = json.xml?.node ?? json.node ?? null; - - if (!node) { - throw new Error("Nie znaleziono 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); -});