epg-grabber 0.34.0 → 0.36.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/package.json +5 -5
- package/src/client.js +59 -12
- package/src/config.js +27 -27
- package/src/index.js +13 -7
- package/tests/__data__/output/guide.xml +1 -1
- package/tests/config.test.js +2 -2
- package/tests/index.test.js +51 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epg-grabber",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Node.js CLI tool for grabbing EPG from different sites",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"preferGlobal": true,
|
|
@@ -28,9 +28,8 @@
|
|
|
28
28
|
"node": ">=10.0.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"axios": "^
|
|
31
|
+
"axios": "^1.6.1",
|
|
32
32
|
"axios-cache-interceptor": "^0.10.3",
|
|
33
|
-
"axios-cookiejar-support": "^1.0.1",
|
|
34
33
|
"axios-mock-adapter": "^1.20.0",
|
|
35
34
|
"commander": "^7.1.0",
|
|
36
35
|
"curl-generator": "^0.2.0",
|
|
@@ -39,9 +38,10 @@
|
|
|
39
38
|
"epg-parser": "^0.1.6",
|
|
40
39
|
"fs-extra": "^11.1.1",
|
|
41
40
|
"glob": "^7.1.6",
|
|
41
|
+
"http-cookie-agent": "^5.0.4",
|
|
42
42
|
"lodash": "^4.17.21",
|
|
43
43
|
"node-gzip": "^1.1.2",
|
|
44
|
-
"tough-cookie": "^4.
|
|
44
|
+
"tough-cookie": "^4.1.3",
|
|
45
45
|
"winston": "^3.3.3",
|
|
46
46
|
"xml-js": "^1.6.11"
|
|
47
47
|
},
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@babel/preset-env": "^7.13.12",
|
|
51
51
|
"babel-jest": "^26.6.3",
|
|
52
52
|
"eslint": "^7.22.0",
|
|
53
|
-
"jest": "^
|
|
53
|
+
"jest": "^29.7.0",
|
|
54
54
|
"jest-mock-axios": "^4.4.1"
|
|
55
55
|
},
|
|
56
56
|
"jest": {
|
package/src/client.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const { CurlGenerator } = require('curl-generator')
|
|
2
|
-
const axios = require('axios')
|
|
3
|
-
const
|
|
2
|
+
const { default: axios } = require('axios')
|
|
3
|
+
const { CookieJar } = require('tough-cookie')
|
|
4
4
|
const { setupCache } = require('axios-cache-interceptor')
|
|
5
5
|
const { isObject, isPromise } = require('./utils')
|
|
6
|
+
const { HttpCookieAgent, HttpsCookieAgent } = require('http-cookie-agent/http')
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
const jar = new CookieJar()
|
|
8
9
|
|
|
9
10
|
module.exports.create = create
|
|
10
11
|
module.exports.buildRequest = buildRequest
|
|
@@ -13,15 +14,20 @@ module.exports.parseResponse = parseResponse
|
|
|
13
14
|
let timeout
|
|
14
15
|
|
|
15
16
|
function create(config) {
|
|
16
|
-
const client =
|
|
17
|
-
axios
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
const client = axios.defaults.cache
|
|
18
|
+
? axios
|
|
19
|
+
: setupCookie(
|
|
20
|
+
setupCache(
|
|
21
|
+
axios.create({
|
|
22
|
+
jar,
|
|
23
|
+
ignoreCookieErrors: true,
|
|
24
|
+
headers: {
|
|
25
|
+
'User-Agent':
|
|
26
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71'
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
)
|
|
25
31
|
|
|
26
32
|
client.interceptors.request.use(
|
|
27
33
|
function (request) {
|
|
@@ -136,3 +142,44 @@ async function getRequestUrl({ channel, date, config }) {
|
|
|
136
142
|
}
|
|
137
143
|
return config.url
|
|
138
144
|
}
|
|
145
|
+
|
|
146
|
+
const AGENT_CREATED_BY_AXIOS_COOKIEJAR_SUPPORT = Symbol('AGENT_CREATED_BY_AXIOS_COOKIEJAR_SUPPORT')
|
|
147
|
+
|
|
148
|
+
function requestInterceptor(config) {
|
|
149
|
+
if (!config.jar) {
|
|
150
|
+
return config
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
config.httpAgent = new HttpCookieAgent({ cookies: { jar: config.jar } })
|
|
154
|
+
Object.defineProperty(config.httpAgent, AGENT_CREATED_BY_AXIOS_COOKIEJAR_SUPPORT, {
|
|
155
|
+
configurable: false,
|
|
156
|
+
enumerable: false,
|
|
157
|
+
value: true,
|
|
158
|
+
writable: false
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
config.httpsAgent = new HttpsCookieAgent({ cookies: { jar: config.jar } })
|
|
162
|
+
Object.defineProperty(config.httpsAgent, AGENT_CREATED_BY_AXIOS_COOKIEJAR_SUPPORT, {
|
|
163
|
+
configurable: false,
|
|
164
|
+
enumerable: false,
|
|
165
|
+
value: true,
|
|
166
|
+
writable: false
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
return config
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function setupCookie(axios) {
|
|
173
|
+
axios.interceptors.request.use(requestInterceptor)
|
|
174
|
+
|
|
175
|
+
if ('create' in axios) {
|
|
176
|
+
const create = axios.create
|
|
177
|
+
axios.create = (...args) => {
|
|
178
|
+
const instance = create.apply(axios, args)
|
|
179
|
+
instance.interceptors.request.use(requestInterceptor)
|
|
180
|
+
return instance
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return axios
|
|
185
|
+
}
|
package/src/config.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
const tough = require('tough-cookie')
|
|
2
2
|
const { merge } = require('lodash')
|
|
3
3
|
|
|
4
|
-
module.exports.
|
|
4
|
+
module.exports.parse = parse
|
|
5
5
|
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
function parse(config) {
|
|
7
|
+
if (!config.site) throw new Error("The required 'site' property is missing")
|
|
8
|
+
if (!config.url) throw new Error("The required 'url' property is missing")
|
|
9
|
+
if (typeof config.url !== 'function' && typeof config.url !== 'string')
|
|
10
|
+
throw new Error("The 'url' property should return the function or string")
|
|
11
|
+
if (!config.parser) throw new Error("The required 'parser' function is missing")
|
|
12
|
+
if (typeof config.parser !== 'function')
|
|
13
|
+
throw new Error("The 'parser' property should return the function")
|
|
14
|
+
if (config.logo && typeof config.logo !== 'function')
|
|
15
|
+
throw new Error("The 'logo' property should return the function")
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
17
|
+
const defaultConfig = {
|
|
18
|
+
days: 1,
|
|
19
|
+
lang: 'en',
|
|
20
|
+
delay: 3000,
|
|
21
|
+
output: 'guide.xml',
|
|
22
|
+
request: {
|
|
23
|
+
method: 'GET',
|
|
24
|
+
maxContentLength: 5 * 1024 * 1024,
|
|
25
|
+
timeout: 5000,
|
|
26
|
+
withCredentials: true,
|
|
27
|
+
jar: new tough.CookieJar(),
|
|
28
|
+
responseType: 'arraybuffer',
|
|
29
|
+
cache: false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
return merge(defaultConfig, config)
|
|
34
34
|
}
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@ const { create: createClient, buildRequest, parseResponse } = require('./client'
|
|
|
3
3
|
const { parseChannels, parsePrograms } = require('./parser')
|
|
4
4
|
const { sleep, isPromise, getUTCDate } = require('./utils')
|
|
5
5
|
const { generate: generateXMLTV } = require('./xmltv')
|
|
6
|
-
const {
|
|
6
|
+
const { parse: parseConfig } = require('./config')
|
|
7
7
|
const Channel = require('./Channel')
|
|
8
8
|
const Program = require('./Program')
|
|
9
9
|
|
|
@@ -14,7 +14,7 @@ module.exports.Program = Program
|
|
|
14
14
|
|
|
15
15
|
class EPGGrabber {
|
|
16
16
|
constructor(config = {}) {
|
|
17
|
-
this.config =
|
|
17
|
+
this.config = config
|
|
18
18
|
this.client = createClient(config)
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -26,18 +26,24 @@ class EPGGrabber {
|
|
|
26
26
|
return logo
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
async grab(channel, date, cb = () => {}) {
|
|
29
|
+
async grab(channel, date, config = {}, cb = () => {}) {
|
|
30
|
+
if (typeof config == 'function') {
|
|
31
|
+
cb = config
|
|
32
|
+
config = {}
|
|
33
|
+
}
|
|
34
|
+
config = merge(this.config, config)
|
|
35
|
+
config = parseConfig(config)
|
|
30
36
|
if (!(channel instanceof Channel)) {
|
|
31
37
|
throw new Error('The first argument must be the "Channel" class')
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
await sleep(
|
|
40
|
+
await sleep(config.delay)
|
|
35
41
|
|
|
36
42
|
date = typeof date === 'string' ? getUTCDate(date) : date
|
|
37
|
-
return buildRequest({ channel, date, config
|
|
43
|
+
return buildRequest({ channel, date, config })
|
|
38
44
|
.then(this.client)
|
|
39
45
|
.then(parseResponse)
|
|
40
|
-
.then(data => merge({ channel, date, config
|
|
46
|
+
.then(data => merge({ channel, date, config }, data))
|
|
41
47
|
.then(parsePrograms)
|
|
42
48
|
.then(programs => {
|
|
43
49
|
cb({ channel, date, programs })
|
|
@@ -45,7 +51,7 @@ class EPGGrabber {
|
|
|
45
51
|
return programs
|
|
46
52
|
})
|
|
47
53
|
.catch(err => {
|
|
48
|
-
if (
|
|
54
|
+
if (config.debug) console.log('Error:', JSON.stringify(err, null, 2))
|
|
49
55
|
cb({ channel, date, programs: [] }, err)
|
|
50
56
|
|
|
51
57
|
return []
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" ?><tv date="
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?><tv date="20231125">
|
|
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><icon src="http://example.com/logos/1TV.png?x=шеллы&sid=777"/><url>https://example.com</url></channel>
|
|
4
4
|
<channel id="3TV.com"><display-name>3 TV</display-name><icon src="http://example.com/logos/1TV.png?x=шеллы&sid=777"/><url>https://example2.com</url></channel>
|
package/tests/config.test.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parse as parseConfig } from '../src/config'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
|
|
5
5
|
it('can load config', () => {
|
|
6
|
-
const config =
|
|
6
|
+
const config = parseConfig(require(path.resolve('./tests/__data__/input/example.config.js')))
|
|
7
7
|
expect(config).toMatchObject({
|
|
8
8
|
days: 2,
|
|
9
9
|
delay: 3000,
|
package/tests/index.test.js
CHANGED
|
@@ -28,16 +28,18 @@ it('return "Connection timeout" error if server does not response', done => {
|
|
|
28
28
|
name: 'CNN'
|
|
29
29
|
})
|
|
30
30
|
const grabber = new EPGGrabber(config)
|
|
31
|
-
grabber
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
grabber
|
|
32
|
+
.grab(channel, '2022-01-01', (data, err) => {
|
|
33
|
+
expect(err.message).toBe('Connection timeout')
|
|
34
|
+
done()
|
|
35
|
+
})
|
|
36
|
+
.catch(done)
|
|
35
37
|
})
|
|
36
38
|
|
|
37
39
|
it('can grab single channel programs', done => {
|
|
38
40
|
const data = {
|
|
39
41
|
data: {
|
|
40
|
-
toString: () => '
|
|
42
|
+
toString: () => '[{"title":"Program1"}]'
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
axios.mockImplementation(() => Promise.resolve(data))
|
|
@@ -45,7 +47,7 @@ it('can grab single channel programs', done => {
|
|
|
45
47
|
const config = {
|
|
46
48
|
site: 'example.com',
|
|
47
49
|
url: 'http://example.com/20210319/1tv.json',
|
|
48
|
-
parser: () =>
|
|
50
|
+
parser: ({ content }) => JSON.parse(content)
|
|
49
51
|
}
|
|
50
52
|
const channel = new Channel({
|
|
51
53
|
site: 'example.com',
|
|
@@ -56,13 +58,53 @@ it('can grab single channel programs', done => {
|
|
|
56
58
|
})
|
|
57
59
|
const grabber = new EPGGrabber(config)
|
|
58
60
|
grabber
|
|
59
|
-
.grab(channel, '2022-01-01'
|
|
61
|
+
.grab(channel, '2022-01-01')
|
|
62
|
+
.then(programs => {
|
|
63
|
+
expect(programs[0].titles).toMatchObject([
|
|
64
|
+
{
|
|
65
|
+
lang: 'fr',
|
|
66
|
+
value: 'Program1'
|
|
67
|
+
}
|
|
68
|
+
])
|
|
69
|
+
done()
|
|
70
|
+
})
|
|
71
|
+
.catch(done)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('can use a different config for different requests', done => {
|
|
75
|
+
const data = {
|
|
76
|
+
data: {
|
|
77
|
+
toString: () => '[{"title":"Program1"}]'
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
axios.mockImplementation(() => Promise.resolve(data))
|
|
81
|
+
|
|
82
|
+
const config = {
|
|
83
|
+
site: 'example.com',
|
|
84
|
+
url: 'http://example.com/20210319/1tv.json',
|
|
85
|
+
parser: ({ content }) => JSON.parse(content)
|
|
86
|
+
}
|
|
87
|
+
const channel = new Channel({
|
|
88
|
+
site: 'example.com',
|
|
89
|
+
site_id: '1',
|
|
90
|
+
xmltv_id: '1TV.fr',
|
|
91
|
+
lang: 'fr',
|
|
92
|
+
name: '1TV'
|
|
93
|
+
})
|
|
94
|
+
const grabber = new EPGGrabber()
|
|
95
|
+
grabber
|
|
96
|
+
.grab(channel, '2022-01-01', config, (data, err) => {
|
|
60
97
|
if (err) {
|
|
61
|
-
done()
|
|
98
|
+
done(err)
|
|
62
99
|
}
|
|
63
100
|
})
|
|
64
101
|
.then(programs => {
|
|
65
|
-
expect(programs.
|
|
102
|
+
expect(programs[0].titles).toMatchObject([
|
|
103
|
+
{
|
|
104
|
+
lang: 'fr',
|
|
105
|
+
value: 'Program1'
|
|
106
|
+
}
|
|
107
|
+
])
|
|
66
108
|
done()
|
|
67
109
|
})
|
|
68
110
|
})
|