epg-grabber 0.16.0 → 0.19.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 +8 -7
- package/bin/epg-grabber.js +2 -2
- package/package.json +2 -2
- package/src/index.js +0 -6
- package/src/utils.js +30 -2
- package/tests/index.test.js +0 -20
- package/tests/input/example.com.config.js +20 -2
- package/tests/input/mini.config.js +0 -1
- package/tests/utils.test.js +28 -5
package/README.md
CHANGED
|
@@ -94,7 +94,6 @@ module.exports = {
|
|
|
94
94
|
lang: 'fr', // default language for all programs (default: 'en')
|
|
95
95
|
days: 3, // number of days for which to grab the program (default: 1)
|
|
96
96
|
delay: 5000, // delay between requests (default: 3000)
|
|
97
|
-
ignore: true, // skip all channels during request (default: false)
|
|
98
97
|
|
|
99
98
|
request: { // request options (details: https://github.com/axios/axios#request-config)
|
|
100
99
|
|
|
@@ -159,12 +158,14 @@ module.exports = {
|
|
|
159
158
|
return [
|
|
160
159
|
{
|
|
161
160
|
title, // program title (required)
|
|
162
|
-
start, //
|
|
163
|
-
stop, //
|
|
164
|
-
description, // program
|
|
165
|
-
category, // program
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
start, // start time of the program (required)
|
|
162
|
+
stop, // end time of the program (required)
|
|
163
|
+
description, // description of the program (optional)
|
|
164
|
+
category, // program type (optional)
|
|
165
|
+
season, // season number (optional)
|
|
166
|
+
episode, // episode number (optional)
|
|
167
|
+
icon, // image associated with the program (optional)
|
|
168
|
+
lang // language of the description (default: 'en')
|
|
168
169
|
},
|
|
169
170
|
...
|
|
170
171
|
]
|
package/bin/epg-grabber.js
CHANGED
|
@@ -7,7 +7,7 @@ const path = require('path')
|
|
|
7
7
|
const grabber = require('../src/index')
|
|
8
8
|
const utils = require('../src/utils')
|
|
9
9
|
const { name, version, description } = require('../package.json')
|
|
10
|
-
const merge = require('lodash
|
|
10
|
+
const { merge } = require('lodash')
|
|
11
11
|
const { createLogger, format, transports } = require('winston')
|
|
12
12
|
const { combine, timestamp, printf } = format
|
|
13
13
|
|
|
@@ -79,7 +79,7 @@ async function main() {
|
|
|
79
79
|
if (!config.channels) return logger.error('Path to [site].channels.xml is missing')
|
|
80
80
|
logger.info(`Loading '${config.channels}'...`)
|
|
81
81
|
const channelsXML = fs.readFileSync(path.resolve(config.channels), { encoding: 'utf-8' })
|
|
82
|
-
const channels = utils.parseChannels(channelsXML)
|
|
82
|
+
const { channels } = utils.parseChannels(channelsXML)
|
|
83
83
|
|
|
84
84
|
let programs = []
|
|
85
85
|
let i = 1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epg-grabber",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Node.js CLI tool for grabbing EPG from different sites",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"preferGlobal": true,
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"commander": "^7.1.0",
|
|
35
35
|
"dayjs": "^1.10.4",
|
|
36
36
|
"glob": "^7.1.6",
|
|
37
|
-
"lodash
|
|
37
|
+
"lodash": "^4.17.21",
|
|
38
38
|
"tough-cookie": "^4.0.0",
|
|
39
39
|
"winston": "^3.3.3",
|
|
40
40
|
"xml-js": "^1.6.11"
|
package/src/index.js
CHANGED
|
@@ -14,12 +14,6 @@ module.exports = {
|
|
|
14
14
|
|
|
15
15
|
let programs = []
|
|
16
16
|
for (let item of queue) {
|
|
17
|
-
if (config.ignore) {
|
|
18
|
-
item.programs = []
|
|
19
|
-
cb(item, new Error('Skipped'))
|
|
20
|
-
continue
|
|
21
|
-
}
|
|
22
|
-
|
|
23
17
|
await utils
|
|
24
18
|
.buildRequest(item, config)
|
|
25
19
|
.then(request => utils.fetchData(request))
|
package/src/utils.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
|
+
const { padStart } = require('lodash')
|
|
2
3
|
const path = require('path')
|
|
3
4
|
const axios = require('axios').default
|
|
4
5
|
const axiosCookieJarSupport = require('axios-cookiejar-support').default
|
|
5
6
|
const tough = require('tough-cookie')
|
|
6
7
|
const convert = require('xml-js')
|
|
7
|
-
const merge = require('lodash
|
|
8
|
+
const { merge } = require('lodash')
|
|
8
9
|
const dayjs = require('dayjs')
|
|
9
10
|
const utc = require('dayjs/plugin/utc')
|
|
10
11
|
dayjs.extend(utc)
|
|
@@ -64,7 +65,7 @@ utils.parseChannels = function (xml) {
|
|
|
64
65
|
return channel
|
|
65
66
|
})
|
|
66
67
|
|
|
67
|
-
return channels
|
|
68
|
+
return { site, channels }
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
utils.sleep = function (ms) {
|
|
@@ -126,6 +127,8 @@ utils.convertToXMLTV = function ({ channels, programs }) {
|
|
|
126
127
|
const start = program.start ? dayjs.unix(program.start).utc().format('YYYYMMDDHHmmss ZZ') : ''
|
|
127
128
|
const stop = program.stop ? dayjs.unix(program.stop).utc().format('YYYYMMDDHHmmss ZZ') : ''
|
|
128
129
|
const lang = program.lang || 'en'
|
|
130
|
+
const xmltv_ns = createXMLTVNS(program.season, program.episode)
|
|
131
|
+
const onscreen = createOnScreen(program.season, program.episode)
|
|
129
132
|
const icon = utils.escapeString(program.icon)
|
|
130
133
|
|
|
131
134
|
if (start && stop && title) {
|
|
@@ -143,6 +146,14 @@ utils.convertToXMLTV = function ({ channels, programs }) {
|
|
|
143
146
|
})
|
|
144
147
|
}
|
|
145
148
|
|
|
149
|
+
if (xmltv_ns) {
|
|
150
|
+
output += `<episode-num system="xmltv_ns">${xmltv_ns}</episode-num>`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (onscreen) {
|
|
154
|
+
output += `<episode-num system="onscreen">${onscreen}</episode-num>`
|
|
155
|
+
}
|
|
156
|
+
|
|
146
157
|
if (icon) {
|
|
147
158
|
output += `<icon src="${icon}"/>`
|
|
148
159
|
}
|
|
@@ -153,6 +164,21 @@ utils.convertToXMLTV = function ({ channels, programs }) {
|
|
|
153
164
|
|
|
154
165
|
output += '</tv>'
|
|
155
166
|
|
|
167
|
+
function createXMLTVNS(s, e) {
|
|
168
|
+
if (!s || !e) return null
|
|
169
|
+
|
|
170
|
+
return `${s - 1}.${e - 1}.0/1`
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function createOnScreen(s, e) {
|
|
174
|
+
if (!s || !e) return null
|
|
175
|
+
|
|
176
|
+
s = padStart(s, 2, '0')
|
|
177
|
+
e = padStart(e, 2, '0')
|
|
178
|
+
|
|
179
|
+
return `S${s}E${e}`
|
|
180
|
+
}
|
|
181
|
+
|
|
156
182
|
return output
|
|
157
183
|
}
|
|
158
184
|
|
|
@@ -269,6 +295,8 @@ utils.parsePrograms = async function (data, config) {
|
|
|
269
295
|
title: program.title,
|
|
270
296
|
description: program.description || null,
|
|
271
297
|
category: program.category || null,
|
|
298
|
+
season: program.season || null,
|
|
299
|
+
episode: program.episode || null,
|
|
272
300
|
icon: program.icon || null,
|
|
273
301
|
channel: channel.xmltv_id,
|
|
274
302
|
lang: program.lang || channel.lang || config.lang || 'en',
|
package/tests/index.test.js
CHANGED
|
@@ -71,23 +71,3 @@ it('can grab single channel programs', done => {
|
|
|
71
71
|
done()
|
|
72
72
|
})
|
|
73
73
|
})
|
|
74
|
-
|
|
75
|
-
it('return "Skipped" error if ignore option in config is true', done => {
|
|
76
|
-
const config = {
|
|
77
|
-
site: 'example.com',
|
|
78
|
-
ignore: true,
|
|
79
|
-
url: `http://example.com/20210319/1tv.json`,
|
|
80
|
-
parser: () => []
|
|
81
|
-
}
|
|
82
|
-
const channel = {
|
|
83
|
-
site: 'example.com',
|
|
84
|
-
site_id: 'cnn',
|
|
85
|
-
xmltv_id: 'CNN.us',
|
|
86
|
-
lang: 'en',
|
|
87
|
-
name: 'CNN'
|
|
88
|
-
}
|
|
89
|
-
grabber.grab(channel, config, (data, err) => {
|
|
90
|
-
expect(err.message).toBe('Skipped')
|
|
91
|
-
done()
|
|
92
|
-
})
|
|
93
|
-
})
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
const dayjs = require('dayjs')
|
|
2
|
+
const utc = require('dayjs/plugin/utc')
|
|
3
|
+
|
|
4
|
+
dayjs.extend(utc)
|
|
5
|
+
|
|
1
6
|
module.exports = {
|
|
2
7
|
site: 'example.com',
|
|
3
|
-
ignore: true,
|
|
4
8
|
channels: 'example.com.channels.xml',
|
|
5
9
|
output: 'tests/output/guide.xml',
|
|
6
10
|
url: () => 'http://example.com/20210319/1tv.json',
|
|
@@ -14,6 +18,20 @@ module.exports = {
|
|
|
14
18
|
return { accountID: '123' }
|
|
15
19
|
}
|
|
16
20
|
},
|
|
17
|
-
parser: () =>
|
|
21
|
+
parser: () => {
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
title: 'Title',
|
|
25
|
+
description: 'Description',
|
|
26
|
+
lang: 'en',
|
|
27
|
+
category: ['Category1', 'Category2'],
|
|
28
|
+
icon: 'https://example.com/image.jpg',
|
|
29
|
+
season: 9,
|
|
30
|
+
episode: 238,
|
|
31
|
+
start: dayjs.utc('2022-01-01 00:00:00'),
|
|
32
|
+
stop: dayjs.utc('2022-01-01 01:00:00')
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
18
36
|
logo: () => 'http://example.com/logos/1TV.png?x=шеллы&sid=777'
|
|
19
37
|
}
|
package/tests/utils.test.js
CHANGED
|
@@ -7,7 +7,6 @@ it('can load valid config.js', () => {
|
|
|
7
7
|
const config = utils.loadConfig(require(path.resolve('./tests/input/example.com.config.js')))
|
|
8
8
|
expect(config).toMatchObject({
|
|
9
9
|
days: 1,
|
|
10
|
-
ignore: true,
|
|
11
10
|
delay: 3000,
|
|
12
11
|
lang: 'en',
|
|
13
12
|
site: 'example.com'
|
|
@@ -29,7 +28,7 @@ it('can load valid config.js', () => {
|
|
|
29
28
|
|
|
30
29
|
it('can parse valid channels.xml', () => {
|
|
31
30
|
const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
|
|
32
|
-
const channels = utils.parseChannels(file)
|
|
31
|
+
const { channels } = utils.parseChannels(file)
|
|
33
32
|
expect(channels).toEqual([
|
|
34
33
|
{
|
|
35
34
|
name: '1 TV',
|
|
@@ -52,7 +51,7 @@ it('can parse valid channels.xml', () => {
|
|
|
52
51
|
|
|
53
52
|
it('can convert object to xmltv string', () => {
|
|
54
53
|
const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
|
|
55
|
-
const channels = utils.parseChannels(file)
|
|
54
|
+
const { channels } = utils.parseChannels(file)
|
|
56
55
|
const programs = [
|
|
57
56
|
{
|
|
58
57
|
title: 'Program 1',
|
|
@@ -60,6 +59,8 @@ it('can convert object to xmltv string', () => {
|
|
|
60
59
|
start: 1616133600,
|
|
61
60
|
stop: 1616135400,
|
|
62
61
|
category: 'Test',
|
|
62
|
+
season: 9,
|
|
63
|
+
episode: 239,
|
|
63
64
|
icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
|
|
64
65
|
channel: '1TV.com',
|
|
65
66
|
lang: 'it'
|
|
@@ -67,7 +68,7 @@ it('can convert object to xmltv string', () => {
|
|
|
67
68
|
]
|
|
68
69
|
const output = utils.convertToXMLTV({ channels, programs })
|
|
69
70
|
expect(output).toBe(
|
|
70
|
-
'<?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=шеллы&sid=777"/></programme>\r\n</tv>'
|
|
71
|
+
'<?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=шеллы&sid=777"/></programme>\r\n</tv>'
|
|
71
72
|
)
|
|
72
73
|
})
|
|
73
74
|
|
|
@@ -100,7 +101,7 @@ it('can convert object to xmltv string without categories', () => {
|
|
|
100
101
|
|
|
101
102
|
it('can convert object to xmltv string with multiple categories', () => {
|
|
102
103
|
const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
|
|
103
|
-
const channels = utils.parseChannels(file)
|
|
104
|
+
const { channels } = utils.parseChannels(file)
|
|
104
105
|
const programs = [
|
|
105
106
|
{
|
|
106
107
|
title: 'Program 1',
|
|
@@ -196,6 +197,28 @@ it('can load logo async', done => {
|
|
|
196
197
|
})
|
|
197
198
|
})
|
|
198
199
|
|
|
200
|
+
it('can parse programs', done => {
|
|
201
|
+
const config = utils.loadConfig(require(path.resolve('./tests/input/example.com.config.js')))
|
|
202
|
+
return utils
|
|
203
|
+
.parsePrograms({ channel: { xmltv_id: '1tv', lang: 'en' } }, config)
|
|
204
|
+
.then(programs => {
|
|
205
|
+
expect(programs).toMatchObject([
|
|
206
|
+
{
|
|
207
|
+
title: 'Title',
|
|
208
|
+
description: 'Description',
|
|
209
|
+
lang: 'en',
|
|
210
|
+
category: ['Category1', 'Category2'],
|
|
211
|
+
icon: 'https://example.com/image.jpg',
|
|
212
|
+
season: 9,
|
|
213
|
+
episode: 238,
|
|
214
|
+
start: 1640995200,
|
|
215
|
+
stop: 1640998800
|
|
216
|
+
}
|
|
217
|
+
])
|
|
218
|
+
done()
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
199
222
|
it('can parse programs async', done => {
|
|
200
223
|
const config = utils.loadConfig(require(path.resolve('./tests/input/async.config.js')))
|
|
201
224
|
return utils
|