epg-grabber 0.32.0 → 0.34.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
@@ -43,11 +43,9 @@ module.exports = {
43
43
 
44
44
  ```xml
45
45
  <?xml version="1.0" ?>
46
- <site site="example.com">
47
- <channels>
48
- <channel site_id="cnn-23" xmltv_id="CNN.us">CNN</channel>
49
- </channels>
50
- </site>
46
+ <channels site="example.com">
47
+ <channel site_id="cnn-23" xmltv_id="CNN.us">CNN</channel>
48
+ </channels>
51
49
  ```
52
50
 
53
51
  ## Example Output
@@ -218,18 +216,17 @@ From each function in `config.js` you can access a `context` object containing t
218
216
 
219
217
  ```xml
220
218
  <?xml version="1.0" ?>
221
- <site site="example.com">
222
- <channels>
223
- <channel site_id="cnn-23" xmltv_id="CNN.us">CNN</channel>
224
- ...
225
- </channels>
226
- </site>
219
+ <channels site="example.com">
220
+ <channel site_id="cnn-23" xmltv_id="CNN.us">CNN</channel>
221
+ ...
222
+ </channels>
227
223
  ```
228
224
 
229
- You can also specify the language and logo for each channel individually, like so:
225
+ You can also specify the language, site and logo for each channel individually, like so:
230
226
 
231
227
  ```xml
232
228
  <channel
229
+ site="example.com"
233
230
  site_id="france-24"
234
231
  xmltv_id="France24.fr"
235
232
  lang="fr"
@@ -84,7 +84,7 @@ async function main() {
84
84
  for (let filepath of files) {
85
85
  logger.info(`Loading '${filepath}'...`)
86
86
  const channelsXML = file.read(filepath)
87
- const { channels } = parseChannels(channelsXML)
87
+ const channels = parseChannels(channelsXML)
88
88
  parsedChannels = parsedChannels.concat(channels)
89
89
  }
90
90
  } else throw new Error('Path to "channels" is missing')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "epg-grabber",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "Node.js CLI tool for grabbing EPG from different sites",
5
5
  "main": "src/index.js",
6
6
  "preferGlobal": true,
@@ -8,7 +8,7 @@
8
8
  "epg-grabber": "bin/epg-grabber.js"
9
9
  },
10
10
  "scripts": {
11
- "lint": "npx eslint ./src/**/*.js",
11
+ "lint": "npx eslint ./src/**/*.js ./tests/**/*.js",
12
12
  "test": "npx jest"
13
13
  },
14
14
  "publishConfig": {
package/src/client.js CHANGED
@@ -15,6 +15,7 @@ let timeout
15
15
  function create(config) {
16
16
  const client = setupCache(
17
17
  axios.create({
18
+ ignoreCookieErrors: true,
18
19
  headers: {
19
20
  'User-Agent':
20
21
  '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'
package/src/index.d.ts ADDED
@@ -0,0 +1,119 @@
1
+ import dayjs from 'dayjs'
2
+
3
+ export declare class Channel {
4
+ xmltv_id?: string
5
+ name: string
6
+ site?: string
7
+ site_id: string
8
+ lang?: string
9
+ logo?: string
10
+ }
11
+
12
+ type TextObject = {
13
+ lang: string
14
+ value: string
15
+ }
16
+
17
+ type IconObject = {
18
+ src: string
19
+ }
20
+
21
+ type UrlObject = {
22
+ system: string
23
+ value: string
24
+ }
25
+
26
+ type RatingObject = {
27
+ system: string
28
+ icon: string
29
+ value: string
30
+ }
31
+
32
+ type ImageObject = {
33
+ type: string
34
+ size: string
35
+ orient: string
36
+ system: string
37
+ value: string
38
+ }
39
+
40
+ type PersonObject = {
41
+ value: string
42
+ url: UrlObject[]
43
+ image: ImageObject[]
44
+ }
45
+
46
+ export declare class Program {
47
+ site?: string
48
+ channel?: string
49
+ titles: TextObject[]
50
+ sub_titles?: TextObject[]
51
+ descriptions?: TextObject[]
52
+ icon?: IconObject
53
+ episodeNumbers?: string[]
54
+ date?: number
55
+ start: number
56
+ stop?: number
57
+ urls?: UrlObject[]
58
+ ratings?: RatingObject[]
59
+ categories?: TextObject[]
60
+ directors?: PersonObject[]
61
+ actors?: PersonObject[]
62
+ writers?: PersonObject[]
63
+ adapters?: PersonObject[]
64
+ producers?: PersonObject[]
65
+ composers?: PersonObject[]
66
+ editors?: PersonObject[]
67
+ presenters?: PersonObject[]
68
+ commentators?: PersonObject[]
69
+ guests?: PersonObject[]
70
+ }
71
+
72
+ export declare type SiteConfig = {
73
+ site: string
74
+ days?: number
75
+ output?: string
76
+ channels?: () => object[] | string | Promise<object[]>
77
+ delay?: number
78
+ maxConnections?: number
79
+ request?: object
80
+ url: () => string | string | Promise<string>
81
+ logo?: () => string | string | Promise<string>
82
+ parser: () => object[] | Promise<object[]>
83
+ }
84
+
85
+ export declare function generateXMLTV({
86
+ channels,
87
+ programs,
88
+ date
89
+ }: {
90
+ channels: Channel[]
91
+ programs: Program[]
92
+ date?: string | null
93
+ }): string
94
+
95
+ export declare function parseChannels(xml: string): { site: string; channels: Channel[] }
96
+
97
+ export type GrabCallbackData = {
98
+ channel: Channel
99
+ programs: Program[]
100
+ date: dayjs.Dayjs
101
+ }
102
+
103
+ export declare class EPGGrabber {
104
+ constructor(config: SiteConfig)
105
+ grab(
106
+ channel: Channel,
107
+ date: string | dayjs.Dayjs,
108
+ cb: (data: GrabCallbackData, err: Error | null) => void
109
+ ): Promise<Program[]>
110
+ }
111
+
112
+ export declare class EPGGrabberMock {
113
+ constructor(config: SiteConfig)
114
+ grab(
115
+ channel: Channel,
116
+ date: string | dayjs.Dayjs,
117
+ cb: (data: GrabCallbackData, err: Error | null) => void
118
+ ): Promise<Program[]>
119
+ }
package/src/index.js CHANGED
@@ -53,4 +53,26 @@ class EPGGrabber {
53
53
  }
54
54
  }
55
55
 
56
+ class EPGGrabberMock {
57
+ constructor(config) {
58
+ this.config = config
59
+ }
60
+
61
+ async grab(channel, date, cb) {
62
+ let _date = getUTCDate(date)
63
+ let _programs = await this.config.parser({ channel, date: _date })
64
+ let programs = _programs.map(data => new Program(data, channel))
65
+
66
+ if (this.config.request?.timeout !== undefined && this.config.request.timeout < 1) {
67
+ cb({ programs: [], date: _date, channel }, new Error('Connection timeout'))
68
+ return []
69
+ }
70
+
71
+ cb({ programs, date: _date, channel }, null)
72
+
73
+ return programs
74
+ }
75
+ }
76
+
56
77
  module.exports.EPGGrabber = EPGGrabber
78
+ module.exports.EPGGrabberMock = EPGGrabberMock
package/src/parser.js CHANGED
@@ -9,11 +9,19 @@ module.exports.parsePrograms = parsePrograms
9
9
  function parseChannels(xml) {
10
10
  const result = convert.xml2js(xml)
11
11
  const siteTag = result.elements.find(el => el.name === 'site') || {}
12
- if (!siteTag.elements) return []
13
- const rootSite = siteTag.attributes.site
14
12
 
15
- const channelsTag = siteTag.elements.find(el => el.name === 'channels')
16
- if (!channelsTag.elements) return []
13
+ const channelsTag =
14
+ siteTag && Array.isArray(siteTag.elements)
15
+ ? siteTag.elements.find(el => el.name === 'channels')
16
+ : result.elements.find(el => el.name === 'channels')
17
+ if (!channelsTag || !channelsTag.elements) return []
18
+
19
+ let rootSite = ''
20
+ if (siteTag && siteTag.attributes && siteTag.attributes.site) {
21
+ rootSite = siteTag.attributes.site
22
+ } else if (channelsTag && channelsTag.attributes && channelsTag.attributes.site) {
23
+ rootSite = channelsTag.attributes.site
24
+ }
17
25
 
18
26
  const channels = channelsTag.elements
19
27
  .filter(el => el.name === 'channel')
@@ -26,7 +34,7 @@ function parseChannels(xml) {
26
34
  return new Channel(c)
27
35
  })
28
36
 
29
- return { site: rootSite, channels }
37
+ return channels
30
38
  }
31
39
 
32
40
  async function parsePrograms(data) {
@@ -1,7 +1,5 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <site site="example.com">
3
- <channels>
4
- <channel xmltv_id="1TV.com" site_id="1" lang="fr" logo="https://example.com/logos/1TV.png">1 TV</channel>
5
- <channel xmltv_id="2TV.com" site="example.com" site_id="2">2 TV</channel>
6
- </channels>
7
- </site>
2
+ <channels site="example.com">
3
+ <channel xmltv_id="1TV.com" site_id="1" lang="fr" logo="https://example.com/logos/1TV.png">1 TV</channel>
4
+ <channel xmltv_id="2TV.com" site_id="2">2 TV</channel>
5
+ </channels>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <channels>
3
+ <channel site="example.com" xmltv_id="1TV.com" site_id="1" lang="fr" logo="https://example.com/logos/1TV.png">1 TV</channel>
4
+ <channel site="example.com" xmltv_id="2TV.com" site_id="2">2 TV</channel>
5
+ </channels>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <site site="example.com">
3
+ <channels>
4
+ <channel xmltv_id="1TV.com" site_id="1" lang="fr" logo="https://example.com/logos/1TV.png">1 TV</channel>
5
+ <channel xmltv_id="2TV.com" site_id="2">2 TV</channel>
6
+ </channels>
7
+ </site>
@@ -1,4 +1,4 @@
1
- <?xml version="1.0" encoding="UTF-8" ?><tv date="20230709">
1
+ <?xml version="1.0" encoding="UTF-8" ?><tv date="20230930">
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=шеллы&amp;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=шеллы&amp;sid=777"/><url>https://example2.com</url></channel>
@@ -1,2 +1,3 @@
1
- import mockAxios from 'jest-mock-axios'
2
- export default mockAxios
1
+ const mockAxios = require('jest-mock-axios')
2
+
3
+ module.exports = mockAxios
package/tests/bin.test.js CHANGED
@@ -87,7 +87,7 @@ it('can produce multiple outputs', () => {
87
87
  })
88
88
 
89
89
  it('removes duplicates of the program', () => {
90
- const stdout = execSync(
90
+ execSync(
91
91
  `node ${pwd}/bin/epg-grabber.js \
92
92
  --config=tests/__data__/input/duplicates.config.js \
93
93
  --channels=tests/__data__/input/example.channels.xml \
@@ -2,7 +2,7 @@
2
2
  * @jest-environment node
3
3
  */
4
4
 
5
- import { EPGGrabber, Channel } from '../src/index'
5
+ import { EPGGrabber, EPGGrabberMock, Channel } from '../src/index'
6
6
  import axios from 'axios'
7
7
 
8
8
  jest.mock('axios')
@@ -66,3 +66,58 @@ it('can grab single channel programs', done => {
66
66
  done()
67
67
  })
68
68
  })
