epg-grabber 0.32.0 → 0.33.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 +9 -12
- package/bin/epg-grabber.js +1 -1
- package/package.json +2 -2
- package/src/client.js +1 -0
- package/src/index.d.ts +110 -0
- package/src/parser.js +13 -5
- package/tests/__data__/input/example.channels.xml +4 -6
- package/tests/__data__/input/example_3.channels.xml +5 -0
- package/tests/__data__/input/legacy.channels.xml +7 -0
- package/tests/__data__/output/guide.xml +1 -1
- package/tests/__mocks__/axios.js +3 -2
- package/tests/bin.test.js +1 -1
- package/tests/parser.test.js +73 -4
package/README.md
CHANGED
|
@@ -43,11 +43,9 @@ module.exports = {
|
|
|
43
43
|
|
|
44
44
|
```xml
|
|
45
45
|
<?xml version="1.0" ?>
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
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
|
-
<
|
|
222
|
-
<
|
|
223
|
-
|
|
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"
|
package/bin/epg-grabber.js
CHANGED
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "0.33.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,110 @@
|
|
|
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: object)
|
|
105
|
+
grab(
|
|
106
|
+
channel: Channel,
|
|
107
|
+
date: string | dayjs.Dayjs,
|
|
108
|
+
cb: (data: GrabCallbackData, err: Error) => void
|
|
109
|
+
): Promise<Program[]>
|
|
110
|
+
}
|
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 =
|
|
16
|
-
|
|
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
|
|
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
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
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,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="
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?><tv date="20230927">
|
|
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=шеллы&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=шеллы&sid=777"/><url>https://example2.com</url></channel>
|
package/tests/__mocks__/axios.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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 \
|
package/tests/parser.test.js
CHANGED
|
@@ -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
|
|
7
|
-
const file = fs.readFileSync('./tests/__data__/input/example.channels.xml', {
|
|
8
|
-
|
|
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 => {
|