import type { GridApiResponse } from "./tvlistings.js";
export function escapeXml(unsafe: string): string {
return unsafe
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
export function formatDate(dateStr: string): string {
// Input: "2025-07-18T19:00:00Z"
// Output: "20250718190000 +0000"
const d = new Date(dateStr);
const pad = (n: number) => n.toString().padStart(2, "0");
const YYYY = d.getUTCFullYear();
const MM = pad(d.getUTCMonth() + 1);
const DD = pad(d.getUTCDate());
const hh = pad(d.getUTCHours());
const mm = pad(d.getUTCMinutes());
const ss = pad(d.getUTCSeconds());
return `${YYYY}${MM}${DD}${hh}${mm}${ss} +0000`;
}
export function buildChannelsXml(data: GridApiResponse): string {
let xml = "";
for (const channel of data.channels) {
xml += ` \n`;
xml += ` ${escapeXml(channel.callSign)}\n`;
if (channel.affiliateName) {
xml += ` ${escapeXml(
channel.affiliateName,
)}\n`;
}
if (channel.channelNo) {
xml += ` ${escapeXml(
channel.channelNo,
)}\n`;
}
if (channel.thumbnail) {
xml += ` \n`;
}
xml += " \n";
}
return xml;
}
export function buildProgramsXml(data: GridApiResponse): string {
let xml = "";
for (const channel of data.channels) {
for (const event of channel.events) {
xml += ` \n`;
xml += ` ${escapeXml(event.program.title)}\n`;
if (event.program.episodeTitle) {
xml += ` ${escapeXml(
event.program.episodeTitle,
)}\n`;
}
if (event.program.shortDesc) {
xml += ` ${escapeXml(event.program.shortDesc)}\n`;
}
if (event.rating) {
xml += ` ${escapeXml(
event.rating,
)}\n`;
}
if (event.flag && event.flag.length > 0) {
if (event.flag.includes("New")) {
xml += ` \n`;
}
if (event.flag.includes("Live")) {
xml += ` \n`;
}
if (event.flag.includes("Premiere")) {
xml += ` \n`;
}
if (event.flag.includes("Finale")) {
xml += ` \n`;
}
}
if (
!event.flag ||
(event.flag &&
event.flag.length > 0 &&
!event.flag.includes("New") &&
!event.flag.includes("Live"))
) {
xml += ` \n`;
}
if (event.tags && event.tags.length > 0) {
if (event.tags.includes("Stereo")) {
xml += ` \n`;
}
if (event.tags.includes("CC")) {
xml += ` \n`;
}
}
if (event.program.season && event.program.episode) {
xml += ` ${escapeXml(
`S${event.program.season.padStart(2, "0")}E${event.program.episode.padStart(2, "0")}`,
)}\n`;
xml += ` ${escapeXml(
`S${event.program.season.padStart(2, "0")}E${event.program.episode.padStart(2, "0")}`,
)}\n`;
if (/..\d{8}\d{4}/.test(event.program.id)) {
xml += ` ${escapeXml(event.program.id)}\n`;
}
xml += ` ${escapeXml(
`${event.program.season} . ${event.program.episode}`,
)}.\n`;
}
if (event.thumbnail) {
const src = event.thumbnail.startsWith("http")
? event.thumbnail
: "https://zap2it.tmsimg.com/assets/" + event.thumbnail + ".jpg";
xml += ` \n`;
}
xml += " \n";
}
}
return xml;
}
export function buildXmltv(data: GridApiResponse): string {
console.log("Building XMLTV file");
let xml = '\n';
xml +=
'\n';
xml += buildChannelsXml(data);
xml += buildProgramsXml(data);
xml += "\n";
return xml;
}