69
+
70
+ it('can mock epg grabber', done => {
71
+ const config = {
72
+ site: 'example.com',
73
+ url: 'http://example.com/20210319/1tv.json',
74
+ parser: ({ channel, date }) => [
75
+ { title: `Test (${channel.name})`, start: '2021-03-19T04:30:00.000Z' }
76
+ ]
77
+ }
78
+ const channel = new Channel({
79
+ site: 'example.com',
80
+ site_id: '1',
81
+ xmltv_id: '1TV.fr',
82
+ lang: 'fr',
83
+ name: '1TV'
84
+ })
85
+ const grabber = new EPGGrabberMock(config)
86
+ grabber
87
+ .grab(channel, '2022-01-01', (data, err) => {
88
+ if (err) {
89
+ done()
90
+ }
91
+ })
92
+ .then(programs => {
93
+ expect(programs.length).toBe(1)
94
+ expect(programs).toEqual([
95
+ {
96
+ site: 'example.com',
97
+ channel: '1TV.fr',
98
+ titles: [{ value: 'Test (1TV)', lang: 'fr' }],
99
+ sub_titles: [],
100
+ descriptions: [],
101
+ icon: { src: '' },
102
+ episodeNumbers: [],
103
+ date: null,
104
+ start: 1616128200000,
105
+ stop: null,
106
+ urls: [],
107
+ ratings: [],
108
+ categories: [],
109
+ directors: [],
110
+ actors: [],
111
+ writers: [],
112
+ adapters: [],
113
+ producers: [],
114
+ composers: [],
115
+ editors: [],
116
+ presenters: [],
117
+ commentators: [],
118
+ guests: []
119
+ }
120
+ ])
121
+ done()
122
+ })
123
+ })
@@ -3,14 +3,83 @@ import Channel from '../src/Channel'
3
3
  import Program from '../src/Program'
