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

View File

@@ -55,7 +55,7 @@ export function buildChannelsXml(data: GridApiResponse): string {
return xml; return xml;
} }
export function buildProgrammesXml(data: GridApiResponse): string { export function buildProgramsXml(data: GridApiResponse): string {
let xml = ""; let xml = "";
for (const channel of data.channels) { for (const channel of data.channels) {
@@ -79,20 +79,23 @@ export function buildProgrammesXml(data: GridApiResponse): string {
} }
if (event.rating) { if (event.rating) {
xml += ` <rating><value>${escapeXml( xml += ` <rating system="MPAA"><value>${escapeXml(
event.rating, event.rating,
)}</value></rating>\n`; )}</value></rating>\n`;
} }
if (event.flag && event.flag.length > 0) { if (event.flag && event.flag.length > 0) {
for (const flag of event.flag) { if (event.flag.includes("New")) {
xml += ` <category>${escapeXml(flag)}</category>\n`; xml += ` <new />\n`;
} }
} }
if (event.tags && event.tags.length > 0) { if (event.tags && event.tags.length > 0) {
for (const tag of event.tags) { if (event.tags.includes("Stereo")) {
xml += ` <category>${escapeXml(tag)}</category>\n`; 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`; )}</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) { if (event.thumbnail) {
const src = event.thumbnail.startsWith("http") const src = event.thumbnail.startsWith("http")
? event.thumbnail ? event.thumbnail
@@ -131,10 +141,11 @@ export function buildProgrammesXml(data: GridApiResponse): string {
export function buildXmltv(data: GridApiResponse): string { export function buildXmltv(data: GridApiResponse): string {
console.log("Building XMLTV file"); console.log("Building XMLTV file");
let xml = let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
'<?xml version="1.0" encoding="UTF-8"?>\n<tv generator-info-name="zap2it-grid">\n'; xml +=
'<tv generator-info-name="jef/zap2xml" generator-info-url="https://github.com/jef/zap2xml">\n';
xml += buildChannelsXml(data); xml += buildChannelsXml(data);
xml += buildProgrammesXml(data); xml += buildProgramsXml(data);
xml += "</tv>\n"; xml += "</tv>\n";
return xml; return xml;