Skip to content

Commit 15d6fd9

Browse files
committed
feat: channelThumbnailWidth to allow for full-size channel thumbnails
1 parent 6e6fa26 commit 15d6fd9

File tree

5 files changed

+107
-38
lines changed

5 files changed

+107
-38
lines changed

README.md

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,30 +41,32 @@ See [Environment variables](#environment-variables) for configuration options.
4141
4242
### Environment variables
4343
44-
| Variable | Description | Default |
45-
| ------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------- |
46-
| `LINEUP_ID` | Lineup ID; Read more in the [Wiki](https://github.com/jef/zap2xml/wiki/Retrieving-Lineup-ID) | `USA-lineupId-DEFAULT` (Attenna) |
47-
| `TIMESPAN` | Timespan in hours (up to 360 = 15 days, default: 6) | 6 |
48-
| `PREF` | User Preferences, comma separated list. `m` for showing music, `p` for showing pay-per-view, `h` for showing HD | (empty) |
49-
| `COUNTRY` | Country code (default: `USA`) | USA |
50-
| `POSTAL_CODE` | Postal code of where shows are available. | 30309 |
51-
| `USER_AGENT` | Custom user agent string for HTTP requests. | Uses random if not specified |
52-
| `TZ` | Timezone | System default |
53-
| `SLEEP_TIME` | Sleep time before next run in seconds (default: 21600, Only used with Docker.) | 21600 |
54-
| `OUTPUT_FILE` | Output file name (default: xmltv.xml) | xmltv.xml |
44+
| Variable | Description | Default |
45+
| ------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------- |
46+
| `LINEUP_ID` | Lineup ID; Read more in the [Wiki](https://github.com/jef/zap2xml/wiki/Retrieving-Lineup-ID) | `USA-lineupId-DEFAULT` (Attenna) |
47+
| `TIMESPAN` | Timespan in hours (up to 360 = 15 days, default: 6) | 6 |
48+
| `PREF` | User Preferences, comma separated list. `m` for showing music, `p` for showing pay-per-view, `h` for showing HD | (empty) |
49+
| `COUNTRY` | Country code (default: `USA`) | USA |
50+
| `POSTAL_CODE` | Postal code of where shows are available. | 30309 |
51+
| `USER_AGENT` | Custom user agent string for HTTP requests. | Uses random if not specified |
52+
| `TZ` | Timezone | System default |
53+
| `SLEEP_TIME` | Sleep time before next run in seconds (default: 21600, Only used with Docker.) | 21600 |
54+
| `OUTPUT_FILE` | Output file name (default: xmltv.xml) | xmltv.xml |
55+
| `CHANNEL_THUMBNAIL_WIDTH` | Width of channel thumbnails in pixels (`0` removes width filter, unset uses API default) | API default (~55) |
5556

5657
### Command line arguments
5758

58-
| Argument | Description | Default |
59-
| -------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------- |
60-
| `--lineupId` | Lineup ID; Read more in the [Wiki](https://github.com/jef/zap2xml/wiki/Retrieving-Lineup-ID) | `USA-lineupId-DEFAULT` (Attenna) |
61-
| `--timespan` | Timespan in hours (up to 360 = 15 days, default: 6) | 6 |
62-
| `--pref` | User Preferences, comma separated list. `m` for showing music, `p` for showing pay-per-view, `h` for showing HD | (empty) |
63-
| `--country` | Country code (default: `USA`) | USA |
64-
| `--postalCode` | Postal code of where shows are available. | 30309 |
65-
| `--userAgent` | Custom user agent string for HTTP requests. | Uses random if not specified |
66-
| `--timezone` | Timezone | System default |
67-
| `--outputFile` | Output file name (default: xmltv.xml) | xmltv.xml |
59+
| Argument | Description | Default |
60+
| ------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------- |
61+
| `--lineupId` | Lineup ID; Read more in the [Wiki](https://github.com/jef/zap2xml/wiki/Retrieving-Lineup-ID) | `USA-lineupId-DEFAULT` (Attenna) |
62+
| `--timespan` | Timespan in hours (up to 360 = 15 days, default: 6) | 6 |
63+
| `--pref` | User Preferences, comma separated list. `m` for showing music, `p` for showing pay-per-view, `h` for showing HD | (empty) |
64+
| `--country` | Country code (default: `USA`) | USA |
65+
| `--postalCode` | Postal code of where shows are available. | 30309 |
66+
| `--userAgent` | Custom user agent string for HTTP requests. | Uses random if not specified |
67+
| `--timezone` | Timezone | System default |
68+
| `--outputFile` | Output file name (default: xmltv.xml) | xmltv.xml |
69+
| `--channelThumbnailWidth` | Width of channel thumbnails in pixels (`0` removes width filter, unset uses API default) | API default (~55) |
6870

6971
## Setup and running in intervals
7072

src/config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,17 @@ export function getConfig() {
6464
.find((arg) => arg.startsWith("--outputFile="))
6565
?.split("=")[1] ||
6666
"xmltv.xml",
67+
channelThumbnailWidth: (() => {
68+
const rawWidth =
69+
process.env["CHANNEL_THUMBNAIL_WIDTH"] ||
70+
process.argv
71+
.find((arg) => arg.startsWith("--channelThumbnailWidth="))
72+
?.split("=")[1];
73+
if (rawWidth === undefined || rawWidth === null || rawWidth === "") {
74+
return null;
75+
}
76+
const parsedWidth = Number(rawWidth);
77+
return Number.isNaN(parsedWidth) ? null : parsedWidth;
78+
})(),
6779
};
6880
}

src/index.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ function isHelp() {
1111
Usage: node dist/index.js [options]
1212
1313
Options:
14-
--help Show this help message
15-
--lineupId=ID Lineup ID (default: USA-lineupId-DEFAULT)
16-
--timespan=NUM Timespan in hours (up to 360 = 15 days, default: 6)
17-
--pref=LIST User preferences, comma separated. Can be m, p, and h (default: empty)'
18-
--country=CON Country code (default: USA)
19-
--postalCode=ZIP Postal code (default: 30309)
20-
--userAgent=UA Custom user agent string (default: Uses random if not specified)
21-
--timezone=TZ Timezone (default: America/New_York)
14+
--help Show this help message
15+
--lineupId=ID Lineup ID (default: USA-lineupId-DEFAULT)
16+
--timespan=NUM Timespan in hours (up to 360 = 15 days, default: 6)
17+
--pref=LIST User preferences, comma separated. Can be m, p, and h (default: empty)'
18+
--country=CON Country code (default: USA)
19+
--postalCode=ZIP Postal code (default: 30309)
20+
--userAgent=UA Custom user agent string (default: Uses random if not specified)
21+
--timezone=TZ Timezone (default: America/New_York)
22+
--channelThumbnailWidth=NUM Width of channel thumbnails in pixels (0 removes the width parameter for full size, unset uses API default)
2223
`);
2324
process.exit(0);
2425
}
@@ -29,7 +30,9 @@ async function main() {
2930
isHelp();
3031

3132
const data = await getTVListings();
32-
const xml = buildXmltv(data);
33+
const xml = buildXmltv(data, {
34+
channelThumbnailWidth: config.channelThumbnailWidth,
35+
});
3336

3437
console.log("Writing XMLTV file");
3538
writeFileSync(config.outputFile, xml, { encoding: "utf-8" });

src/xmltv.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,21 @@ describe("buildXmltv", () => {
177177
expect(result).not.toContain("<episode-num");
178178
expect(result).not.toContain("<icon");
179179
});
180+
181+
it("should honor channel thumbnail width option", () => {
182+
const result = buildXmltv(mockData, { channelThumbnailWidth: 120 });
183+
expect(result).toContain(
184+
'<icon src="https://zap2it.tmsimg.com/h3/NowShowing/19629/s28708_ll_h15_ac.png?w=120" />',
185+
);
186+
});
187+
188+
it("should remove width when option is zero", () => {
189+
const result = buildXmltv(mockData, { channelThumbnailWidth: 0 });
190+
expect(result).toContain(
191+
'<icon src="https://zap2it.tmsimg.com/h3/NowShowing/19629/s28708_ll_h15_ac.png" />',
192+
);
193+
expect(result).not.toContain("?w=55");
194+
});
180195
});
181196

182197
describe("escapeXml", () => {
@@ -256,6 +271,21 @@ describe("buildChannelsXml", () => {
256271
expect(result).toContain("<display-name>TEST</display-name>");
257272
expect(result).not.toContain("<icon");
258273
});
274+
275+
it("should respect channel thumbnail width option", () => {
276+
const result = buildChannelsXml(mockData, { channelThumbnailWidth: 200 });
277+
expect(result).toContain(
278+
'<icon src="https://zap2it.tmsimg.com/h3/NowShowing/19629/s28708_ll_h15_ac.png?w=200" />',
279+
);
280+
});
281+
282+
it("should strip width parameter when option is zero", () => {
283+
const result = buildChannelsXml(mockData, { channelThumbnailWidth: 0 });
284+
expect(result).toContain(
285+
'<icon src="https://zap2it.tmsimg.com/h3/NowShowing/19629/s28708_ll_h15_ac.png" />',
286+
);
287+
expect(result).not.toContain("?w=55");
288+
});
259289
});
260290

261291
describe("buildProgramsXml", () => {

src/xmltv.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { GridApiResponse } from "./tvlistings.js";
22

3+
export type XmltvOptions = {
4+
channelThumbnailWidth?: number | null;
5+
};
6+
37
export function escapeXml(unsafe: string): string {
48
return unsafe
59
.replace(/&/g, "&amp;")
@@ -23,7 +27,12 @@ export function formatDate(dateStr: string): string {
2327
return `${YYYY}${MM}${DD}${hh}${mm}${ss} +0000`;
2428
}
2529

26-
export function buildChannelsXml(data: GridApiResponse): string {
30+
export function buildChannelsXml(
31+
data: GridApiResponse,
32+
options: XmltvOptions = {},
33+
): string {
34+
// Handle the caller ommitting XmltvOptions
35+
const thumbnailWidth = options.channelThumbnailWidth ?? null;
2736
let xml = "";
2837

2938
for (const channel of data.channels) {
@@ -43,11 +52,21 @@ export function buildChannelsXml(data: GridApiResponse): string {
4352
}
4453

4554
if (channel.thumbnail) {
46-
xml += ` <icon src="${escapeXml(
47-
channel.thumbnail.startsWith("http")
48-
? channel.thumbnail
49-
: "https:" + channel.thumbnail,
50-
)}" />\n`;
55+
let iconUrl = channel.thumbnail.startsWith("http")
56+
? channel.thumbnail
57+
: "https:" + channel.thumbnail;
58+
59+
if (thumbnailWidth !== null) {
60+
const parsedUrl = new URL(iconUrl);
61+
if (thumbnailWidth === 0) {
62+
parsedUrl.searchParams.delete("w");
63+
} else {
64+
parsedUrl.searchParams.set("w", String(thumbnailWidth));
65+
}
66+
iconUrl = parsedUrl.toString();
67+
}
68+
69+
xml += ` <icon src="${escapeXml(iconUrl)}" />\n`;
5170
}
5271

5372
xml += " </channel>\n";
@@ -153,13 +172,16 @@ export function buildProgramsXml(data: GridApiResponse): string {
153172
return xml;
154173
}
155174

156-
export function buildXmltv(data: GridApiResponse): string {
175+
export function buildXmltv(
176+
data: GridApiResponse,
177+
options: XmltvOptions = {},
178+
): string {
157179
console.log("Building XMLTV file");
158180

159181
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
160182
xml +=
161183
'<tv generator-info-name="jef/zap2xml" generator-info-url="https://github.com/jef/zap2xml">\n';
162-
xml += buildChannelsXml(data);
184+
xml += buildChannelsXml(data, options);
163185
xml += buildProgramsXml(data);
164186
xml += "</tv>\n";
165187

0 commit comments

Comments
 (0)