epg-grabber 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,857 @@
1
+ import utc from 'dayjs/plugin/utc.js';
2
+ import dayjs from 'dayjs';
3
+ import { URL } from 'node:url';
4
+ import path from 'node:path';
5
+ import AxiosMockAdapter from 'axios-mock-adapter';
6
+ import padStart from 'lodash.padstart';
7
+ import axios from 'axios';
8
+ import { setupCache } from 'axios-cache-interceptor';
9
+ import merge from 'lodash.merge';
10
+ import xmlJs from 'xml-js';
11
+
12
+ dayjs.extend(utc);
13
+ function toUnix(date) {
14
+ return dayjs.utc(date).valueOf();
15
+ }
16
+ function parseProxy(string) {
17
+ const parsed = new URL(string);
18
+ const proxy = {
19
+ protocol: parsed.protocol.replace(":", ""),
20
+ host: parsed.hostname,
21
+ port: parsed.port ? parseInt(parsed.port) : 8080
22
+ };
23
+ if (parsed.username || parsed.password) {
24
+ proxy.auth = { username: parsed.username, password: parsed.password };
25
+ }
26
+ return proxy;
27
+ }
28
+ async function loadJs(filepath) {
29
+ const absPath = path.resolve(filepath);
30
+ return (await import(absPath)).default;
31
+ }
32
+ function parseNumber(value) {
33
+ return parseInt(value);
34
+ }
35
+ function sleep(ms) {
36
+ return new Promise((resolve) => setTimeout(resolve, ms));
37
+ }
38
+ function isDate(date) {
39
+ return dayjs(date).isValid();
40
+ }
41
+ function isObject(value) {
42
+ return !!value && value.constructor === Object;
43
+ }
44
+ function isPromise(promise) {
45
+ return promise instanceof Promise;
46
+ }
47
+ function getUTCDate(date) {
48
+ if (dayjs.isDayjs(date) && date.isUTC()) return date;
49
+ return dayjs.utc(date).startOf("d");
50
+ }
51
+ function getAbsPath(filepath, rootDir) {
52
+ if (path.isAbsolute(filepath)) return filepath;
53
+ return path.resolve(rootDir, filepath);
54
+ }
55
+ function escapeString(value, defaultValue = "") {
56
+ if (!value) return defaultValue;
57
+ if (typeof value === "number") value = value.toString();
58
+ const regex = new RegExp(
59
+ "((?:[\0-\b\v\f-\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))|([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|(?:\\uD83F[\\uDFFE\\uDFFF])|(?:\\uD87F[\\uDFFE\\uDFFF])|(?:\\uD8BF[\\uDFFE\\uDFFF])|(?:\\uD8FF[\\uDFFE\\uDFFF])|(?:\\uD93F[\\uDFFE\\uDFFF])|(?:\\uD97F[\\uDFFE\\uDFFF])|(?:\\uD9BF[\\uDFFE\\uDFFF])|(?:\\uD9FF[\\uDFFE\\uDFFF])|(?:\\uDA3F[\\uDFFE\\uDFFF])|(?:\\uDA7F[\\uDFFE\\uDFFF])|(?:\\uDABF[\\uDFFE\\uDFFF])|(?:\\uDAFF[\\uDFFE\\uDFFF])|(?:\\uDB3F[\\uDFFE\\uDFFF])|(?:\\uDB7F[\\uDFFE\\uDFFF])|(?:\\uDBBF[\\uDFFE\\uDFFF])|(?:\\uDBFF[\\uDFFE\\uDFFF])(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))",
60
+ "g"
61
+ );
62
+ value = String(value || "").replace(regex, "");
63
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/\n|\r/g, " ").replace(/ +/g, " ").trim();
64
+ }
65
+ function formatDate(date, format) {
66
+ return date ? dayjs.utc(date).format(format) : "";
67
+ }
68
+ function toURL(domain) {
69
+ return domain ? `https://${domain}` : "";
70
+ }
71
+ function createXMLElement(name, attrs, children) {
72
+ return convertXMLElementToString({ name, attrs, children });
73
+ }
74
+ function convertXMLElementToString(elem) {
75
+ if (!elem) return "";
76
+ if (typeof elem === "string") return elem;
77
+ if (typeof elem === "number") return elem.toString();
78
+ if (!elem.attrs && !elem.children) return `<${elem.name}/>`;
79
+ let attrs = "";
80
+ for (const key in elem.attrs) {
81
+ const value = elem.attrs[key];
82
+ if (value) {
83
+ attrs += ` ${key}="${escapeString(value)}"`;
84
+ }
85
+ }
86
+ const children = elem.children || [];
87
+ if (children.filter(Boolean).length) {
88
+ let _children = "";
89
+ children.forEach((childElem) => {
90
+ if (!childElem) return;
91
+ _children += convertXMLElementToString(childElem);
92
+ });
93
+ return `<${elem.name}${attrs}>${_children}</${elem.name}>`;
94
+ }
95
+ if (!attrs && !["previously-shown"].includes(elem.name)) return "";
96
+ return `<${elem.name}${attrs}/>`;
97
+ }
98
+ function toArray(value) {
99
+ if (value === null || value === void 0) return [];
100
+ if (Array.isArray(value)) return value.filter(Boolean);
101
+ return [value].filter(Boolean);
102
+ }
103
+
104
+ var name = "epg-grabber";
105
+ var version = "0.45.0";
106
+ var description = "Node.js CLI tool for grabbing EPG from different sites";
107
+ var homepage = "https://github.com/freearhey/epg-grabber";
108
+
109
+ var defaultConfig = {
110
+ days: 1,
111
+ delay: 3e3,
112
+ output: "guide.xml",
113
+ channels: [],
114
+ request: {
115
+ method: "GET",
116
+ maxContentLength: 5 * 1024 * 1024,
117
+ timeout: 5e3,
118
+ withCredentials: true,
119
+ responseType: "arraybuffer",
120
+ cache: false,
121
+ headers: {
122
+ "User-Agent": `EPGGrabber/${version} (${homepage})`
123
+ }
124
+ },
125
+ maxConnections: 1,
126
+ curl: false,
127
+ debug: false,
128
+ gzip: false
129
+ };
130
+
131
+ class Channel {
132
+ xmltv_id;
133
+ name;
134
+ site;
135
+ site_id;
136
+ lang;
137
+ logo;
138
+ url;
139
+ lcn;
140
+ index;
141
+ constructor(data) {
142
+ this.xmltv_id = data.xmltv_id;
143
+ this.name = data.name;
144
+ this.site = data.site;
145
+ this.site_id = data.site_id;
146
+ this.lang = data.lang;
147
+ this.logo = data.logo;
148
+ this.url = data.url || toURL(data.site);
149
+ this.lcn = data.lcn;
150
+ this.index = data.index !== void 0 ? data.index : -1;
151
+ }
152
+ static fromXmlJsElement(site, element, index) {
153
+ if (!element.elements) return;
154
+ const textElement = element.elements.find((element2) => element2.type === "text");
155
+ if (!textElement || !textElement.text) return;
156
+ const attributes = element.attributes;
157
+ if (!attributes) return;
158
+ const channel = new Channel({
159
+ xmltv_id: attributes.xmltv_id ? attributes.xmltv_id.toString() : "",
160
+ name: textElement.text.toString(),
161
+ site: attributes.site ? attributes.site.toString() : site,
162
+ site_id: attributes.site_id ? attributes.site_id.toString() : "",
163
+ lang: attributes.lang ? attributes.lang.toString() : null,
164
+ logo: attributes.logo ? attributes.logo.toString() : null,
165
+ url: attributes.url ? attributes.url.toString() : null,
166
+ lcn: attributes.lcn ? attributes.lcn.toString() : null,
167
+ index
168
+ });
169
+ return channel;
170
+ }
171
+ toXML() {
172
+ const el = createXMLElement;
173
+ const children = [el("display-name", {}, [escapeString(this.name)])];
174
+ if (this.logo) children.push(el("icon", { src: this.logo }, []));
175
+ if (this.url) children.push(el("url", {}, [escapeString(this.url)]));
176
+ if (this.lcn) children.push(el("lcn", {}, [escapeString(this.lcn)]));
177
+ return el("channel", { id: this.xmltv_id }, children);
178
+ }
179
+ toObject() {
180
+ return {
181
+ xmltv_id: this.xmltv_id,
182
+ name: this.name,
183
+ site: this.site,
184
+ site_id: this.site_id,
185
+ lang: this.lang,
186
+ logo: this.logo,
187
+ url: this.url,
188
+ lcn: this.lcn,
189
+ index: this.index
190
+ };
191
+ }
192
+ }
193
+
194
+ dayjs.extend(utc);
195
+ class Program {
196
+ site;
197
+ start;
198
+ stop;
199
+ channel;
200
+ titles;
201
+ subTitles;
202
+ descriptions;
203
+ date;
204
+ categories;
205
+ keywords;
206
+ languages;
207
+ origLanguages;
208
+ length;
209
+ urls;
210
+ countries;
211
+ episodeNumbers;
212
+ video;
213
+ audio;
214
+ previouslyShown;
215
+ premiere;
216
+ lastChance;
217
+ new;
218
+ subtitles;
219
+ ratings;
220
+ starRatings;
221
+ reviews;
222
+ directors;
223
+ actors;
224
+ writers;
225
+ adapters;
226
+ producers;
227
+ composers;
228
+ editors;
229
+ presenters;
230
+ commentators;
231
+ guests;
232
+ images;
233
+ icons;
234
+ constructor(data) {
235
+ if (typeof data.site !== "string")
236
+ throw new Error('The "site" property must be of type "string"');
237
+ if (typeof data.start !== "number")
238
+ throw new Error('The "start" property must be of type "number"');
239
+ if (typeof data.stop !== "number")
240
+ throw new Error('The "stop" property must be of type "number"');
241
+ if (typeof data.channel !== "string")
242
+ throw new Error('The "channel" property must be of type "string"');
243
+ if (!Array.isArray(data.titles)) throw new Error('The "start" property must be of type "array"');
244
+ this.site = data.site;
245
+ this.channel = data.channel;
246
+ this.start = data.start;
247
+ this.stop = data.stop;
248
+ this.titles = data.titles;
249
+ this.subTitles = Array.isArray(data.subTitles) ? data.subTitles : [];
250
+ this.descriptions = Array.isArray(data.descriptions) ? data.descriptions : [];
251
+ this.date = typeof data.date === "number" ? data.date : null;
252
+ this.categories = Array.isArray(data.categories) ? data.categories : [];
253
+ this.keywords = Array.isArray(data.keywords) ? data.keywords : [];
254
+ this.languages = Array.isArray(data.languages) ? data.languages : [];
255
+ this.origLanguages = Array.isArray(data.origLanguages) ? data.origLanguages : [];
256
+ this.length = Array.isArray(data.length) ? data.length : [];
257
+ this.urls = Array.isArray(data.urls) ? data.urls : [];
258
+ this.countries = Array.isArray(data.countries) ? data.countries : [];
259
+ this.episodeNumbers = Array.isArray(data.episodeNumbers) ? data.episodeNumbers : [];
260
+ this.video = typeof data.video === "object" ? data.video : null;
261
+ this.audio = typeof data.audio === "object" ? data.audio : null;
262
+ this.previouslyShown = Array.isArray(data.previouslyShown) ? data.previouslyShown : [];
263
+ this.premiere = Array.isArray(data.premiere) ? data.premiere : [];
264
+ this.lastChance = Array.isArray(data.lastChance) ? data.lastChance : [];
265
+ this.new = typeof data.new === "boolean" ? data.new : false;
266
+ this.subtitles = Array.isArray(data.subtitles) ? data.subtitles : [];
267
+ this.ratings = Array.isArray(data.ratings) ? data.ratings : [];
268
+ this.starRatings = Array.isArray(data.starRatings) ? data.starRatings : [];
269
+ this.reviews = Array.isArray(data.reviews) ? data.reviews : [];
270
+ this.directors = Array.isArray(data.directors) ? data.directors : [];
271
+ this.actors = Array.isArray(data.actors) ? data.actors : [];
272
+ this.writers = Array.isArray(data.writers) ? data.writers : [];
273
+ this.adapters = Array.isArray(data.adapters) ? data.adapters : [];
274
+ this.producers = Array.isArray(data.producers) ? data.producers : [];
275
+ this.composers = Array.isArray(data.composers) ? data.composers : [];
276
+ this.editors = Array.isArray(data.editors) ? data.editors : [];
277
+ this.presenters = Array.isArray(data.presenters) ? data.presenters : [];
278
+ this.commentators = Array.isArray(data.commentators) ? data.commentators : [];
279
+ this.guests = Array.isArray(data.guests) ? data.guests : [];
280
+ this.images = Array.isArray(data.images) ? data.images : [];
281
+ this.icons = Array.isArray(data.icons) ? data.icons : [];
282
+ }
283
+ static fromParserResult(data, channel) {
284
+ function toTextObject(value, lang2 = "") {
285
+ if (!value) return { value: "", lang: lang2 };
286
+ if (typeof value === "string") return { value, lang: lang2 };
287
+ return {
288
+ value: typeof value.value === "string" ? value.value : "",
289
+ lang: typeof value.lang === "string" ? value.lang : ""
290
+ };
291
+ }
292
+ function toPersonObject(value) {
293
+ if (!value) return { value: "", urls: [], images: [] };
294
+ if (typeof value === "string") return { value, urls: [], images: [] };
295
+ return {
296
+ value: value.value,
297
+ urls: toArray(value.urls || value.url).map(toUrlObject),
298
+ images: toArray(value.images || value.image).map(toImageObject)
299
+ };
300
+ }
301
+ function toSubtitlesObject(value) {
302
+ if (!value) return { type: "", language: [] };
303
+ return {
304
+ type: typeof value.type === "string" ? value.type : "",
305
+ language: toArray(value.language).map((text) => toTextObject(text))
306
+ };
307
+ }
308
+ function toImageObject(value) {
309
+ if (!value) return { type: "", size: "", orient: "", system: "", value: "" };
310
+ if (typeof value === "string") return { type: "", size: "", orient: "", system: "", value };
311
+ return {
312
+ type: typeof value.type === "string" ? value.type : "",
313
+ size: typeof value.size === "string" ? value.size : "",
314
+ orient: typeof value.orient === "string" ? value.orient : "",
315
+ system: typeof value.system === "string" ? value.system : "",
316
+ value: typeof value.value === "string" ? value.value : ""
317
+ };
318
+ }
319
+ function toRatingObject(value) {
320
+ if (!value) return { system: "", icons: [], value: "" };
321
+ if (typeof value === "string") return { system: "", icons: [], value };
322
+ return {
323
+ system: typeof value.system === "string" ? value.system : "",
324
+ icons: toArray(value.icons || value.icon).map(toIconObject),
325
+ value: typeof value.value === "string" ? value.value : ""
326
+ };
327
+ }
328
+ function toLengthObject(value) {
329
+ if (!value) return { units: "", value: "" };
330
+ if (typeof value === "string") return { units: "", value };
331
+ if (typeof value === "number") return { units: "", value: value.toString() };
332
+ return {
333
+ units: typeof value.units === "string" ? value.units : "",
334
+ value: typeof value.value === "string" ? value.value : ""
335
+ };
336
+ }
337
+ function toUrlObject(value) {
338
+ if (!value) return { system: "", value: "" };
339
+ if (typeof value === "string") return { system: "", value };
340
+ return {
341
+ system: typeof value.system === "string" ? value.system : "",
342
+ value: typeof value.value === "string" ? value.value : ""
343
+ };
344
+ }
345
+ function toVideoObject(value) {
346
+ if (!value) return { present: "", colour: "", aspect: "", quality: "" };
347
+ return {
348
+ present: typeof value.present === "string" ? value.present : "",
349
+ colour: typeof value.colour === "string" ? value.colour : "",
350
+ aspect: typeof value.aspect === "string" ? value.aspect : "",
351
+ quality: typeof value.quality === "string" ? value.quality : ""
352
+ };
353
+ }
354
+ function toAudioObject(value) {
355
+ if (!value) return { present: "", stereo: "" };
356
+ return {
357
+ present: typeof value.present === "string" ? value.present : "",
358
+ stereo: typeof value.stereo === "string" ? value.stereo : ""
359
+ };
360
+ }
361
+ function toReviewObject(value) {
362
+ if (!value) return { type: "", source: "", reviewer: "", lang: "", value: "" };
363
+ return {
364
+ type: typeof value.type === "string" ? value.type : "",
365
+ source: typeof value.source === "string" ? value.source : "",
366
+ reviewer: typeof value.reviewer === "string" ? value.reviewer : "",
367
+ lang: typeof value.lang === "string" ? value.lang : "",
368
+ value: typeof value.value === "string" ? value.value : ""
369
+ };
370
+ }
371
+ function toPreviouslyShownObject(value) {
372
+ if (!value) return { start: "", channel: "" };
373
+ return {
374
+ start: typeof value.start === "string" ? value.start : "",
375
+ channel: typeof value.channel === "string" ? value.channel : ""
376
+ };
377
+ }
378
+ function toIconObject(value) {
379
+ if (!value) return { src: "", width: "", height: "" };
380
+ if (typeof value === "string") return { src: value, width: "", height: "" };
381
+ return {
382
+ src: typeof value.src === "string" ? value.src : "",
383
+ width: typeof value.width === "string" ? value.width : typeof value.width === "number" ? value.width.toString() : "",
384
+ height: typeof value.height === "string" ? value.height : typeof value.height === "number" ? value.height.toString() : ""
385
+ };
386
+ }
387
+ function makeEpisodeNumberObjects(seasonNumberAny, episodeNumberAny) {
388
+ if (!seasonNumberAny) seasonNumberAny = 1;
389
+ if (!episodeNumberAny) return [];
390
+ const seasonNumber = typeof seasonNumberAny === "number" ? seasonNumberAny : parseInt(seasonNumberAny);
391
+ const episodeNumber = typeof episodeNumberAny === "number" ? episodeNumberAny : parseInt(episodeNumberAny);
392
+ return [
393
+ createXMLTVNS(seasonNumber, episodeNumber),
394
+ createOnScreen(seasonNumber, episodeNumber)
395
+ ].filter((value) => value !== null);
396
+ }
397
+ function toEpisodeNumberObject(value) {
398
+ if (!value) return { system: "", value: "" };
399
+ return {
400
+ system: typeof value.system === "string" ? value.system : "",
401
+ value: typeof value.value === "string" ? value.value : ""
402
+ };
403
+ }
404
+ function createXMLTVNS(seasonNumber, episodeNumber) {
405
+ if (!episodeNumber) return null;
406
+ seasonNumber = seasonNumber || 1;
407
+ return {
408
+ system: "xmltv_ns",
409
+ value: `${seasonNumber - 1}.${episodeNumber - 1}.0/1`
410
+ };
411
+ }
412
+ function createOnScreen(seasonNumber, episodeNumber) {
413
+ if (!episodeNumber) return null;
414
+ seasonNumber = seasonNumber || 1;
415
+ const seasonNumberString = padStart(seasonNumber.toString(), 2, "0");
416
+ const episodeNumberString = padStart(episodeNumber.toString(), 2, "0");
417
+ return {
418
+ system: "onscreen",
419
+ value: `S${seasonNumberString}E${episodeNumberString}`
420
+ };
421
+ }
422
+ const lang = channel.lang || void 0;
423
+ const program = new Program({
424
+ site: channel.site,
425
+ start: data.start ? toUnix(data.start) : 0,
426
+ stop: data.stop ? toUnix(data.stop) : 0,
427
+ channel: channel.xmltv_id || "",
428
+ titles: toArray(data.titles || data.title).map((value) => toTextObject(value, lang)),
429
+ subTitles: toArray(data.subTitles || data.subTitle || data.sub_titles || data.sub_title).map(
430
+ (value) => toTextObject(value, lang)
431
+ ),
432
+ descriptions: toArray(data.descriptions || data.description || data.desc).map(
433
+ (value) => toTextObject(value, lang)
434
+ ),
435
+ date: data.date ? toUnix(data.date) : 0,
436
+ categories: toArray(data.categories || data.category).map((value) => toTextObject(value, lang)),
437
+ keywords: toArray(data.keywords || data.keyword).map((value) => toTextObject(value, lang)),
438
+ languages: toArray(data.languages || data.language).map((value) => toTextObject(value, lang)),
439
+ origLanguages: toArray(data.origLanguages || data.origLanguage).map(
440
+ (value) => toTextObject(value, lang)
441
+ ),
442
+ length: toArray(data.length).map(toLengthObject),
443
+ urls: toArray(data.urls || data.url).map(toUrlObject),
444
+ countries: toArray(data.countries || data.country).map((value) => toTextObject(value, lang)),
445
+ episodeNumbers: toArray(
446
+ data.episodeNum || data.episodeNumber || data.episodeNumbers || makeEpisodeNumberObjects(data.season, data.episode)
447
+ ).map(toEpisodeNumberObject),
448
+ video: toVideoObject(data.video),
449
+ audio: toAudioObject(data.audio),
450
+ previouslyShown: toArray(data.previouslyShown).map(toPreviouslyShownObject),
451
+ premiere: toArray(data.premiere).map((value) => toTextObject(value, lang)),
452
+ lastChance: toArray(data.lastChance).map((value) => toTextObject(value, lang)),
453
+ new: data.new || false,
454
+ subtitles: toArray(data.subtitles).map(toSubtitlesObject),
455
+ ratings: toArray(data.ratings || data.rating).map(toRatingObject),
456
+ starRatings: toArray(data.starRatings || data.starRating).map(toRatingObject),
457
+ reviews: toArray(data.reviews || data.review).map(toReviewObject),
458
+ directors: toArray(data.directors || data.director).map(toPersonObject),
459
+ actors: toArray(data.actors || data.actor).map(toPersonObject),
460
+ writers: toArray(data.writers || data.writer).map(toPersonObject),
461
+ adapters: toArray(data.adapters || data.adapter).map(toPersonObject),
462
+ producers: toArray(data.producers || data.producer).map(toPersonObject),
463
+ composers: toArray(data.composers || data.composer).map(toPersonObject),
464
+ editors: toArray(data.editors || data.editor).map(toPersonObject),
465
+ presenters: toArray(data.presenters || data.presenter).map(toPersonObject),
466
+ commentators: toArray(data.commentators || data.commentator).map(toPersonObject),
467
+ guests: toArray(data.guests || data.guest).map(toPersonObject),
468
+ images: toArray(data.images || data.image).map(toImageObject),
469
+ icons: toArray(data.icons || data.icon).map(toIconObject)
470
+ });
471
+ return program;
472
+ }
473
+ toXML() {
474
+ const el = createXMLElement;
475
+ function createCastMember(position, person) {
476
+ return el(position, {}, [
477
+ escapeString(person.value),
478
+ ...person.urls.map(createURLElement),
479
+ ...person.images.map(createImageElement)
480
+ ]);
481
+ }
482
+ function createImageElement(image) {
483
+ return el(
484
+ "image",
485
+ {
486
+ type: image.type,
487
+ size: image.size,
488
+ orient: image.orient,
489
+ system: image.system
490
+ },
491
+ [escapeString(image.value)]
492
+ );
493
+ }
494
+ function createURLElement(url) {
495
+ return el("url", { system: url.system }, [escapeString(url.value)]);
496
+ }
497
+ const children = [
498
+ ...this.titles.map((title) => el("title", { lang: title.lang }, [escapeString(title.value)])),
499
+ ...this.subTitles.map(
500
+ (subTitle) => el("sub-title", { lang: subTitle.lang }, [escapeString(subTitle.value)])
501
+ ),
502
+ ...this.descriptions.map((desc) => el("desc", { lang: desc.lang }, [escapeString(desc.value)])),
503
+ el("credits", {}, [
504
+ ...this.directors.map((data) => createCastMember("director", data)),
505
+ ...this.actors.map((data) => createCastMember("actor", data)),
506
+ ...this.writers.map((data) => createCastMember("writer", data)),
507
+ ...this.adapters.map((data) => createCastMember("adapter", data)),
508
+ ...this.producers.map((data) => createCastMember("producer", data)),
509
+ ...this.composers.map((data) => createCastMember("composer", data)),
510
+ ...this.editors.map((data) => createCastMember("editor", data)),
511
+ ...this.presenters.map((data) => createCastMember("presenter", data)),
512
+ ...this.commentators.map((data) => createCastMember("commentator", data)),
513
+ ...this.guests.map((data) => createCastMember("guest", data))
514
+ ]),
515
+ el("date", {}, [formatDate(this.date, "YYYYMMDD")]),
516
+ ...this.categories.map(
517
+ (category) => el("category", { lang: category.lang }, [escapeString(category.value)])
518
+ ),
519
+ ...this.keywords.map(
520
+ (keyword) => el("keyword", { lang: keyword.lang }, [escapeString(keyword.value)])
521
+ ),
522
+ ...this.languages.map(
523
+ (language) => el("language", { lang: language.lang }, [escapeString(language.value)])
524
+ ),
525
+ ...this.origLanguages.map(
526
+ (origLanguage) => el("orig-language", { lang: origLanguage.lang }, [escapeString(origLanguage.value)])
527
+ ),
528
+ ...this.length.map(
529
+ (length) => el("length", { units: length.units }, [escapeString(length.value)])
530
+ ),
531
+ ...this.countries.map(
532
+ (country) => el("country", { lang: country.lang }, [escapeString(country.value)])
533
+ ),
534
+ ...this.urls.map(createURLElement),
535
+ ...this.episodeNumbers.map(
536
+ (episode) => el("episode-num", { system: episode.system }, [escapeString(episode.value)])
537
+ ),
538
+ this.video ? el("video", {}, [
539
+ el("present", {}, typeof this.video.present === "string" ? [this.video.present] : []),
540
+ el("colour", {}, typeof this.video.colour === "string" ? [this.video.colour] : []),
541
+ el("aspect", {}, typeof this.video.aspect === "string" ? [this.video.aspect] : []),
542
+ el("quality", {}, typeof this.video.quality === "string" ? [this.video.quality] : [])
543
+ ]) : null,
544
+ this.audio ? el("audio", {}, [
545
+ el("present", {}, typeof this.audio.present === "string" ? [this.audio.present] : []),
546
+ el("stereo", {}, typeof this.audio.stereo === "string" ? [this.audio.stereo] : [])
547
+ ]) : null,
548
+ ...this.previouslyShown.map(
549
+ (previouslyShown) => el(
550
+ "previously-shown",
551
+ {
552
+ start: previouslyShown.start,
553
+ channel: previouslyShown.channel
554
+ },
555
+ []
556
+ )
557
+ ),
558
+ ...this.premiere.map(
559
+ (premiere) => el("premiere", { lang: premiere.lang }, [escapeString(premiere.value)])
560
+ ),
561
+ ...this.lastChance.map(
562
+ (lastChance) => el("last-chance", { lang: lastChance.lang }, [escapeString(lastChance.value)])
563
+ ),
564
+ this.new ? el("new") : "",
565
+ ...this.subtitles.map(
566
+ (subtitles) => el(
567
+ "subtitles",
568
+ { type: subtitles.type },
569
+ subtitles.language.map(
570
+ (language) => el("language", { lang: language.lang }, [escapeString(language.value)])
571
+ )
572
+ )
573
+ ),
574
+ ...this.ratings.map(
575
+ (rating) => el("rating", { system: rating.system }, [
576
+ el("value", {}, [escapeString(rating.value)]),
577
+ ...rating.icons.map(
578
+ (icon) => el("icon", { src: icon.src, width: icon.width, height: icon.height })
579
+ )
580
+ ])
581
+ ),
582
+ ...this.starRatings.map(
583
+ (rating) => el("star-rating", { system: rating.system }, [
584
+ el("value", {}, [escapeString(rating.value)]),
585
+ ...rating.icons.map(
586
+ (icon) => el("icon", { src: icon.src, width: icon.width, height: icon.height })
587
+ )
588
+ ])
589
+ ),
590
+ ...this.reviews.map(
591
+ (review) => el(
592
+ "review",
593
+ {
594
+ type: review.type,
595
+ source: review.source,
596
+ reviewer: review.reviewer,
597
+ lang: review.lang
598
+ },
599
+ [escapeString(review.value || "")]
600
+ )
601
+ ),
602
+ ...this.images.map(createImageElement),
603
+ ...this.icons.map(
604
+ (icon) => el("icon", { src: icon.src, width: icon.width, height: icon.height })
605
+ )
606
+ ];
607
+ return el(
608
+ "programme",
609
+ {
610
+ start: formatDate(this.start, "YYYYMMDDHHmmss ZZ"),
611
+ stop: formatDate(this.stop, "YYYYMMDDHHmmss ZZ"),
612
+ channel: this.channel
613
+ },
614
+ children
615
+ );
616
+ }
617
+ toObject() {
618
+ return {
619
+ site: this.site,
620
+ start: this.start,
621
+ stop: this.stop,
622
+ channel: this.channel,
623
+ titles: this.titles,
624
+ subTitles: this.subTitles,
625
+ descriptions: this.descriptions,
626
+ date: this.date,
627
+ categories: this.categories,
628
+ keywords: this.keywords,
629
+ languages: this.languages,
630
+ origLanguages: this.origLanguages,
631
+ length: this.length,
632
+ urls: this.urls,
633
+ countries: this.countries,
634
+ episodeNumbers: this.episodeNumbers,
635
+ video: this.video,
636
+ audio: this.audio,
637
+ previouslyShown: this.previouslyShown,
638
+ premiere: this.premiere,
639
+ lastChance: this.lastChance,
640
+ new: this.new,
641
+ subtitles: this.subtitles,
642
+ ratings: this.ratings,
643
+ starRatings: this.starRatings,
644
+ reviews: this.reviews,
645
+ directors: this.directors,
646
+ actors: this.actors,
647
+ writers: this.writers,
648
+ adapters: this.adapters,
649
+ producers: this.producers,
650
+ composers: this.composers,
651
+ editors: this.editors,
652
+ presenters: this.presenters,
653
+ commentators: this.commentators,
654
+ guests: this.guests,
655
+ images: this.images,
656
+ icons: this.icons
657
+ };
658
+ }
659
+ }
660
+
661
+ class Client {
662
+ instance;
663
+ constructor() {
664
+ this.instance = setupCache(axios.create());
665
+ }
666
+ static async buildRequest(context) {
667
+ async function getRequestHeaders(requestContext) {
668
+ const config = requestContext.config;
669
+ if (config.request && typeof config.request.headers === "function") {
670
+ if (typeof config.request.headers === "function") {
671
+ const headers = config.request.headers(requestContext);
672
+ if (isPromise(headers)) {
673
+ return await headers;
674
+ }
675
+ return headers;
676
+ }
677
+ }
678
+ return typeof config.request?.headers === "object" ? config.request?.headers : void 0;
679
+ }
680
+ async function getRequestData(requestContext) {
681
+ const config = requestContext.config;
682
+ if (!config.request) return void 0;
683
+ if (typeof config.request.data === "function") {
684
+ const data = config.request.data(requestContext);
685
+ if (isPromise(data)) {
686
+ return await data;
687
+ }
688
+ return data;
689
+ }
690
+ return config.request?.data;
691
+ }
692
+ async function getRequestUrl(requestContext) {
693
+ const config = requestContext.config;
694
+ if (typeof config.url === "function") {
695
+ const url = config.url(requestContext);
696
+ if (isPromise(url)) {
697
+ return await url;
698
+ }
699
+ return url;
700
+ }
701
+ return config.url;
702
+ }
703
+ const request = context.config.request;
704
+ request.headers = await getRequestHeaders(context);
705
+ request.url = await getRequestUrl(context);
706
+ request.data = await getRequestData(context);
707
+ return request;
708
+ }
709
+ async sendRequest(request) {
710
+ const response = await this.instance(request);
711
+ return this.parseResponse(response);
712
+ }
713
+ parseResponse(response) {
714
+ return {
715
+ content: response.data.toString(),
716
+ buffer: response.data,
717
+ headers: response.headers,
718
+ request: response.request,
719
+ cached: response.cached
720
+ };
721
+ }
722
+ }
723
+
724
+ class EPGGrabber {
725
+ globalConfig = {};
726
+ client;
727
+ constructor(config = {}) {
728
+ this.globalConfig = merge(defaultConfig, config);
729
+ this.client = new Client();
730
+ }
731
+ async loadLogo(channel, date, config = {}) {
732
+ if (!(channel instanceof Channel))
733
+ throw new Error('The first argument must be the "Channel" class');
734
+ config = merge(config, this.globalConfig);
735
+ if (typeof config.logo !== "function") return null;
736
+ const requestContext = { channel, date: getUTCDate(date), config };
737
+ const logo = config.logo(requestContext);
738
+ if (isPromise(logo)) {
739
+ return await logo;
740
+ }
741
+ return logo;
742
+ }
743
+ async grab(channel, date, config = {}, callback = () => {
744
+ }) {
745
+ if (!(channel instanceof Channel))
746
+ throw new Error('The first argument must be the "Channel" class');
747
+ if (typeof config === "function") {
748
+ callback = config;
749
+ config = {};
750
+ }
751
+ const utcDate = getUTCDate(date);
752
+ const requestContext = { channel, date: utcDate, config };
753
+ config = merge(config, this.globalConfig);
754
+ if (!config.parser) throw new Error("Could not find parser() in the config file");
755
+ if (!config.site) throw new Error("The required 'site' property is missing");
756
+ if (!config.url) throw new Error("The required 'url' property is missing");
757
+ if (typeof config.url !== "function" && typeof config.url !== "string")
758
+ throw new Error("The 'url' property should return the function or string");
759
+ if (!config.parser) throw new Error("The required 'parser' function is missing");
760
+ if (typeof config.parser !== "function")
761
+ throw new Error("The 'parser' property should return the function");
762
+ if (config.logo && typeof config.logo !== "function")
763
+ throw new Error("The 'logo' property should return the function");
764
+ try {
765
+ if (typeof config.delay === "number") await sleep(config.delay);
766
+ const request = await Client.buildRequest(requestContext);
767
+ const response = await this.client.sendRequest(request);
768
+ const parserContext = {
769
+ ...response,
770
+ channel,
771
+ date: utcDate,
772
+ config
773
+ };
774
+ let parsedPrograms = config.parser(parserContext);
775
+ if (isPromise(parsedPrograms)) {
776
+ parsedPrograms = await parsedPrograms;
777
+ }
778
+ const programs = parsedPrograms.filter(Boolean).map((data) => Program.fromParserResult(data, channel));
779
+ callback({ channel, date: utcDate, programs }, null);
780
+ return programs;
781
+ } catch (error) {
782
+ callback({ channel, date: utcDate, programs: [] }, error);
783
+ return [];
784
+ }
785
+ }
786
+ static parseChannelsXML(xml) {
787
+ const result = xmlJs.xml2js(xml);
788
+ const siteTag = result.elements.find((element) => element.name === "site") || {};
789
+ const channelsTag = siteTag && Array.isArray(siteTag.elements) ? siteTag.elements.find((element) => element.name === "channels") : result.elements.find((element) => element.name === "channels");
790
+ if (!channelsTag || !channelsTag.elements) return [];
791
+ let site = "";
792
+ if (siteTag && siteTag.attributes && siteTag.attributes.site) {
793
+ site = siteTag.attributes.site;
794
+ } else if (channelsTag && channelsTag.attributes && channelsTag.attributes.site) {
795
+ site = channelsTag.attributes.site;
796
+ }
797
+ const channels = channelsTag.elements.filter((element) => element.name === "channel").map(
798
+ (element, index) => Channel.fromXmlJsElement(site, element, index)
799
+ ).filter(Boolean);
800
+ return channels;
801
+ }
802
+ static generateXMLTV(channels, programs, date = getUTCDate()) {
803
+ if (!channels.every((channel) => channel instanceof Channel)) {
804
+ throw new Error('"channels" must be an array of Channels');
805
+ }
806
+ if (!programs.every((program) => program instanceof Program)) {
807
+ throw new Error('"programs" must be an array of Programs');
808
+ }
809
+ if (!isDate(date)) {
810
+ throw new Error('"date" must be a valid date');
811
+ }
812
+ let output = `<?xml version="1.0" encoding="UTF-8" ?>`;
813
+ output += createXMLElement("tv", { date: formatDate(date, "YYYYMMDD") }, [
814
+ ...channels.map((channel) => "\r\n" + channel.toXML()),
815
+ ...programs.map((program) => "\r\n" + program.toXML()),
816
+ "\r\n"
817
+ ]);
818
+ return output;
819
+ }
820
+ }
821
+ class EPGGrabberMock extends EPGGrabber {
822
+ async grab(channel, date, config = {}, callback = () => {
823
+ }) {
824
+ if (typeof config === "function") {
825
+ callback = config;
826
+ config = {};
827
+ }
828
+ const utcDate = getUTCDate(date);
829
+ config = merge(config, this.globalConfig);
830
+ if (!config.parser) throw new Error("Could not find parser() in the config file");
831
+ const requestContext = { channel, date: utcDate, config };
832
+ try {
833
+ const request = await Client.buildRequest(requestContext);
834
+ const mock = new AxiosMockAdapter(this.client.instance);
835
+ mock.onAny().reply(200, Buffer.from(JSON.stringify([]), "utf8"));
836
+ const response = await this.client.sendRequest(request);
837
+ const parserContext = {
838
+ ...response,
839
+ channel,
840
+ date: utcDate,
841
+ config
842
+ };
843
+ let parsedPrograms = config.parser(parserContext);
844
+ if (isPromise(parsedPrograms)) {
845
+ parsedPrograms = await parsedPrograms;
846
+ }
847
+ const programs = parsedPrograms.filter(Boolean).map((data) => Program.fromParserResult(data, channel));
848
+ callback({ channel, date: utcDate, programs }, null);
849
+ return programs;
850
+ } catch (error) {
851
+ callback({ channel, date: utcDate, programs: [] }, error);
852
+ return [];
853
+ }
854
+ }
855
+ }
856
+
857
+ export { Channel as C, EPGGrabberMock as E, Program as P, parseProxy as a, EPGGrabber as b, getUTCDate as c, description as d, getAbsPath as g, isObject as i, loadJs as l, name as n, parseNumber as p, version as v };