epg-grabber 0.29.8 → 0.30.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.
@@ -0,0 +1,15 @@
1
+ name: test
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+ workflow_dispatch:
7
+ pull_request:
8
+ types: [opened, synchronize, reopened, edited]
9
+ jobs:
10
+ main:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v3
14
+ - run: npm install
15
+ - run: npm test
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # EPG Grabber [![Build Status](https://app.travis-ci.com/freearhey/epg-grabber.svg?branch=master)](https://app.travis-ci.com/freearhey/epg-grabber)
1
+ # EPG Grabber [![test](https://github.com/freearhey/epg-grabber/actions/workflows/test.yml/badge.svg)](https://github.com/freearhey/epg-grabber/actions/workflows/test.yml)
2
2
 
3
3
  Node.js CLI tool for grabbing EPG from different websites.
4
4
 
@@ -74,25 +74,25 @@ epg-grabber --config=example.com.config.js
74
74
  Arguments:
75
75
 
76
76
  - `-c, --config`: path to config file
77
- - `-o, --output`: path to output file (default: 'guide.xml')
78
- - `--channels`: path to list of channels (can be specified via config file)
79
- - `--lang`: set default language for all programs (default: 'en')
80
- - `--days`: number of days for which to grab the program (default: 1)
81
- - `--delay`: delay between requests in milliseconds (default: 3000)
82
- - `--timeout`: set a timeout for each request in milliseconds (default: 5000)
83
- - `--cache-ttl`: maximum time for storing each request in milliseconds (default: 0)
84
- - `--gzip`: compress the output (default: false)
85
- - `--debug`: enable debug mode (default: false)
86
- - `--curl`: display current request as CURL (default: false)
77
+ - `-o, --output`: path to output file or path template (example: `guides/{site}.{lang}.xml`; default: `guide.xml`)
78
+ - `--channels`: path to list of channels
79
+ - `--lang`: set default language for all programs (default: `en`)
80
+ - `--days`: number of days for which to grab the program (default: `1`)
81
+ - `--delay`: delay between requests in milliseconds (default: `3000`)
82
+ - `--timeout`: set a timeout for each request in milliseconds (default: `5000`)
83
+ - `--cache-ttl`: maximum time for storing each request in milliseconds (default: `0`)
84
+ - `--gzip`: compress the output (default: `false`)
85
+ - `--debug`: enable debug mode (default: `false`)
86
+ - `--curl`: display current request as CURL (default: `false`)
87
87
  - `--log`: path to log file (optional)
88
- - `--log-level`: set the log level (default: 'info')
88
+ - `--log-level`: set the log level (default: `info`)
89
89
 
90
90
  ## Site Config
91
91
 
92
92
  ```js
93
93
  module.exports = {
94
94
  site: 'example.com', // site domain name (required)
95
- output: 'example.com.guide.xml', // path to output file (default: 'guide.xml')
95
+ output: 'example.com.guide.xml', // path to output file or path template (example: 'guides/{site}.{lang}.xml'; default: 'guide.xml')
96
96
  channels: 'example.com.channels.xml', // path to channels.xml file (required)
97
97
  lang: 'fr', // default language for all programs (default: 'en')
98
98
  days: 3, // number of days for which to grab the program (default: 1)
@@ -235,6 +235,34 @@ You can also specify the language and logo for each channel individually, like s
235
235
  >France 24</channel>
236
236
  ```
237
237
 
