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 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
@@ -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 grabber = require('../src/index')
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, config, (data, err) => {
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
- const outputPath = options.output || config.output || 'guide.xml'
112
- utils.writeToFile(outputPath, xml)
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.22.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
- module.exports = {
4
- grab: async function (channel, date, config, cb) {
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
- config = utils.loadConfig(config)
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.debug) {
208
- console.log('Request:', JSON.stringify(request, null, 2))
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
- axios.interceptors.response.use(
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
- if (config.debug) {
270
- console.log(
271
- 'Response:',
272
- JSON.stringify(
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: response.data.toString(),
285
- buffer: response.data,
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
+ })
@@ -2,7 +2,7 @@
2
2
  * @jest-environment node
3
3
  */
4
4
 
5
- import grabber from '../src/index'
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.grab(channel, '2022-01-01', config, (data, err) => {
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', config, (data, err) => {
59
+ .grab(channel, '2022-01-01', (data, err) => {
58
60
  if (err) {
59
61
  console.log(` Error: ${err.message}`)
60
62
  done()
@@ -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' },