epg-grabber 0.29.7 → 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.
- package/.github/workflows/test.yml +15 -0
- package/README.md +41 -13
- package/bin/epg-grabber.js +60 -42
- package/package.json +2 -1
- package/src/Channel.js +15 -15
- package/src/Program.js +100 -100
- package/src/file.js +28 -9
- package/src/xmltv.js +1 -1
- package/tests/Channel.test.js +1 -22
- package/tests/__data__/input/example.config.js +1 -1
- package/tests/__data__/output/duplicates.guide.xml +1 -1
- package/tests/bin.test.js +25 -1
- package/.travis.yml +0 -8
- package/tests/__data__/output/duplicates.guide.xml.gz +0 -0
- package/tests/__data__/output/mini.guide.xml +0 -4
- package/tests/__data__/output/mini.guide.xml.gz +0 -0
|
@@ -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 [](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:
|
|
78
|
-
- `--channels`: path to list of channels
|
|
79
|
-
- `--lang`: set default language for all programs (default:
|
|
80
|
-
- `--days`: number of days for which to grab the program (default: 1)
|
|
81
|
-
- `--delay`: delay between requests (default: 3000)
|
|
82
|
-
- `--timeout`: set a timeout for each request (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:
|
|
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).
|
package/bin/epg-grabber.js
CHANGED
|
@@ -24,8 +24,8 @@ program
|
|
|
24
24
|
.option('--channels <channels>', 'Path to channels.xml file')
|
|
25
25
|
.option('--lang <lang>', 'Set default language for all programs')
|
|
26
26
|
.option('--days <days>', 'Number of days for which to grab the program', parseNumber)
|
|
27
|
-
.option('--delay <delay>', 'Delay between requests (in
|
|
28
|
-
.option('--timeout <timeout>', 'Set a timeout for each request (in
|
|
27
|
+
.option('--delay <delay>', 'Delay between requests (in milliseconds)', parseNumber)
|
|
28
|
+
.option('--timeout <timeout>', 'Set a timeout for each request (in milliseconds)', parseNumber)
|
|
29
29
|
.option(
|
|
30
30
|
'--cache-ttl <cacheTtl>',
|
|
31
31
|
'Maximum time for storing each request (in milliseconds)',
|
|
@@ -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
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.
|
|
98
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
87
|
+
if (typeof rating === 'string') return { system: '', icon: '', value: rating }
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
97
|
+
if (typeof url === 'string') return { system: '', value: url }
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
return {
|
|
100
|
+
system: url.system || '',
|
|
101
|
+
value: url.value || ''
|
|
102
|
+
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function toIconObject(icon) {
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
if (!icon) return { src: '' }
|
|
107
|
+
if (typeof icon === 'string') return { src: icon }
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
return {
|
|
110
|
+
src: icon.src || ''
|
|
111
|
+
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
function getEpisodeNumbers(s, e) {
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
s = parseNumber(s)
|
|
116
|
+
e = parseNumber(e)
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
return [createXMLTVNS(s, e), createOnScreen(s, e)].filter(Boolean)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function createXMLTVNS(s, e) {
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
if (!e) return null
|
|
123
|
+
s = s || 1
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
133
|
-
|
|
132
|
+
if (!e) return null
|
|
133
|
+
s = s || 1
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
s = padStart(s, 2, '0')
|
|
136
|
+
e = padStart(e, 2, '0')
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
13
|
+
return fs.readFileSync(path.resolve(filepath), { encoding: 'utf-8' })
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
function write(filepath, data) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const dir = path.resolve(path.dirname(filepath))
|
|
18
|
+
if (!fs.existsSync(dir)) {
|
|
19
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
20
|
+
}
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
fs.writeFileSync(path.resolve(filepath), data)
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
function resolve(filepath) {
|
|
24
|
-
|
|
26
|
+
return path.resolve(filepath)
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
function join(path1, path2) {
|
|
28
|
-
|
|
30
|
+
return path.join(path1, path2)
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
function dirname(filepath) {
|
|
32
|
-
|
|
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.
|
|
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])
|
package/tests/Channel.test.js
CHANGED
|
@@ -12,28 +12,7 @@ it('can create new Channel', () => {
|
|
|
12
12
|
|
|
13
13
|
expect(channel).toMatchObject({
|
|
14
14
|
name: '1 TV',
|
|
15
|
-
|
|
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',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" ?><tv date="
|
|
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
|
Binary file
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" ?><tv date="20230110">
|
|
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>
|
|
Binary file
|