xtream bundler
Build and Push Docker Images / build-and-push (push) Successful in 36s

This commit is contained in:
Bram committed 2026-11-06 19:24:39 +01:00
1 parent d7aea0be65
commit 91f22b54b4
30 files changed
+2475

No files matched your search

+116
View File
@@ -0,0 +1,116 @@
'use strict';
const { settings } = require('./settings');
const { compareEvents } = require('./events');
const EPG_HORIZON_MS = 14 * 24 * 60 * 60 * 1000;
const EPG_PAST_MS = 6 * 60 * 60 * 1000;
function xmlEscape(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
function xmltvDate(date) {
return `${date.toISOString().slice(0, 19).replace(/[-:T]/g, '')} +0000`;
}
function buildXmltv(scheduler, now = Date.now()) {
const state = scheduler.state;
const count = state.slots.length;
const programmes = [];
const slotEnds = new Array(count).fill(now);
const assignedKeys = new Set();
for (let index = 0; index < count; index += 1) {
const slot = state.slots[index];
const event = slot && slot.eventKey ? state.events.get(slot.eventKey) : null;
if (!event) continue;
assignedKeys.add(event.key);
programmes.push({ slot: index, event, startAt: event.startAt, endAt: event.endAt });
slotEnds[index] = Math.max(slotEnds[index], event.endAt.getTime());
}
const future = (state.upcoming || [])
.filter((event) => !assignedKeys.has(event.key))
.filter((event) => event.endAt.getTime() > now)
.filter((event) => event.startAt.getTime() <= now + EPG_HORIZON_MS)
.slice()
.sort(compareEvents);
for (const event of future) {
const duration = event.endAt.getTime() - event.startAt.getTime();
let bestSlot = -1;
let bestEnd = Infinity;
for (let index = 0; index < count; index += 1) {
if (slotEnds[index] <= event.startAt.getTime()) {
if (slotEnds[index] < bestEnd) {
bestEnd = slotEnds[index];
bestSlot = index;
}
}
}
if (bestSlot === -1) {
for (let index = 0; index < count; index += 1) {
if (slotEnds[index] < bestEnd) {
bestEnd = slotEnds[index];
bestSlot = index;
}
}
}
if (bestSlot === -1) continue;
const startAt = Math.max(event.startAt.getTime(), bestEnd);
const endAt = startAt + duration;
if (event.endAt.getTime() <= bestEnd) continue;
programmes.push({
slot: bestSlot,
event,
startAt: new Date(startAt),
endAt: new Date(endAt),
});
slotEnds[bestSlot] = endAt;
}
const bySlot = Array.from({ length: count }, () => []);
for (const programme of programmes) {
if (programme.endAt.getTime() < now - EPG_PAST_MS) continue;
bySlot[programme.slot].push(programme);
}
for (const list of bySlot) {
list.sort((a, b) => a.startAt.getTime() - b.startAt.getTime());
}
const lines = ['<?xml version="1.0" encoding="UTF-8"?>'];
lines.push('<!DOCTYPE tv SYSTEM "xmltv.dtd">');
lines.push('<tv generator-info-name="xtream-ppv-bundler">');
for (let index = 0; index < count; index += 1) {
lines.push(` <channel id="jupiler-${index + 1}">`);
lines.push(` <display-name>Jupiler ${index + 1}</display-name>`);
lines.push(' </channel>');
}
for (let index = 0; index < count; index += 1) {
for (const programme of bySlot[index]) {
const event = programme.event;
lines.push(
` <programme start="${xmltvDate(programme.startAt)}" stop="${xmltvDate(programme.endAt)}" channel="jupiler-${index + 1}">`
);
lines.push(` <title lang="nl">${xmlEscape(event.title)}</title>`);
if (event.league) {
lines.push(` <sub-title lang="nl">${xmlEscape(event.league)}</sub-title>`);
}
lines.push(
` <desc lang="nl">${xmlEscape(`[${event.league || settings.searchTerm}] ${event.title}`)}</desc>`
);
lines.push(` <category lang="nl">Sport</category>`);
lines.push(' </programme>');
}
}
lines.push('</tv>');
return `${lines.join('\n')}\n`;
}
module.exports = { buildXmltv, xmlEscape, xmltvDate };