epg-grabber 0.25.3 → 0.27.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.
package/README.md CHANGED
@@ -80,7 +80,7 @@ Arguments:
80
80
  - `--days`: number of days for which to grab the program (default: 1)
81
81
  - `--delay`: delay between requests (default: 3000)
82
82
  - `--timeout`: set a timeout for each request (default: 5000)
83
- - `--cache-max-age`: maximum time for storing each request in milliseconds (default: 0)
83
+ - `--cache-ttl`: maximum time for storing each request in milliseconds (default: 0)
84
84
  - `--gzip`: compress the output (default: false)
85
85
  - `--debug`: enable debug mode (default: false)
86
86
  - `--curl`: display current request as CURL (default: false)
@@ -102,12 +102,8 @@ module.exports = {
102
102
 
103
103
  method: 'GET',
104
104
  timeout: 5000,
105
- cache: { // cache options (details: https://github.com/RasCarlito/axios-cache-adapter#options)
106
- maxAge: 0,
107
- readHeaders: false,
108
- exclude: {
109
- query: false
110
- }
105
+ cache: { // cache options (details: https://axios-cache-interceptor.js.org/#/pages/per-request-configuration)
106
+ ttl: 60 * 1000 // 60s
111
107
  },
112
108
 
113
109
  /**
@@ -192,6 +188,8 @@ From each function in `config.js` you can access a `context` object containing t
192
188
  - `content`: The response data as a String
193
189
  - `buffer`: The response data as an ArrayBuffer
194
190
  - `headers`: The response headers
191
+ - `request`: The request config
192
+ - `cached`: A boolean to check whether this request was cached or not
195
193
 
196
194
  ## Channels List
197
195
 
@@ -24,7 +24,7 @@ program
24
24
  .option('--delay <delay>', 'Delay between requests (in mileseconds)', parseInteger)
25
25
  .option('--timeout <timeout>', 'Set a timeout for each request (in mileseconds)', parseInteger)
26
26
  .option(
27
- '--cache-max-age <cacheMaxAge>',
27
+ '--cache-ttl <cacheTtl>',
28
28
  'Maximum time for storing each request (in milliseconds)',
29
29
  parseInteger
30
30
  )
@@ -76,14 +76,11 @@ async function main() {
76
76
  curl: options.curl,
77
77
  lang: options.lang,
78
78
  delay: options.delay,
79
- request: {
80
- timeout: options.timeout,
81
- cache: {
82
- maxAge: options.cacheMaxAge
83
- }
84
- }
79
+ request: {}
85
80
  })
86
81
 
82
+ if (options.timeout) config.request.timeout = options.timeout
83
+ if (options.cacheTtl) config.request.cache.ttl = options.cacheTtl
87
84
  if (options.channels) config.channels = options.channels
88
85
  else if (config.channels)
89
86
  config.channels = path.join(path.dirname(options.config), config.channels)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "epg-grabber",
3
- "version": "0.25.3",
3
+ "version": "0.27.0",
4
4
  "description": "Node.js CLI tool for grabbing EPG from different sites",
5
5
  "main": "src/index.js",
6
6
  "preferGlobal": true,
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "axios": "^0.21.1",
32
- "axios-cache-adapter": "^2.7.3",
32
+ "axios-cache-interceptor": "^0.10.3",
33
33
  "axios-cookiejar-support": "^1.0.1",
34
34
  "axios-mock-adapter": "^1.20.0",
35
35
  "commander": "^7.1.0",
package/src/utils.js CHANGED
@@ -3,7 +3,7 @@ const { padStart } = require('lodash')
3
3
  const path = require('path')
4
4
  const axios = require('axios').default
5
5
  const axiosCookieJarSupport = require('axios-cookiejar-support').default
6
- const axiosCacheAdapter = require('axios-cache-adapter')
6
+ const { setupCache } = require('axios-cache-interceptor')
7
7
  const tough = require('tough-cookie')
8
8
  const convert = require('xml-js')
9
9
  const { merge } = require('lodash')
@@ -41,13 +41,7 @@ utils.loadConfig = function (config) {
41
41
  withCredentials: true,
42
42
  jar: new tough.CookieJar(),
43
43
  responseType: 'arraybuffer',
44
- cache: {
45
- readHeaders: false,
46
- exclude: {
47
- query: false
48
- },
49
- maxAge: 0
50
- }
44
+ cache: false
51
45
  }
52
46
  }
53
47
 
@@ -55,7 +49,7 @@ utils.loadConfig = function (config) {
55
49
  }
56
50
 
57
51
  utils.createClient = function (config) {
58
- const client = axiosCacheAdapter.setup()
52
+ const client = setupCache(axios.create())
59
53
  client.interceptors.request.use(
60
54
  function (request) {
61
55
  if (config.debug) {
@@ -80,7 +74,7 @@ utils.createClient = function (config) {
80
74
  {
81
75
  headers: response.headers,
82
76
  data,
83
- fromCache: response.request.fromCache === true
77
+ cached: response.cached
84
78
  },
85
79
  null,
86
80
  2
@@ -156,7 +150,9 @@ utils.escapeString = function (string, defaultValue = '') {
156
150
  }
157
151
 
158
152
  utils.convertToXMLTV = function ({ channels, programs }) {
159
- let output = `<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n`
153
+ let output = `<?xml version="1.0" encoding="UTF-8" ?><tv date="${dayjs
154
+ .utc()
155
+ .format('YYYYMMDD')}">\r\n`
160
156
  for (let channel of channels) {
161
157
  const id = utils.escapeString(channel['xmltv_id'])
162
158
  const displayName = utils.escapeString(channel.name)
@@ -184,11 +180,30 @@ utils.convertToXMLTV = function ({ channels, programs }) {
184
180
  const lang = program.lang || 'en'
185
181
  const xmltv_ns = createXMLTVNS(program.season, program.episode)
186
182
  const onscreen = createOnScreen(program.season, program.episode)
183
+ const date = program.date || ''
184
+ const credits = createCredits({
185
+ director: program.director,
186
+ actor: program.actor,
187
+ writer: program.writer,
188
+ adapter: program.adapter,
189
+ producer: program.producer,
190
+ composer: program.composer,
191
+ editor: program.editor,
192
+ presenter: program.presenter,
193
+ commentator: program.commentator,
194
+ guest: program.guest
195
+ })
187
196
  const icon = utils.escapeString(program.icon)
197
+ const sub_title = program.sub_title || ''
198
+ const url = program.url ? createURL(program.url, channel) : ''
188
199
 
189
200
  if (start && stop && title) {
190
201
  output += `<programme start="${start}" stop="${stop}" channel="${channel}"><title lang="${lang}">${title}</title>`
191
202
 
203
+ if (sub_title) {
204
+ output += `<sub-title>${sub_title}</sub-title>`
205
+ }
206
+
192
207
  if (description) {
193
208
  output += `<desc lang="${lang}">${description}</desc>`
194
209
  }
@@ -201,6 +216,10 @@ utils.convertToXMLTV = function ({ channels, programs }) {
201
216
  })
202
217
  }
203
218
 
219
+ if (url) {
220
+ output += url
221
+ }
222
+
204
223
  if (xmltv_ns) {
205
224
  output += `<episode-num system="xmltv_ns">${xmltv_ns}</episode-num>`
206
225
  }
@@ -208,11 +227,18 @@ utils.convertToXMLTV = function ({ channels, programs }) {
208
227
  if (onscreen) {
209
228
  output += `<episode-num system="onscreen">${onscreen}</episode-num>`
210
229
  }
230
+ if (date) {
231
+ output += `<date>${date}</date>`
232
+ }
211
233
 
212
234
  if (icon) {
213
235
  output += `<icon src="${icon}"/>`
214
236
  }
215
237
 
238
+ if (credits) {
239
+ output += `<credits>${credits}</credits>`
240
+ }
241
+
216
242
  output += '</programme>\r\n'
217
243
  }
218
244
  }
@@ -220,14 +246,14 @@ utils.convertToXMLTV = function ({ channels, programs }) {
220
246
  output += '</tv>'
221
247
 
222
248
  function createXMLTVNS(s, e) {
223
- if (!s && !e) return null
249
+ if (!e) return null
224
250
  s = s || 1
225
251
 
226
252
  return `${s - 1}.${e - 1}.0/1`
227
253
  }
228
254
 
229
255
  function createOnScreen(s, e) {
230
- if (!s && !e) return null
256
+ if (!e) return null
231
257
  s = s || 1
232
258
 
233
259
  s = padStart(s, 2, '0')
@@ -236,6 +262,105 @@ utils.convertToXMLTV = function ({ channels, programs }) {
236
262
  return `S${s}E${e}`
237
263
  }
238
264
 
265
+ function createURL(urlObj, channel = '') {
266
+ const urls = Array.isArray(urlObj) ? urlObj : [urlObj]
267
+ let output = ''
268
+ for (let url of urls) {
269
+ if (typeof url === 'string' || url instanceof String) {
270
+ url = { value: url }
271
+ }
272
+
273
+ let attr = url.system ? ` system="${url.system}"` : ''
274
+ if (url.value.includes('http')) {
275
+ output += `<url${attr}>${url.value}</url>`
276
+ } else if (channel) {
277
+ let chan = channels.find(c => c.xmltv_id.localeCompare(channel) === 0)
278
+ if (chan && chan.site) {
279
+ output += `<url${attr}>https://${chan.site}${url.value}</url>`
280
+ }
281
+ }
282
+ }
283
+
284
+ return output
285
+ }
286
+
287
+ function createImage(imgObj, channel = '') {
288
+ const imgs = Array.isArray(imgObj) ? imgObj : [imgObj]
289
+ let output = ''
290
+ for (let img of imgs) {
291
+ if (typeof img === 'string' || img instanceof String) {
292
+ img = { value: img }
293
+ }
294
+
295
+ const imageTypes = ['poster', 'backdrop', 'still', 'person', 'character']
296
+ const imageSizes = ['1', '2', '3']
297
+ const imageOrients = ['P', 'L']
298
+
299
+ let attr = ''
300
+
301
+ if (img.type && imageTypes.some(el => img.type.includes(el))) {
302
+ attr += ` type="${img.type}"`
303
+ }
304
+
305
+ if (img.size && imageSizes.some(el => img.size.includes(el))) {
306
+ attr += ` size="${img.size}"`
307
+ }
308
+
309
+ if (img.orient && imageOrients.some(el => img.orient.includes(el))) {
310
+ attr += ` orient="${img.orient}"`
311
+ }
312
+
313
+ if (img.system) {
314
+ attr += ` system="${img.system}"`
315
+ }
316
+
317
+ if (img.value.includes('http')) {
318
+ output += `<image${attr}>${img.value}</image>`
319
+ } else if (channel) {
320
+ let chan = channels.find(c => c.xmltv_id.localeCompare(channel) === 0)
321
+ if (chan && chan.site) {
322
+ output += `<image${attr}>https://${chan.site}${img.value}</image>`
323
+ }
324
+ }
325
+ }
326
+
327
+ return output
328
+ }
329
+
330
+ function createCredits(obj) {
331
+ let cast = Object.entries(obj)
332
+ .filter(x => x[1])
333
+ .map(([name, value]) => ({ name, value }))
334
+
335
+ let output = ''
336
+ for (let type of cast) {
337
+ const r = Array.isArray(type.value) ? type.value : [type.value]
338
+ for (let person of r) {
339
+ if (typeof person === 'string' || person instanceof String) {
340
+ person = { value: person }
341
+ }
342
+
343
+ let attr = ''
344
+ if (type.name.localeCompare('actor') === 0 && type.value.role) {
345
+ attr += ` role="${type.value.role}"`
346
+ }
347
+ if (type.name.localeCompare('actor') === 0 && type.value.guest) {
348
+ attr += ` guest="${type.value.guest}"`
349
+ }
350
+ output += `<${type.name}${attr}>${person.value}`
351
+
352
+ if (person.url) {
353
+ output += createURL(person.url)
354
+ }
355
+ if (person.image) {
356
+ output += createImage(person.image)
357
+ }
358
+
359
+ output += `</${type.name}>`
360
+ }
361
+ }
362
+ return output
363
+ }
239
364
  return output
240
365
  }
241
366
 
@@ -318,20 +443,12 @@ utils.getUTCDate = function (d = null) {
318
443
  }
319
444
 
320
445
  utils.parseResponse = async (item, response, config) => {
321
- let buffer
322
- let content
323
- if (utils.isObject(response.data) || Array.isArray(response.data)) {
324
- content = JSON.stringify(response.data)
325
- buffer = Buffer.from(content, 'utf8')
326
- } else {
327
- content = response.data.toString()
328
- buffer = response.data
329
- }
330
446
  const data = merge(item, config, {
331
- content,
332
- buffer,
447
+ content: response.data.toString(),
448
+ buffer: response.data,
333
449
  headers: response.headers,
334
- request: response.request
450
+ request: response.request,
451
+ cached: response.cached
335
452
  })
336
453
 
337
454
  if (!item.channel.logo && config.logo) {
@@ -362,11 +479,24 @@ utils.parsePrograms = async function (data, config) {
362
479
  category: program.category || null,
363
480
  season: program.season || null,
364
481
  episode: program.episode || null,
482
+ sub_title: program.sub_title || null,
483
+ url: program.url || null,
365
484
  icon: program.icon || null,
366
485
  channel: channel.xmltv_id,
367
486
  lang: program.lang || channel.lang || config.lang || 'en',
368
487
  start: program.start ? dayjs(program.start).unix() : null,
369
- stop: program.stop ? dayjs(program.stop).unix() : null
488
+ stop: program.stop ? dayjs(program.stop).unix() : null,
489
+ date: program.date || null,
490
+ director: program.director || null,
491
+ actor: program.actor || null,
492
+ writer: program.writer || null,
493
+ adapter: program.adapter || null,
494
+ producer: program.producer || null,
495
+ composer: program.composer || null,
496
+ editor: program.editor || null,
497
+ presenter: program.presenter || null,
498
+ commentator: program.commentator || null,
499
+ guest: program.guest || null
370
500
  }
371
501
  })
372
502
  }
@@ -4,6 +4,8 @@ import axios from 'axios'
4
4
  import path from 'path'
5
5
  import fs from 'fs'
6
6
 
7
+ jest.useFakeTimers('modern').setSystemTime(new Date('2022-05-05'))
8
+
7
9
  it('can load valid config.js', () => {
8
10
  const config = utils.loadConfig(require(path.resolve('./tests/input/example.com.config.js')))
9
11
  expect(config).toMatchObject({
@@ -56,7 +58,9 @@ it('can convert object to xmltv string', () => {
56
58
  const programs = [
57
59
  {
58
60
  title: 'Program 1',
61
+ sub_title: 'Sub-title 1',
59
62
  description: 'Description for Program 1',
63
+ url: 'http://example.com/title.html',
60
64
  start: 1616133600,
61
65
  stop: 1616135400,
62
66
  category: 'Test',
@@ -64,12 +68,19 @@ it('can convert object to xmltv string', () => {
64
68
  episode: 239,
65
69
  icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
66
70
  channel: '1TV.com',
67
- lang: 'it'
71
+ lang: 'it',
72
+ date: '20220505',
73
+ director: {
74
+ value: 'Director 1',
75
+ url: { value: 'http://example.com/director1.html', system: 'TestSystem' }
76
+ },
77
+ actor: ['Actor 1', 'Actor 2'],
78
+ writer: 'Writer 1'
68
79
  }
69
80
  ]
70
81
  const output = utils.convertToXMLTV({ channels, programs })
71
82
  expect(output).toBe(
72
- '<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><episode-num system="xmltv_ns">8.238.0/1</episode-num><episode-num system="onscreen">S09E239</episode-num><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
83
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><sub-title>Sub-title 1</sub-title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><url>http://example.com/title.html</url><episode-num system="xmltv_ns">8.238.0/1</episode-num><episode-num system="onscreen">S09E239</episode-num><date>20220505</date><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/><credits><director>Director 1<url system="TestSystem">http://example.com/director1.html</url></director><actor>Actor 1</actor><actor>Actor 2</actor><writer>Writer 1</writer></credits></programme>\r\n</tv>'
73
84
  )
74
85
  })
75
86
 
@@ -91,7 +102,29 @@ it('can convert object to xmltv string without season number', () => {
91
102
  ]
92
103
  const output = utils.convertToXMLTV({ channels, programs })
93
104
  expect(output).toBe(
94
- '<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><episode-num system="xmltv_ns">0.238.0/1</episode-num><episode-num system="onscreen">S01E239</episode-num><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
105
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><episode-num system="xmltv_ns">0.238.0/1</episode-num><episode-num system="onscreen">S01E239</episode-num><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
106
+ )
107
+ })
108
+
109
+ it('can convert object to xmltv string without episode number', () => {
110
+ const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
111
+ const { channels } = utils.parseChannels(file)
112
+ const programs = [
113
+ {
114
+ title: 'Program 1',
115
+ description: 'Description for Program 1',
116
+ start: 1616133600,
117
+ stop: 1616135400,
118
+ category: 'Test',
119
+ season: 1,
120
+ icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
121
+ channel: '1TV.com',
122
+ lang: 'it'
123
+ }
124
+ ]
125
+ const output = utils.convertToXMLTV({ channels, programs })
126
+ expect(output).toBe(
127
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
95
128
  )
96
129
  })
97
130
 
@@ -118,7 +151,7 @@ it('can convert object to xmltv string without categories', () => {
118
151
  const config = { site: 'example.com' }
119
152
  const output = utils.convertToXMLTV({ config, channels, programs })
120
153
  expect(output).toBe(
121
- '<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title></programme>\r\n</tv>'
154
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title></programme>\r\n</tv>'
122
155
  )
123
156
  })
124
157
 
@@ -139,7 +172,110 @@ it('can convert object to xmltv string with multiple categories', () => {
139
172
  ]
140
173
  const output = utils.convertToXMLTV({ channels, programs })
141
174
  expect(output).toBe(
142
- '<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test1</category><category lang="it">Test2</category><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
175
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test1</category><category lang="it">Test2</category><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
176
+ )
177
+ })
178
+
179
+ it('can convert object to xmltv string with multiple urls', () => {
180
+ const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
181
+ const { channels } = utils.parseChannels(file)
182
+ const programs = [
183
+ {
184
+ title: 'Program 1',
185
+ description: 'Description for Program 1',
186
+ start: 1616133600,
187
+ stop: 1616135400,
188
+ category: ['Test1', 'Test2'],
189
+ url: [
190
+ 'https://example.com/noattr.html',
191
+ { value: 'https://example.com/attr.html', system: 'TestSystem' }
192
+ ],
193
+ icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
194
+ channel: '1TV.com',
195
+ lang: 'it'
196
+ }
197
+ ]
198
+ const output = utils.convertToXMLTV({ channels, programs })
199
+ expect(output).toBe(
200
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test1</category><category lang="it">Test2</category><url>https://example.com/noattr.html</url><url system="TestSystem">https://example.com/attr.html</url><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
201
+ )
202
+ })
203
+
204
+ it('can convert object to xmltv string with multiple images', () => {
205
+ const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
206
+ const { channels } = utils.parseChannels(file)
207
+ const programs = [
208
+ {
209
+ title: 'Program 1',
210
+ description: 'Description for Program 1',
211
+ start: 1616133600,
212
+ stop: 1616135400,
213
+ category: ['Test1', 'Test2'],
214
+ url: [
215
+ 'https://example.com/noattr.html',
216
+ { value: 'https://example.com/attr.html', system: 'TestSystem' }
217
+ ],
218
+ actor: {
219
+ value: 'Actor 1',
220
+ image: [
221
+ 'https://example.com/image1.jpg',
222
+ {
223
+ value: 'https://example.com/image2.jpg',
224
+ type: 'person',
225
+ size: '2',
226
+ system: 'TestSystem',
227
+ orient: 'P'
228
+ }
229
+ ]
230
+ },
231
+ icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
232
+ channel: '1TV.com',
233
+ lang: 'it'
234
+ }
235
+ ]
236
+ const output = utils.convertToXMLTV({ channels, programs })
237
+ expect(output).toBe(
238
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><desc lang="it">Description for Program 1</desc><category lang="it">Test1</category><category lang="it">Test2</category><url>https://example.com/noattr.html</url><url system="TestSystem">https://example.com/attr.html</url><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/><credits><actor>Actor 1<image>https://example.com/image1.jpg</image><image type="person" size="2" orient="P" system="TestSystem">https://example.com/image2.jpg</image></actor></credits></programme>\r\n</tv>'
239
+ )
240
+ })
241
+
242
+ it('can convert object to xmltv string with multiple credits member', () => {
243
+ const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
244
+ const { channels } = utils.parseChannels(file)
245
+ const programs = [
246
+ {
247
+ title: 'Program 1',
248
+ sub_title: 'Sub-title 1',
249
+ description: 'Description for Program 1',
250
+ url: 'http://example.com/title.html',
251
+ start: 1616133600,
252
+ stop: 1616135400,
253
+ category: 'Test',
254
+ season: 9,
255
+ episode: 239,
256
+ icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
257
+ channel: '1TV.com',
258
+ lang: 'it',
259
+ date: '20220505',
260
+ director: {
261
+ value: 'Director 1',
262
+ url: { value: 'http://example.com/director1.html', system: 'TestSystem' }
263
+ },
264
+ actor: {
265
+ value: 'Actor 1',
266
+ role: 'Manny',
267
+ guest: 'yes',
268
+ url: { value: 'http://example.com/actor1.html', system: 'TestSystem' }
269
+ },
270
+ writer: [
271
+ { value: 'Writer 1', url: { value: 'http://example.com/w1.html', system: 'TestSystem' } },
272
+ { value: 'Writer 2', url: { value: 'http://example.com/w2.html', system: 'TestSystem' } }
273
+ ]
274
+ }
275
+ ]
276
+ const output = utils.convertToXMLTV({ channels, programs })
277
+ expect(output).toBe(
278
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.com"><title lang="it">Program 1</title><sub-title>Sub-title 1</sub-title><desc lang="it">Description for Program 1</desc><category lang="it">Test</category><url>http://example.com/title.html</url><episode-num system="xmltv_ns">8.238.0/1</episode-num><episode-num system="onscreen">S09E239</episode-num><date>20220505</date><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/><credits><director>Director 1<url system="TestSystem">http://example.com/director1.html</url></director><actor role="Manny" guest="yes">Actor 1<url system="TestSystem">http://example.com/actor1.html</url></actor><writer>Writer 1<url system="TestSystem">http://example.com/w1.html</url></writer><writer>Writer 2<url system="TestSystem">http://example.com/w2.html</url></writer></credits></programme>\r\n</tv>'
143
279
  )
144
280
  })
145
281
 
@@ -171,7 +307,7 @@ it('can fetch data', () => {
171
307
  url: 'http://example.com/20210319/1tv.json',
172
308
  withCredentials: true
173
309
  }
174
- utils.fetchData(axios, request).then(jest.fn).catch(jest.fn)
310
+ utils.fetchData(mockAxios, request).then(jest.fn).catch(jest.fn)
175
311
  expect(mockAxios).toHaveBeenCalledWith(
176
312
  expect.objectContaining({
177
313
  data: { accountID: '123' },
@@ -192,37 +328,46 @@ it('can fetch data', () => {
192
328
 
193
329
  it('can build request async', done => {
194
330
  const config = utils.loadConfig(require(path.resolve('./tests/input/async.config.js')))
195
- return utils.buildRequest({}, config).then(request => {
196
- expect(request).toMatchObject({
197
- data: { accountID: '123' },
198
- headers: {
199
- 'Content-Type': 'application/json',
200
- Cookie: 'abc=123',
201
- 'User-Agent':
202
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71'
203
- },
204
- maxContentLength: 5242880,
205
- method: 'POST',
206
- responseType: 'arraybuffer',
207
- timeout: 5000,
208
- url: 'http://example.com/20210319/1tv.json',
209
- withCredentials: true
331
+
332
+ utils
333
+ .buildRequest({}, config)
334
+ .then(request => {
335
+ expect(request).toMatchObject({
336
+ data: { accountID: '123' },
337
+ headers: {
338
+ 'Content-Type': 'application/json',
339
+ Cookie: 'abc=123',
340
+ 'User-Agent':
341
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71'
342
+ },
343
+ maxContentLength: 5242880,
344
+ method: 'POST',
345
+ responseType: 'arraybuffer',
346
+ timeout: 5000,
347
+ url: 'http://example.com/20210319/1tv.json',
348
+ withCredentials: true
349
+ })
350
+ done()
210
351
  })
211
- done()
212
- })
352
+ .catch(done)
213
353
  })
214
354
 
215
355
  it('can load logo async', done => {
216
356
  const config = utils.loadConfig(require(path.resolve('./tests/input/async.config.js')))
217
- return utils.loadLogo({}, config).then(logo => {
218
- expect(logo).toBe('http://example.com/logos/1TV.png?x=шеллы&sid=777')
219
- done()
220
- })
357
+
358
+ utils
359
+ .loadLogo({}, config)
360
+ .then(logo => {
361
+ expect(logo).toBe('http://example.com/logos/1TV.png?x=шеллы&sid=777')
362
+ done()
363
+ })
364
+ .catch(done)
221
365
  })
222
366
 
223
367
  it('can parse programs', done => {
224
368
  const config = utils.loadConfig(require(path.resolve('./tests/input/example.com.config.js')))
225
- return utils
369
+
370
+ utils
226
371
  .parsePrograms({ channel: { xmltv_id: '1tv', lang: 'en' } }, config)
227
372
  .then(programs => {
228
373
  expect(programs).toMatchObject([
@@ -240,14 +385,17 @@ it('can parse programs', done => {
240
385
  ])
241
386
  done()
242
387
  })
388
+ .catch(done)
243
389
  })
244
390
 
245
391
  it('can parse programs async', done => {
246
392
  const config = utils.loadConfig(require(path.resolve('./tests/input/async.config.js')))
247
- return utils
393
+
394
+ utils
248
395
  .parsePrograms({ channel: { xmltv_id: '1tv', lang: 'en' } }, config)
249
396
  .then(programs => {
250
397
  expect(programs.length).toBe(0)
251
398
  done()
252
399
  })
400
+ .catch(done)
253
401
  })