epg-grabber 0.37.5 → 0.39.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 +4 -24
- package/bin/epg-grabber.js +32 -11
- package/package.json +3 -1
- package/src/client.js +4 -56
- package/src/config.js +5 -3
- package/src/index.d.ts +1 -0
- package/src/index.js +11 -0
- package/src/utils.js +16 -0
- package/tests/__data__/output/guide.xml +5 -1
- package/tests/bin.test.js +1 -0
- package/tests/utils.test.js +8 -1
package/README.md
CHANGED
|
@@ -73,6 +73,7 @@ Arguments:
|
|
|
73
73
|
|
|
74
74
|
- `-c, --config`: path to config file
|
|
75
75
|
- `-o, --output`: path to output file or path template (example: `guides/{site}.{lang}.xml`; default: `guide.xml`)
|
|
76
|
+
- `-x, --proxy`: use the specified proxy (example: `socks5://username:password@127.0.0.1:1234`)
|
|
76
77
|
- `--channels`: path to list of channels; you can also use wildcard to specify the path to multiple files at once (example: `example.com_*.channels.xml`)
|
|
77
78
|
- `--lang`: set default language for all programs (default: `en`)
|
|
78
79
|
- `--days`: number of days for which to grab the program (default: `1`)
|
|
@@ -97,6 +98,7 @@ module.exports = {
|
|
|
97
98
|
days: 3, // number of days for which to grab the program (default: 1)
|
|
98
99
|
delay: 5000, // delay between requests (default: 3000)
|
|
99
100
|
maxConnections: 200, // limit on the number of concurrent requests (default: 1)
|
|
101
|
+
curl: true, // display each request as CURL (default: false)
|
|
100
102
|
|
|
101
103
|
request: { // request options (details: https://github.com/axios/axios#request-config)
|
|
102
104
|
|
|
@@ -196,7 +198,7 @@ From each function in `config.js` you can access a `context` object containing t
|
|
|
196
198
|
|
|
197
199
|
## Program Object
|
|
198
200
|
|
|
199
|
-
| Property
|
|
201
|
+
| Property | Aliases | Type | Required |
|
|
200
202
|
| --------------- | -------------------------------- | ------------------------------------------------ | -------- |
|
|
201
203
|
| start | | `String` or `Number` or `Date()` | true |
|
|
202
204
|
| stop | | `String` or `Number` or `Date()` | true |
|
|
@@ -390,30 +392,8 @@ You can also specify the language, site, url, logo and LCN (Logical Channel Numb
|
|
|
390
392
|
|
|
391
393
|
## How to use SOCKS proxy?
|
|
392
394
|
|
|
393
|
-
First, you need to install [socks-proxy-agent](https://www.npmjs.com/package/socks-proxy-agent):
|
|
394
|
-
|
|
395
|
-
```sh
|
|
396
|
-
npm install socks-proxy-agent
|
|
397
395
|
```
|
|
398
|
-
|
|
399
|
-
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:
|
|
400
|
-
|
|
401
|
-
```js
|
|
402
|
-
const { SocksProxyAgent } = require('socks-proxy-agent')
|
|
403
|
-
|
|
404
|
-
const torProxyAgent = new SocksProxyAgent('socks://localhost:9050')
|
|
405
|
-
|
|
406
|
-
module.exports = {
|
|
407
|
-
site: 'example.com',
|
|
408
|
-
url: 'https://example.com/epg.json',
|
|
409
|
-
request: {
|
|
410
|
-
httpsAgent: torProxyAgent,
|
|
411
|
-
httpAgent: torProxyAgent
|
|
412
|
-
},
|
|
413
|
-
parser(context) {
|
|
414
|
-
// ...
|
|
415
|
-
}
|
|
416
|
-
}
|
|
396
|
+
epg-grabber --config=example.com.config.js --proxy=socks://localhost:9050
|
|
417
397
|
```
|
|
418
398
|
|
|
419
399
|
## Contribution
|
package/bin/epg-grabber.js
CHANGED
|
@@ -5,14 +5,15 @@ const program = new Command()
|
|
|
5
5
|
const { merge } = require('lodash')
|
|
6
6
|
const { gzip } = require('node-gzip')
|
|
7
7
|
const file = require('../src/file')
|
|
8
|
-
const { EPGGrabber, parseChannels, generateXMLTV } = require('../src/index')
|
|
8
|
+
const { EPGGrabber, EPGGrabberMock, parseChannels, generateXMLTV } = require('../src/index')
|
|
9
9
|
const { create: createLogger } = require('../src/logger')
|
|
10
|
-
const { parseNumber, getUTCDate } = require('../src/utils')
|
|
10
|
+
const { parseNumber, getUTCDate, parseProxy } = require('../src/utils')
|
|
11
11
|
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
15
|
const { TaskQueue } = require('cwait')
|
|
16
|
+
const { SocksProxyAgent } = require('socks-proxy-agent')
|
|
16
17
|
|
|
17
18
|
dayjs.extend(utc)
|
|
18
19
|
|
|
@@ -22,6 +23,7 @@ program
|
|
|
22
23
|
.description(description)
|
|
23
24
|
.requiredOption('-c, --config <config>', 'Path to [site].config.js file')
|
|
24
25
|
.option('-o, --output <output>', 'Path to output file')
|
|
26
|
+
.option('-x, --proxy <url>', 'Use the specified proxy')
|
|
25
27
|
.option('--channels <channels>', 'Path to list of channels')
|
|
26
28
|
.option('--lang <lang>', 'Set default language for all programs')
|
|
27
29
|
.option('--days <days>', 'Number of days for which to grab the program', parseNumber)
|
|
@@ -63,10 +65,26 @@ async function main() {
|
|
|
63
65
|
request: {}
|
|
64
66
|
})
|
|
65
67
|
|
|
66
|
-
if (options.timeout) config.request.timeout = options.timeout
|
|
67
|
-
if (options.cacheTtl) config.request.cache.ttl = options.cacheTtl
|
|
68
|
-
|
|
69
|
-
if (options.
|
|
68
|
+
if (options.timeout !== undefined) config.request.timeout = options.timeout
|
|
69
|
+
if (options.cacheTtl !== undefined) config.request.cache.ttl = options.cacheTtl
|
|
70
|
+
if (options.channels !== undefined) config.channels = options.channels
|
|
71
|
+
if (options.proxy !== undefined) {
|
|
72
|
+
const proxy = parseProxy(options.proxy)
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
proxy.protocol &&
|
|
76
|
+
['socks', 'socks5', 'socks5h', 'socks4', 'socks4a'].includes(String(proxy.protocol))
|
|
77
|
+
) {
|
|
78
|
+
const socksProxyAgent = new SocksProxyAgent(options.proxy)
|
|
79
|
+
|
|
80
|
+
config.request = {
|
|
81
|
+
...config.request,
|
|
82
|
+
...{ httpAgent: socksProxyAgent, httpsAgent: socksProxyAgent }
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
config.request = { ...config.request, ...{ proxy } }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
70
88
|
|
|
71
89
|
let parsedChannels = []
|
|
72
90
|
if (config.channels) {
|
|
@@ -89,9 +107,10 @@ async function main() {
|
|
|
89
107
|
}
|
|
90
108
|
} else throw new Error('Path to "channels" is missing')
|
|
91
109
|
|
|
92
|
-
const grabber =
|
|
110
|
+
const grabber =
|
|
111
|
+
process.env.NODE_ENV === 'test' ? new EPGGrabberMock(config) : new EPGGrabber(config)
|
|
93
112
|
|
|
94
|
-
let template = options.output || config.output
|
|
113
|
+
let template = options.output || config.output || 'guide.xml'
|
|
95
114
|
const variables = file.templateVariables(template)
|
|
96
115
|
|
|
97
116
|
const groups = _.groupBy(parsedChannels, channel => {
|
|
@@ -133,9 +152,11 @@ async function main() {
|
|
|
133
152
|
await grabber
|
|
134
153
|
.grab(channel, date, (data, err) => {
|
|
135
154
|
logger.info(
|
|
136
|
-
`[${i}/${total}] ${config.site} - ${
|
|
137
|
-
.
|
|
138
|
-
|
|
155
|
+
`[${i}/${total}] ${config.site} - ${
|
|
156
|
+
data.channel.xmltv_id || data.channel.site_id
|
|
157
|
+
} - ${dayjs.utc(data.date).format('MMM D, YYYY')} (${
|
|
158
|
+
data.programs.length
|
|
159
|
+
} programs)`
|
|
139
160
|
)
|
|
140
161
|
|
|
141
162
|
if (err) logger.error(err.message)
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epg-grabber",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Node.js CLI tool for grabbing EPG from different sites",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"preferGlobal": true,
|
|
7
|
+
"homepage": "https://github.com/freearhey/epg-grabber",
|
|
7
8
|
"bin": {
|
|
8
9
|
"epg-grabber": "bin/epg-grabber.js"
|
|
9
10
|
},
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"http-cookie-agent": "^6.0.8",
|
|
42
43
|
"lodash": "^4.17.21",
|
|
43
44
|
"node-gzip": "^1.1.2",
|
|
45
|
+
"socks-proxy-agent": "^8.0.5",
|
|
44
46
|
"tough-cookie": "^5.0.0",
|
|
45
47
|
"winston": "^3.3.3",
|
|
46
48
|
"xml-js": "^1.6.11"
|
package/src/client.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
const { CurlGenerator } = require('curl-generator')
|
|
2
2
|
const { default: axios } = require('axios')
|
|
3
|
-
const { CookieJar } = require('tough-cookie')
|
|
4
3
|
const { setupCache } = require('axios-cache-interceptor')
|
|
5
4
|
const { isObject, isPromise } = require('./utils')
|
|
6
|
-
const { HttpCookieAgent, HttpsCookieAgent } = require('http-cookie-agent/http')
|
|
7
|
-
|
|
8
|
-
const jar = new CookieJar()
|
|
9
5
|
|
|
10
6
|
module.exports.create = create
|
|
11
7
|
module.exports.buildRequest = buildRequest
|
|
@@ -16,17 +12,10 @@ let timeout
|
|
|
16
12
|
function create(config) {
|
|
17
13
|
const client = axios.defaults.cache
|
|
18
14
|
? axios
|
|
19
|
-
:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
)
|
|
15
|
+
: setupCache(
|
|
16
|
+
axios.create({
|
|
17
|
+
ignoreCookieErrors: true
|
|
18
|
+
})
|
|
30
19
|
)
|
|
31
20
|
|
|
32
21
|
client.interceptors.request.use(
|
|
@@ -142,44 +131,3 @@ async function getRequestUrl({ channel, date, config }) {
|
|
|
142
131
|
}
|
|
143
132
|
return config.url
|
|
144
133
|
}
|
|
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,5 +1,5 @@
|
|
|
1
|
-
const tough = require('tough-cookie')
|
|
2
1
|
const { merge } = require('lodash')
|
|
2
|
+
const { version, homepage } = require('../package.json')
|
|
3
3
|
|
|
4
4
|
module.exports.parse = parse
|
|
5
5
|
|
|
@@ -24,9 +24,11 @@ function parse(config) {
|
|
|
24
24
|
maxContentLength: 5 * 1024 * 1024,
|
|
25
25
|
timeout: 5000,
|
|
26
26
|
withCredentials: true,
|
|
27
|
-
jar: new tough.CookieJar(),
|
|
28
27
|
responseType: 'arraybuffer',
|
|
29
|
-
cache: false
|
|
28
|
+
cache: false,
|
|
29
|
+
headers: {
|
|
30
|
+
'User-Agent': `EPGGrabber/${version} (${homepage})`
|
|
31
|
+
}
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
package/src/index.d.ts
CHANGED
package/src/index.js
CHANGED
|
@@ -64,6 +64,14 @@ class EPGGrabberMock {
|
|
|
64
64
|
this.config = config
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
async loadLogo(channel) {
|
|
68
|
+
const logo = this.config.logo({ channel })
|
|
69
|
+
if (isPromise(logo)) {
|
|
70
|
+
return await logo
|
|
71
|
+
}
|
|
72
|
+
return logo
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
async grab(channel, date, config = {}, cb = () => {}) {
|
|
68
76
|
let _date = getUTCDate(date)
|
|
69
77
|
if (typeof config == 'function') {
|
|
@@ -72,6 +80,9 @@ class EPGGrabberMock {
|
|
|
72
80
|
}
|
|
73
81
|
config = merge(this.config, config)
|
|
74
82
|
config = parseConfig(config)
|
|
83
|
+
|
|
84
|
+
await buildRequest({ channel, date, config })
|
|
85
|
+
|
|
75
86
|
let _programs = await config.parser({ channel, date: _date })
|
|
76
87
|
let programs = _programs.map(data => new Program(data, channel))
|
|
77
88
|
|
package/src/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { URL } = require('node:url')
|
|
1
2
|
const dayjs = require('dayjs')
|
|
2
3
|
const utc = require('dayjs/plugin/utc')
|
|
3
4
|
|
|
@@ -13,6 +14,21 @@ module.exports.formatDate = formatDate
|
|
|
13
14
|
module.exports.toArray = toArray
|
|
14
15
|
module.exports.toUnix = toUnix
|
|
15
16
|
module.exports.isDate = isDate
|
|
17
|
+
module.exports.parseProxy = parseProxy
|
|
18
|
+
|
|
19
|
+
function parseProxy(_url) {
|
|
20
|
+
const parsed = new URL(_url)
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
protocol: parsed.protocol.replace(':', '') || null,
|
|
24
|
+
auth: {
|
|
25
|
+
username: parsed.username || null,
|
|
26
|
+
password: parsed.password || null
|
|
27
|
+
},
|
|
28
|
+
host: parsed.hostname,
|
|
29
|
+
port: parsed.port ? parseInt(parsed.port) : null
|
|
30
|
+
}
|
|
31
|
+
}
|
|
16
32
|
|
|
17
33
|
function sleep(ms) {
|
|
18
34
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" ?><tv date="
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?><tv date="20250715">
|
|
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
|
<channel id="3TV.com"><display-name>3 TV</display-name><url>https://example2.com</url></channel>
|
|
5
5
|
<channel id="4TV.com"><display-name>4 TV</display-name><url>https://example2.com</url></channel>
|
|
6
|
+
<programme start="20220101000000 +0000" stop="20220101010000 +0000" channel="1TV.com"><title lang="fr">Program1</title></programme>
|
|
7
|
+
<programme start="20220101000000 +0000" stop="20220101010000 +0000" channel="2TV.com"><title>Program1</title></programme>
|
|
8
|
+
<programme start="20220101000000 +0000" stop="20220101010000 +0000" channel="4TV.com"><title>Program1</title></programme>
|
|
9
|
+
<programme start="20220101000000 +0000" stop="20220101010000 +0000" channel="3TV.com"><title>Program1</title></programme>
|
|
6
10
|
</tv>
|
package/tests/bin.test.js
CHANGED
package/tests/utils.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { escapeString } from '../src/utils'
|
|
1
|
+
import { escapeString, parseProxy } from '../src/utils'
|
|
2
2
|
|
|
3
3
|
it('can escape string', () => {
|
|
4
4
|
const string = 'Música тест dun. &<>"\'\r\n'
|
|
@@ -9,3 +9,10 @@ it('can escape url', () => {
|
|
|
9
9
|
const string = 'http://example.com/logos/1TV.png?param1=val¶m2=val'
|
|
10
10
|
expect(escapeString(string)).toBe('http://example.com/logos/1TV.png?param1=val&param2=val')
|
|
11
11
|
})
|
|
12
|
+
|
|
13
|
+
it('can parse proxy', () => {
|
|
14
|
+
const string = 'socks://127.0.0.1:1234'
|
|
15
|
+
expect(parseProxy(string)).toMatchObject({
|
|
16
|
+
protocol: 'socks'
|
|
17
|
+
})
|
|
18
|
+
})
|