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 =
|
const DB_PATH =
|
||||||
process.env.FUZ_DB_PATH ||
|
process.env.FUZ_DB_PATH || path.join(process.cwd(), "src", "data", "ServicesRange.db");
|
||||||
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() {
|
function getDb() {
|
||||||
const db = new Database(DB_PATH);
|
const db = new Database(DB_PATH);
|
||||||
@@ -22,9 +28,7 @@ function getDb() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchXml(url) {
|
async function fetchXml(url) {
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, { headers: { accept: "application/xml,text/xml,*/*" } });
|
||||||
headers: { accept: "application/xml,text/xml,*/*" },
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error(`XML HTTP ${res.status} for ${url}`);
|
if (!res.ok) throw new Error(`XML HTTP ${res.status} for ${url}`);
|
||||||
return await res.text();
|
return await res.text();
|
||||||
}
|
}
|
||||||
@@ -50,14 +54,8 @@ function decodeEntities(input) {
|
|||||||
.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(/&#(\d+);/g, (_, d) =>
|
s = s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16)));
|
||||||
String.fromCodePoint(Number(d))
|
|
||||||
);
|
|
||||||
s = s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) =>
|
|
||||||
String.fromCodePoint(parseInt(h, 16))
|
|
||||||
);
|
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,8 +66,7 @@ function htmlToMarkdown(input) {
|
|||||||
if (typeof input === "string") html = input;
|
if (typeof input === "string") html = input;
|
||||||
else if (input?.p) {
|
else if (input?.p) {
|
||||||
if (typeof input.p === "string") html = `<p>${input.p}</p>`;
|
if (typeof input.p === "string") html = `<p>${input.p}</p>`;
|
||||||
else if (Array.isArray(input.p))
|
else if (Array.isArray(input.p)) html = input.p.map((p) => `<p>${p}</p>`).join("");
|
||||||
html = input.p.map((p) => `<p>${p}</p>`).join("");
|
|
||||||
} else html = String(input);
|
} else html = String(input);
|
||||||
|
|
||||||
let s = decodeEntities(html);
|
let s = decodeEntities(html);
|
||||||
@@ -131,18 +128,23 @@ async function downloadLogoAsBase64(url) {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
|
|
||||||
const ct = res.headers.get("content-type") || "image/png";
|
const ct = res.headers.get("content-type") || "image/png";
|
||||||
const buf = Buffer.from(await res.arrayBuffer());
|
const buf = Buffer.from(await res.arrayBuffer());
|
||||||
if (!buf.length) return null;
|
if (!buf.length) return null;
|
||||||
|
|
||||||
return `data:${ct};base64,${buf.toString("base64")}`;
|
return `data:${ct};base64,${buf.toString("base64")}`;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
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 db = getDb();
|
||||||
|
|
||||||
const upsert = db.prepare(`
|
const upsert = db.prepare(`
|
||||||
@@ -153,7 +155,7 @@ export async function POST() {
|
|||||||
opis = COALESCE(excluded.opis, jambox_channels.opis)
|
opis = COALESCE(excluded.opis, jambox_channels.opis)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const logoCache = new Map();
|
const logoCache = new Map();
|
||||||
const rows = [];
|
const rows = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -190,20 +192,15 @@ export async function POST() {
|
|||||||
|
|
||||||
trx(rows);
|
trx(rows);
|
||||||
|
|
||||||
return new Response(
|
return new Response(JSON.stringify({ ok: true, rows: rows.length, db: DB_PATH }), {
|
||||||
JSON.stringify({
|
headers: { "content-type": "application/json; charset=utf-8" },
|
||||||
ok: true,
|
});
|
||||||
rows: rows.length,
|
|
||||||
db: DB_PATH,
|
|
||||||
}),
|
|
||||||
{ headers: { "content-type": "application/json; charset=utf-8" } }
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("import jambox_channels:", e);
|
console.error("import jambox_channels:", e);
|
||||||
return new Response(
|
return new Response(JSON.stringify({ ok: false, error: String(e?.message || e) }), {
|
||||||
JSON.stringify({ ok: false, error: String(e.message || e) }),
|
status: 500,
|
||||||
{ status: 500 }
|
headers: { "content-type": "application/json; charset=utf-8" },
|
||||||
);
|
});
|
||||||
} finally {
|
} finally {
|
||||||
try { db.close(); } catch {}
|
try { db.close(); } catch {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,13 @@ import fs from "node:fs/promises";
|
|||||||
|
|
||||||
const URL = "https://www.jambox.pl/xml/mozliwosci.xml";
|
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) {
|
function toArray(v) {
|
||||||
if (!v) return [];
|
if (!v) return [];
|
||||||
return Array.isArray(v) ? v : [v];
|
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(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
|
||||||
s = s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) =>
|
s = s.replace(/&#x([0-9a-fA-F]+);/g, (_, h) =>
|
||||||
String.fromCodePoint(parseInt(h, 16))
|
String.fromCodePoint(parseInt(h, 16)),
|
||||||
);
|
);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
@@ -171,13 +178,23 @@ function toYaml(sections) {
|
|||||||
return out.join("\n").trimEnd() + "\n";
|
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, {
|
const res = await fetch(URL, {
|
||||||
headers: { accept: "application/xml,text/xml,*/*" },
|
headers: { accept: "application/xml,text/xml,*/*" },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
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();
|
const xml = await res.text();
|
||||||
@@ -195,10 +212,7 @@ export async function POST() {
|
|||||||
|
|
||||||
if (!title) return null;
|
if (!title) return null;
|
||||||
|
|
||||||
const blocks = [
|
const blocks = [...parseHtmlContent(n?.teaser), ...parseHtmlContent(n?.description)];
|
||||||
...parseHtmlContent(n?.teaser),
|
|
||||||
...parseHtmlContent(n?.description),
|
|
||||||
];
|
|
||||||
|
|
||||||
const content = blocksToMarkdown(blocks);
|
const content = blocksToMarkdown(blocks);
|
||||||
if (!content) return null;
|
if (!content) return null;
|
||||||
@@ -216,7 +230,7 @@ export async function POST() {
|
|||||||
await fs.mkdir(outDir, { recursive: true });
|
await fs.mkdir(outDir, { recursive: true });
|
||||||
await fs.writeFile(outFile, toYaml(sections), "utf8");
|
await fs.writeFile(outFile, toYaml(sections), "utf8");
|
||||||
|
|
||||||
return new Response(JSON.stringify({ ok: true, count: sections.length }), {
|
return new Response(JSON.stringify({ ok: true, count: sections.length, file: outFile }), {
|
||||||
headers: { "content-type": "application/json" },
|
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;
|
return url;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Budowa XML
|
|
||||||
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
${urls
|
${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