epg-grabber 0.22.0 → 0.25.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 +10 -0
- package/bin/epg-grabber.js +26 -5
- package/package.json +4 -1
- package/src/index.js +19 -13
- package/src/utils.js +77 -33
- package/tests/bin.test.js +16 -0
- package/tests/index.test.js +5 -3
- package/tests/utils.test.js +2 -1
package/README.md
CHANGED
|
@@ -80,7 +80,10 @@ 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)
|
|
84
|
+
- `--gzip`: compress the output (default: false)
|
|
83
85
|
- `--debug`: enable debug mode (default: false)
|
|
86
|
+
- `--curl`: display current request as CURL (default: false)
|
|
84
87
|
- `--log`: path to log file (optional)
|
|
85
88
|
- `--log-level`: set the log level (default: 'info')
|
|
86
89
|
|
|
@@ -99,6 +102,13 @@ module.exports = {
|
|
|
99
102
|
|
|
100
103
|
method: 'GET',
|
|
101
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
|
+
}
|
|
111
|
+
},
|
|
102
112
|
|
|
103
113
|
/**
|
|
104
114
|
* @param {object} context
|
package/bin/epg-grabber.js
CHANGED
|
@@ -4,10 +4,11 @@ const { Command } = require('commander')
|
|
|
4
4
|
const program = new Command()
|
|
5
5
|
const fs = require('fs')
|
|
6
6
|
const path = require('path')
|
|
7
|
-
const
|
|
7
|
+
const EPGGrabber = require('../src/index')
|
|
8
8
|
const utils = require('../src/utils')
|
|
9
9
|
const { name, version, description } = require('../package.json')
|
|
10
10
|
const { merge } = require('lodash')
|
|
11
|
+
const { gzip } = require('node-gzip')
|
|
11
12
|
const { createLogger, format, transports } = require('winston')
|
|
12
13
|
const { combine, timestamp, printf } = format
|
|
13
14
|
|
|
@@ -22,7 +23,14 @@ program
|
|
|
22
23
|
.option('--days <days>', 'Number of days for which to grab the program', parseInteger, 1)
|
|
23
24
|
.option('--delay <delay>', 'Delay between requests (in mileseconds)', parseInteger)
|
|
24
25
|
.option('--timeout <timeout>', 'Set a timeout for each request (in mileseconds)', parseInteger)
|
|
26
|
+
.option(
|
|
27
|
+
'--cache-max-age <cacheMaxAge>',
|
|
28
|
+
'Maximum time for storing each request (in milliseconds)',
|
|
29
|
+
parseInteger
|
|
30
|
+
)
|
|
31
|
+
.option('--gzip', 'Compress the output', false)
|
|
25
32
|
.option('--debug', 'Enable debug mode', false)
|
|
33
|
+
.option('--curl', 'Display request as CURL', false)
|
|
26
34
|
.option('--log <log>', 'Path to log file')
|
|
27
35
|
.option('--log-level <level>', 'Set log level', 'info')
|
|
28
36
|
.parse(process.argv)
|
|
@@ -64,10 +72,15 @@ async function main() {
|
|
|
64
72
|
config = merge(config, {
|
|
65
73
|
days: options.days,
|
|
66
74
|
debug: options.debug,
|
|
75
|
+
gzip: options.gzip,
|
|
76
|
+
curl: options.curl,
|
|
67
77
|
lang: options.lang,
|
|
68
78
|
delay: options.delay,
|
|
69
79
|
request: {
|
|
70
|
-
timeout: options.timeout
|
|
80
|
+
timeout: options.timeout,
|
|
81
|
+
cache: {
|
|
82
|
+
maxAge: options.cacheMaxAge
|
|
83
|
+
}
|
|
71
84
|
}
|
|
72
85
|
})
|
|
73
86
|
|
|
@@ -87,10 +100,11 @@ async function main() {
|
|
|
87
100
|
const total = channels.length * days
|
|
88
101
|
const utcDate = utils.getUTCDate()
|
|
89
102
|
const dates = Array.from({ length: config.days }, (_, i) => utcDate.add(i, 'd'))
|
|
103
|
+
const grabber = new EPGGrabber(config)
|
|
90
104
|
for (let channel of channels) {
|
|
91
105
|
for (let date of dates) {
|
|
92
106
|
await grabber
|
|
93
|
-
.grab(channel, date,
|
|
107
|
+
.grab(channel, date, (data, err) => {
|
|
94
108
|
logger.info(
|
|
95
109
|
`[${i}/${total}] ${config.site} - ${data.channel.xmltv_id} - ${data.date.format(
|
|
96
110
|
'MMM D, YYYY'
|
|
@@ -108,8 +122,15 @@ async function main() {
|
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
const xml = utils.convertToXMLTV({ config, channels, programs })
|
|
111
|
-
|
|
112
|
-
|
|
125
|
+
let outputPath = options.output || config.output
|
|
126
|
+
if (options.gzip) {
|
|
127
|
+
outputPath = outputPath || 'guide.xml.gz'
|
|
128
|
+
const compressed = await gzip(xml)
|
|
129
|
+
utils.writeToFile(outputPath, compressed)
|
|
130
|
+
} else {
|
|
131
|
+
outputPath = outputPath || 'guide.xml'
|
|
132
|
+
utils.writeToFile(outputPath, xml)
|
|
133
|
+
}
|
|
113
134
|
|
|
114
135
|
logger.info(`File '${outputPath}' successfully saved`)
|
|
115
136
|
logger.info('Finish')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epg-grabber",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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,12 +29,15 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"axios": "^0.21.1",
|
|
32
|
+
"axios-cache-adapter": "^2.7.3",
|
|
32
33
|
"axios-cookiejar-support": "^1.0.1",
|
|
33
34
|
"axios-mock-adapter": "^1.20.0",
|
|
34
35
|
"commander": "^7.1.0",
|
|
36
|
+
"curl-generator": "^0.2.0",
|
|
35
37
|
"dayjs": "^1.10.4",
|
|
36
38
|
"glob": "^7.1.6",
|
|
37
39
|
"lodash": "^4.17.21",
|
|
40
|
+
"node-gzip": "^1.1.2",
|
|
38
41
|
"tough-cookie": "^4.0.0",
|
|
39
42
|
"winston": "^3.3.3",
|
|
40
43
|
"xml-js": "^1.6.11"
|
package/src/index.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
const utils = require('./utils')
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
class EPGGrabber {
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
this.config = utils.loadConfig(config)
|
|
6
|
+
this.client = utils.createClient(config)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async grab(channel, date, cb = () => {}) {
|
|
5
10
|
date = typeof date === 'string' ? utils.getUTCDate(date) : date
|
|
6
|
-
|
|
7
|
-
channel.lang = channel.lang || config.lang || null
|
|
11
|
+
channel.lang = channel.lang || this.config.lang || null
|
|
8
12
|
|
|
9
13
|
let programs = []
|
|
10
|
-
|
|
11
14
|
const item = { date, channel }
|
|
12
15
|
await utils
|
|
13
|
-
.buildRequest(item, config)
|
|
14
|
-
.then(request => utils.fetchData(request))
|
|
15
|
-
.then(response => utils.parseResponse(item, response, config))
|
|
16
|
+
.buildRequest(item, this.config)
|
|
17
|
+
.then(request => utils.fetchData(this.client, request))
|
|
18
|
+
.then(response => utils.parseResponse(item, response, this.config))
|
|
16
19
|
.then(results => {
|
|
17
20
|
item.programs = results
|
|
18
21
|
cb(item, null)
|
|
@@ -20,16 +23,19 @@ module.exports = {
|
|
|
20
23
|
})
|
|
21
24
|
.catch(error => {
|
|
22
25
|
item.programs = []
|
|
23
|
-
if (config.debug) {
|
|
26
|
+
if (this.config.debug) {
|
|
24
27
|
console.log('Error:', JSON.stringify(error, null, 2))
|
|
25
28
|
}
|
|
26
29
|
cb(item, error)
|
|
27
30
|
})
|
|
28
31
|
|
|
29
|
-
await utils.sleep(config.delay)
|
|
32
|
+
await utils.sleep(this.config.delay)
|
|
30
33
|
|
|
31
34
|
return programs
|
|
32
|
-
}
|
|
33
|
-
convertToXMLTV: utils.convertToXMLTV,
|
|
34
|
-
parseChannels: utils.parseChannels
|
|
35
|
+
}
|
|
35
36
|
}
|
|
37
|
+
|
|
38
|
+
EPGGrabber.convertToXMLTV = utils.convertToXMLTV
|
|
39
|
+
EPGGrabber.parseChannels = utils.parseChannels
|
|
40
|
+
|
|
41
|
+
module.exports = EPGGrabber
|
package/src/utils.js
CHANGED
|
@@ -3,11 +3,13 @@ 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
7
|
const tough = require('tough-cookie')
|
|
7
8
|
const convert = require('xml-js')
|
|
8
9
|
const { merge } = require('lodash')
|
|
9
10
|
const dayjs = require('dayjs')
|
|
10
11
|
const utc = require('dayjs/plugin/utc')
|
|
12
|
+
const { CurlGenerator } = require('curl-generator')
|
|
11
13
|
dayjs.extend(utc)
|
|
12
14
|
axiosCookieJarSupport(axios)
|
|
13
15
|
|
|
@@ -38,13 +40,65 @@ utils.loadConfig = function (config) {
|
|
|
38
40
|
timeout: 5000,
|
|
39
41
|
withCredentials: true,
|
|
40
42
|
jar: new tough.CookieJar(),
|
|
41
|
-
responseType: 'arraybuffer'
|
|
43
|
+
responseType: 'arraybuffer',
|
|
44
|
+
cache: {
|
|
45
|
+
readHeaders: false,
|
|
46
|
+
exclude: {
|
|
47
|
+
query: false
|
|
48
|
+
},
|
|
49
|
+
maxAge: 0
|
|
50
|
+
}
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
return merge(defaultConfig, config)
|
|
46
55
|
}
|
|
47
56
|
|
|
57
|
+
utils.createClient = function (config) {
|
|
58
|
+
const client = axiosCacheAdapter.setup()
|
|
59
|
+
client.interceptors.request.use(
|
|
60
|
+
function (request) {
|
|
61
|
+
if (config.debug) {
|
|
62
|
+
console.log('Request:', JSON.stringify(request, null, 2))
|
|
63
|
+
}
|
|
64
|
+
return request
|
|
65
|
+
},
|
|
66
|
+
function (error) {
|
|
67
|
+
return Promise.reject(error)
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
client.interceptors.response.use(
|
|
71
|
+
function (response) {
|
|
72
|
+
if (config.debug) {
|
|
73
|
+
const data = utils.isObject(response.data)
|
|
74
|
+
? JSON.stringify(response.data)
|
|
75
|
+
: response.data.toString()
|
|
76
|
+
console.log(
|
|
77
|
+
'Response:',
|
|
78
|
+
JSON.stringify(
|
|
79
|
+
{
|
|
80
|
+
headers: response.headers,
|
|
81
|
+
data,
|
|
82
|
+
fromCache: response.request.fromCache === true
|
|
83
|
+
},
|
|
84
|
+
null,
|
|
85
|
+
2
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
clearTimeout(timeout)
|
|
91
|
+
return response
|
|
92
|
+
},
|
|
93
|
+
function (error) {
|
|
94
|
+
clearTimeout(timeout)
|
|
95
|
+
return Promise.reject(error)
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return client
|
|
100
|
+
}
|
|
101
|
+
|
|
48
102
|
utils.parseChannels = function (xml) {
|
|
49
103
|
const result = convert.xml2js(xml)
|
|
50
104
|
const siteTag = result.elements.find(el => el.name === 'site') || {}
|
|
@@ -204,26 +258,21 @@ utils.buildRequest = async function (item, config) {
|
|
|
204
258
|
request.data = await utils.getRequestData(item, config)
|
|
205
259
|
request.cancelToken = source.token
|
|
206
260
|
|
|
207
|
-
if (config.
|
|
208
|
-
|
|
261
|
+
if (config.curl) {
|
|
262
|
+
const curl = CurlGenerator({
|
|
263
|
+
url: request.url,
|
|
264
|
+
method: request.method,
|
|
265
|
+
headers: request.headers,
|
|
266
|
+
body: request.data
|
|
267
|
+
})
|
|
268
|
+
console.log(curl)
|
|
209
269
|
}
|
|
210
270
|
|
|
211
271
|
return request
|
|
212
272
|
}
|
|
213
273
|
|
|
214
|
-
utils.fetchData = function (request) {
|
|
215
|
-
|
|
216
|
-
function (response) {
|
|
217
|
-
clearTimeout(timeout)
|
|
218
|
-
return response
|
|
219
|
-
},
|
|
220
|
-
function (error) {
|
|
221
|
-
clearTimeout(timeout)
|
|
222
|
-
return Promise.reject(error)
|
|
223
|
-
}
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
return axios(request)
|
|
274
|
+
utils.fetchData = function (client, request) {
|
|
275
|
+
return client(request)
|
|
227
276
|
}
|
|
228
277
|
|
|
229
278
|
utils.getRequestHeaders = async function (item, config) {
|
|
@@ -266,24 +315,15 @@ utils.getUTCDate = function (d = null) {
|
|
|
266
315
|
}
|
|
267
316
|
|
|
268
317
|
utils.parseResponse = async (item, response, config) => {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
{
|
|
274
|
-
headers: response.headers,
|
|
275
|
-
data: response.data.toString()
|
|
276
|
-
},
|
|
277
|
-
null,
|
|
278
|
-
2
|
|
279
|
-
)
|
|
280
|
-
)
|
|
281
|
-
}
|
|
282
|
-
|
|
318
|
+
const content = utils.isObject(response.data)
|
|
319
|
+
? JSON.stringify(response.data)
|
|
320
|
+
: response.data.toString()
|
|
321
|
+
const buffer = Buffer.from(content, 'utf8')
|
|
283
322
|
const data = merge(item, config, {
|
|
284
|
-
content
|
|
285
|
-
buffer
|
|
286
|
-
headers: response.headers
|
|
323
|
+
content,
|
|
324
|
+
buffer,
|
|
325
|
+
headers: response.headers,
|
|
326
|
+
request: response.request
|
|
287
327
|
})
|
|
288
328
|
|
|
289
329
|
if (!item.channel.logo && config.logo) {
|
|
@@ -335,4 +375,8 @@ utils.isPromise = function (promise) {
|
|
|
335
375
|
return !!promise && typeof promise.then === 'function'
|
|
336
376
|
}
|
|
337
377
|
|
|
378
|
+
utils.isObject = function (a) {
|
|
379
|
+
return !!a && a.constructor === Object
|
|
380
|
+
}
|
|
381
|
+
|
|
338
382
|
module.exports = utils
|
package/tests/bin.test.js
CHANGED
|
@@ -38,3 +38,19 @@ it('can load mini config', () => {
|
|
|
38
38
|
expect(stdoutResultTester(result)).toBe(true)
|
|
39
39
|
expect(result.includes("File 'tests/output/mini.guide.xml' successfully saved")).toBe(true)
|
|
40
40
|
})
|
|
41
|
+
|
|
42
|
+
it('can generate gzip version', () => {
|
|
43
|
+
const result = execSync(
|
|
44
|
+
`node ${pwd}/bin/epg-grabber.js \
|
|
45
|
+
--config=tests/input/mini.config.js \
|
|
46
|
+
--channels=tests/input/example.com.channels.xml \
|
|
47
|
+
--output=tests/output/mini.guide.xml.gz \
|
|
48
|
+
--gzip`,
|
|
49
|
+
{
|
|
50
|
+
encoding: 'utf8'
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
expect(stdoutResultTester(result)).toBe(true)
|
|
55
|
+
expect(result.includes("File 'tests/output/mini.guide.xml.gz' successfully saved")).toBe(true)
|
|
56
|
+
})
|
package/tests/index.test.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @jest-environment node
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import EPGGrabber from '../src/index'
|
|
6
6
|
import axios from 'axios'
|
|
7
7
|
|
|
8
8
|
jest.mock('axios')
|
|
@@ -27,7 +27,8 @@ it('return "Connection timeout" error if server does not response', done => {
|
|
|
27
27
|
lang: 'en',
|
|
28
28
|
name: 'CNN'
|
|
29
29
|
}
|
|
30
|
-
grabber
|
|
30
|
+
const grabber = new EPGGrabber(config)
|
|
31
|
+
grabber.grab(channel, '2022-01-01', (data, err) => {
|
|
31
32
|
expect(err.message).toBe('Connection timeout')
|
|
32
33
|
done()
|
|
33
34
|
})
|
|
@@ -53,8 +54,9 @@ it('can grab single channel programs', done => {
|
|
|
53
54
|
lang: 'fr',
|
|
54
55
|
name: '1TV'
|
|
55
56
|
}
|
|
57
|
+
const grabber = new EPGGrabber(config)
|
|
56
58
|
grabber
|
|
57
|
-
.grab(channel, '2022-01-01',
|
|
59
|
+
.grab(channel, '2022-01-01', (data, err) => {
|
|
58
60
|
if (err) {
|
|
59
61
|
console.log(` Error: ${err.message}`)
|
|
60
62
|
done()
|
package/tests/utils.test.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import mockAxios from 'jest-mock-axios'
|
|
2
2
|
import utils from '../src/utils'
|
|
3
|
+
import axios from 'axios'
|
|
3
4
|
import path from 'path'
|
|
4
5
|
import fs from 'fs'
|
|
5
6
|
|
|
@@ -148,7 +149,7 @@ it('can fetch data', () => {
|
|
|
148
149
|
url: 'http://example.com/20210319/1tv.json',
|
|
149
150
|
withCredentials: true
|
|
150
151
|
}
|
|
151
|
-
utils.fetchData(request).then(jest.fn).catch(jest.fn)
|
|
152
|
+
utils.fetchData(axios, request).then(jest.fn).catch(jest.fn)
|
|
152
153
|
expect(mockAxios).toHaveBeenCalledWith(
|
|
153
154
|
expect.objectContaining({
|
|
154
155
|
data: { accountID: '123' },
|