Jef LeCompte
2025-07-21 19:28:30 -07:00
parent 3ab0370d72
commit e077f2721c
2 changed files with 45 additions and 24 deletions

View File

@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import type { GridApiResponse } from "./tvlistings.js";
import {
buildChannelsXml,
buildProgrammesXml,
buildProgramsXml,
buildXmltv,
escapeXml,
formatDate,
@@ -57,7 +57,9 @@ describe("buildXmltv", () => {
it("should generate valid XML structure", () => {
const result = buildXmltv(mockData);
expect(result).toContain('<?xml version="1.0" encoding="UTF-8"?>');
expect(result).toContain('<tv generator-info-name="zap2it-grid">');
expect(result).toContain(
'<tv generator-info-name="jef/zap2xml" generator-info-url="https://github.com/jef/zap2xml">',
);
expect(result).toContain("</tv>");
});
@@ -85,14 +87,16 @@ describe("buildXmltv", () => {
it("should include rating information", () => {
const result = buildXmltv(mockData);
expect(result).toContain("<rating><value>TV-PG</value></rating>");
expect(result).toContain(
'<rating system="MPAA"><value>TV-PG</value></rating>',
);
});
it("should include categories from flags and tags", () => {
const result = buildXmltv(mockData);
expect(result).toContain("<category>New</category>");
expect(result).toContain("<category>Stereo</category>");
expect(result).toContain("<category>CC</category>");
expect(result).toContain("<new />");
expect(result).toContain('<audio type="stereo" />');
expect(result).toContain('<audio type="cc" />');
});
it("should include episode information", () => {
@@ -108,7 +112,9 @@ describe("buildXmltv", () => {
const emptyData: GridApiResponse = { channels: [] };
const result = buildXmltv(emptyData);
expect(result).toContain('<?xml version="1.0" encoding="UTF-8"?>');
expect(result).toContain('<tv generator-info-name="zap2it-grid">');
expect(result).toContain(
'<tv generator-info-name="jef/zap2xml" generator-info-url="https://github.com/jef/zap2xml">',
);
expect(result).toContain("</tv>");
expect(result).not.toContain("<channel");
expect(result).not.toContain("<programme");
@@ -252,9 +258,9 @@ describe("buildChannelsXml", () => {
});
});
describe("buildProgrammesXml", () => {
describe("buildProgramsXml", () => {
it("should build programme XML correctly", () => {
const result = buildProgrammesXml(mockData);
const result = buildProgramsXml(mockData);
expect(result).toContain(
'<programme start="20250718190000 +0000" stop="20250718200000 +0000" channel="19629">',
);
@@ -263,16 +269,20 @@ describe("buildProgrammesXml", () => {
expect(result).toContain(
"<desc>BIA performs; comic Zarna Garg; lifestyle contributor Lori Bergamotto; ABC News chief medical correspondent Dr. Tara Narula.</desc>",
);
expect(result).toContain("<rating><value>TV-PG</value></rating>");
expect(result).toContain("<category>New</category>");
expect(result).toContain("<category>Stereo</category>");
expect(result).toContain("<category>CC</category>");
expect(result).toContain(
'<rating system="MPAA"><value>TV-PG</value></rating>',
);
expect(result).toContain("<new />");
expect(result).toContain('<audio type="stereo" />');
expect(result).toContain('<audio type="cc" />');
expect(result).toContain('<episode-num system="season">5</episode-num>');
expect(result).toContain('<episode-num system="episode">217</episode-num>');
expect(result).toContain(
'<episode-num system="series">SH05918266</episode-num>',
);
expect(result).toContain('<icon src="p30687311_b_v13_aa" />');
expect(result).toContain(
'<icon src="https://zap2it.tmsimg.com/assets/p30687311_b_v13_aa.jpg" />',
);
});
it("should handle programmes without optional fields", () => {
@@ -318,7 +328,7 @@ describe("buildProgrammesXml", () => {
},
],
};
const result = buildProgrammesXml(minimalProgramme);
const result = buildProgramsXml(minimalProgramme);
expect(result).toContain(
'<programme start="20250718190000 +0000" stop="20250718193000 +0000" channel="123">',
);

View File

@@ -55,7 +55,7 @@ export function buildChannelsXml(data: GridApiResponse): string {
return xml;
}
export function buildProgrammesXml(data: GridApiResponse): string {
export function buildProgramsXml(data: GridApiResponse): string {
let xml = "";
for (const channel of data.channels) {
@@ -79,20 +79,23 @@ export function buildProgrammesXml(data: GridApiResponse): string {
}
if (event.rating) {
xml += ` <rating><value>${escapeXml(
xml += ` <rating system="MPAA"><value>${escapeXml(
event.rating,
)}</value></rating>\n`;
}
if (event.flag && event.flag.length > 0) {
for (const flag of event.flag) {
xml += ` <category>${escapeXml(flag)}</category>\n`;
if (event.flag.includes("New")) {
xml += ` <new />\n`;
}
}
if (event.tags && event.tags.length > 0) {
for (const tag of event.tags) {
xml += ` <category>${escapeXml(tag)}</category>\n`;
if (event.tags.includes("Stereo")) {
xml += ` <audio type="stereo" />\n`;
}
if (event.tags.includes("CC")) {
xml += ` <audio type="cc" />\n`;
}
}
@@ -114,6 +117,13 @@ export function buildProgrammesXml(data: GridApiResponse): string {
)}</episode-num>\n`;
}
// S01E01 and S11E22
if (event.program.season && event.program.episode) {
xml += ` <episode-num system="onscreen">${escapeXml(
`S${event.program.season.padStart(2, "0")}E${event.program.episode.padStart(2, "0")}`,
)}</episode-num>\n`;
}
if (event.thumbnail) {
const src = event.thumbnail.startsWith("http")
? event.thumbnail
@@ -131,10 +141,11 @@ export function buildProgrammesXml(data: GridApiResponse): string {
export function buildXmltv(data: GridApiResponse): string {
console.log("Building XMLTV file");
let xml =
'<?xml version="1.0" encoding="UTF-8"?>\n<tv generator-info-name="zap2it-grid">\n';
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
xml +=
'<tv generator-info-name="jef/zap2xml" generator-info-url="https://github.com/jef/zap2xml">\n';
xml += buildChannelsXml(data);
xml += buildProgrammesXml(data);
xml += buildProgramsXml(data);
xml += "</tv>\n";
return xml;