epg-grabber 0.37.4 → 0.38.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 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
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`)
@@ -196,7 +197,7 @@ From each function in `config.js` you can access a `context` object containing t
196
197
 
197
198
  ## Program Object
198
199
 
199
- | Property | Aliases | Type | Required |
200
+ | Property | Aliases | Type | Required |
200
201
  | --------------- | -------------------------------- | ------------------------------------------------ | -------- |
201
202
  | start | | `String` or `Number` or `Date()` | true |
202
203
  | stop | | `String` or `Number` or `Date()` | true |
@@ -390,30 +391,8 @@ You can also specify the language, site, url, logo and LCN (Logical Channel Numb
390
391
 
391
392
  ## How to use SOCKS proxy?
392
393
 
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
394
  ```
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
- }
395
+ epg-grabber --config=example.com.config.js --proxy=socks://localhost:9050
417
396
  ```
418
397
 
419
398
  ## Contribution
@@ -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.channels) config.channels = options.channels
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 = new EPGGrabber(config)
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} - ${data.channel.xmltv_id} - ${dayjs
137
- .utc(data.date)
138
- .format('MMM D, YYYY')} (${data.programs.length} programs)`
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)
@@ -0,0 +1,25 @@
1
+ import globals from "globals";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import js from "@eslint/js";
5
+ import { FlatCompat } from "@eslint/eslintrc";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const compat = new FlatCompat({
10
+ baseDirectory: __dirname,
11
+ recommendedConfig: js.configs.recommended,
12
+ allConfig: js.configs.all
13
+ });
14
+
15
+ export default [...compat.extends("eslint:recommended"), {
16
+ languageOptions: {
17
+ globals: {
18
+ ...Object.fromEntries(Object.entries(globals.browser).map(([key]) => [key, "off"])),
19
+ ...globals.node,
20
+ },
21
+
22
+ ecmaVersion: 12,
23
+ sourceType: "commonjs",
24
+ },
25
+ }];
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "epg-grabber",
3
- "version": "0.37.4",
3
+ "version": "0.38.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"
@@ -48,8 +50,11 @@
48
50
  "devDependencies": {
49
51
  "@babel/core": "^7.13.14",
50
52
  "@babel/preset-env": "^7.13.12",
53
+ "@eslint/eslintrc": "^3.2.0",
54
+ "@eslint/js": "^9.18.0",
51
55
  "babel-jest": "^29.7.0",
52
56
  "eslint": "^9.17.0",
57
+ "globals": "^15.14.0",
53
58
  "jest": "^29.7.0",
54
59
  "jest-mock-axios": "^4.4.1"
55
60
  },
package/src/Channel.js CHANGED
@@ -1,5 +1,3 @@
1
- const { toArray } = require('./utils')
2
-
3
1
  class Channel {
4
2
  constructor(c) {
5
3
  const data = {
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
- : 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
- )
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
@@ -101,19 +101,21 @@ export type GrabCallbackData = {
101
101
  }
102
102
 
103
103
  export declare class EPGGrabber {
104
- constructor(config: SiteConfig)
104
+ constructor(config?: SiteConfig)
105
105
  grab(
106
106
  channel: Channel,
107
107
  date: string | dayjs.Dayjs,
108
- cb: (data: GrabCallbackData, err: Error | null) => void
108
+ config?: SiteConfig,
109
+ cb?: (data: GrabCallbackData, err: Error | null) => void
109
110
  ): Promise<Program[]>
110
111
  }
111
112
 
112
113
  export declare class EPGGrabberMock {
113
- constructor(config: SiteConfig)
114
+ constructor(config?: SiteConfig)
114
115
  grab(
115
116
  channel: Channel,
116
117
  date: string | dayjs.Dayjs,
117
- cb: (data: GrabCallbackData, err: Error | null) => void
118
+ config?: SiteConfig,
119
+ cb?: (data: GrabCallbackData, err: Error | null) => void
118
120
  ): Promise<Program[]>
119
121
  }
package/src/index.js CHANGED
@@ -60,10 +60,18 @@ class EPGGrabber {
60
60
  }
61
61
 
62
62
  class EPGGrabberMock {
63
- constructor(config) {
63
+ constructor(config = {}) {
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') {
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="20241215">
1
+ <?xml version="1.0" encoding="UTF-8" ?><tv date="20250131">
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="4TV.com"><title>Program1</title></programme>
8
+ <programme start="20220101000000 +0000" stop="20220101010000 +0000" channel="3TV.com"><title>Program1</title></programme>
9
+ <programme start="20220101000000 +0000" stop="20220101010000 +0000" channel="2TV.com"><title>Program1</title></programme>
6
10
  </tv>
package/tests/bin.test.js CHANGED
@@ -36,6 +36,7 @@ it('can load mini config', () => {
36
36
  --days=3 \
37
37
  --delay=0 \
38
38
  --debug \
39
+ --proxy=socks://127.0.0.1:1086 \
39
40
  --timeout=1`,
40
41
  {
41
42
  encoding: 'utf8'
@@ -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&param2=val'
10
10
  expect(escapeString(string)).toBe('http://example.com/logos/1TV.png?param1=val&amp;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
+ })
package/.eslintrc.js DELETED
@@ -1,11 +0,0 @@
1
- module.exports = {
2
- env: {
3
- browser: false,
4
- node: true,
5
- es6: true
6
- },
7
- extends: 'eslint:recommended',
8
- parserOptions: {
9
- ecmaVersion: 12
10
- }
11
- }