4
4
  import fs from 'fs'
5
5
 
6
- it('can parse valid channels.xml', () => {
7
- const file = fs.readFileSync('./tests/__data__/input/example.channels.xml', { encoding: 'utf-8' })
8
- const { channels, site } = parseChannels(file)
6
+ it('can parse channels.xml', () => {
7
+ const file = fs.readFileSync('./tests/__data__/input/example.channels.xml', {
8
+ encoding: 'utf-8'
9
+ })
10
+ const channels = parseChannels(file)
9
11
 
10
- expect(typeof site).toBe('string')
11
12
  expect(channels.length).toBe(2)
12
13
  expect(channels[0]).toBeInstanceOf(Channel)
13
14
  expect(channels[1]).toBeInstanceOf(Channel)
15
+ expect(channels[0]).toMatchObject({
16
+ site: 'example.com',
17
+ site_id: '1',
18
+ xmltv_id: '1TV.com',
19
+ lang: 'fr',
20
+ logo: 'https://example.com/logos/1TV.png',
21
+ name: '1 TV'
22
+ })
23
+ expect(channels[1]).toMatchObject({
24
+ site: 'example.com',
25
+ site_id: '2',
26
+ lang: '',
27
+ logo: '',
28
+ xmltv_id: '2TV.com',
29
+ name: '2 TV'
30
+ })
31
+ })
32
+
33
+ it('can parse channels.xml with inline site attribute', () => {
34
+ const file = fs.readFileSync('./tests/__data__/input/example_3.channels.xml', {
35
+ encoding: 'utf-8'
36
+ })
37
+ const channels = parseChannels(file)
38
+
39
+ expect(channels.length).toBe(2)
40
+ expect(channels[0]).toBeInstanceOf(Channel)
41
+ expect(channels[1]).toBeInstanceOf(Channel)
42
+ expect(channels[0]).toMatchObject({
43
+ site: 'example.com',
44
+ site_id: '1',
45
+ xmltv_id: '1TV.com',
46
+ lang: 'fr',
47
+ logo: 'https://example.com/logos/1TV.png',
48
+ name: '1 TV'
49
+ })
50
+ expect(channels[1]).toMatchObject({
51
+ site: 'example.com',
52
+ site_id: '2',
53
+ lang: '',
54
+ logo: '',
55
+ xmltv_id: '2TV.com',
56
+ name: '2 TV'
57
+ })
58
+ })
59
+
60
+ it('can parse legacy channels.xml', () => {
61
+ const file = fs.readFileSync('./tests/__data__/input/legacy.channels.xml', { encoding: 'utf-8' })
62
+ const channels = parseChannels(file)
63
+
64
+ expect(channels.length).toBe(2)
65
+ expect(channels[0]).toBeInstanceOf(Channel)
66
+ expect(channels[1]).toBeInstanceOf(Channel)
67
+ expect(channels[0]).toMatchObject({
68
+ site: 'example.com',
69
+ site_id: '1',
70
+ xmltv_id: '1TV.com',
71
+ lang: 'fr',
72
+ logo: 'https://example.com/logos/1TV.png',
73
+ name: '1 TV'
74
+ })
75
+ expect(channels[1]).toMatchObject({
76
+ site: 'example.com',
77
+ site_id: '2',
78
+ lang: '',
79
+ logo: '',
80
+ xmltv_id: '2TV.com',
81
+ name: '2 TV'
82
+ })
14
83
  })
15
84
 
16
85
  it('can parse programs', done => {