'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, '>') .replace(/"/g, '"') .replace(/'/g, '''); } 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 = ['']; lines.push(''); lines.push(''); for (let index = 0; index < count; index += 1) { lines.push(` `); lines.push(` Jupiler ${index + 1}`); lines.push(' '); } for (let index = 0; index < count; index += 1) { for (const programme of bySlot[index]) { const event = programme.event; lines.push( ` ` ); lines.push(` ${xmlEscape(event.title)}`); if (event.league) { lines.push(` ${xmlEscape(event.league)}`); } lines.push( ` ${xmlEscape(`[${event.league || settings.searchTerm}] ${event.title}`)}` ); lines.push(` Sport`); lines.push(' '); } } lines.push(''); return `${lines.join('\n')}\n`; } module.exports = { buildXmltv, xmlEscape, xmltvDate };