epg-grabber 0.25.4 → 0.27.1

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.4",
3
+ "version": "0.27.1",
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 = utils.escapeString(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
  }
@@ -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 &amp; 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,7 @@ 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>'
95
106
  )
96
107
  })
97
108
 
@@ -113,7 +124,7 @@ it('can convert object to xmltv string without episode number', () => {
113
124
  ]
114
125
  const output = utils.convertToXMLTV({ channels, programs })
115
126
  expect(output).toBe(
116
- '<?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><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/></programme>\r\n</tv>'
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>'
117
128
  )
118
129
  })
119
130
 
@@ -140,7 +151,7 @@ it('can convert object to xmltv string without categories', () => {
140
151
  const config = { site: 'example.com' }
141
152
  const output = utils.convertToXMLTV({ config, channels, programs })
142
153
  expect(output).toBe(
143
- '<?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>'
144
155
  )
145
156
  })
146
157
 
@@ -161,7 +172,110 @@ it('can convert object to xmltv string with multiple categories', () => {
161
172
  ]
162
173
  const output = utils.convertToXMLTV({ channels, programs })
163
174
  expect(output).toBe(
164
- '<?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>'
165
279
  )
166
280
  })
167
281