epg-grabber 0.30.1 → 0.31.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 +2 -0
- package/bin/epg-grabber.js +34 -15
- package/package.json +2 -1
- package/src/client.js +106 -106
- package/tests/__data__/output/duplicates.guide.xml +1 -1
package/README.md
CHANGED
|
@@ -80,6 +80,7 @@ Arguments:
|
|
|
80
80
|
- `--days`: number of days for which to grab the program (default: `1`)
|
|
81
81
|
- `--delay`: delay between requests in milliseconds (default: `3000`)
|
|
82
82
|
- `--timeout`: set a timeout for each request in milliseconds (default: `5000`)
|
|
83
|
+
- `--max-connections`: set a limit on the number of concurrent requests per site (default: `1`)
|
|
83
84
|
- `--cache-ttl`: maximum time for storing each request in milliseconds (default: `0`)
|
|
84
85
|
- `--gzip`: compress the output (default: `false`)
|
|
85
86
|
- `--debug`: enable debug mode (default: `false`)
|
|
@@ -97,6 +98,7 @@ module.exports = {
|
|
|
97
98
|
lang: 'fr', // default language for all programs (default: 'en')
|
|
98
99
|
days: 3, // number of days for which to grab the program (default: 1)
|
|
99
100
|
delay: 5000, // delay between requests (default: 3000)
|
|
101
|
+
maxConnections: 200, // limit on the number of concurrent requests (default: 1)
|
|
100
102
|
|
|
101
103
|
request: { // request options (details: https://github.com/axios/axios#request-config)
|
|
102
104
|
|
package/bin/epg-grabber.js
CHANGED
|
@@ -12,6 +12,7 @@ const { name, version, description } = require('../package.json')
|
|
|
12
12
|
const _ = require('lodash')
|
|
13
13
|
const dayjs = require('dayjs')
|
|
14
14
|
const utc = require('dayjs/plugin/utc')
|
|
15
|
+
const { TaskQueue } = require('cwait')
|
|
15
16
|
|
|
16
17
|
dayjs.extend(utc)
|
|
17
18
|
|
|
@@ -26,6 +27,11 @@ program
|
|
|
26
27
|
.option('--days <days>', 'Number of days for which to grab the program', parseNumber)
|
|
27
28
|
.option('--delay <delay>', 'Delay between requests (in milliseconds)', parseNumber)
|
|
28
29
|
.option('--timeout <timeout>', 'Set a timeout for each request (in milliseconds)', parseNumber)
|
|
30
|
+
.option(
|
|
31
|
+
'--max-connections <maxConnections>',
|
|
32
|
+
'Set a limit on the number of concurrent requests per site',
|
|
33
|
+
parseNumber
|
|
34
|
+
)
|
|
29
35
|
.option(
|
|
30
36
|
'--cache-ttl <cacheTtl>',
|
|
31
37
|
'Maximum time for storing each request (in milliseconds)',
|
|
@@ -53,6 +59,7 @@ async function main() {
|
|
|
53
59
|
curl: options.curl,
|
|
54
60
|
lang: options.lang,
|
|
55
61
|
delay: options.delay,
|
|
62
|
+
maxConnections: options.maxConnections,
|
|
56
63
|
request: {}
|
|
57
64
|
})
|
|
58
65
|
|
|
@@ -89,33 +96,45 @@ async function main() {
|
|
|
89
96
|
let programs = []
|
|
90
97
|
let i = 1
|
|
91
98
|
let days = config.days || 1
|
|
99
|
+
const maxConnections = config.maxConnections || 1
|
|
92
100
|
const total = channels.length * days
|
|
93
101
|
const utcDate = getUTCDate()
|
|
94
102
|
const dates = Array.from({ length: days }, (_, i) => utcDate.add(i, 'd'))
|
|
103
|
+
const taskQueue = new TaskQueue(Promise, maxConnections)
|
|
104
|
+
|
|
105
|
+
let queue = []
|
|
95
106
|
for (let channel of channels) {
|
|
96
107
|
if (!channel.logo && config.logo) {
|
|
97
108
|
channel.logo = await grabber.loadLogo(channel)
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
for (let date of dates) {
|
|
101
|
-
|
|
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
|
-
})
|
|
112
|
+
queue.push({ channel, date })
|
|
116
113
|
}
|
|
117
114
|
}
|
|
118
115
|
|
|
116
|
+
await Promise.all(
|
|
117
|
+
queue.map(
|
|
118
|
+
taskQueue.wrap(async ({ channel, date }) => {
|
|
119
|
+
await grabber
|
|
120
|
+
.grab(channel, date, (data, err) => {
|
|
121
|
+
logger.info(
|
|
122
|
+
`[${i}/${total}] ${config.site} - ${data.channel.xmltv_id} - ${dayjs
|
|
123
|
+
.utc(data.date)
|
|
124
|
+
.format('MMM D, YYYY')} (${data.programs.length} programs)`
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if (err) logger.error(err.message)
|
|
128
|
+
|
|
129
|
+
if (i < total) i++
|
|
130
|
+
})
|
|
131
|
+
.then(results => {
|
|
132
|
+
programs = programs.concat(results)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
119
138
|
programs = _.uniqBy(programs, p => p.start + p.channel)
|
|
120
139
|
|
|
121
140
|
const xml = generateXMLTV({ channels, programs })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epg-grabber",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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,6 +34,7 @@
|
|
|
34
34
|
"axios-mock-adapter": "^1.20.0",
|
|
35
35
|
"commander": "^7.1.0",
|
|
36
36
|
"curl-generator": "^0.2.0",
|
|
37
|
+
"cwait": "^1.1.2",
|
|
37
38
|
"dayjs": "^1.10.4",
|
|
38
39
|
"epg-parser": "^0.1.6",
|
|
39
40
|
"fs-extra": "^11.1.1",
|
package/src/client.js
CHANGED
|
@@ -13,125 +13,125 @@ module.exports.parseResponse = parseResponse
|
|
|
13
13
|
let timeout
|
|
14
14
|
|
|
15
15
|
function create(config) {
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
16
|
+
const client = setupCache(
|
|
17
|
+
axios.create({
|
|
18
|
+
headers: {
|
|
19
|
+
'User-Agent':
|
|
20
|
+
'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'
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
client.interceptors.request.use(
|
|
26
|
+
function (request) {
|
|
27
|
+
if (config.debug) {
|
|
28
|
+
console.log('Request:', JSON.stringify(request, null, 2))
|
|
29
|
+
}
|
|
30
|
+
return request
|
|
31
|
+
},
|
|
32
|
+
function (error) {
|
|
33
|
+
return Promise.reject(error)
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
client.interceptors.response.use(
|
|
38
|
+
function (response) {
|
|
39
|
+
if (config.debug) {
|
|
40
|
+
const data =
|
|
41
|
+
isObject(response.data) || Array.isArray(response.data)
|
|
42
|
+
? JSON.stringify(response.data)
|
|
43
|
+
: response.data.toString()
|
|
44
|
+
console.log(
|
|
45
|
+
'Response:',
|
|
46
|
+
JSON.stringify(
|
|
47
|
+
{
|
|
48
|
+
headers: response.headers,
|
|
49
|
+
data,
|
|
50
|
+
cached: response.cached
|
|
51
|
+
},
|
|
52
|
+
null,
|
|
53
|
+
2
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
clearTimeout(timeout)
|
|
59
|
+
return response
|
|
60
|
+
},
|
|
61
|
+
function (error) {
|
|
62
|
+
clearTimeout(timeout)
|
|
63
|
+
return Promise.reject(error)
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return client
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
async function buildRequest({ channel, date, config }) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
71
|
+
const CancelToken = axios.CancelToken
|
|
72
|
+
const source = CancelToken.source()
|
|
73
|
+
const request = { ...config.request }
|
|
74
|
+
timeout = setTimeout(() => {
|
|
75
|
+
source.cancel('Connection timeout')
|
|
76
|
+
}, request.timeout)
|
|
77
|
+
request.headers = await getRequestHeaders({ channel, date, config })
|
|
78
|
+
request.url = await getRequestUrl({ channel, date, config })
|
|
79
|
+
request.data = await getRequestData({ channel, date, config })
|
|
80
|
+
request.cancelToken = source.token
|
|
81
|
+
|
|
82
|
+
if (config.curl) {
|
|
83
|
+
const curl = CurlGenerator({
|
|
84
|
+
url: request.url,
|
|
85
|
+
method: request.method,
|
|
86
|
+
headers: request.headers,
|
|
87
|
+
body: request.data
|
|
88
|
+
})
|
|
89
|
+
console.log(curl)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return request
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
function parseResponse(response) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
return {
|
|
97
|
+
content: response.data.toString(),
|
|
98
|
+
buffer: response.data,
|
|
99
|
+
headers: response.headers,
|
|
100
|
+
request: response.request,
|
|
101
|
+
cached: response.cached
|
|
102
|
+
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
async function getRequestHeaders({ channel, date, config }) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
if (typeof config.request.headers === 'function') {
|
|
107
|
+
const headers = config.request.headers({ channel, date })
|
|
108
|
+
if (isPromise(headers)) {
|
|
109
|
+
return await headers
|
|
110
|
+
}
|
|
111
|
+
return headers
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return config.request.headers || null
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
async function getRequestData({ channel, date, config }) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
if (typeof config.request.data === 'function') {
|
|
119
|
+
const data = config.request.data({ channel, date })
|
|
120
|
+
if (isPromise(data)) {
|
|
121
|
+
return await data
|
|
122
|
+
}
|
|
123
|
+
return data
|
|
124
|
+
}
|
|
125
|
+
return config.request.data || null
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
async function getRequestUrl({ channel, date, config }) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
129
|
+
if (typeof config.url === 'function') {
|
|
130
|
+
const url = config.url({ channel, date })
|
|
131
|
+
if (isPromise(url)) {
|
|
132
|
+
return await url
|
|
133
|
+
}
|
|
134
|
+
return url
|
|
135
|
+
}
|
|
136
|
+
return config.url
|
|
137
137
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" ?><tv date="
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?><tv date="20230521">
|
|
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>
|