epg-grabber 0.28.4 → 0.29.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "epg-grabber",
3
- "version": "0.28.4",
3
+ "version": "0.29.0",
4
4
  "description": "Node.js CLI tool for grabbing EPG from different sites",
5
5
  "main": "src/index.js",
6
6
  "preferGlobal": true,
package/src/Program.js CHANGED
@@ -1,21 +1,29 @@
1
1
  const { padStart } = require('lodash')
2
2
  const { toArray, toUnix, parseNumber } = require('./utils')
3
+ const Channel = require('./Channel')
3
4
 
4
5
  class Program {
5
- constructor(p) {
6
+ constructor(p, c) {
7
+ if (!(c instanceof Channel)) {
8
+ throw new Error('The second argument in the constructor must be the "Channel" class')
9
+ }
10
+
6
11
  const data = {
7
- channel: p.channel,
8
- title: p.title,
9
- sub_title: p.sub_title || '',
10
- description: [p.description, p.desc, ''].find(i => i !== undefined),
12
+ site: p.site || c.site || '',
13
+ channel: p.channel || c.id || '',
14
+ titles: toArray(p.titles || p.title).map(text => toTextObject(text, c.lang)),
15
+ sub_titles: toArray(p.sub_titles || p.sub_title).map(text => toTextObject(text, c.lang)),
16
+ descriptions: toArray(p.descriptions || p.description || p.desc).map(text =>
17
+ toTextObject(text, c.lang)
18
+ ),
11
19
  icon: toIconObject(p.icon),
12
- episodeNumbers: getEpisodeNumbers(p.season, p.episode),
20
+ episodeNumbers: p.episodeNumbers || getEpisodeNumbers(p.season, p.episode),
13
21
  date: p.date ? toUnix(p.date) : null,
14
- start: toUnix(p.start),
15
- stop: toUnix(p.stop),
22
+ start: p.start ? toUnix(p.start) : null,
23
+ stop: p.stop ? toUnix(p.stop) : null,
16
24
  urls: toArray(p.urls || p.url).map(toUrlObject),
17
25
  ratings: toArray(p.ratings || p.rating).map(toRatingObject),
18
- categories: toArray(p.categories || p.category),
26
+ categories: toArray(p.categories || p.category).map(text => toTextObject(text, c.lang)),
19
27
  directors: toArray(p.directors || p.director).map(toPersonObject),
20
28
  actors: toArray(p.actors || p.actor).map(toPersonObject),
21
29
  writers: toArray(p.writers || p.writer).map(toPersonObject),
@@ -36,6 +44,17 @@ class Program {
36
44
 
37
45
  module.exports = Program
38
46
 
47
+ function toTextObject(text, lang) {
48
+ if (typeof text === 'string') {
49
+ return { value: text, lang }
50
+ }
51
+
52
+ return {
53
+ value: text.value,
54
+ lang: text.lang
55
+ }
56
+ }
57
+
39
58
  function toPersonObject(person) {
40
59
  if (typeof person === 'string') {
41
60
  return {
@@ -84,7 +103,8 @@ function toUrlObject(url) {
84
103
  }
85
104
 
86
105
  function toIconObject(icon) {
87
- if (!icon || typeof icon === 'string') return { src: icon }
106
+ if (!icon) return { src: '' }
107
+ if (typeof icon === 'string') return { src: icon }
88
108
 
89
109
  return {
90
110
  src: icon.src || ''
package/src/client.js CHANGED
@@ -2,7 +2,7 @@ const { CurlGenerator } = require('curl-generator')
2
2
  const axios = require('axios').default
3
3
  const axiosCookieJarSupport = require('axios-cookiejar-support').default
4
4
  const { setupCache } = require('axios-cache-interceptor')
5
- const { isObject, isPromise, getUTCDate } = require('./utils')
5
+ const { isObject, isPromise } = require('./utils')
6
6
 
7
7
  axiosCookieJarSupport(axios)
8
8
 
@@ -68,7 +68,6 @@ function create(config) {
68
68
  }
69
69
 
70
70
  async function buildRequest({ channel, date, config }) {
71
- date = typeof date === 'string' ? getUTCDate(date) : date
72
71
  const CancelToken = axios.CancelToken
73
72
  const source = CancelToken.source()
74
73
  const request = { ...config.request }
package/src/index.js CHANGED
@@ -1,12 +1,16 @@
1
1
  const { merge } = require('lodash')
2
2
  const { create: createClient, buildRequest, parseResponse } = require('./client')
3
3
  const { parseChannels, parsePrograms } = require('./parser')
4
+ const { sleep, isPromise, getUTCDate } = require('./utils')
4
5
  const { generate: generateXMLTV } = require('./xmltv')
5
6
  const { load: loadConfig } = require('./config')
6
- const { sleep, isPromise } = require('./utils')
7
+ const Channel = require('./Channel')
8
+ const Program = require('./Program')
7
9
 
8
10
  module.exports.generateXMLTV = generateXMLTV
9
11
  module.exports.parseChannels = parseChannels
12
+ module.exports.Channel = Channel
13
+ module.exports.Program = Program
10
14
 
11
15
  class EPGGrabber {
12
16
  constructor(config = {}) {
@@ -25,6 +29,7 @@ class EPGGrabber {
25
29
  async grab(channel, date, cb = () => {}) {
26
30
  await sleep(this.config.delay)
27
31
 
32
+ date = typeof date === 'string' ? getUTCDate(date) : date
28
33
  return buildRequest({ channel, date, config: this.config })
29
34
  .then(this.client)
30
35
  .then(parseResponse)
package/src/parser.js CHANGED
@@ -41,11 +41,5 @@ async function parsePrograms(data) {
41
41
  throw new Error('Parser should return an array')
42
42
  }
43
43
 
44
- return programs
45
- .filter(i => i)
46
- .map(p => {
47
- p.channel = p.channel || channel.id
48
-
49
- return new Program(p)
50
- })
44
+ return programs.filter(i => i).map(p => new Program(p, channel))
51
45
  }
package/src/utils.js CHANGED
@@ -12,11 +12,16 @@ module.exports.parseNumber = parseNumber
12
12
  module.exports.formatDate = formatDate
13
13
  module.exports.toArray = toArray
14
14
  module.exports.toUnix = toUnix
15
+ module.exports.isDate = isDate
15
16
 
16
17
  function sleep(ms) {
17
18
  return new Promise(resolve => setTimeout(resolve, ms))
18
19
  }
19
20
 
21
+ function isDate(d) {
22
+ return dayjs(d).isValid()
23
+ }
24
+
20
25
  function isObject(a) {
21
26
  return !!a && a.constructor === Object
22
27
  }
package/src/xmltv.js CHANGED
@@ -1,9 +1,23 @@
1
- const { escapeString, getUTCDate, formatDate } = require('./utils')
1
+ const Channel = require('./Channel')
2
+ const Program = require('./Program')
3
+ const { escapeString, getUTCDate, formatDate, isDate } = require('./utils')
2
4
  const el = createElement
3
5
 
4
6
  module.exports.generate = generate
5
7
 
6
8
  function generate({ channels, programs, date = getUTCDate() }) {
9
+ if (!channels.every(c => c instanceof Channel)) {
10
+ throw new Error('"channels" must be an array of Channels')
11
+ }
12
+
13
+ if (!programs.every(p => p instanceof Program)) {
14
+ throw new Error('"programs" must be an array of Programs')
15
+ }
16
+
17
+ if (!isDate(date)) {
18
+ throw new Error('"date" must be a valid date')
19
+ }
20
+
7
21
  let output = `<?xml version="1.0" encoding="UTF-8" ?>`
8
22
  output += createElements(channels, programs, date)
9
23
 
@@ -33,9 +47,15 @@ function createElements(channels, programs, date) {
33
47
  channel: program.channel
34
48
  },
35
49
  [
36
- el('title', {}, [escapeString(program.title)]),
37
- el('sub-title', {}, [escapeString(program.sub_title)]),
38
- el('desc', {}, [escapeString(program.description)]),
50
+ ...program.titles.map(title =>
51
+ el('title', { lang: title.lang }, [escapeString(title.value)])
52
+ ),
53
+ ...program.sub_titles.map(sub_title =>
54
+ el('sub-title', { lang: sub_title.lang }, [escapeString(sub_title.value)])
55
+ ),
56
+ ...program.descriptions.map(desc =>
57
+ el('desc', { lang: desc.lang }, [escapeString(desc.value)])
58
+ ),
39
59
  el('credits', {}, [
40
60
  ...program.directors.map(data => createCastMember('director', data)),
41
61
  ...program.actors.map(data => createCastMember('actor', data)),
@@ -49,7 +69,9 @@ function createElements(channels, programs, date) {
49
69
  ...program.guests.map(data => createCastMember('guest', data))
50
70
  ]),
51
71
  el('date', {}, [formatDate(program.date, 'YYYYMMDD')]),
52
- ...program.categories.map(category => el('category', {}, [escapeString(category)])),
72
+ ...program.categories.map(category =>
73
+ el('category', { lang: category.lang }, [escapeString(category.value)])
74
+ ),
53
75
  el('icon', { src: program.icon.src }),
54
76
  ...program.urls.map(createURL),
55
77
  ...program.episodeNumbers.map(episode =>
@@ -1,59 +1,65 @@
1
1
  import Channel from '../src/Channel'
2
2
  import Program from '../src/Program'
3
3
 
4
- const channel = new Channel({ xmltv_id: '1tv', lang: 'en' })
4
+ const channel = new Channel({ xmltv_id: '1tv', lang: 'fr', site: 'example.com' })
5
5
 
6
6
  it('can create new Program', () => {
7
- const program = new Program({
8
- channel: channel.id,
9
- title: 'Title',
10
- sub_title: 'Subtitle',
11
- description: 'Description',
12
- icon: 'https://example.com/image.jpg',
13
- season: 9,
14
- episode: 238,
15
- date: '20220506',
16
- start: 1616133600000,
17
- stop: '2021-03-19T06:30:00.000Z',
18
- url: 'http://example.com/title.html',
19
- category: ['Category1', 'Category2'],
20
- rating: {
21
- system: 'MPAA',
22
- value: 'PG',
23
- icon: 'http://example.com/pg_symbol.png'
24
- },
25
- directors: 'Director1',
26
- actors: [
27
- 'Actor1',
28
- { value: 'Actor2', url: 'http://actor2.com', image: 'http://actor2.com/image.png' }
29
- ],
30
- writer: {
31
- value: 'Writer1',
32
- url: { system: 'imdb', value: 'http://imdb.com/p/writer1' },
33
- image: {
34
- value: 'https://example.com/image.jpg',
35
- type: 'person',
36
- size: '2',
37
- system: 'TestSystem',
38
- orient: 'P'
39
- }
7
+ const program = new Program(
8
+ {
9
+ title: 'Title',
10
+ sub_title: 'Subtitle',
11
+ description: 'Description',
12
+ icon: 'https://example.com/image.jpg',
13
+ season: 9,
14
+ episode: 238,
15
+ date: '20220506',
16
+ start: 1616133600000,
17
+ stop: '2021-03-19T06:30:00.000Z',
18
+ url: 'http://example.com/title.html',
19
+ category: ['Category1', 'Category2'],
20
+ rating: {
21
+ system: 'MPAA',
22
+ value: 'PG',
23
+ icon: 'http://example.com/pg_symbol.png'
24
+ },
25
+ directors: 'Director1',
26
+ actors: [
27
+ 'Actor1',
28
+ { value: 'Actor2', url: 'http://actor2.com', image: 'http://actor2.com/image.png' }
29
+ ],
30
+ writer: {
31
+ value: 'Writer1',
32
+ url: { system: 'imdb', value: 'http://imdb.com/p/writer1' },
33
+ image: {
34
+ value: 'https://example.com/image.jpg',
35
+ type: 'person',
36
+ size: '2',
37
+ system: 'TestSystem',
38
+ orient: 'P'
39
+ }
40
+ },
41
+ adapters: [
42
+ {
43
+ value: 'Adapter1',
44
+ url: ['http://imdb.com/p/adapter1', 'http://imdb.com/p/adapter2'],
45
+ image: ['https://example.com/image1.jpg', 'https://example.com/image2.jpg']
46
+ }
47
+ ]
40
48
  },
41
- adapters: [
42
- {
43
- value: 'Adapter1',
44
- url: ['http://imdb.com/p/adapter1', 'http://imdb.com/p/adapter2'],
45
- image: ['https://example.com/image1.jpg', 'https://example.com/image2.jpg']
46
- }
47
- ]
48
- })
49
+ channel
50
+ )
49
51
 
50
52
  expect(program).toMatchObject({
53
+ site: 'example.com',
51
54
  channel: '1tv',
52
- title: 'Title',
53
- sub_title: 'Subtitle',
54
- description: 'Description',
55
+ titles: [{ value: 'Title', lang: 'fr' }],
56
+ sub_titles: [{ value: 'Subtitle', lang: 'fr' }],
57
+ descriptions: [{ value: 'Description', lang: 'fr' }],
55
58
  urls: [{ system: '', value: 'http://example.com/title.html' }],
56
- categories: ['Category1', 'Category2'],
59
+ categories: [
60
+ { value: 'Category1', lang: 'fr' },
61
+ { value: 'Category2', lang: 'fr' }
62
+ ],
57
63
  icon: { src: 'https://example.com/image.jpg' },
58
64
  episodeNumbers: [
59
65
  { system: 'xmltv_ns', value: '8.237.0/1' },
@@ -130,24 +136,26 @@ it('can create new Program', () => {
130
136
  })
131
137
 
132
138
  it('can create program from exist object', () => {
133
- const program = new Program({
134
- channel: channel.id,
135
- title: 'Program 1',
136
- start: '2021-03-19T06:00:00.000Z',
137
- stop: '2021-03-19T06:30:00.000Z',
138
- ratings: {
139
- system: 'MPAA',
140
- value: 'PG',
141
- icon: 'http://example.com/pg_symbol.png'
139
+ const program = new Program(
140
+ {
141
+ titles: [{ value: 'Program 1', lang: 'de' }],
142
+ start: '2021-03-19T06:00:00.000Z',
143
+ stop: '2021-03-19T06:30:00.000Z',
144
+ ratings: {
145
+ system: 'MPAA',
146
+ value: 'PG',
147
+ icon: 'http://example.com/pg_symbol.png'
148
+ },
149
+ actors: [{ value: 'Actor1', url: [], image: [] }]
142
150
  },
143
- actors: [{ value: 'Actor1', url: [], image: [] }]
144
- })
151
+ channel
152
+ )
145
153
 
146
154
  expect(program).toMatchObject({
147
155
  channel: '1tv',
148
- title: 'Program 1',
149
- sub_title: '',
150
- description: '',
156
+ titles: [{ value: 'Program 1', lang: 'de' }],
157
+ sub_titles: [],
158
+ descriptions: [],
151
159
  urls: [],
152
160
  categories: [],
153
161
  icon: {},
@@ -176,13 +184,15 @@ it('can create program from exist object', () => {
176
184
  })
177
185
 
178
186
  it('can create program without season number', () => {
179
- const program = new Program({
180
- channel: channel.id,
181
- title: 'Program 1',
182
- start: '2021-03-19T06:00:00.000Z',
183
- stop: '2021-03-19T06:30:00.000Z',
184
- episode: 238
185
- })
187
+ const program = new Program(
188
+ {
189
+ title: 'Program 1',
190
+ start: '2021-03-19T06:00:00.000Z',
191
+ stop: '2021-03-19T06:30:00.000Z',
192
+ episode: 238
193
+ },
194
+ channel
195
+ )
186
196
 
187
197
  expect(program.episodeNumbers).toMatchObject([
188
198
  { system: 'xmltv_ns', value: '0.237.0/1' },
@@ -191,13 +201,16 @@ it('can create program without season number', () => {
191
201
  })
192
202
 
193
203
  it('can create program without episode number', () => {
194
- const program = new Program({
195
- channel: channel.id,
196
- title: 'Program 1',
197
- start: '2021-03-19T06:00:00.000Z',
198
- stop: '2021-03-19T06:30:00.000Z',
199
- season: 3
200
- })
204
+ const program = new Program(
205
+ {
206
+ channel: channel.id,
207
+ title: 'Program 1',
208
+ start: '2021-03-19T06:00:00.000Z',
209
+ stop: '2021-03-19T06:30:00.000Z',
210
+ season: 3
211
+ },
212
+ channel
213
+ )
201
214
 
202
215
  expect(program.episodeNumbers).toMatchObject([])
203
216
  })
@@ -14,7 +14,7 @@ it('can parse valid channels.xml', () => {
14
14
  })
15
15
 
16
16
  it('can parse programs', done => {
17
- const channel = { xmltv_id: '1tv' }
17
+ const channel = new Channel({ xmltv_id: '1tv' })
18
18
  const config = require('./input/example.config.js')
19
19
 
20
20
  parsePrograms({ channel, config })
@@ -27,7 +27,7 @@ it('can parse programs', done => {
27
27
  })
28
28
 
29
29
  it('can parse programs async', done => {
30
- const channel = { xmltv_id: '1tv' }
30
+ const channel = new Channel({ xmltv_id: '1tv' })
31
31
  const config = require('./input/async.config.js')
32
32
 
33
33
  parsePrograms({ channel, config })
@@ -14,11 +14,12 @@ const channels = [
14
14
  new Channel({
15
15
  xmltv_id: '2TV.co',
16
16
  name: '2 TV',
17
- site: 'example.com'
17
+ site: 'example.com',
18
+ lang: 'es'
18
19
  })
19
20
  ]
20
21
 
21
- fit('can generate xmltv', () => {
22
+ it('can generate xmltv', () => {
22
23
  const programs = [
23
24
  new Program(
24
25
  {
@@ -33,7 +34,6 @@ fit('can generate xmltv', () => {
33
34
  season: 9,
34
35
  episode: 239,
35
36
  icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
36
- channel: '1TV.co',
37
37
  rating: {
38
38
  system: 'MPAA',
39
39
  value: 'PG',
@@ -60,12 +60,20 @@ fit('can generate xmltv', () => {
60
60
  writer: 'Writer 1'
61
61
  },
62
62
  channels[0]
63
+ ),
64
+ new Program(
65
+ {
66
+ title: 'Program 2',
67
+ start: '2021-03-19T06:00:00.000Z',
68
+ stop: '2021-03-19T06:30:00.000Z'
69
+ },
70
+ channels[1]
63
71
  )
64
72
  ]
65
73
 
66
74
  const output = xmltv.generate({ channels, programs })
67
75
 
68
76
  expect(output).toBe(
69
- '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.co"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.co"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.co"><title>Program 1</title><sub-title>Sub-title &amp; 1</sub-title><desc>Description for Program 1</desc><credits><director>Director 1<url system="TestSystem">http://example.com/director1.html</url><image>https://example.com/image1.jpg</image><image type="person" size="2" orient="P" system="TestSystem">https://example.com/image2.jpg</image></director><director>Director 2</director><actor>Actor 1</actor><actor>Actor 2</actor><writer>Writer 1</writer></credits><date>20220506</date><category>Test</category><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/><url>http://example.com/title.html</url><episode-num system="xmltv_ns">8.238.0/1</episode-num><episode-num system="onscreen">S09E239</episode-num><rating system="MPAA"><value>PG</value><icon src="http://example.com/pg_symbol.png"/></rating></programme>\r\n</tv>'
77
+ '<?xml version="1.0" encoding="UTF-8" ?><tv date="20220505">\r\n<channel id="1TV.co"><display-name>1 TV</display-name><icon src="https://example.com/logos/1TV.png"/><url>https://example.com</url></channel>\r\n<channel id="2TV.co"><display-name>2 TV</display-name><url>https://example.com</url></channel>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="1TV.co"><title>Program 1</title><sub-title>Sub-title &amp; 1</sub-title><desc>Description for Program 1</desc><credits><director>Director 1<url system="TestSystem">http://example.com/director1.html</url><image>https://example.com/image1.jpg</image><image type="person" size="2" orient="P" system="TestSystem">https://example.com/image2.jpg</image></director><director>Director 2</director><actor>Actor 1</actor><actor>Actor 2</actor><writer>Writer 1</writer></credits><date>20220506</date><category>Test</category><icon src="https://example.com/images/Program1.png?x=шеллы&amp;sid=777"/><url>http://example.com/title.html</url><episode-num system="xmltv_ns">8.238.0/1</episode-num><episode-num system="onscreen">S09E239</episode-num><rating system="MPAA"><value>PG</value><icon src="http://example.com/pg_symbol.png"/></rating></programme>\r\n<programme start="20210319060000 +0000" stop="20210319063000 +0000" channel="2TV.co"><title lang="es">Program 2</title></programme>\r\n</tv>'
70
78
  )
71
79
  })