238
+ ## How to use SOCKS proxy?
239
+
240
+ First, you need to install [socks-proxy-agent](https://www.npmjs.com/package/socks-proxy-agent):
241
+
242
+ ```sh
243
+ npm install socks-proxy-agent
244
+ ```
245
+
246
+ Then you can use it to create an agent that acts as a SOCKS proxy. Here is an example of how to do it with the Tor SOCKS proxy:
247
+
248
+ ```js
249
+ const { SocksProxyAgent } = require('socks-proxy-agent')
250
+
251
+ const torProxyAgent = new SocksProxyAgent('socks://localhost:9050')
252
+
253
+ module.exports = {
254
+ site: 'example.com',
255
+ url: 'https://example.com/epg.json',
256
+ request: {
257
+ httpsAgent: torProxyAgent,
258
+ httpAgent: torProxyAgent
259
+ },
260
+ parser(context) {
261
+ // ...
262
+ }
263
+ }
264
+ ```
265
+
238
266
  ## Contribution
239
267
 
240
268
  If you find a bug or want to contribute to the code or documentation, you can help by submitting an [issue](https://github.com/freearhey/epg-grabber/issues) or a [pull request](https://github.com/freearhey/epg-grabber/pulls).
@@ -68,52 +68,70 @@ async function main() {
68
68
  const grabber = new EPGGrabber(config)
69
69
 
70
70
  const channelsXML = file.read(config.channels)
71
- const { channels } = parseChannels(channelsXML)
72
-
73
- let programs = []
74
- let i = 1
75
- let days = config.days || 1
76
- const total = channels.length * days
77
- const utcDate = getUTCDate()
78
- const dates = Array.from({ length: days }, (_, i) => utcDate.add(i, 'd'))
79
- for (let channel of channels) {
80
- if (!channel.logo && config.logo) {
81
- channel.logo = await grabber.loadLogo(channel)
71
+ const { channels: parsedChannels } = parseChannels(channelsXML)
72
+
73
+ let template = options.output || config.output
74
+ const variables = file.templateVariables(template)
75
+
76
+ const groups = _.groupBy(parsedChannels, channel => {
77
+ let groupId = ''
78
+ for (let key in channel) {
79
+ if (variables.includes(key)) {
80
+ groupId += channel[key]
81
+ }
82
82
  }
83
83
 
84
- for (let date of dates) {
85
- await grabber
86
- .grab(channel, date, (data, err) => {
87
- logger.info(
88
- `[${i}/${total}] ${config.site} - ${data.channel.id} - ${dayjs
89
- .utc(data.date)
90
- .format('MMM D, YYYY')} (${data.programs.length} programs)`
91
- )
92
-
93
- if (err) logger.error(err.message)
94
-
95
- if (i < total) i++
96
- })
97
- .then(results => {
98
- programs = programs.concat(results)
99
- })
84
+ return groupId
85
+ })
86
+
87
+ for (let groupId in groups) {
88
+ const channels = groups[groupId]
89
+ let programs = []
90
+ let i = 1
91
+ let days = config.days || 1
92
+ const total = channels.length * days
93
+ const utcDate = getUTCDate()
94
+ const dates = Array.from({ length: days }, (_, i) => utcDate.add(i, 'd'))
95
+ for (let channel of channels) {
96
+ if (!channel.logo && config.logo) {
97
+ channel.logo = await grabber.loadLogo(channel)
98
+ }
99
+
100
+ for (let date of dates) {
101
+ await grabber
102
+ .grab(channel, date, (data, err) => {
103
+ logger.info(
104
+ `[${i}/${total}] ${config.site} - ${data.channel.id} - ${dayjs
105
+ .utc(data.date)
106
+ .format('MMM D, YYYY')} (${data.programs.length} programs)`
107
+ )
108
+
109
+ if (err) logger.error(err.message)
110
+
111
+ if (i < total) i++
112
+ })
113
+ .then(results => {
114
+ programs = programs.concat(results)
115
+ })
116
+ }
117
+ }
118
+
119
+ programs = _.uniqBy(programs, p => p.start + p.channel)
120
+
121
+ const xml = generateXMLTV({ channels, programs })
122
+ let outputPath = file.templateFormat(template, channels[0])
123
+ if (options.gzip) {
124
+ outputPath = outputPath || 'guide.xml.gz'
125
+ const compressed = await gzip(xml)
126
+ file.write(outputPath, compressed)
127
+ } else {
128
+ outputPath = outputPath || 'guide.xml'
129
+ file.write(outputPath, xml)
100
130
  }
101
- }
102
131
 
103
- programs = _.uniqBy(programs, p => p.start + p.channel)
104
-
105
- const xml = generateXMLTV({ channels, programs })
106
- let outputPath = options.output || config.output
107
- if (options.gzip) {
108
- outputPath = outputPath || 'guide.xml.gz'
109
- const compressed = await gzip(xml)
110
- file.write(outputPath, compressed)
111
- } else {
112
- outputPath = outputPath || 'guide.xml'
113
- file.write(outputPath, xml)
132
+ logger.info(`File '${outputPath}' successfully saved`)
114
133
  }
115
134
 
116
- logger.info(`File '${outputPath}' successfully saved`)
117
135
  logger.info('Finish')
118
136
  }
119
137
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "epg-grabber",
3
- "version": "0.29.8",
3
+ "version": "0.30.1",
4
4
  "description": "Node.js CLI tool for grabbing EPG from different sites",
5
5
  "main": "src/index.js",
6
6
  "preferGlobal": true,
@@ -36,6 +36,7 @@
36
36
  "curl-generator": "^0.2.0",
37
37
  "dayjs": "^1.10.4",
38
38
  "epg-parser": "^0.1.6",
39
+ "fs-extra": "^11.1.1",
39
40
  "glob": "^7.1.6",
40
41
  "lodash": "^4.17.21",
41
42
  "node-gzip": "^1.1.2",
package/src/Channel.js CHANGED
@@ -1,23 +1,23 @@
1
1
  class Channel {
2
- constructor(c) {
3
- const data = {
4
- id: c.id || c.xmltv_id,
5
- name: c.name,
6
- site: c.site || '',
7
- site_id: c.site_id,
8
- lang: c.lang || '',
9
- logo: c.logo || '',
10
- url: c.url || toURL(c.site)
11
- }
2
+ constructor(c) {
3
+ const data = {
4
+ xmltv_id: c.xmltv_id,
5
+ name: c.name,
6
+ site: c.site || '',
7
+ site_id: c.site_id,
8
+ lang: c.lang || '',
9
+ logo: c.logo || '',
10
+ url: c.url || toURL(c.site)
11
+ }
12
12
 
13
- for (let key in data) {
14
- this[key] = data[key]
15
- }
16
- }
13
+ for (let key in data) {
14
+ this[key] = data[key]
15
+ }
16
+ }
17
17
  }
18
18
 
19
19
  module.exports = Channel
20
20
 
21
21
  function toURL(site) {
22
- return site ? `https://${site}` : ''
22
+ return site ? `https://${site}` : ''
23
23
  }
package/src/Program.js CHANGED
@@ -3,140 +3,140 @@ const { toArray, toUnix, parseNumber } = require('./utils')
3
3
  const Channel = require('./Channel')
4
4
 
5
5
  class Program {
6
- constructor(p, c) {
7
- if (!(c instanceof Channel)) {
8
- throw new Error('The second argument in the constructor must be the "Channel" class')
9
- }
10
-
11
- const data = {
12
- site: c.site || '',
13
- channel: c.id || '',
14
- titles: toArray(p.titles || p.title).map(text => toTextObject(text, c.lang)),
15
- sub_titles: toArray(p.sub_titles || p.sub_title).map(text => toTextObject(text, c.lang)),
16
- descriptions: toArray(p.descriptions || p.description || p.desc).map(text =>
17
- toTextObject(text, c.lang)
18
- ),
19
- icon: toIconObject(p.icon),
20
- episodeNumbers: p.episodeNum || p.episodeNumbers || getEpisodeNumbers(p.season, p.episode),
21
- date: p.date ? toUnix(p.date) : null,
22
- start: p.start ? toUnix(p.start) : null,
23
- stop: p.stop ? toUnix(p.stop) : null,
24
- urls: toArray(p.urls || p.url).map(toUrlObject),
25
- ratings: toArray(p.ratings || p.rating).map(toRatingObject),
26
- categories: toArray(p.categories || p.category).map(text => toTextObject(text, c.lang)),
27
- directors: toArray(p.directors || p.director).map(toPersonObject),
28
- actors: toArray(p.actors || p.actor).map(toPersonObject),
29
- writers: toArray(p.writers || p.writer).map(toPersonObject),
30
- adapters: toArray(p.adapters || p.adapter).map(toPersonObject),
31
- producers: toArray(p.producers || p.producer).map(toPersonObject),
32
- composers: toArray(p.composers || p.composer).map(toPersonObject),
33
- editors: toArray(p.editors || p.editor).map(toPersonObject),
34
- presenters: toArray(p.presenters || p.presenter).map(toPersonObject),
35
- commentators: toArray(p.commentators || p.commentator).map(toPersonObject),
36
- guests: toArray(p.guests || p.guest).map(toPersonObject)
37
- }
38
-
39
- for (let key in data) {
40
- this[key] = data[key]
41
- }
42
- }
6
+ constructor(p, c) {
7
+ if (!(c instanceof Channel)) {
8
+ throw new Error('The second argument in the constructor must be the "Channel" class')
9
+ }
10
+
11
+ const data = {
12
+ site: c.site || '',
13
+ channel: c.xmltv_id || '',
14
+ titles: toArray(p.titles || p.title).map(text => toTextObject(text, c.lang)),
15
+ sub_titles: toArray(p.sub_titles || p.sub_title).map(text => toTextObject(text, c.lang)),
16
+ descriptions: toArray(p.descriptions || p.description || p.desc).map(text =>
17
+ toTextObject(text, c.lang)
18
+ ),
19
+ icon: toIconObject(p.icon),
20
+ episodeNumbers: p.episodeNum || p.episodeNumbers || getEpisodeNumbers(p.season, p.episode),
21
+ date: p.date ? toUnix(p.date) : null,
22
+ start: p.start ? toUnix(p.start) : null,
23
+ stop: p.stop ? toUnix(p.stop) : null,
24
+ urls: toArray(p.urls || p.url).map(toUrlObject),
25
+ ratings: toArray(p.ratings || p.rating).map(toRatingObject),
26
+ categories: toArray(p.categories || p.category).map(text => toTextObject(text, c.lang)),
27
+ directors: toArray(p.directors || p.director).map(toPersonObject),
28
+ actors: toArray(p.actors || p.actor).map(toPersonObject),
29
+ writers: toArray(p.writers || p.writer).map(toPersonObject),
30
+ adapters: toArray(p.adapters || p.adapter).map(toPersonObject),
31
+ producers: toArray(p.producers || p.producer).map(toPersonObject),
32
+ composers: toArray(p.composers || p.composer).map(toPersonObject),
33
+ editors: toArray(p.editors || p.editor).map(toPersonObject),
34
+ presenters: toArray(p.presenters || p.presenter).map(toPersonObject),
35
+ commentators: toArray(p.commentators || p.commentator).map(toPersonObject),
36
+ guests: toArray(p.guests || p.guest).map(toPersonObject)
37
+ }
38
+
39
+ for (let key in data) {
40
+ this[key] = data[key]
41
+ }
42
+ }
43
43
  }
44
44
 
45
45
  module.exports = Program
46
46
 
47
47
  function toTextObject(text, lang) {
48
- if (typeof text === 'string') {
49
- return { value: text, lang }
50
- }
51
-
52
- return {
53
- value: text.value,
54
- lang: text.lang
55
- }
48
+ if (typeof text === 'string') {
49
+ return { value: text, lang }
50
+ }
51
+
52
+ return {
53
+ value: text.value,
54
+ lang: text.lang
55
+ }
56
56
  }
57
57
 
58
58
  function toPersonObject(person) {
59
- if (typeof person === 'string') {
60
- return {
61
- value: person,
62
- url: [],
63
- image: []
64
- }
65
- }
66
-
67
- return {
68
- value: person.value,
69
- url: toArray(person.url).map(toUrlObject),
70
- image: toArray(person.image).map(toImageObject)
71
- }
59
+ if (typeof person === 'string') {
60
+ return {
61
+ value: person,
62
+ url: [],
63
+ image: []
64
+ }
65
+ }
66
+
67
+ return {
68
+ value: person.value,
69
+ url: toArray(person.url).map(toUrlObject),
70
+ image: toArray(person.image).map(toImageObject)
71
+ }
72
72
  }
73
73
 
74
74
  function toImageObject(image) {
75
- if (typeof image === 'string') return { type: '', size: '', orient: '', system: '', value: image }
76
-
77
- return {
78
- type: image.type || '',
79
- size: image.size || '',
80
- orient: image.orient || '',
81
- system: image.system || '',
82
- value: image.value
83
- }
75
+ if (typeof image === 'string') return { type: '', size: '', orient: '', system: '', value: image }
76
+
77
+ return {
78
+ type: image.type || '',
79
+ size: image.size || '',
80
+ orient: image.orient || '',
81
+ system: image.system || '',
82
+ value: image.value
83
+ }
84
84
  }
85
85
 
86
86
  function toRatingObject(rating) {
87
- if (typeof rating === 'string') return { system: '', icon: '', value: rating }
87
+ if (typeof rating === 'string') return { system: '', icon: '', value: rating }
88
88
 
89
- return {
90
- system: rating.system || '',
91
- icon: rating.icon || '',
92
- value: rating.value || ''
93
- }
89
+ return {
90
+ system: rating.system || '',
91
+ icon: rating.icon || '',
92
+ value: rating.value || ''
93
+ }
94
94
  }
95
95
 
96
96
  function toUrlObject(url) {
97
- if (typeof url === 'string') return { system: '', value: url }
97
+ if (typeof url === 'string') return { system: '', value: url }
98
98
 
99
- return {
100
- system: url.system || '',
101
- value: url.value || ''
102
- }
99
+ return {
100
+ system: url.system || '',
101
+ value: url.value || ''
102
+ }
103
103
  }
104
104
 
105
105
  function toIconObject(icon) {
106
- if (!icon) return { src: '' }
107
- if (typeof icon === 'string') return { src: icon }
106
+ if (!icon) return { src: '' }
107
+ if (typeof icon === 'string') return { src: icon }
108
108
 
109
- return {
110
- src: icon.src || ''
111
- }
109
+ return {
110
+ src: icon.src || ''
111
+ }
112
112
  }
113
113
 
114
114
  function getEpisodeNumbers(s, e) {
115
- s = parseNumber(s)
116
- e = parseNumber(e)
115
+ s = parseNumber(s)
116
+ e = parseNumber(e)
117
117
 
118
- return [createXMLTVNS(s, e), createOnScreen(s, e)].filter(Boolean)
118
+ return [createXMLTVNS(s, e), createOnScreen(s, e)].filter(Boolean)
119
119
  }
120
120
 
121
121
  function createXMLTVNS(s, e) {
122
- if (!e) return null
123
- s = s || 1
122
+ if (!e) return null
123
+ s = s || 1
124
124
 
125
- return {
126
- system: 'xmltv_ns',
127
- value: `${s - 1}.${e - 1}.0/1`
128
- }
125
+ return {
126
+ system: 'xmltv_ns',
127
+ value: `${s - 1}.${e - 1}.0/1`
128
+ }
129
129
  }
130
130
 
131
131
  function createOnScreen(s, e) {
132
- if (!e) return null
133
- s = s || 1
132
+ if (!e) return null
133
+ s = s || 1
134
134
 
135
- s = padStart(s, 2, '0')
136
- e = padStart(e, 2, '0')
135
+ s = padStart(s, 2, '0')
136
+ e = padStart(e, 2, '0')
137
137
 
138
- return {
139
- system: 'onscreen',
140
- value: `S${s}E${e}`
141
- }
138
+ return {
139
+ system: 'onscreen',
140
+ value: `S${s}E${e}`
141
+ }
142
142
  }
package/src/file.js CHANGED
@@ -6,28 +6,47 @@ module.exports.write = write
6
6
  module.exports.resolve = resolve
7
7
  module.exports.join = join
8
8
  module.exports.dirname = dirname
9
+ module.exports.templateVariables = templateVariables
10
+ module.exports.templateFormat = templateFormat
9
11
 
10
12
  function read(filepath) {
11
- return fs.readFileSync(path.resolve(filepath), { encoding: 'utf-8' })
13
+ return fs.readFileSync(path.resolve(filepath), { encoding: 'utf-8' })
12
14
  }
13
15
 
14
16
  function write(filepath, data) {
15
- const dir = path.resolve(path.dirname(filepath))
16
- if (!fs.existsSync(dir)) {
17
- fs.mkdirSync(dir, { recursive: true })
18
- }
17
+ const dir = path.resolve(path.dirname(filepath))
18
+ if (!fs.existsSync(dir)) {
19
+ fs.mkdirSync(dir, { recursive: true })
20
+ }
19
21
 
20
- fs.writeFileSync(path.resolve(filepath), data)
22
+ fs.writeFileSync(path.resolve(filepath), data)
21
23
  }
22
24
 
23
25
  function resolve(filepath) {
24
- return path.resolve(filepath)
26
+ return path.resolve(filepath)
25
27
  }
26
28
 
27
29
  function join(path1, path2) {
28
- return path.join(path1, path2)
30
+ return path.join(path1, path2)
29
31
  }
30
32
 
31
33
  function dirname(filepath) {
32
- return path.dirname(filepath)
34
+ return path.dirname(filepath)
35
+ }
36
+
37
+ function templateVariables(template) {
38
+ const match = template.match(/{[^}]+}/g)
39
+
40
+ return Array.isArray(match) ? match.map(s => s.substring(1, s.length - 1)) : []
41
+ }
42
+
43
+ function templateFormat(template, obj) {
44
+ let output = template
45
+ for (let key in obj) {
46
+ const regex = new RegExp(`{${key}}`, 'g')
47
+ const value = obj[key] || undefined
48
+ output = output.replace(regex, value)
49
+ }
50
+
51
+ return output
33
52
  }
package/src/xmltv.js CHANGED
@@ -29,7 +29,7 @@ function createElements(channels, programs, date) {
29
29
  ...channels.map(channel => {
30
30
  return (
31
31
  '\r\n' +
32
- el('channel', { id: channel.id }, [
32
+ el('channel', { id: channel.xmltv_id }, [
33
33
  el('display-name', {}, [escapeString(channel.name)]),
34
34
  el('icon', { src: channel.logo }),
35
35
  el('url', {}, [channel.url])
@@ -12,28 +12,7 @@ it('can create new Channel', () => {
12
12
 
13
13
  expect(channel).toMatchObject({
14
14
  name: '1 TV',
15
- id: '1TV.com',
16
- site_id: '1',
17
- site: 'example.com',
18
- url: 'https://example.com',
19
- lang: 'fr',
20
- logo: 'https://example.com/logos/1TV.png'
21
- })
22
- })
23
-
24
- it('can create channel from exist object', () => {
25
- const channel = new Channel({
26
- name: '1 TV',
27
- id: '1TV.com',
28
- site_id: '1',
29
- site: 'example.com',
30
- lang: 'fr',
31
- logo: 'https://example.com/logos/1TV.png'
32
- })
33
-
34
- expect(channel).toMatchObject({
35
- name: '1 TV',
36
- id: '1TV.com',
15
+ xmltv_id: '1TV.com',
37
16
  site_id: '1',
38
17
  site: 'example.com',
39
18
  url: 'https://example.com',
@@ -7,7 +7,7 @@ module.exports = {
7
7
  site: 'example.com',
8
8
  days: 2,
9
9
  channels: 'example.channels.xml',
10
- output: 'tests/output/guide.xml',
10
+ output: 'tests/__data__/output/guide.xml',
11
11
  url: () => 'http://example.com/20210319/1tv.json',
12
12
  request: {
13
13
  method: 'POST',
@@ -1,4 +1,4 @@
1
- <?xml version="1.0" encoding="UTF-8" ?><tv date="20230113">
1
+ <?xml version="1.0" encoding="UTF-8" ?><tv date="20230511">
2
2
  <channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>
3
3
  <channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>
4
4
  <programme start="20220101000000 +0000" stop="20220101010000 +0000" channel="1TV.com"><title lang="fr">Program1</title></programme>
package/tests/bin.test.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const { execSync } = require('child_process')
2
- const fs = require('fs')
2
+ const fs = require('fs-extra')
3
3
  const path = require('path')
4
4
  const epgParser = require('epg-parser')
5
5
 
@@ -11,6 +11,10 @@ function stdoutResultTester(stdout) {
11
11
  })
12
12
  }
13
13
 
14
+ beforeEach(() => {
15
+ fs.emptyDirSync('tests/__data__/output')
16
+ })
17
+
14
18
  it('can load config', () => {
15
19
  const stdout = execSync(
16
20
  `node ${pwd}/bin/epg-grabber.js --config=tests/__data__/input/example.config.js --delay=0`,
@@ -62,6 +66,26 @@ it('can generate gzip version', () => {
62
66
  )
63
67
  })
64
68
 
69
+ it('can produce multiple outputs', () => {
70
+ const stdout = execSync(
71
+ `node ${pwd}/bin/epg-grabber.js \
72
+ --config=tests/__data__/input/mini.config.js \
73
+ --channels=tests/__data__/input/example.channels.xml \
74
+ --output=tests/__data__/output/{lang}/{xmltv_id}.xml`,
75
+ {
76
+ encoding: 'utf8'
77
+ }
78
+ )
79
+
80
+ expect(stdoutResultTester(stdout)).toBe(true)
81
+ expect(stdout.includes("File 'tests/__data__/output/fr/1TV.com.xml' successfully saved")).toBe(
82
+ true
83
+ )
84
+ expect(
85
+ stdout.includes("File 'tests/__data__/output/undefined/2TV.com.xml' successfully saved")
86
+ ).toBe(true)
87
+ })
88
+
65
89
  it('removes duplicates of the program', () => {
66
90
  const stdout = execSync(
67
91
  `node ${pwd}/bin/epg-grabber.js \
package/.travis.yml DELETED
@@ -1,8 +0,0 @@
1
- language: node_js
2
-
3
- node_js:
4
- - '10'
5
-
6
- script:
7
- - npm run lint
8
- - npm run test
@@ -1,4 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8" ?><tv date="20230113">
2
- <channel id="1TV.com"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>
3
- <channel id="2TV.com"><display-name>2 TV</display-name><url>https://example.com</url></channel>
4
- </tv>