wip
This commit is contained in:
parent
71e3d1ccd1
commit
cb7679933c
|
@ -7,8 +7,12 @@ const { gzip } = require('node-gzip')
|
|||
const file = require('../src/file')
|
||||
const { EPGGrabber, parseChannels, generateXMLTV, loadLogo } = require('../src/index')
|
||||
const { create: createLogger } = require('../src/logger')
|
||||
const { parseInteger, getUTCDate } = require('../src/utils')
|
||||
const { parseNumber, getUTCDate } = require('../src/utils')
|
||||
const { name, version, description } = require('../package.json')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
program
|
||||
.name(name)
|
||||
|
@ -18,13 +22,13 @@ program
|
|||
.option('-o, --output <output>', 'Path to output file')
|
||||
.option('--channels <channels>', 'Path to channels.xml file')
|
||||
.option('--lang <lang>', 'Set default language for all programs')
|
||||
.option('--days <days>', 'Number of days for which to grab the program', parseInteger, 1)
|
||||
.option('--delay <delay>', 'Delay between requests (in mileseconds)', parseInteger)
|
||||
.option('--timeout <timeout>', 'Set a timeout for each request (in mileseconds)', parseInteger)
|
||||
.option('--days <days>', 'Number of days for which to grab the program', parseNumber, 1)
|
||||
.option('--delay <delay>', 'Delay between requests (in mileseconds)', parseNumber)
|
||||
.option('--timeout <timeout>', 'Set a timeout for each request (in mileseconds)', parseNumber)
|
||||
.option(
|
||||
'--cache-ttl <cacheTtl>',
|
||||
'Maximum time for storing each request (in milliseconds)',
|
||||
parseInteger
|
||||
parseNumber
|
||||
)
|
||||
.option('--gzip', 'Compress the output', false)
|
||||
.option('--debug', 'Enable debug mode', false)
|
||||
|
@ -80,9 +84,9 @@ async function main() {
|
|||
await grabber
|
||||
.grab(channel, date, (data, err) => {
|
||||
logger.info(
|
||||
`[${i}/${total}] ${config.site} - ${data.channel.xmltv_id} - ${data.date.format(
|
||||
'MMM D, YYYY'
|
||||
)} (${data.programs.length} programs)`
|
||||
`[${i}/${total}] ${config.site} - ${data.channel.xmltv_id} - ${dayjs(data.date)
|
||||
.utc()
|
||||
.format('MMM D, YYYY')} (${data.programs.length} programs)`
|
||||
)
|
||||
|
||||
if (err) logger.error(err.message)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
class Channel {
|
||||
constructor(c) {
|
||||
const data = {
|
||||
id: c.xmltv_id,
|
||||
name: c.name,
|
||||
site_id: c.site_id,
|
||||
site: c.site || '',
|
||||
lang: c.lang || '',
|
||||
logo: c.logo || '',
|
||||
url: c.site ? `https://${c.site}` : ''
|
||||
}
|
||||
|
||||
for (let key in data) {
|
||||
this[key] = data[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Channel
|
|
@ -0,0 +1,122 @@
|
|||
const { padStart } = require('lodash')
|
||||
const { toArray, toUnix, parseNumber } = require('./utils')
|
||||
|
||||
class Program {
|
||||
constructor(p, c) {
|
||||
const data = {
|
||||
channel: c.id,
|
||||
title: p.title,
|
||||
sub_title: p.sub_title || '',
|
||||
description: p.description || p.desc,
|
||||
icon: toIconObject(p.icon),
|
||||
episodeNumbers: getEpisodeNumbers(p.season, p.episode),
|
||||
date: p.date ? toUnix(p.date) : null,
|
||||
start: toUnix(p.start),
|
||||
stop: toUnix(p.stop),
|
||||
urls: toArray(p.url).map(toUrlObject),
|
||||
ratings: toArray(p.ratings || p.rating).map(toRatingObject),
|
||||
categories: toArray(p.categories || p.category),
|
||||
directors: toArray(p.directors || p.director).map(toPersonObject),
|
||||
actors: toArray(p.actors || p.actor).map(toPersonObject),
|
||||
writers: toArray(p.writers || p.writer).map(toPersonObject),
|
||||
adapters: toArray(p.adapters || p.adapter).map(toPersonObject),
|
||||
producers: toArray(p.producers || p.producer).map(toPersonObject),
|
||||
composers: toArray(p.composers || p.composer).map(toPersonObject),
|
||||
editors: toArray(p.editors || p.editor).map(toPersonObject),
|
||||
presenters: toArray(p.presenters || p.presenter).map(toPersonObject),
|
||||
commentators: toArray(p.commentators || p.commentator).map(toPersonObject),
|
||||
guests: toArray(p.guests || p.guest).map(toPersonObject)
|
||||
}
|
||||
|
||||
for (let key in data) {
|
||||
this[key] = data[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Program
|
||||
|
||||
function toPersonObject(person) {
|
||||
if (typeof person === 'string') {
|
||||
return {
|
||||
value: person,
|
||||
url: [],
|
||||
image: []
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
value: person.value,
|
||||
url: toArray(person.url).map(toUrlObject),
|
||||
image: toArray(person.image).map(toImageObject)
|
||||
}
|
||||
}
|
||||
|
||||
function toImageObject(image) {
|
||||
if (typeof image === 'string') return { type: '', size: '', orient: '', system: '', value: image }
|
||||
|
||||
return {
|
||||
type: image.type || '',
|
||||
size: image.size || '',
|
||||
orient: image.orient || '',
|
||||
system: image.system || '',
|
||||
value: image.value
|
||||
}
|
||||
}
|
||||
|
||||
function toRatingObject(rating) {
|
||||
if (typeof rating === 'string') return { system: '', icon: '', value: rating }
|
||||
|
||||
return {
|
||||
system: rating.system || '',
|
||||
icon: rating.icon || '',
|
||||
value: rating.value || ''
|
||||
}
|
||||
}
|
||||
|
||||
function toUrlObject(url) {
|
||||
if (typeof url === 'string') return { system: '', value: url }
|
||||
|
||||
return {
|
||||
system: url.system || '',
|
||||
value: url.value || ''
|
||||
}
|
||||
}
|
||||
|
||||
function toIconObject(icon) {
|
||||
if (!icon || typeof icon === 'string') return { src: icon }
|
||||
|
||||
return {
|
||||
src: icon.src || ''
|
||||
}
|
||||
}
|
||||
|
||||
function getEpisodeNumbers(s, e) {
|
||||
s = parseNumber(s)
|
||||
e = parseNumber(e)
|
||||
|
||||
return [createXMLTVNS(s, e), createOnScreen(s, e)].filter(Boolean)
|
||||
}
|
||||
|
||||
function createXMLTVNS(s, e) {
|
||||
if (!e) return null
|
||||
s = s || 1
|
||||
|
||||
return {
|
||||
system: 'xmltv_ns',
|
||||
value: `${s - 1}.${e - 1}.0/1`
|
||||
}
|
||||
}
|
||||
|
||||
function createOnScreen(s, e) {
|
||||
if (!e) return null
|
||||
s = s || 1
|
||||
|
||||
s = padStart(s, 2, '0')
|
||||
e = padStart(e, 2, '0')
|
||||
|
||||
return {
|
||||
system: 'onscreen',
|
||||
value: `S${s}E${e}`
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
const convert = require('xml-js')
|
||||
|
||||
module.exports.parse = parse
|
||||
|
||||
function parse(xml) {
|
||||
const result = convert.xml2js(xml)
|
||||
const siteTag = result.elements.find(el => el.name === 'site') || {}
|
||||
if (!siteTag.elements) return []
|
||||
const site = siteTag.attributes.site
|
||||
|
||||
const channelsTag = siteTag.elements.find(el => el.name === 'channels')
|
||||
if (!channelsTag.elements) return []
|
||||
|
||||
const channels = channelsTag.elements
|
||||
.filter(el => el.name === 'channel')
|
||||
.map(el => {
|
||||
const channel = el.attributes
|
||||
if (!el.elements) throw new Error(`Channel '${channel.xmltv_id}' has no valid name`)
|
||||
channel.name = el.elements.find(el => el.type === 'text').text
|
||||
channel.site = channel.site || site
|
||||
|
||||
return channel
|
||||
})
|
||||
|
||||
return { site, channels }
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
const { merge } = require('lodash')
|
||||
const { create: createClient, buildRequest, parseResponse } = require('./client')
|
||||
const { parseChannels, parsePrograms } = require('./parser')
|
||||
const { generate: generateXMLTV } = require('./xmltv')
|
||||
const { parse: parseChannels } = require('./channels')
|
||||
const { parse: parsePrograms } = require('./programs')
|
||||
const { load: loadConfig } = require('./config')
|
||||
const { sleep, isPromise } = require('./utils')
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
const convert = require('xml-js')
|
||||
const Channel = require('./Channel')
|
||||
const Program = require('./Program')
|
||||
const { isPromise } = require('./utils')
|
||||
|
||||
module.exports.parseChannels = parseChannels
|
||||
module.exports.parsePrograms = parsePrograms
|
||||
|
||||
function parseChannels(xml) {
|
||||
const result = convert.xml2js(xml)
|
||||
const siteTag = result.elements.find(el => el.name === 'site') || {}
|
||||
if (!siteTag.elements) return []
|
||||
const rootSite = siteTag.attributes.site
|
||||
|
||||
const channelsTag = siteTag.elements.find(el => el.name === 'channels')
|
||||
if (!channelsTag.elements) return []
|
||||
|
||||
const channels = channelsTag.elements
|
||||
.filter(el => el.name === 'channel')
|
||||
.map(el => {
|
||||
let { xmltv_id, site, logo } = el.attributes
|
||||
const name = el.elements.find(el => el.type === 'text').text
|
||||
if (!name) throw new Error(`Channel '${xmltv_id}' has no valid name`)
|
||||
site = site || rootSite
|
||||
|
||||
return new Channel({ name, xmltv_id, logo, site })
|
||||
})
|
||||
|
||||
return { site: rootSite, channels }
|
||||
}
|
||||
|
||||
async function parsePrograms(data) {
|
||||
const { config, channel } = data
|
||||
let programs = config.parser(data)
|
||||
|
||||
if (isPromise(programs)) {
|
||||
programs = await programs
|
||||
}
|
||||
|
||||
if (!Array.isArray(programs)) {
|
||||
throw new Error('Parser should return an array')
|
||||
}
|
||||
|
||||
return programs.filter(i => i).map(p => new Program(p, channel))
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
const { isPromise } = require('./utils')
|
||||
|
||||
module.exports.parse = parse
|
||||
|
||||
async function parse(data) {
|
||||
const { config, channel } = data
|
||||
let programs = config.parser(data)
|
||||
|
||||
if (isPromise(programs)) {
|
||||
programs = await programs
|
||||
}
|
||||
|
||||
if (!Array.isArray(programs)) {
|
||||
throw new Error('Parser should return an array')
|
||||
}
|
||||
|
||||
return programs
|
||||
.filter(i => i)
|
||||
.map(program => {
|
||||
program.channel = channel.xmltv_id
|
||||
|
||||
return program
|
||||
})
|
||||
}
|
21
src/utils.js
21
src/utils.js
|
@ -8,7 +8,10 @@ module.exports.getUTCDate = getUTCDate
|
|||
module.exports.isPromise = isPromise
|
||||
module.exports.isObject = isObject
|
||||
module.exports.escapeString = escapeString
|
||||
module.exports.parseInteger = parseInteger
|
||||
module.exports.parseNumber = parseNumber
|
||||
module.exports.formatDate = formatDate
|
||||
module.exports.toArray = toArray
|
||||
module.exports.toUnix = toUnix
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
@ -28,6 +31,10 @@ function getUTCDate(d = null) {
|
|||
return dayjs.utc().startOf('d')
|
||||
}
|
||||
|
||||
function toUnix(d) {
|
||||
return dayjs.utc(d).valueOf()
|
||||
}
|
||||
|
||||
function escapeString(string, defaultValue = '') {
|
||||
if (!string) return defaultValue
|
||||
|
||||
|
@ -56,6 +63,16 @@ function escapeString(string, defaultValue = '') {
|
|||
.trim()
|
||||
}
|
||||
|
||||
function parseInteger(val) {
|
||||
function parseNumber(val) {
|
||||
return val ? parseInt(val) : null
|
||||
}
|
||||
|
||||
function formatDate(date, format) {
|
||||
return date ? dayjs.utc(date).format(format) : null
|
||||
}
|
||||
|
||||
function toArray(value) {
|
||||
if (Array.isArray(value)) return value.filter(Boolean)
|
||||
|
||||
return [value].filter(Boolean)
|
||||
}
|
||||
|
|
113
src/xmltv.js
113
src/xmltv.js
|
@ -1,9 +1,5 @@
|
|||
const { padStart } = require('lodash')
|
||||
const { escapeString, getUTCDate } = require('./utils')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
const { escapeString, getUTCDate, formatDate } = require('./utils')
|
||||
const el = createElement
|
||||
|
||||
module.exports.generate = generate
|
||||
|
||||
|
@ -14,26 +10,19 @@ function generate({ channels, programs, date = getUTCDate() }) {
|
|||
return output
|
||||
}
|
||||
|
||||
const el = createElement
|
||||
|
||||
function createElements(channels, programs, date) {
|
||||
date = formatDate(date, 'YYYYMMDD')
|
||||
return el('tv', { date }, [
|
||||
return el('tv', { date: formatDate(date, 'YYYYMMDD') }, [
|
||||
...channels.map(channel => {
|
||||
const url = channel.site ? `https://${channel.site}` : ''
|
||||
|
||||
return (
|
||||
'\r\n' +
|
||||
el('channel', { id: channel.xmltv_id }, [
|
||||
el('channel', { id: channel.id }, [
|
||||
el('display-name', {}, [escapeString(channel.name)]),
|
||||
el('icon', { src: channel.logo }),
|
||||
el('url', {}, [url])
|
||||
el('url', {}, [channel.url])
|
||||
])
|
||||
)
|
||||
}),
|
||||
...programs.map(program => {
|
||||
const programDate = program.date ? formatDate(program.date, 'YYYYMMDD') : ''
|
||||
|
||||
return (
|
||||
'\r\n' +
|
||||
el(
|
||||
|
@ -48,30 +37,25 @@ function createElements(channels, programs, date) {
|
|||
el('sub-title', {}, [escapeString(program.sub_title)]),
|
||||
el('desc', {}, [escapeString(program.description)]),
|
||||
el('credits', {}, [
|
||||
...toArray(program.director).map(data => createCastMember('director', data)),
|
||||
...toArray(program.actor).map(data => createCastMember('actor', data)),
|
||||
...toArray(program.writer).map(data => createCastMember('writer', data)),
|
||||
...toArray(program.adapter).map(data => createCastMember('adapter', data)),
|
||||
...toArray(program.producer).map(data => createCastMember('producer', data)),
|
||||
...toArray(program.composer).map(data => createCastMember('composer', data)),
|
||||
...toArray(program.editor).map(data => createCastMember('editor', data)),
|
||||
...toArray(program.presenter).map(data => createCastMember('presenter', data)),
|
||||
...toArray(program.commentator).map(data => createCastMember('commentator', data)),
|
||||
...toArray(program.guest).map(data => createCastMember('guest', data))
|
||||
...program.directors.map(data => createCastMember('director', data)),
|
||||
...program.actors.map(data => createCastMember('actor', data)),
|
||||
...program.writers.map(data => createCastMember('writer', data)),
|
||||
...program.adapters.map(data => createCastMember('adapter', data)),
|
||||
...program.producers.map(data => createCastMember('producer', data)),
|
||||
...program.composers.map(data => createCastMember('composer', data)),
|
||||
...program.editors.map(data => createCastMember('editor', data)),
|
||||
...program.presenters.map(data => createCastMember('presenter', data)),
|
||||
...program.commentators.map(data => createCastMember('commentator', data)),
|
||||
...program.guests.map(data => createCastMember('guest', data))
|
||||
]),
|
||||
el('date', {}, [programDate]),
|
||||
...toArray(program.category).map(category =>
|
||||
el('category', {}, [escapeString(category)])
|
||||
el('date', {}, [formatDate(program.date, 'YYYYMMDD')]),
|
||||
...program.categories.map(category => el('category', {}, [escapeString(category)])),
|
||||
el('icon', { src: program.icon.src }),
|
||||
...program.urls.map(createURL),
|
||||
...program.episodeNumbers.map(episode =>
|
||||
el('episode-num', { system: episode.system }, [episode.value])
|
||||
),
|
||||
el('icon', { src: program.icon }),
|
||||
...toArray(program.url).map(createURL),
|
||||
el('episode-num', { system: 'xmltv_ns' }, [
|
||||
formatEpisodeNum(program.season, program.episode, 'xmltv_ns')
|
||||
]),
|
||||
el('episode-num', { system: 'onscreen' }, [
|
||||
formatEpisodeNum(program.season, program.episode, 'onscreen')
|
||||
]),
|
||||
...toArray(program.rating).map(rating =>
|
||||
...program.ratings.map(rating =>
|
||||
el('rating', { system: rating.system }, [
|
||||
el('value', {}, [rating.value]),
|
||||
el('icon', { src: rating.icon })
|
||||
|
@ -84,45 +68,15 @@ function createElements(channels, programs, date) {
|
|||
])
|
||||
}
|
||||
|
||||
function formatEpisodeNum(s, e, system) {
|
||||
switch (system) {
|
||||
case 'xmltv_ns':
|
||||
return createXMLTVNS(s, e)
|
||||
case 'onscreen':
|
||||
return createOnScreen(s, e)
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
function createXMLTVNS(s, e) {
|
||||
if (!e) return ''
|
||||
s = s || 1
|
||||
|
||||
return `${s - 1}.${e - 1}.0/1`
|
||||
}
|
||||
|
||||
function createOnScreen(s, e) {
|
||||
if (!e) return ''
|
||||
s = s || 1
|
||||
|
||||
s = padStart(s, 2, '0')
|
||||
e = padStart(e, 2, '0')
|
||||
|
||||
return `S${s}E${e}`
|
||||
}
|
||||
|
||||
function createCastMember(position, data) {
|
||||
data = toObject(data)
|
||||
return el(position, {}, [
|
||||
escapeString(data.value),
|
||||
...toArray(data.url).map(createURL),
|
||||
...toArray(data.image).map(createImage)
|
||||
...data.url.map(createURL),
|
||||
...data.image.map(createImage)
|
||||
])
|
||||
}
|
||||
|
||||
function createImage(image) {
|
||||
image = toObject(image)
|
||||
return el(
|
||||
'image',
|
||||
{
|
||||
|
@ -136,32 +90,15 @@ function createImage(image) {
|
|||
}
|
||||
|
||||
function createURL(url) {
|
||||
url = toObject(url)
|
||||
return el('url', { system: url.system }, [url.value])
|
||||
}
|
||||
|
||||
function toObject(value) {
|
||||
if (typeof value === 'string') return { value }
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
function toArray(value) {
|
||||
if (Array.isArray(value)) return value.filter(Boolean)
|
||||
|
||||
return [value].filter(Boolean)
|
||||
}
|
||||
|
||||
function formatDate(date, format) {
|
||||
return date ? dayjs.utc(date).format(format) : null
|
||||
}
|
||||
|
||||
function createElement(name, attrs = {}, children = []) {
|
||||
return toString({ name, attrs, children })
|
||||
}
|
||||
|
||||
function toString(elem) {
|
||||
if (typeof elem === 'string') return elem
|
||||
if (typeof elem === 'string' || typeof elem === 'number') return elem
|
||||
|
||||
let attrs = ''
|
||||
for (let key in elem.attrs) {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import Channel from '../src/Channel'
|
||||
|
||||
it('can create new Channel', () => {
|
||||
const channel = new Channel({
|
||||
name: '1 TV',
|
||||
xmltv_id: '1TV.com',
|
||||
site_id: '1',
|
||||
site: 'example.com',
|
||||
lang: 'fr',
|
||||
logo: 'https://example.com/logos/1TV.png'
|
||||
})
|
||||
|
||||
expect(channel).toMatchObject({
|
||||
name: '1 TV',
|
||||
id: '1TV.com',
|
||||
site_id: '1',
|
||||
site: 'example.com',
|
||||
url: 'https://example.com',
|
||||
lang: 'fr',
|
||||
logo: 'https://example.com/logos/1TV.png'
|
||||
})
|
||||
})
|
|
@ -0,0 +1,163 @@
|
|||
import Channel from '../src/Channel'
|
||||
import Program from '../src/Program'
|
||||
|
||||
const channel = new Channel({ xmltv_id: '1tv', lang: 'en' })
|
||||
|
||||
it('can create new Program', () => {
|
||||
const program = new Program(
|
||||
{
|
||||
title: 'Title',
|
||||
sub_title: 'Subtitle',
|
||||
description: 'Description',
|
||||
icon: 'https://example.com/image.jpg',
|
||||
season: 9,
|
||||
episode: 238,
|
||||
date: '20220506',
|
||||
start: 1616133600000,
|
||||
stop: '2021-03-19T06:30:00.000Z',
|
||||
url: 'http://example.com/title.html',
|
||||
category: ['Category1', 'Category2'],
|
||||
rating: {
|
||||
system: 'MPAA',
|
||||
value: 'PG',
|
||||
icon: 'http://example.com/pg_symbol.png'
|
||||
},
|
||||
directors: 'Director1',
|
||||
actors: [
|
||||
'Actor1',
|
||||
{ value: 'Actor2', url: 'http://actor2.com', image: 'http://actor2.com/image.png' }
|
||||
],
|
||||
writer: {
|
||||
value: 'Writer1',
|
||||
url: { system: 'imdb', value: 'http://imdb.com/p/writer1' },
|
||||
image: {
|
||||
value: 'https://example.com/image.jpg',
|
||||
type: 'person',
|
||||
size: '2',
|
||||
system: 'TestSystem',
|
||||
orient: 'P'
|
||||
}
|
||||
},
|
||||
adapters: [
|
||||
{
|
||||
value: 'Adapter1',
|
||||
url: ['http://imdb.com/p/adapter1', 'http://imdb.com/p/adapter2'],
|
||||
image: ['https://example.com/image1.jpg', 'https://example.com/image2.jpg']
|
||||
}
|
||||
]
|
||||
},
|
||||
channel
|
||||
)
|
||||
|
||||
expect(program).toMatchObject({
|
||||
channel: '1tv',
|
||||
title: 'Title',
|
||||
sub_title: 'Subtitle',
|
||||
description: 'Description',
|
||||
urls: [{ system: '', value: 'http://example.com/title.html' }],
|
||||
categories: ['Category1', 'Category2'],
|
||||
icon: { src: 'https://example.com/image.jpg' },
|
||||
episodeNumbers: [
|
||||
{ system: 'xmltv_ns', value: '8.237.0/1' },
|
||||
{ system: 'onscreen', value: 'S09E238' }
|
||||
],
|
||||
date: 1651795200000,
|
||||
start: 1616133600000,
|
||||
stop: 1616135400000,
|
||||
ratings: [
|
||||
{
|
||||
system: 'MPAA',
|
||||
value: 'PG',
|
||||
icon: 'http://example.com/pg_symbol.png'
|
||||
}
|
||||
],
|
||||
directors: [{ value: 'Director1', url: [], image: [] }],
|
||||
actors: [
|
||||
{ value: 'Actor1', url: [], image: [] },
|
||||
{
|
||||
value: 'Actor2',
|
||||
url: [{ system: '', value: 'http://actor2.com' }],
|
||||
image: [
|
||||
{ type: '', size: '', orient: '', system: '', value: 'http://actor2.com/image.png' }
|
||||
]
|
||||
}
|
||||
],
|
||||
writers: [
|
||||
{
|
||||
value: 'Writer1',
|
||||
url: [{ system: 'imdb', value: 'http://imdb.com/p/writer1' }],
|
||||
image: [
|
||||
{
|
||||
value: 'https://example.com/image.jpg',
|
||||
type: 'person',
|
||||
size: '2',
|
||||
system: 'TestSystem',
|
||||
orient: 'P'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
adapters: [
|
||||
{
|
||||
value: 'Adapter1',
|
||||
url: [
|
||||
{ system: '', value: 'http://imdb.com/p/adapter1' },
|
||||
{ system: '', value: 'http://imdb.com/p/adapter2' }
|
||||
],
|
||||
image: [
|
||||
{
|
||||
value: 'https://example.com/image1.jpg',
|
||||
type: '',
|
||||
size: '',
|
||||
system: '',
|
||||
orient: ''
|
||||
},
|
||||
{
|
||||
value: 'https://example.com/image2.jpg',
|
||||
type: '',
|
||||
size: '',
|
||||
system: '',
|
||||
orient: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
producers: [],
|
||||
composers: [],
|
||||
editors: [],
|
||||
presenters: [],
|
||||
commentators: [],
|
||||
guests: []
|
||||
})
|
||||
})
|
||||
|
||||
it('can create program without season number', () => {
|
||||
const program = new Program(
|
||||
{
|
||||
title: 'Program 1',
|
||||
start: '2021-03-19T06:00:00.000Z',
|
||||
stop: '2021-03-19T06:30:00.000Z',
|
||||
episode: 238
|
||||
},
|
||||
channel
|
||||
)
|
||||
|
||||
expect(program.episodeNumbers).toMatchObject([
|
||||
{ system: 'xmltv_ns', value: '0.237.0/1' },
|
||||
{ system: 'onscreen', value: 'S01E238' }
|
||||
])
|
||||
})
|
||||
|
||||
it('can create program without episode number', () => {
|
||||
const program = new Program(
|
||||
{
|
||||
title: 'Program 1',
|
||||
start: '2021-03-19T06:00:00.000Z',
|
||||
stop: '2021-03-19T06:30:00.000Z',
|
||||
season: 3
|
||||
},
|
||||
channel
|
||||
)
|
||||
|
||||
expect(program.episodeNumbers).toMatchObject([])
|
||||
})
|
|
@ -10,7 +10,7 @@ function stdoutResultTester(stdout) {
|
|||
|
||||
it('can load config', () => {
|
||||
const result = execSync(
|
||||
`node ${pwd}/bin/epg-grabber.js --config=tests/input/example.com.config.js --delay=0`,
|
||||
`node ${pwd}/bin/epg-grabber.js --config=tests/input/example.config.js --delay=0`,
|
||||
{
|
||||
encoding: 'utf8'
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ it('can load mini config', () => {
|
|||
const result = execSync(
|
||||
`node ${pwd}/bin/epg-grabber.js \
|
||||
--config=tests/input/mini.config.js \
|
||||
--channels=tests/input/example.com.channels.xml \
|
||||
--channels=tests/input/example.channels.xml \
|
||||
--output=tests/output/mini.guide.xml \
|
||||
--lang=fr \
|
||||
--days=3 \
|
||||
|
@ -43,7 +43,7 @@ it('can generate gzip version', () => {
|
|||
const result = execSync(
|
||||
`node ${pwd}/bin/epg-grabber.js \
|
||||
--config=tests/input/mini.config.js \
|
||||
--channels=tests/input/example.com.channels.xml \
|
||||
--channels=tests/input/example.channels.xml \
|
||||
--output=tests/output/mini.guide.xml.gz \
|
||||
--gzip`,
|
||||
{
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { parse as parseChannels } from '../src/channels'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
|
||||
it('can parse valid channels.xml', () => {
|
||||
const file = fs.readFileSync('./tests/input/example.com.channels.xml', { encoding: 'utf-8' })
|
||||
const { channels } = parseChannels(file)
|
||||
expect(channels).toEqual([
|
||||
{
|
||||
name: '1 TV',
|
||||
xmltv_id: '1TV.com',
|
||||
site_id: '1',
|
||||
site: 'example.com',
|
||||
lang: 'fr',
|
||||
logo: 'https://example.com/logos/1TV.png'
|
||||
},
|
||||
{
|
||||
name: '2 TV',
|
||||
xmltv_id: '2TV.com',
|
||||
site_id: '2',
|
||||
site: 'example.com',
|
||||
lang: undefined,
|
||||
logo: undefined
|
||||
}
|
||||
])
|
||||
})
|
|
@ -3,7 +3,7 @@ import path from 'path'
|
|||
import fs from 'fs'
|
||||
|
||||
it('can load config', () => {
|
||||
const config = loadConfig(require(path.resolve('./tests/input/example.com.config.js')))
|
||||
const config = loadConfig(require(path.resolve('./tests/input/example.config.js')))
|
||||
expect(config).toMatchObject({
|
||||
days: 1,
|
||||
delay: 3000,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = {
|
||||
site: 'example.com',
|
||||
channels: 'example.com.channels.xml',
|
||||
channels: 'example.channels.xml',
|
||||
url() {
|
||||
return Promise.resolve('http://example.com/20210319/1tv.json')
|
||||
},
|
||||
|
@ -17,7 +17,13 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
parser() {
|
||||
return Promise.resolve([])
|
||||
return Promise.resolve([
|
||||
{
|
||||
title: 'Program1',
|
||||
start: 1640995200000,
|
||||
stop: 1640998800000
|
||||
}
|
||||
])
|
||||
},
|
||||
logo() {
|
||||
return Promise.resolve('http://example.com/logos/1TV.png?x=шеллы&sid=777')
|
||||
|
|
|
@ -5,7 +5,7 @@ dayjs.extend(utc)
|
|||
|
||||
module.exports = {
|
||||
site: 'example.com',
|
||||
channels: 'example.com.channels.xml',
|
||||
channels: 'example.channels.xml',
|
||||
output: 'tests/output/guide.xml',
|
||||
url: () => 'http://example.com/20210319/1tv.json',
|
||||
request: {
|
||||
|
@ -21,15 +21,9 @@ module.exports = {
|
|||
parser: () => {
|
||||
return [
|
||||
{
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
lang: 'en',
|
||||
category: ['Category1', 'Category2'],
|
||||
icon: 'https://example.com/image.jpg',
|
||||
season: 9,
|
||||
episode: 238,
|
||||
start: dayjs.utc('2022-01-01 00:00:00'),
|
||||
stop: dayjs.utc('2022-01-01 01:00:00')
|
||||
title: 'Program1',
|
||||
start: 1640995200000,
|
||||
stop: 1640998800000
|
||||
}
|
||||
]
|
||||
},
|
|
@ -0,0 +1,40 @@
|
|||
import { parseChannels, parsePrograms } from '../src/parser'
|
||||
import Channel from '../src/Channel'
|
||||
import Program from '../src/Program'
|
||||
import fs from 'fs'
|
||||
|
||||
it('can parse valid channels.xml', () => {
|
||||
const file = fs.readFileSync('./tests/input/example.channels.xml', { encoding: 'utf-8' })
|
||||
const { channels, site } = parseChannels(file)
|
||||
|
||||
expect(typeof site).toBe('string')
|
||||
expect(channels.length).toBe(2)
|
||||
expect(channels[0]).toBeInstanceOf(Channel)
|
||||
expect(channels[1]).toBeInstanceOf(Channel)
|
||||
})
|
||||
|
||||
it('can parse programs', done => {
|
||||
const channel = { xmltv_id: '1tv' }
|
||||
const config = require('./input/example.config.js')
|
||||
|
||||
parsePrograms({ channel, config })
|
||||
.then(programs => {
|
||||
expect(programs.length).toBe(1)
|
||||
expect(programs[0]).toBeInstanceOf(Program)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can parse programs async', done => {
|
||||
const channel = { xmltv_id: '1tv' }
|
||||
const config = require('./input/async.config.js')
|
||||
|
||||
parsePrograms({ channel, config })
|
||||
.then(programs => {
|
||||
expect(programs.length).toBe(1)
|
||||
expect(programs[0]).toBeInstanceOf(Program)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
|
@ -1,63 +0,0 @@
|
|||
import { parse as parsePrograms } from '../src/programs'
|
||||
|
||||
const channel = { xmltv_id: '1tv', lang: 'en' }
|
||||
|
||||
it('can parse programs', done => {
|
||||
const config = {
|
||||
parser: () => [
|
||||
{
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
category: ['Category1', 'Category2'],
|
||||
icon: 'https://example.com/image.jpg',
|
||||
season: 9,
|
||||
episode: 238,
|
||||
start: 1640995200000,
|
||||
stop: 1640998800000
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
parsePrograms({ channel, config })
|
||||
.then(programs => {
|
||||
expect(programs).toMatchObject([
|
||||
{
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
category: ['Category1', 'Category2'],
|
||||
icon: 'https://example.com/image.jpg',
|
||||
season: 9,
|
||||
episode: 238,
|
||||
start: 1640995200000,
|
||||
stop: 1640998800000,
|
||||
channel: '1tv'
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can parse programs async', done => {
|
||||
const config = {
|
||||
parser: async () => [
|
||||
{
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
category: ['Category1', 'Category2'],
|
||||
icon: 'https://example.com/image.jpg',
|
||||
season: 9,
|
||||
episode: 238,
|
||||
start: 1640995200000,
|
||||
stop: 1640998800000
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
parsePrograms({ channel, config })
|
||||
.then(programs => {
|
||||
expect(programs.length).toBe(1)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
|
@ -1,161 +1,71 @@
|
|||
import Channel from '../src/Channel'
|
||||
import Program from '../src/Program'
|
||||
import xmltv from '../src/xmltv'
|
||||
|
||||
jest.useFakeTimers('modern').setSystemTime(new Date('2022-05-05'))
|
||||
|
||||
const channels = [
|
||||
{
|
||||
new Channel({
|
||||
xmltv_id: '1TV.co',
|
||||
name: '1 TV',
|
||||
logo: 'https://example.com/logos/1TV.png',
|
||||
site: 'example.com'
|
||||
},
|
||||
{
|
||||
}),
|
||||
new Channel({
|
||||
xmltv_id: '2TV.co',
|
||||
name: '2 TV',
|
||||
site: 'example.com'
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
it('can generate xmltv', () => {
|
||||
fit('can generate xmltv', () => {
|
||||
const programs = [
|
||||
{
|
||||
title: 'Program 1',
|
||||
sub_title: 'Sub-title & 1',
|
||||
description: 'Description for Program 1',
|
||||
url: 'http://example.com/title.html',
|
||||
start: '2021-03-19T06:00:00.000Z',
|
||||
stop: '2021-03-19T06:30:00.000Z',
|
||||
category: 'Test',
|
||||
date: '2022-05-06',
|
||||
season: 9,
|
||||
episode: 239,
|
||||
icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
|
||||
channel: '1TV.co',
|
||||
rating: {
|
||||
system: 'MPAA',
|
||||
value: 'PG',
|
||||
icon: 'http://example.com/pg_symbol.png'
|
||||
},
|
||||
director: [
|
||||
{
|
||||
value: 'Director 1',
|
||||
url: { value: 'http://example.com/director1.html', system: 'TestSystem' },
|
||||
image: [
|
||||
'https://example.com/image1.jpg',
|
||||
{
|
||||
value: 'https://example.com/image2.jpg',
|
||||
type: 'person',
|
||||
size: '2',
|
||||
system: 'TestSystem',
|
||||
orient: 'P'
|
||||
}
|
||||
]
|
||||
new Program(
|
||||
{
|
||||
title: 'Program 1',
|
||||
sub_title: 'Sub-title & 1',
|
||||
description: 'Description for Program 1',
|
||||
url: 'http://example.com/title.html',
|
||||
start: '2021-03-19T06:00:00.000Z',
|
||||
stop: '2021-03-19T06:30:00.000Z',
|
||||
category: 'Test',
|
||||
date: '2022-05-06',
|
||||
season: 9,
|
||||
episode: 239,
|
||||
icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777',
|
||||
channel: '1TV.co',
|
||||
rating: {
|
||||
system: 'MPAA',
|
||||
value: 'PG',
|
||||
icon: 'http://example.com/pg_symbol.png'
|
||||
},
|
||||
'Director 2'
|
||||
],
|
||||
actor: ['Actor 1', 'Actor 2'],
|
||||
writer: 'Writer 1'
|
||||
}
|
||||
director: [
|
||||
{
|
||||
value: 'Director 1',
|
||||
url: { value: 'http://example.com/director1.html', system: 'TestSystem' },
|
||||
image: [
|
||||
'https://example.com/image1.jpg',
|
||||
{
|
||||
value: 'https://example.com/image2.jpg',
|
||||
type: 'person',
|
||||
size: '2',
|
||||
system: 'TestSystem',
|
||||
orient: 'P'
|
||||
}
|
||||
]
|
||||
},
|
||||
'Director 2'
|
||||
],
|
||||
actor: ['Actor 1', 'Actor 2'],
|
||||
writer: 'Writer 1'
|
||||
},
|
||||
channels[0]
|
||||
)
|
||||
]
|
||||
|
||||
const output = xmltv.generate({ channels, programs })
|
||||
|
||||
expect(output).toBe(
|
||||
'<?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 & 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=шеллы&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></tv>'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate xmltv with unix timestamps', () => {
|
||||
const programs = [
|
||||
{
|
||||
channel: '1TV.co',
|
||||
title: 'Program 1',
|
||||
start: 1616133600000,
|
||||
stop: 1616135400000
|
||||
}
|
||||
]
|
||||
const output = xmltv.generate({ channels, programs })
|
||||
expect(output).toBe(
|
||||
'<?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></programme></tv>'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate xmltv without season number', () => {
|
||||
const programs = [
|
||||
{
|
||||
channel: '1TV.co',
|
||||
title: 'Program 1',
|
||||
start: '2021-03-19T06:00:00.000Z',
|
||||
stop: '2021-03-19T06:30:00.000Z',
|
||||
episode: 239
|
||||
}
|
||||
]
|
||||
const output = xmltv.generate({ channels, programs })
|
||||
expect(output).toBe(
|
||||
'<?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><episode-num system="xmltv_ns">0.238.0/1</episode-num><episode-num system="onscreen">S01E239</episode-num></programme></tv>'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate xmltv without episode number', () => {
|
||||
const programs = [
|
||||
{
|
||||
channel: '1TV.co',
|
||||
title: 'Program 1',
|
||||
start: '2021-03-19T06:00:00.000Z',
|
||||
stop: '2021-03-19T06:30:00.000Z',
|
||||
season: 1
|
||||
}
|
||||
]
|
||||
const output = xmltv.generate({ channels, programs })
|
||||
expect(output).toBe(
|
||||
'<?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></programme></tv>'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate xmltv without categories', () => {
|
||||
const programs = [
|
||||
{
|
||||
channel: '1TV.co',
|
||||
title: 'Program 1',
|
||||
start: '2021-03-19T06:00:00.000Z',
|
||||
stop: '2021-03-19T06:30:00.000Z'
|
||||
}
|
||||
]
|
||||
const output = xmltv.generate({ channels, programs })
|
||||
expect(output).toBe(
|
||||
'<?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></programme></tv>'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate xmltv with multiple categories', () => {
|
||||
const programs = [
|
||||
{
|
||||
channel: '1TV.co',
|
||||
title: 'Program 1',
|
||||
start: '2021-03-19T06:00:00.000Z',
|
||||
stop: '2021-03-19T06:30:00.000Z',
|
||||
category: ['Category 1', 'Category 2']
|
||||
}
|
||||
]
|
||||
const output = xmltv.generate({ channels, programs })
|
||||
expect(output).toBe(
|
||||
'<?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><category>Category 1</category><category>Category 2</category></programme></tv>'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate xmltv with multiple urls', () => {
|
||||
const programs = [
|
||||
{
|
||||
channel: '1TV.co',
|
||||
title: 'Program 1',
|
||||
start: '2021-03-19T06:00:00.000Z',
|
||||
stop: '2021-03-19T06:30:00.000Z',
|
||||
url: [
|
||||
'https://example.com/noattr.html',
|
||||
{ value: 'https://example.com/attr.html', system: 'TestSystem' }
|
||||
]
|
||||
}
|
||||
]
|
||||
const output = xmltv.generate({ channels, programs })
|
||||
expect(output).toBe(
|
||||
'<?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><url>https://example.com/noattr.html</url><url system="TestSystem">https://example.com/attr.html</url></programme></tv>'
|
||||
)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue