mirror of
https://github.com/iptv-org/epg.git
synced 2025-05-09 08:30:06 -04:00
Fixes linter errors
This commit is contained in:
parent
57e508fc3b
commit
63c86a2b30
393 changed files with 28447 additions and 28443 deletions
|
@ -1,18 +1,18 @@
|
||||||
import { Logger } from '@freearhey/core'
|
import { Logger } from '@freearhey/core'
|
||||||
import { ApiClient } from '../../core'
|
import { ApiClient } from '../../core'
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
const client = new ApiClient({ logger })
|
const client = new ApiClient({ logger })
|
||||||
|
|
||||||
const requests = [
|
const requests = [
|
||||||
client.download('channels.json'),
|
client.download('channels.json'),
|
||||||
client.download('countries.json'),
|
client.download('countries.json'),
|
||||||
client.download('regions.json'),
|
client.download('regions.json'),
|
||||||
client.download('subdivisions.json')
|
client.download('subdivisions.json')
|
||||||
]
|
]
|
||||||
|
|
||||||
await Promise.all(requests)
|
await Promise.all(requests)
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,180 +1,180 @@
|
||||||
import { DATA_DIR } from '../../constants'
|
import { DATA_DIR } from '../../constants'
|
||||||
import { Storage, Collection, Dictionary, Logger } from '@freearhey/core'
|
import { Storage, Collection, Dictionary, Logger } from '@freearhey/core'
|
||||||
import { ChannelsParser, XML, ApiChannel } from '../../core'
|
import { ChannelsParser, XML, ApiChannel } from '../../core'
|
||||||
import { Channel } from 'epg-grabber'
|
import { Channel } from 'epg-grabber'
|
||||||
import { transliterate } from 'transliteration'
|
import { transliterate } from 'transliteration'
|
||||||
import nodeCleanup from 'node-cleanup'
|
import nodeCleanup from 'node-cleanup'
|
||||||
import { program } from 'commander'
|
import { program } from 'commander'
|
||||||
import inquirer, { QuestionCollection } from 'inquirer'
|
import inquirer, { QuestionCollection } from 'inquirer'
|
||||||
|
|
||||||
program
|
program
|
||||||
.argument('<filepath>', 'Path to *.channels.xml file to edit')
|
.argument('<filepath>', 'Path to *.channels.xml file to edit')
|
||||||
.option('-c, --country <name>', 'Default country (ISO 3166 code)', 'US')
|
.option('-c, --country <name>', 'Default country (ISO 3166 code)', 'US')
|
||||||
.parse(process.argv)
|
.parse(process.argv)
|
||||||
|
|
||||||
const filepath = program.args[0]
|
const filepath = program.args[0]
|
||||||
const programOptions = program.opts()
|
const programOptions = program.opts()
|
||||||
const defaultCountry = programOptions.country.toLowerCase()
|
const defaultCountry = programOptions.country.toLowerCase()
|
||||||
const newLabel = ` [new]`
|
const newLabel = ' [new]'
|
||||||
|
|
||||||
let options = new Collection()
|
let options = new Collection()
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const storage = new Storage()
|
const storage = new Storage()
|
||||||
|
|
||||||
if (!(await storage.exists(filepath))) {
|
if (!(await storage.exists(filepath))) {
|
||||||
throw new Error(`File "${filepath}" does not exists`)
|
throw new Error(`File "${filepath}" does not exists`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const parser = new ChannelsParser({ storage })
|
const parser = new ChannelsParser({ storage })
|
||||||
|
|
||||||
const parsedChannels = await parser.parse(filepath)
|
const parsedChannels = await parser.parse(filepath)
|
||||||
options = parsedChannels.map((channel: Channel) => {
|
options = parsedChannels.map((channel: Channel) => {
|
||||||
return {
|
return {
|
||||||
channel,
|
channel,
|
||||||
delete: false
|
delete: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const dataStorage = new Storage(DATA_DIR)
|
const dataStorage = new Storage(DATA_DIR)
|
||||||
const channelsContent = await dataStorage.json('channels.json')
|
const channelsContent = await dataStorage.json('channels.json')
|
||||||
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
||||||
|
|
||||||
const buffer = new Dictionary()
|
const buffer = new Dictionary()
|
||||||
options.forEach(async (option: { channel: Channel; delete: boolean }) => {
|
options.forEach(async (option: { channel: Channel; delete: boolean }) => {
|
||||||
const channel = option.channel
|
const channel = option.channel
|
||||||
if (channel.xmltv_id) {
|
if (channel.xmltv_id) {
|
||||||
if (channel.xmltv_id !== '-') {
|
if (channel.xmltv_id !== '-') {
|
||||||
buffer.set(`${channel.xmltv_id}/${channel.lang}`, true)
|
buffer.set(`${channel.xmltv_id}/${channel.lang}`, true)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let choices = getOptions(channels, channel)
|
const choices = getOptions(channels, channel)
|
||||||
const question: QuestionCollection = {
|
const question: QuestionCollection = {
|
||||||
name: 'option',
|
name: 'option',
|
||||||
message: `Choose an option:`,
|
message: 'Choose an option:',
|
||||||
type: 'list',
|
type: 'list',
|
||||||
choices,
|
choices,
|
||||||
pageSize: 10
|
pageSize: 10
|
||||||
}
|
}
|
||||||
|
|
||||||
await inquirer.prompt(question).then(async selected => {
|
await inquirer.prompt(question).then(async selected => {
|
||||||
switch (selected.option) {
|
switch (selected.option) {
|
||||||
case 'Overwrite':
|
case 'Overwrite':
|
||||||
const input = await getInput(channel)
|
const input = await getInput(channel)
|
||||||
channel.xmltv_id = input.xmltv_id
|
channel.xmltv_id = input.xmltv_id
|
||||||
break
|
break
|
||||||
case 'Skip':
|
case 'Skip':
|
||||||
channel.xmltv_id = '-'
|
channel.xmltv_id = '-'
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
const [, xmltv_id] = selected.option
|
const [, xmltv_id] = selected.option
|
||||||
.replace(/ \[.*\]/, '')
|
.replace(/ \[.*\]/, '')
|
||||||
.split('|')
|
.split('|')
|
||||||
.map((i: string) => i.trim().replace(newLabel, ''))
|
.map((i: string) => i.trim().replace(newLabel, ''))
|
||||||
channel.xmltv_id = xmltv_id
|
channel.xmltv_id = xmltv_id
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
const found = buffer.has(`${channel.xmltv_id}/${channel.lang}`)
|
const found = buffer.has(`${channel.xmltv_id}/${channel.lang}`)
|
||||||
if (found) {
|
if (found) {
|
||||||
const question: QuestionCollection = {
|
const question: QuestionCollection = {
|
||||||
name: 'option',
|
name: 'option',
|
||||||
message: `"${channel.xmltv_id}" already on the list. Choose an option:`,
|
message: `"${channel.xmltv_id}" already on the list. Choose an option:`,
|
||||||
type: 'list',
|
type: 'list',
|
||||||
choices: ['Skip', 'Add', 'Delete'],
|
choices: ['Skip', 'Add', 'Delete'],
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
}
|
}
|
||||||
await inquirer.prompt(question).then(async selected => {
|
await inquirer.prompt(question).then(async selected => {
|
||||||
switch (selected.option) {
|
switch (selected.option) {
|
||||||
case 'Skip':
|
case 'Skip':
|
||||||
channel.xmltv_id = '-'
|
channel.xmltv_id = '-'
|
||||||
break
|
break
|
||||||
case 'Delete':
|
case 'Delete':
|
||||||
option.delete = true
|
option.delete = true
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if (channel.xmltv_id !== '-') {
|
if (channel.xmltv_id !== '-') {
|
||||||
buffer.set(`${channel.xmltv_id}/${channel.lang}`, true)
|
buffer.set(`${channel.xmltv_id}/${channel.lang}`, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
const storage = new Storage()
|
const storage = new Storage()
|
||||||
|
|
||||||
if (!storage.existsSync(filepath)) return
|
if (!storage.existsSync(filepath)) return
|
||||||
|
|
||||||
const channels = options
|
const channels = options
|
||||||
.filter((option: { channel: Channel; delete: boolean }) => !option.delete)
|
.filter((option: { channel: Channel; delete: boolean }) => !option.delete)
|
||||||
.map((option: { channel: Channel; delete: boolean }) => option.channel)
|
.map((option: { channel: Channel; delete: boolean }) => option.channel)
|
||||||
|
|
||||||
const xml = new XML(channels)
|
const xml = new XML(channels)
|
||||||
|
|
||||||
storage.saveSync(filepath, xml.toString())
|
storage.saveSync(filepath, xml.toString())
|
||||||
|
|
||||||
logger.info(`\nFile '${filepath}' successfully saved`)
|
logger.info(`\nFile '${filepath}' successfully saved`)
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeCleanup(() => {
|
nodeCleanup(() => {
|
||||||
save()
|
save()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getInput(channel: Channel) {
|
async function getInput(channel: Channel) {
|
||||||
const name = channel.name.trim()
|
const name = channel.name.trim()
|
||||||
const input = await inquirer.prompt([
|
const input = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
name: 'xmltv_id',
|
name: 'xmltv_id',
|
||||||
message: ' ID:',
|
message: ' ID:',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
default: generateCode(name, defaultCountry)
|
default: generateCode(name, defaultCountry)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
return { name, xmltv_id: input['xmltv_id'] }
|
return { name, xmltv_id: input['xmltv_id'] }
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOptions(channels: Collection, channel: Channel) {
|
function getOptions(channels: Collection, channel: Channel) {
|
||||||
const channelId = generateCode(channel.name, defaultCountry)
|
const channelId = generateCode(channel.name, defaultCountry)
|
||||||
const similar = getSimilar(channels, channelId)
|
const similar = getSimilar(channels, channelId)
|
||||||
|
|
||||||
const variants = new Collection()
|
const variants = new Collection()
|
||||||
variants.add(`${channel.name.trim()} | ${channelId}${newLabel}`)
|
variants.add(`${channel.name.trim()} | ${channelId}${newLabel}`)
|
||||||
similar.forEach((_channel: ApiChannel) => {
|
similar.forEach((_channel: ApiChannel) => {
|
||||||
const altNames = _channel.altNames.notEmpty() ? ` (${_channel.altNames.join(',')})` : ''
|
const altNames = _channel.altNames.notEmpty() ? ` (${_channel.altNames.join(',')})` : ''
|
||||||
const closed = _channel.closed ? `[closed:${_channel.closed}]` : ``
|
const closed = _channel.closed ? `[closed:${_channel.closed}]` : ''
|
||||||
const replacedBy = _channel.replacedBy ? `[replaced_by:${_channel.replacedBy}]` : ''
|
const replacedBy = _channel.replacedBy ? `[replaced_by:${_channel.replacedBy}]` : ''
|
||||||
|
|
||||||
variants.add(`${_channel.name}${altNames} | ${_channel.id} ${closed}${replacedBy}[api]`)
|
variants.add(`${_channel.name}${altNames} | ${_channel.id} ${closed}${replacedBy}[api]`)
|
||||||
})
|
})
|
||||||
variants.add(`Overwrite`)
|
variants.add('Overwrite')
|
||||||
variants.add(`Skip`)
|
variants.add('Skip')
|
||||||
|
|
||||||
return variants.all()
|
return variants.all()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSimilar(channels: Collection, channelId: string) {
|
function getSimilar(channels: Collection, channelId: string) {
|
||||||
const normChannelId = channelId.split('.')[0].slice(0, 8).toLowerCase()
|
const normChannelId = channelId.split('.')[0].slice(0, 8).toLowerCase()
|
||||||
|
|
||||||
return channels.filter((channel: ApiChannel) =>
|
return channels.filter((channel: ApiChannel) =>
|
||||||
channel.id.split('.')[0].toLowerCase().startsWith(normChannelId)
|
channel.id.split('.')[0].toLowerCase().startsWith(normChannelId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateCode(name: string, country: string) {
|
function generateCode(name: string, country: string) {
|
||||||
const channelId: string = transliterate(name)
|
const channelId: string = transliterate(name)
|
||||||
.replace(/\+/gi, 'Plus')
|
.replace(/\+/gi, 'Plus')
|
||||||
.replace(/^\&/gi, 'And')
|
.replace(/^&/gi, 'And')
|
||||||
.replace(/[^a-z\d]+/gi, '')
|
.replace(/[^a-z\d]+/gi, '')
|
||||||
|
|
||||||
return `${channelId}.${country}`
|
return `${channelId}.${country}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +1,78 @@
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import libxml, { ValidationError } from 'libxmljs2'
|
import libxml, { ValidationError } from 'libxmljs2'
|
||||||
import { program } from 'commander'
|
import { program } from 'commander'
|
||||||
import { Logger, Storage, File } from '@freearhey/core'
|
import { Logger, Storage, File } from '@freearhey/core'
|
||||||
|
|
||||||
const xsd = `<?xml version="1.0" encoding="UTF-8"?>
|
const xsd = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
|
||||||
<xs:element name="channels">
|
<xs:element name="channels">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="channel"/>
|
<xs:element minOccurs="0" maxOccurs="unbounded" ref="channel"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="channel">
|
<xs:element name="channel">
|
||||||
<xs:complexType mixed="true">
|
<xs:complexType mixed="true">
|
||||||
<xs:attribute name="site" use="required" type="xs:string"/>
|
<xs:attribute name="site" use="required" type="xs:string"/>
|
||||||
<xs:attribute name="lang" use="required" type="xs:string"/>
|
<xs:attribute name="lang" use="required" type="xs:string"/>
|
||||||
<xs:attribute name="site_id" use="required" type="xs:string"/>
|
<xs:attribute name="site_id" use="required" type="xs:string"/>
|
||||||
<xs:attribute name="xmltv_id" use="required" type="xs:string"/>
|
<xs:attribute name="xmltv_id" use="required" type="xs:string"/>
|
||||||
<xs:attribute name="logo" type="xs:string"/>
|
<xs:attribute name="logo" type="xs:string"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
</xs:schema>`
|
</xs:schema>`
|
||||||
|
|
||||||
program
|
program
|
||||||
.option(
|
.option(
|
||||||
'-c, --channels <path>',
|
'-c, --channels <path>',
|
||||||
'Path to channels.xml file to validate',
|
'Path to channels.xml file to validate',
|
||||||
'sites/**/*.channels.xml'
|
'sites/**/*.channels.xml'
|
||||||
)
|
)
|
||||||
.parse(process.argv)
|
.parse(process.argv)
|
||||||
|
|
||||||
const options = program.opts()
|
const options = program.opts()
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
const storage = new Storage()
|
const storage = new Storage()
|
||||||
|
|
||||||
logger.info('options:')
|
logger.info('options:')
|
||||||
logger.tree(options)
|
logger.tree(options)
|
||||||
|
|
||||||
let errors: ValidationError[] = []
|
let errors: ValidationError[] = []
|
||||||
|
|
||||||
let files: string[] = await storage.list(options.channels)
|
const files: string[] = await storage.list(options.channels)
|
||||||
for (const filepath of files) {
|
for (const filepath of files) {
|
||||||
const file = new File(filepath)
|
const file = new File(filepath)
|
||||||
if (file.extension() !== 'xml') continue
|
if (file.extension() !== 'xml') continue
|
||||||
|
|
||||||
const xml = await storage.load(filepath)
|
const xml = await storage.load(filepath)
|
||||||
|
|
||||||
let localErrors: ValidationError[] = []
|
let localErrors: ValidationError[] = []
|
||||||
|
|
||||||
const xsdDoc = libxml.parseXml(xsd)
|
const xsdDoc = libxml.parseXml(xsd)
|
||||||
const doc = libxml.parseXml(xml)
|
const doc = libxml.parseXml(xml)
|
||||||
|
|
||||||
if (!doc.validate(xsdDoc)) {
|
if (!doc.validate(xsdDoc)) {
|
||||||
localErrors = doc.validationErrors
|
localErrors = doc.validationErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localErrors.length) {
|
if (localErrors.length) {
|
||||||
console.log(`\n${chalk.underline(filepath)}`)
|
console.log(`\n${chalk.underline(filepath)}`)
|
||||||
localErrors.forEach((error: ValidationError) => {
|
localErrors.forEach((error: ValidationError) => {
|
||||||
const position = `${error.line}:${error.column}`
|
const position = `${error.line}:${error.column}`
|
||||||
console.log(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`)
|
console.log(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
errors = errors.concat(localErrors)
|
errors = errors.concat(localErrors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
console.log(chalk.red(`\n${errors.length} error(s)`))
|
console.log(chalk.red(`\n${errors.length} error(s)`))
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,81 +1,85 @@
|
||||||
import { Logger, File, Collection, Storage } from '@freearhey/core'
|
import { Logger, File, Collection, Storage } from '@freearhey/core'
|
||||||
import { ChannelsParser, XML } from '../../core'
|
import { ChannelsParser, XML } from '../../core'
|
||||||
import { Channel } from 'epg-grabber'
|
import { Channel } from 'epg-grabber'
|
||||||
import { Command, OptionValues } from 'commander'
|
import { Command } from 'commander'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
const program = new Command()
|
const program = new Command()
|
||||||
program
|
program
|
||||||
.requiredOption('-c, --config <config>', 'Config file')
|
.requiredOption('-c, --config <config>', 'Config file')
|
||||||
.option('-s, --set [args...]', 'Set custom arguments')
|
.option('-s, --set [args...]', 'Set custom arguments')
|
||||||
.option('-o, --output <output>', 'Output file')
|
.option('-o, --output <output>', 'Output file')
|
||||||
.option('--clean', 'Delete the previous *.channels.xml if exists')
|
.option('--clean', 'Delete the previous *.channels.xml if exists')
|
||||||
.parse(process.argv)
|
.parse(process.argv)
|
||||||
|
|
||||||
type ParseOptions = {
|
type ParseOptions = {
|
||||||
config: string
|
config: string
|
||||||
set?: string
|
set?: string
|
||||||
output?: string
|
output?: string
|
||||||
clean?: boolean
|
clean?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: ParseOptions = program.opts()
|
const options: ParseOptions = program.opts()
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const storage = new Storage()
|
const storage = new Storage()
|
||||||
const parser = new ChannelsParser({ storage })
|
const parser = new ChannelsParser({ storage })
|
||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
const file = new File(options.config)
|
const file = new File(options.config)
|
||||||
const dir = file.dirname()
|
const dir = file.dirname()
|
||||||
const config = require(path.resolve(options.config))
|
const config = require(path.resolve(options.config))
|
||||||
const outputFilepath = options.output || `${dir}/${config.site}.channels.xml`
|
const outputFilepath = options.output || `${dir}/${config.site}.channels.xml`
|
||||||
|
|
||||||
let channels = new Collection()
|
let channels = new Collection()
|
||||||
if (!options.clean && (await storage.exists(outputFilepath))) {
|
if (!options.clean && (await storage.exists(outputFilepath))) {
|
||||||
channels = await parser.parse(outputFilepath)
|
channels = await parser.parse(outputFilepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
const args: {
|
const args: {
|
||||||
[key: string]: any
|
[key: string]: string
|
||||||
} = {}
|
} = {}
|
||||||
|
|
||||||
if (Array.isArray(options.set)) {
|
if (Array.isArray(options.set)) {
|
||||||
options.set.forEach((arg: string) => {
|
options.set.forEach((arg: string) => {
|
||||||
const [key, value] = arg.split(':')
|
const [key, value] = arg.split(':')
|
||||||
args[key] = value
|
args[key] = value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsedChannels = config.channels(args)
|
let parsedChannels = config.channels(args)
|
||||||
if (isPromise(parsedChannels)) {
|
if (isPromise(parsedChannels)) {
|
||||||
parsedChannels = await parsedChannels
|
parsedChannels = await parsedChannels
|
||||||
}
|
}
|
||||||
parsedChannels = parsedChannels.map((channel: Channel) => {
|
parsedChannels = parsedChannels.map((channel: Channel) => {
|
||||||
channel.site = config.site
|
channel.site = config.site
|
||||||
|
|
||||||
return channel
|
return channel
|
||||||
})
|
})
|
||||||
|
|
||||||
channels = channels
|
channels = channels
|
||||||
.mergeBy(
|
.mergeBy(
|
||||||
new Collection(parsedChannels),
|
new Collection(parsedChannels),
|
||||||
(channel: Channel) => channel.site_id.toString() + channel.lang
|
(channel: Channel) => channel.site_id.toString() + channel.lang
|
||||||
)
|
)
|
||||||
.orderBy([
|
.orderBy([
|
||||||
(channel: Channel) => channel.lang,
|
(channel: Channel) => channel.lang,
|
||||||
(channel: Channel) => (channel.xmltv_id ? channel.xmltv_id.toLowerCase() : '_'),
|
(channel: Channel) => (channel.xmltv_id ? channel.xmltv_id.toLowerCase() : '_'),
|
||||||
(channel: Channel) => channel.site_id
|
(channel: Channel) => channel.site_id
|
||||||
])
|
])
|
||||||
|
|
||||||
const xml = new XML(channels)
|
const xml = new XML(channels)
|
||||||
|
|
||||||
await storage.save(outputFilepath, xml.toString())
|
await storage.save(outputFilepath, xml.toString())
|
||||||
|
|
||||||
logger.info(`File '${outputFilepath}' successfully saved`)
|
logger.info(`File '${outputFilepath}' successfully saved`)
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
function isPromise(promise: any) {
|
function isPromise(promise: object[] | Promise<object[]>) {
|
||||||
return !!promise && typeof promise.then === 'function'
|
return (
|
||||||
}
|
!!promise &&
|
||||||
|
typeof promise === 'object' &&
|
||||||
|
typeof (promise as Promise<object[]>).then === 'function'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,95 +1,95 @@
|
||||||
import { Storage, Collection, Dictionary, File, Logger } from '@freearhey/core'
|
import { Storage, Collection, Dictionary, File, Logger } from '@freearhey/core'
|
||||||
import { ChannelsParser, ApiChannel } from '../../core'
|
import { ChannelsParser, ApiChannel } from '../../core'
|
||||||
import { program } from 'commander'
|
import { program } from 'commander'
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import langs from 'langs'
|
import langs from 'langs'
|
||||||
import { DATA_DIR } from '../../constants'
|
import { DATA_DIR } from '../../constants'
|
||||||
import { Channel } from 'epg-grabber'
|
import { Channel } from 'epg-grabber'
|
||||||
|
|
||||||
program
|
program
|
||||||
.option(
|
.option(
|
||||||
'-c, --channels <path>',
|
'-c, --channels <path>',
|
||||||
'Path to channels.xml file to validate',
|
'Path to channels.xml file to validate',
|
||||||
'sites/**/*.channels.xml'
|
'sites/**/*.channels.xml'
|
||||||
)
|
)
|
||||||
.parse(process.argv)
|
.parse(process.argv)
|
||||||
|
|
||||||
const options = program.opts()
|
const options = program.opts()
|
||||||
|
|
||||||
type ValidationError = {
|
type ValidationError = {
|
||||||
type: 'duplicate' | 'wrong_xmltv_id' | 'wrong_lang'
|
type: 'duplicate' | 'wrong_xmltv_id' | 'wrong_lang'
|
||||||
name: string
|
name: string
|
||||||
lang?: string
|
lang?: string
|
||||||
xmltv_id?: string
|
xmltv_id?: string
|
||||||
site_id?: string
|
site_id?: string
|
||||||
logo?: string
|
logo?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
|
|
||||||
logger.info('options:')
|
logger.info('options:')
|
||||||
logger.tree(options)
|
logger.tree(options)
|
||||||
|
|
||||||
const parser = new ChannelsParser({ storage: new Storage() })
|
const parser = new ChannelsParser({ storage: new Storage() })
|
||||||
|
|
||||||
const dataStorage = new Storage(DATA_DIR)
|
const dataStorage = new Storage(DATA_DIR)
|
||||||
const channelsContent = await dataStorage.json('channels.json')
|
const channelsContent = await dataStorage.json('channels.json')
|
||||||
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
||||||
|
|
||||||
let totalFiles = 0
|
let totalFiles = 0
|
||||||
let totalErrors = 0
|
let totalErrors = 0
|
||||||
const storage = new Storage()
|
const storage = new Storage()
|
||||||
let files: string[] = await storage.list(options.channels)
|
const files: string[] = await storage.list(options.channels)
|
||||||
for (const filepath of files) {
|
for (const filepath of files) {
|
||||||
const file = new File(filepath)
|
const file = new File(filepath)
|
||||||
if (file.extension() !== 'xml') continue
|
if (file.extension() !== 'xml') continue
|
||||||
|
|
||||||
const parsedChannels = await parser.parse(filepath)
|
const parsedChannels = await parser.parse(filepath)
|
||||||
|
|
||||||
const bufferById = new Dictionary()
|
const bufferById = new Dictionary()
|
||||||
const bufferBySiteId = new Dictionary()
|
const bufferBySiteId = new Dictionary()
|
||||||
const errors: ValidationError[] = []
|
const errors: ValidationError[] = []
|
||||||
parsedChannels.forEach((channel: Channel) => {
|
parsedChannels.forEach((channel: Channel) => {
|
||||||
const bufferId: string = `${channel.xmltv_id}:${channel.lang}`
|
const bufferId: string = `${channel.xmltv_id}:${channel.lang}`
|
||||||
if (bufferById.missing(bufferId)) {
|
if (bufferById.missing(bufferId)) {
|
||||||
bufferById.set(bufferId, true)
|
bufferById.set(bufferId, true)
|
||||||
} else {
|
} else {
|
||||||
errors.push({ type: 'duplicate', ...channel })
|
errors.push({ type: 'duplicate', ...channel })
|
||||||
totalErrors++
|
totalErrors++
|
||||||
}
|
}
|
||||||
|
|
||||||
const bufferSiteId: string = `${channel.site_id}:${channel.lang}`
|
const bufferSiteId: string = `${channel.site_id}:${channel.lang}`
|
||||||
if (bufferBySiteId.missing(bufferSiteId)) {
|
if (bufferBySiteId.missing(bufferSiteId)) {
|
||||||
bufferBySiteId.set(bufferSiteId, true)
|
bufferBySiteId.set(bufferSiteId, true)
|
||||||
} else {
|
} else {
|
||||||
errors.push({ type: 'duplicate', ...channel })
|
errors.push({ type: 'duplicate', ...channel })
|
||||||
totalErrors++
|
totalErrors++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channels.missing((_channel: ApiChannel) => _channel.id === channel.xmltv_id)) {
|
if (channels.missing((_channel: ApiChannel) => _channel.id === channel.xmltv_id)) {
|
||||||
errors.push({ type: 'wrong_xmltv_id', ...channel })
|
errors.push({ type: 'wrong_xmltv_id', ...channel })
|
||||||
totalErrors++
|
totalErrors++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!langs.where('1', channel.lang)) {
|
if (!langs.where('1', channel.lang)) {
|
||||||
errors.push({ type: 'wrong_lang', ...channel })
|
errors.push({ type: 'wrong_lang', ...channel })
|
||||||
totalErrors++
|
totalErrors++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
console.log(chalk.underline(filepath))
|
console.log(chalk.underline(filepath))
|
||||||
console.table(errors, ['type', 'lang', 'xmltv_id', 'site_id', 'name'])
|
console.table(errors, ['type', 'lang', 'xmltv_id', 'site_id', 'name'])
|
||||||
console.log()
|
console.log()
|
||||||
totalFiles++
|
totalFiles++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalErrors > 0) {
|
if (totalErrors > 0) {
|
||||||
console.log(chalk.red(`${totalErrors} error(s) in ${totalFiles} file(s)`))
|
console.log(chalk.red(`${totalErrors} error(s) in ${totalFiles} file(s)`))
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,117 +1,117 @@
|
||||||
import { Logger, Timer, Storage, Collection } from '@freearhey/core'
|
import { Logger, Timer, Storage, Collection } from '@freearhey/core'
|
||||||
import { program } from 'commander'
|
import { program } from 'commander'
|
||||||
import { CronJob } from 'cron'
|
import { CronJob } from 'cron'
|
||||||
import { QueueCreator, Job, ChannelsParser } from '../../core'
|
import { QueueCreator, Job, ChannelsParser } from '../../core'
|
||||||
import { Channel } from 'epg-grabber'
|
import { Channel } from 'epg-grabber'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { SITES_DIR } from '../../constants'
|
import { SITES_DIR } from '../../constants'
|
||||||
|
|
||||||
program
|
program
|
||||||
.option('-s, --site <name>', 'Name of the site to parse')
|
.option('-s, --site <name>', 'Name of the site to parse')
|
||||||
.option(
|
.option(
|
||||||
'-c, --channels <path>',
|
'-c, --channels <path>',
|
||||||
'Path to *.channels.xml file (required if the "--site" attribute is not specified)'
|
'Path to *.channels.xml file (required if the "--site" attribute is not specified)'
|
||||||
)
|
)
|
||||||
.option('-o, --output <path>', 'Path to output file', 'guide.xml')
|
.option('-o, --output <path>', 'Path to output file', 'guide.xml')
|
||||||
.option('-l, --lang <code>', 'Filter channels by language (ISO 639-2 code)')
|
.option('-l, --lang <code>', 'Filter channels by language (ISO 639-2 code)')
|
||||||
.option('-t, --timeout <milliseconds>', 'Override the default timeout for each request')
|
.option('-t, --timeout <milliseconds>', 'Override the default timeout for each request')
|
||||||
.option(
|
.option(
|
||||||
'--days <days>',
|
'--days <days>',
|
||||||
'Override the number of days for which the program will be loaded (defaults to the value from the site config)',
|
'Override the number of days for which the program will be loaded (defaults to the value from the site config)',
|
||||||
value => parseInt(value)
|
value => parseInt(value)
|
||||||
)
|
)
|
||||||
.option(
|
.option(
|
||||||
'--maxConnections <number>',
|
'--maxConnections <number>',
|
||||||
'Limit on the number of concurrent requests',
|
'Limit on the number of concurrent requests',
|
||||||
value => parseInt(value),
|
value => parseInt(value),
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
.option('--cron <expression>', 'Schedule a script run (example: "0 0 * * *")')
|
.option('--cron <expression>', 'Schedule a script run (example: "0 0 * * *")')
|
||||||
.option('--gzip', 'Create a compressed version of the guide as well', false)
|
.option('--gzip', 'Create a compressed version of the guide as well', false)
|
||||||
.parse(process.argv)
|
.parse(process.argv)
|
||||||
|
|
||||||
export type GrabOptions = {
|
export type GrabOptions = {
|
||||||
site?: string
|
site?: string
|
||||||
channels?: string
|
channels?: string
|
||||||
output: string
|
output: string
|
||||||
gzip: boolean
|
gzip: boolean
|
||||||
maxConnections: number
|
maxConnections: number
|
||||||
timeout?: string
|
timeout?: string
|
||||||
lang?: string
|
lang?: string
|
||||||
days?: number
|
days?: number
|
||||||
cron?: string
|
cron?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: GrabOptions = program.opts()
|
const options: GrabOptions = program.opts()
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
if (!options.site && !options.channels)
|
if (!options.site && !options.channels)
|
||||||
throw new Error('One of the arguments must be presented: `--site` or `--channels`')
|
throw new Error('One of the arguments must be presented: `--site` or `--channels`')
|
||||||
|
|
||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
|
|
||||||
logger.start('staring...')
|
logger.start('staring...')
|
||||||
|
|
||||||
logger.info('config:')
|
logger.info('config:')
|
||||||
logger.tree(options)
|
logger.tree(options)
|
||||||
|
|
||||||
logger.info(`loading channels...`)
|
logger.info('loading channels...')
|
||||||
const storage = new Storage()
|
const storage = new Storage()
|
||||||
const parser = new ChannelsParser({ storage })
|
const parser = new ChannelsParser({ storage })
|
||||||
|
|
||||||
let files: string[] = []
|
let files: string[] = []
|
||||||
if (options.site) {
|
if (options.site) {
|
||||||
let pattern = path.join(SITES_DIR, options.site, '*.channels.xml')
|
let pattern = path.join(SITES_DIR, options.site, '*.channels.xml')
|
||||||
pattern = pattern.replace(/\\/g, '/')
|
pattern = pattern.replace(/\\/g, '/')
|
||||||
files = await storage.list(pattern)
|
files = await storage.list(pattern)
|
||||||
} else if (options.channels) {
|
} else if (options.channels) {
|
||||||
files = await storage.list(options.channels)
|
files = await storage.list(options.channels)
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsedChannels = new Collection()
|
let parsedChannels = new Collection()
|
||||||
for (let filepath of files) {
|
for (const filepath of files) {
|
||||||
parsedChannels = parsedChannels.concat(await parser.parse(filepath))
|
parsedChannels = parsedChannels.concat(await parser.parse(filepath))
|
||||||
}
|
}
|
||||||
if (options.lang) {
|
if (options.lang) {
|
||||||
parsedChannels = parsedChannels.filter((channel: Channel) => channel.lang === options.lang)
|
parsedChannels = parsedChannels.filter((channel: Channel) => channel.lang === options.lang)
|
||||||
}
|
}
|
||||||
logger.info(` found ${parsedChannels.count()} channels`)
|
logger.info(` found ${parsedChannels.count()} channels`)
|
||||||
|
|
||||||
logger.info('creating queue...')
|
logger.info('creating queue...')
|
||||||
const queueCreator = new QueueCreator({
|
const queueCreator = new QueueCreator({
|
||||||
parsedChannels,
|
parsedChannels,
|
||||||
logger,
|
logger,
|
||||||
options
|
options
|
||||||
})
|
})
|
||||||
const queue = await queueCreator.create()
|
const queue = await queueCreator.create()
|
||||||
logger.info(` added ${queue.size()} items`)
|
logger.info(` added ${queue.size()} items`)
|
||||||
|
|
||||||
const job = new Job({
|
const job = new Job({
|
||||||
queue,
|
queue,
|
||||||
logger,
|
logger,
|
||||||
options
|
options
|
||||||
})
|
})
|
||||||
|
|
||||||
let runIndex = 1
|
let runIndex = 1
|
||||||
if (options.cron) {
|
if (options.cron) {
|
||||||
const cronJob = new CronJob(options.cron, async () => {
|
const cronJob = new CronJob(options.cron, async () => {
|
||||||
logger.info(`run #${runIndex}:`)
|
logger.info(`run #${runIndex}:`)
|
||||||
const timer = new Timer()
|
const timer = new Timer()
|
||||||
timer.start()
|
timer.start()
|
||||||
await job.run()
|
await job.run()
|
||||||
runIndex++
|
runIndex++
|
||||||
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
|
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
|
||||||
})
|
})
|
||||||
cronJob.start()
|
cronJob.start()
|
||||||
} else {
|
} else {
|
||||||
logger.info(`run #${runIndex}:`)
|
logger.info(`run #${runIndex}:`)
|
||||||
const timer = new Timer()
|
const timer = new Timer()
|
||||||
timer.start()
|
timer.start()
|
||||||
await job.run()
|
await job.run()
|
||||||
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
|
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('finished')
|
logger.info('finished')
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const SITES_DIR = process.env.SITES_DIR || './sites'
|
export const SITES_DIR = process.env.SITES_DIR || './sites'
|
||||||
export const GUIDES_DIR = process.env.GUIDES_DIR || './guides'
|
export const GUIDES_DIR = process.env.GUIDES_DIR || './guides'
|
||||||
export const DATA_DIR = process.env.DATA_DIR || './temp/data'
|
export const DATA_DIR = process.env.DATA_DIR || './temp/data'
|
||||||
export const CURR_DATE = process.env.CURR_DATE || new Date().toISOString()
|
export const CURR_DATE = process.env.CURR_DATE || new Date().toISOString()
|
||||||
|
|
|
@ -1,79 +1,79 @@
|
||||||
import { Collection } from '@freearhey/core'
|
import { Collection } from '@freearhey/core'
|
||||||
|
|
||||||
type ApiChannelProps = {
|
type ApiChannelProps = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
alt_names: string[]
|
alt_names: string[]
|
||||||
network: string
|
network: string
|
||||||
owners: string[]
|
owners: string[]
|
||||||
country: string
|
country: string
|
||||||
subdivision: string
|
subdivision: string
|
||||||
city: string
|
city: string
|
||||||
broadcast_area: string[]
|
broadcast_area: string[]
|
||||||
languages: string[]
|
languages: string[]
|
||||||
categories: string[]
|
categories: string[]
|
||||||
is_nsfw: boolean
|
is_nsfw: boolean
|
||||||
launched: string
|
launched: string
|
||||||
closed: string
|
closed: string
|
||||||
replaced_by: string
|
replaced_by: string
|
||||||
website: string
|
website: string
|
||||||
logo: string
|
logo: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApiChannel {
|
export class ApiChannel {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
altNames: Collection
|
altNames: Collection
|
||||||
network: string
|
network: string
|
||||||
owners: Collection
|
owners: Collection
|
||||||
country: string
|
country: string
|
||||||
subdivision: string
|
subdivision: string
|
||||||
city: string
|
city: string
|
||||||
broadcastArea: Collection
|
broadcastArea: Collection
|
||||||
languages: Collection
|
languages: Collection
|
||||||
categories: Collection
|
categories: Collection
|
||||||
isNSFW: boolean
|
isNSFW: boolean
|
||||||
launched: string
|
launched: string
|
||||||
closed: string
|
closed: string
|
||||||
replacedBy: string
|
replacedBy: string
|
||||||
website: string
|
website: string
|
||||||
logo: string
|
logo: string
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
alt_names,
|
alt_names,
|
||||||
network,
|
network,
|
||||||
owners,
|
owners,
|
||||||
country,
|
country,
|
||||||
subdivision,
|
subdivision,
|
||||||
city,
|
city,
|
||||||
broadcast_area,
|
broadcast_area,
|
||||||
languages,
|
languages,
|
||||||
categories,
|
categories,
|
||||||
is_nsfw,
|
is_nsfw,
|
||||||
launched,
|
launched,
|
||||||
closed,
|
closed,
|
||||||
replaced_by,
|
replaced_by,
|
||||||
website,
|
website,
|
||||||
logo
|
logo
|
||||||
}: ApiChannelProps) {
|
}: ApiChannelProps) {
|
||||||
this.id = id
|
this.id = id
|
||||||
this.name = name
|
this.name = name
|
||||||
this.altNames = new Collection(alt_names)
|
this.altNames = new Collection(alt_names)
|
||||||
this.network = network
|
this.network = network
|
||||||
this.owners = new Collection(owners)
|
this.owners = new Collection(owners)
|
||||||
this.country = country
|
this.country = country
|
||||||
this.subdivision = subdivision
|
this.subdivision = subdivision
|
||||||
this.city = city
|
this.city = city
|
||||||
this.broadcastArea = new Collection(broadcast_area)
|
this.broadcastArea = new Collection(broadcast_area)
|
||||||
this.languages = new Collection(languages)
|
this.languages = new Collection(languages)
|
||||||
this.categories = new Collection(categories)
|
this.categories = new Collection(categories)
|
||||||
this.isNSFW = is_nsfw
|
this.isNSFW = is_nsfw
|
||||||
this.launched = launched
|
this.launched = launched
|
||||||
this.closed = closed
|
this.closed = closed
|
||||||
this.replacedBy = replaced_by
|
this.replacedBy = replaced_by
|
||||||
this.website = website
|
this.website = website
|
||||||
this.logo = logo
|
this.logo = logo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +1,59 @@
|
||||||
import { Logger, Storage } from '@freearhey/core'
|
import { Logger, Storage } from '@freearhey/core'
|
||||||
import axios, { AxiosInstance, AxiosResponse, AxiosProgressEvent } from 'axios'
|
import axios, { AxiosInstance, AxiosResponse, AxiosProgressEvent } from 'axios'
|
||||||
import cliProgress, { MultiBar } from 'cli-progress'
|
import cliProgress, { MultiBar } from 'cli-progress'
|
||||||
import numeral from 'numeral'
|
import numeral from 'numeral'
|
||||||
|
|
||||||
export class ApiClient {
|
export class ApiClient {
|
||||||
progressBar: MultiBar
|
progressBar: MultiBar
|
||||||
client: AxiosInstance
|
client: AxiosInstance
|
||||||
storage: Storage
|
storage: Storage
|
||||||
logger: Logger
|
logger: Logger
|
||||||
|
|
||||||
constructor({ logger }: { logger: Logger }) {
|
constructor({ logger }: { logger: Logger }) {
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
this.client = axios.create({
|
this.client = axios.create({
|
||||||
responseType: 'stream'
|
responseType: 'stream'
|
||||||
})
|
})
|
||||||
this.storage = new Storage()
|
this.storage = new Storage()
|
||||||
this.progressBar = new cliProgress.MultiBar({
|
this.progressBar = new cliProgress.MultiBar({
|
||||||
stopOnComplete: true,
|
stopOnComplete: true,
|
||||||
hideCursor: true,
|
hideCursor: true,
|
||||||
forceRedraw: true,
|
forceRedraw: true,
|
||||||
barsize: 36,
|
barsize: 36,
|
||||||
format(options, params, payload) {
|
format(options, params, payload) {
|
||||||
const filename = payload.filename.padEnd(18, ' ')
|
const filename = payload.filename.padEnd(18, ' ')
|
||||||
const barsize = options.barsize || 40
|
const barsize = options.barsize || 40
|
||||||
const percent = (params.progress * 100).toFixed(2)
|
const percent = (params.progress * 100).toFixed(2)
|
||||||
const speed = payload.speed ? numeral(payload.speed).format('0.0 b') + '/s' : 'N/A'
|
const speed = payload.speed ? numeral(payload.speed).format('0.0 b') + '/s' : 'N/A'
|
||||||
const total = numeral(params.total).format('0.0 b')
|
const total = numeral(params.total).format('0.0 b')
|
||||||
const completeSize = Math.round(params.progress * barsize)
|
const completeSize = Math.round(params.progress * barsize)
|
||||||
const incompleteSize = barsize - completeSize
|
const incompleteSize = barsize - completeSize
|
||||||
const bar =
|
const bar =
|
||||||
options.barCompleteString && options.barIncompleteString
|
options.barCompleteString && options.barIncompleteString
|
||||||
? options.barCompleteString.substr(0, completeSize) +
|
? options.barCompleteString.substr(0, completeSize) +
|
||||||
options.barGlue +
|
options.barGlue +
|
||||||
options.barIncompleteString.substr(0, incompleteSize)
|
options.barIncompleteString.substr(0, incompleteSize)
|
||||||
: '-'.repeat(barsize)
|
: '-'.repeat(barsize)
|
||||||
|
|
||||||
return `${filename} [${bar}] ${percent}% | ETA: ${params.eta}s | ${total} | ${speed}`
|
return `${filename} [${bar}] ${percent}% | ETA: ${params.eta}s | ${total} | ${speed}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async download(filename: string) {
|
async download(filename: string) {
|
||||||
const stream = await this.storage.createStream(`/temp/data/${filename}`)
|
const stream = await this.storage.createStream(`/temp/data/${filename}`)
|
||||||
|
|
||||||
const bar = this.progressBar.create(0, 0, { filename })
|
const bar = this.progressBar.create(0, 0, { filename })
|
||||||
|
|
||||||
this.client
|
this.client
|
||||||
.get(`https://iptv-org.github.io/api/${filename}`, {
|
.get(`https://iptv-org.github.io/api/${filename}`, {
|
||||||
onDownloadProgress({ total, loaded, rate }: AxiosProgressEvent) {
|
onDownloadProgress({ total, loaded, rate }: AxiosProgressEvent) {
|
||||||
if (total) bar.setTotal(total)
|
if (total) bar.setTotal(total)
|
||||||
bar.update(loaded, { speed: rate })
|
bar.update(loaded, { speed: rate })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response: AxiosResponse) => {
|
.then((response: AxiosResponse) => {
|
||||||
response.data.pipe(stream)
|
response.data.pipe(stream)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
import { parseChannels } from 'epg-grabber'
|
import { parseChannels } from 'epg-grabber'
|
||||||
import { Storage, Collection } from '@freearhey/core'
|
import { Storage, Collection } from '@freearhey/core'
|
||||||
|
|
||||||
type ChannelsParserProps = {
|
type ChannelsParserProps = {
|
||||||
storage: Storage
|
storage: Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChannelsParser {
|
export class ChannelsParser {
|
||||||
storage: Storage
|
storage: Storage
|
||||||
|
|
||||||
constructor({ storage }: ChannelsParserProps) {
|
constructor({ storage }: ChannelsParserProps) {
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
}
|
}
|
||||||
|
|
||||||
async parse(filepath: string) {
|
async parse(filepath: string) {
|
||||||
let parsedChannels = new Collection()
|
let parsedChannels = new Collection()
|
||||||
|
|
||||||
const content = await this.storage.load(filepath)
|
const content = await this.storage.load(filepath)
|
||||||
const channels = parseChannels(content)
|
const channels = parseChannels(content)
|
||||||
parsedChannels = parsedChannels.concat(new Collection(channels))
|
parsedChannels = parsedChannels.concat(new Collection(channels))
|
||||||
|
|
||||||
return parsedChannels
|
return parsedChannels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import { SiteConfig } from 'epg-grabber'
|
import { SiteConfig } from 'epg-grabber'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { pathToFileURL } from 'url'
|
import { pathToFileURL } from 'url'
|
||||||
|
|
||||||
export class ConfigLoader {
|
export class ConfigLoader {
|
||||||
async load(filepath: string): Promise<SiteConfig> {
|
async load(filepath: string): Promise<SiteConfig> {
|
||||||
const fileUrl = pathToFileURL(filepath).toString()
|
const fileUrl = pathToFileURL(filepath).toString()
|
||||||
const config = (await import(fileUrl)).default
|
const config = (await import(fileUrl)).default
|
||||||
|
|
||||||
return _.merge(
|
return _.merge(
|
||||||
{
|
{
|
||||||
delay: 0,
|
delay: 0,
|
||||||
maxConnections: 1,
|
maxConnections: 1,
|
||||||
request: {
|
request: {
|
||||||
timeout: 30000
|
timeout: 30000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = {}
|
const date = {}
|
||||||
|
|
||||||
date.getUTC = function (d = null) {
|
date.getUTC = function (d = null) {
|
||||||
if (typeof d === 'string') return dayjs.utc(d).startOf('d')
|
if (typeof d === 'string') return dayjs.utc(d).startOf('d')
|
||||||
|
|
||||||
return dayjs.utc().startOf('d')
|
return dayjs.utc().startOf('d')
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = date
|
module.exports = date
|
||||||
|
|
|
@ -1,75 +1,75 @@
|
||||||
import { EPGGrabber, GrabCallbackData, EPGGrabberMock, SiteConfig, Channel } from 'epg-grabber'
|
import { EPGGrabber, GrabCallbackData, EPGGrabberMock, SiteConfig, Channel } from 'epg-grabber'
|
||||||
import { Logger, Collection } from '@freearhey/core'
|
import { Logger, Collection } from '@freearhey/core'
|
||||||
import { Queue } from './'
|
import { Queue } from './'
|
||||||
import { GrabOptions } from '../commands/epg/grab'
|
import { GrabOptions } from '../commands/epg/grab'
|
||||||
import { TaskQueue, PromisyClass } from 'cwait'
|
import { TaskQueue, PromisyClass } from 'cwait'
|
||||||
|
|
||||||
type GrabberProps = {
|
type GrabberProps = {
|
||||||
logger: Logger
|
logger: Logger
|
||||||
queue: Queue
|
queue: Queue
|
||||||
options: GrabOptions
|
options: GrabOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Grabber {
|
export class Grabber {
|
||||||
logger: Logger
|
logger: Logger
|
||||||
queue: Queue
|
queue: Queue
|
||||||
options: GrabOptions
|
options: GrabOptions
|
||||||
|
|
||||||
constructor({ logger, queue, options }: GrabberProps) {
|
constructor({ logger, queue, options }: GrabberProps) {
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
this.queue = queue
|
this.queue = queue
|
||||||
this.options = options
|
this.options = options
|
||||||
}
|
}
|
||||||
|
|
||||||
async grab(): Promise<{ channels: Collection; programs: Collection }> {
|
async grab(): Promise<{ channels: Collection; programs: Collection }> {
|
||||||
const taskQueue = new TaskQueue(Promise as PromisyClass, this.options.maxConnections)
|
const taskQueue = new TaskQueue(Promise as PromisyClass, this.options.maxConnections)
|
||||||
|
|
||||||
const total = this.queue.size()
|
const total = this.queue.size()
|
||||||
|
|
||||||
const channels = new Collection()
|
const channels = new Collection()
|
||||||
let programs = new Collection()
|
let programs = new Collection()
|
||||||
let i = 1
|
let i = 1
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.queue.items().map(
|
this.queue.items().map(
|
||||||
taskQueue.wrap(
|
taskQueue.wrap(
|
||||||
async (queueItem: { channel: Channel; config: SiteConfig; date: string }) => {
|
async (queueItem: { channel: Channel; config: SiteConfig; date: string }) => {
|
||||||
const { channel, config, date } = queueItem
|
const { channel, config, date } = queueItem
|
||||||
|
|
||||||
channels.add(channel)
|
channels.add(channel)
|
||||||
|
|
||||||
if (this.options.timeout !== undefined) {
|
if (this.options.timeout !== undefined) {
|
||||||
const timeout = parseInt(this.options.timeout)
|
const timeout = parseInt(this.options.timeout)
|
||||||
config.request = { ...config.request, ...{ timeout } }
|
config.request = { ...config.request, ...{ timeout } }
|
||||||
}
|
}
|
||||||
|
|
||||||
const grabber =
|
const grabber =
|
||||||
process.env.NODE_ENV === 'test' ? new EPGGrabberMock(config) : new EPGGrabber(config)
|
process.env.NODE_ENV === 'test' ? new EPGGrabberMock(config) : new EPGGrabber(config)
|
||||||
const _programs = await grabber.grab(
|
const _programs = await grabber.grab(
|
||||||
channel,
|
channel,
|
||||||
date,
|
date,
|
||||||
(data: GrabCallbackData, error: Error | null) => {
|
(data: GrabCallbackData, error: Error | null) => {
|
||||||
const { programs, date } = data
|
const { programs, date } = data
|
||||||
|
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
` [${i}/${total}] ${channel.site} (${channel.lang}) - ${
|
` [${i}/${total}] ${channel.site} (${channel.lang}) - ${
|
||||||
channel.xmltv_id
|
channel.xmltv_id
|
||||||
} - ${date.format('MMM D, YYYY')} (${programs.length} programs)`
|
} - ${date.format('MMM D, YYYY')} (${programs.length} programs)`
|
||||||
)
|
)
|
||||||
if (i < total) i++
|
if (i < total) i++
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.info(` ERR: ${error.message}`)
|
this.logger.info(` ERR: ${error.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
programs = programs.concat(new Collection(_programs))
|
programs = programs.concat(new Collection(_programs))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return { channels, programs }
|
return { channels, programs }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +1,55 @@
|
||||||
import { Collection, Logger, DateTime, Storage, Zip } from '@freearhey/core'
|
import { Collection, Logger, DateTime, Storage, Zip } from '@freearhey/core'
|
||||||
import { Channel } from 'epg-grabber'
|
import { Channel } from 'epg-grabber'
|
||||||
import { XMLTV } from '../core'
|
import { XMLTV } from '../core'
|
||||||
import { CURR_DATE } from '../constants'
|
import { CURR_DATE } from '../constants'
|
||||||
|
|
||||||
type GuideProps = {
|
type GuideProps = {
|
||||||
channels: Collection
|
channels: Collection
|
||||||
programs: Collection
|
programs: Collection
|
||||||
logger: Logger
|
logger: Logger
|
||||||
filepath: string
|
filepath: string
|
||||||
gzip: boolean
|
gzip: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Guide {
|
export class Guide {
|
||||||
channels: Collection
|
channels: Collection
|
||||||
programs: Collection
|
programs: Collection
|
||||||
logger: Logger
|
logger: Logger
|
||||||
storage: Storage
|
storage: Storage
|
||||||
filepath: string
|
filepath: string
|
||||||
gzip: boolean
|
gzip: boolean
|
||||||
|
|
||||||
constructor({ channels, programs, logger, filepath, gzip }: GuideProps) {
|
constructor({ channels, programs, logger, filepath, gzip }: GuideProps) {
|
||||||
this.channels = channels
|
this.channels = channels
|
||||||
this.programs = programs
|
this.programs = programs
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
this.storage = new Storage()
|
this.storage = new Storage()
|
||||||
this.filepath = filepath
|
this.filepath = filepath
|
||||||
this.gzip = gzip || false
|
this.gzip = gzip || false
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
const channels = this.channels.uniqBy(
|
const channels = this.channels.uniqBy(
|
||||||
(channel: Channel) => `${channel.xmltv_id}:${channel.site}`
|
(channel: Channel) => `${channel.xmltv_id}:${channel.site}`
|
||||||
)
|
)
|
||||||
const programs = this.programs
|
const programs = this.programs
|
||||||
|
|
||||||
const xmltv = new XMLTV({
|
const xmltv = new XMLTV({
|
||||||
channels,
|
channels,
|
||||||
programs,
|
programs,
|
||||||
date: new DateTime(CURR_DATE, { zone: 'UTC' })
|
date: new DateTime(CURR_DATE, { zone: 'UTC' })
|
||||||
})
|
})
|
||||||
|
|
||||||
const xmlFilepath = this.filepath
|
const xmlFilepath = this.filepath
|
||||||
this.logger.info(` saving to "${xmlFilepath}"...`)
|
this.logger.info(` saving to "${xmlFilepath}"...`)
|
||||||
await this.storage.save(xmlFilepath, xmltv.toString())
|
await this.storage.save(xmlFilepath, xmltv.toString())
|
||||||
|
|
||||||
if (this.gzip) {
|
if (this.gzip) {
|
||||||
const zip = new Zip()
|
const zip = new Zip()
|
||||||
const compressed = await zip.compress(xmltv.toString())
|
const compressed = await zip.compress(xmltv.toString())
|
||||||
const gzFilepath = `${this.filepath}.gz`
|
const gzFilepath = `${this.filepath}.gz`
|
||||||
this.logger.info(` saving to "${gzFilepath}"...`)
|
this.logger.info(` saving to "${gzFilepath}"...`)
|
||||||
await this.storage.save(gzFilepath, compressed)
|
await this.storage.save(gzFilepath, compressed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +1,61 @@
|
||||||
import { Collection, Logger, Storage, StringTemplate } from '@freearhey/core'
|
import { Collection, Logger, Storage, StringTemplate } from '@freearhey/core'
|
||||||
import { OptionValues } from 'commander'
|
import { OptionValues } from 'commander'
|
||||||
import { Channel, Program } from 'epg-grabber'
|
import { Channel, Program } from 'epg-grabber'
|
||||||
import { Guide } from '.'
|
import { Guide } from '.'
|
||||||
|
|
||||||
type GuideManagerProps = {
|
type GuideManagerProps = {
|
||||||
options: OptionValues
|
options: OptionValues
|
||||||
logger: Logger
|
logger: Logger
|
||||||
channels: Collection
|
channels: Collection
|
||||||
programs: Collection
|
programs: Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GuideManager {
|
export class GuideManager {
|
||||||
options: OptionValues
|
options: OptionValues
|
||||||
storage: Storage
|
storage: Storage
|
||||||
logger: Logger
|
logger: Logger
|
||||||
channels: Collection
|
channels: Collection
|
||||||
programs: Collection
|
programs: Collection
|
||||||
|
|
||||||
constructor({ channels, programs, logger, options }: GuideManagerProps) {
|
constructor({ channels, programs, logger, options }: GuideManagerProps) {
|
||||||
this.options = options
|
this.options = options
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
this.channels = channels
|
this.channels = channels
|
||||||
this.programs = programs
|
this.programs = programs
|
||||||
this.storage = new Storage()
|
this.storage = new Storage()
|
||||||
}
|
}
|
||||||
|
|
||||||
async createGuides() {
|
async createGuides() {
|
||||||
const pathTemplate = new StringTemplate(this.options.output)
|
const pathTemplate = new StringTemplate(this.options.output)
|
||||||
|
|
||||||
const groupedChannels = this.channels
|
const groupedChannels = this.channels
|
||||||
.orderBy([(channel: Channel) => channel.xmltv_id])
|
.orderBy([(channel: Channel) => channel.xmltv_id])
|
||||||
.uniqBy((channel: Channel) => `${channel.xmltv_id}:${channel.site}:${channel.lang}`)
|
.uniqBy((channel: Channel) => `${channel.xmltv_id}:${channel.site}:${channel.lang}`)
|
||||||
.groupBy((channel: Channel) => {
|
.groupBy((channel: Channel) => {
|
||||||
return pathTemplate.format({ lang: channel.lang || 'en', site: channel.site || '' })
|
return pathTemplate.format({ lang: channel.lang || 'en', site: channel.site || '' })
|
||||||
})
|
})
|
||||||
|
|
||||||
const groupedPrograms = this.programs
|
const groupedPrograms = this.programs
|
||||||
.orderBy([(program: Program) => program.channel, (program: Program) => program.start])
|
.orderBy([(program: Program) => program.channel, (program: Program) => program.start])
|
||||||
.groupBy((program: Program) => {
|
.groupBy((program: Program) => {
|
||||||
const lang =
|
const lang =
|
||||||
program.titles && program.titles.length && program.titles[0].lang
|
program.titles && program.titles.length && program.titles[0].lang
|
||||||
? program.titles[0].lang
|
? program.titles[0].lang
|
||||||
: 'en'
|
: 'en'
|
||||||
|
|
||||||
return pathTemplate.format({ lang, site: program.site || '' })
|
return pathTemplate.format({ lang, site: program.site || '' })
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const groupKey of groupedPrograms.keys()) {
|
for (const groupKey of groupedPrograms.keys()) {
|
||||||
const guide = new Guide({
|
const guide = new Guide({
|
||||||
filepath: groupKey,
|
filepath: groupKey,
|
||||||
gzip: this.options.gzip,
|
gzip: this.options.gzip,
|
||||||
channels: new Collection(groupedChannels.get(groupKey)),
|
channels: new Collection(groupedChannels.get(groupKey)),
|
||||||
programs: new Collection(groupedPrograms.get(groupKey)),
|
programs: new Collection(groupedPrograms.get(groupKey)),
|
||||||
logger: this.logger
|
logger: this.logger
|
||||||
})
|
})
|
||||||
|
|
||||||
await guide.save()
|
await guide.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
export * from './xml'
|
export * from './xml'
|
||||||
export * from './channelsParser'
|
export * from './channelsParser'
|
||||||
export * from './xmltv'
|
export * from './xmltv'
|
||||||
export * from './configLoader'
|
export * from './configLoader'
|
||||||
export * from './grabber'
|
export * from './grabber'
|
||||||
export * from './job'
|
export * from './job'
|
||||||
export * from './queue'
|
export * from './queue'
|
||||||
export * from './guideManager'
|
export * from './guideManager'
|
||||||
export * from './guide'
|
export * from './guide'
|
||||||
export * from './apiChannel'
|
export * from './apiChannel'
|
||||||
export * from './apiClient'
|
export * from './apiClient'
|
||||||
export * from './queueCreator'
|
export * from './queueCreator'
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
import { Logger } from '@freearhey/core'
|
import { Logger } from '@freearhey/core'
|
||||||
import { Queue, Grabber, GuideManager } from '.'
|
import { Queue, Grabber, GuideManager } from '.'
|
||||||
import { GrabOptions } from '../commands/epg/grab'
|
import { GrabOptions } from '../commands/epg/grab'
|
||||||
|
|
||||||
type JobProps = {
|
type JobProps = {
|
||||||
options: GrabOptions
|
options: GrabOptions
|
||||||
logger: Logger
|
logger: Logger
|
||||||
queue: Queue
|
queue: Queue
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Job {
|
export class Job {
|
||||||
options: GrabOptions
|
options: GrabOptions
|
||||||
logger: Logger
|
logger: Logger
|
||||||
grabber: Grabber
|
grabber: Grabber
|
||||||
|
|
||||||
constructor({ queue, logger, options }: JobProps) {
|
constructor({ queue, logger, options }: JobProps) {
|
||||||
this.options = options
|
this.options = options
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
this.grabber = new Grabber({ logger, queue, options })
|
this.grabber = new Grabber({ logger, queue, options })
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
const { channels, programs } = await this.grabber.grab()
|
const { channels, programs } = await this.grabber.grab()
|
||||||
|
|
||||||
const manager = new GuideManager({
|
const manager = new GuideManager({
|
||||||
channels,
|
channels,
|
||||||
programs,
|
programs,
|
||||||
options: this.options,
|
options: this.options,
|
||||||
logger: this.logger
|
logger: this.logger
|
||||||
})
|
})
|
||||||
|
|
||||||
await manager.createGuides()
|
await manager.createGuides()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,45 @@
|
||||||
import { Dictionary } from '@freearhey/core'
|
import { Dictionary } from '@freearhey/core'
|
||||||
import { SiteConfig, Channel } from 'epg-grabber'
|
import { SiteConfig, Channel } from 'epg-grabber'
|
||||||
|
|
||||||
export type QueueItem = {
|
export type QueueItem = {
|
||||||
channel: Channel
|
channel: Channel
|
||||||
date: string
|
date: string
|
||||||
config: SiteConfig
|
config: SiteConfig
|
||||||
error: string | null
|
error: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Queue {
|
export class Queue {
|
||||||
_data: Dictionary
|
_data: Dictionary
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._data = new Dictionary()
|
this._data = new Dictionary()
|
||||||
}
|
}
|
||||||
|
|
||||||
missing(key: string): boolean {
|
missing(key: string): boolean {
|
||||||
return this._data.missing(key)
|
return this._data.missing(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
add(
|
add(
|
||||||
key: string,
|
key: string,
|
||||||
{ channel, config, date }: { channel: Channel; date: string | null; config: SiteConfig }
|
{ channel, config, date }: { channel: Channel; date: string | null; config: SiteConfig }
|
||||||
) {
|
) {
|
||||||
this._data.set(key, {
|
this._data.set(key, {
|
||||||
channel,
|
channel,
|
||||||
date,
|
date,
|
||||||
config,
|
config,
|
||||||
error: null
|
error: null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
size(): number {
|
size(): number {
|
||||||
return Object.values(this._data.data()).length
|
return Object.values(this._data.data()).length
|
||||||
}
|
}
|
||||||
|
|
||||||
items(): QueueItem[] {
|
items(): QueueItem[] {
|
||||||
return Object.values(this._data.data()) as QueueItem[]
|
return Object.values(this._data.data()) as QueueItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmpty(): boolean {
|
isEmpty(): boolean {
|
||||||
return this.size() === 0
|
return this.size() === 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +1,71 @@
|
||||||
import { Storage, Collection, DateTime, Logger } from '@freearhey/core'
|
import { Storage, Collection, DateTime, Logger } from '@freearhey/core'
|
||||||
import { ChannelsParser, ConfigLoader, ApiChannel, Queue } from './'
|
import { ChannelsParser, ConfigLoader, ApiChannel, Queue } from './'
|
||||||
import { SITES_DIR, DATA_DIR, CURR_DATE } from '../constants'
|
import { SITES_DIR, DATA_DIR, CURR_DATE } from '../constants'
|
||||||
import { SiteConfig } from 'epg-grabber'
|
import { SiteConfig } from 'epg-grabber'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { GrabOptions } from '../commands/epg/grab'
|
import { GrabOptions } from '../commands/epg/grab'
|
||||||
|
|
||||||
type QueueCreatorProps = {
|
type QueueCreatorProps = {
|
||||||
logger: Logger
|
logger: Logger
|
||||||
options: GrabOptions
|
options: GrabOptions
|
||||||
parsedChannels: Collection
|
parsedChannels: Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueueCreator {
|
export class QueueCreator {
|
||||||
configLoader: ConfigLoader
|
configLoader: ConfigLoader
|
||||||
logger: Logger
|
logger: Logger
|
||||||
sitesStorage: Storage
|
sitesStorage: Storage
|
||||||
dataStorage: Storage
|
dataStorage: Storage
|
||||||
parser: ChannelsParser
|
parser: ChannelsParser
|
||||||
parsedChannels: Collection
|
parsedChannels: Collection
|
||||||
options: GrabOptions
|
options: GrabOptions
|
||||||
date: DateTime
|
date: DateTime
|
||||||
|
|
||||||
constructor({ parsedChannels, logger, options }: QueueCreatorProps) {
|
constructor({ parsedChannels, logger, options }: QueueCreatorProps) {
|
||||||
this.parsedChannels = parsedChannels
|
this.parsedChannels = parsedChannels
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
this.sitesStorage = new Storage()
|
this.sitesStorage = new Storage()
|
||||||
this.dataStorage = new Storage(DATA_DIR)
|
this.dataStorage = new Storage(DATA_DIR)
|
||||||
this.parser = new ChannelsParser({ storage: new Storage() })
|
this.parser = new ChannelsParser({ storage: new Storage() })
|
||||||
this.date = new DateTime(CURR_DATE)
|
this.date = new DateTime(CURR_DATE)
|
||||||
this.options = options
|
this.options = options
|
||||||
this.configLoader = new ConfigLoader()
|
this.configLoader = new ConfigLoader()
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(): Promise<Queue> {
|
async create(): Promise<Queue> {
|
||||||
const channelsContent = await this.dataStorage.json('channels.json')
|
const channelsContent = await this.dataStorage.json('channels.json')
|
||||||
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
||||||
|
|
||||||
const queue = new Queue()
|
const queue = new Queue()
|
||||||
for (const channel of this.parsedChannels.all()) {
|
for (const channel of this.parsedChannels.all()) {
|
||||||
if (!channel.site || !channel.xmltv_id) continue
|
if (!channel.site || !channel.xmltv_id) continue
|
||||||
if (this.options.lang && channel.lang !== this.options.lang) continue
|
if (this.options.lang && channel.lang !== this.options.lang) continue
|
||||||
|
|
||||||
const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`)
|
const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`)
|
||||||
const config: SiteConfig = await this.configLoader.load(configPath)
|
const config: SiteConfig = await this.configLoader.load(configPath)
|
||||||
|
|
||||||
const found: ApiChannel = channels.first(
|
const found: ApiChannel = channels.first(
|
||||||
(_channel: ApiChannel) => _channel.id === channel.xmltv_id
|
(_channel: ApiChannel) => _channel.id === channel.xmltv_id
|
||||||
)
|
)
|
||||||
if (found) {
|
if (found) {
|
||||||
channel.logo = found.logo
|
channel.logo = found.logo
|
||||||
}
|
}
|
||||||
|
|
||||||
const days = this.options.days || config.days || 1
|
const days = this.options.days || config.days || 1
|
||||||
const dates = Array.from({ length: days }, (_, day) => this.date.add(day, 'd'))
|
const dates = Array.from({ length: days }, (_, day) => this.date.add(day, 'd'))
|
||||||
dates.forEach((date: DateTime) => {
|
dates.forEach((date: DateTime) => {
|
||||||
const dateString = date.toJSON()
|
const dateString = date.toJSON()
|
||||||
const key = `${channel.site}:${channel.lang}:${channel.xmltv_id}:${dateString}`
|
const key = `${channel.site}:${channel.lang}:${channel.xmltv_id}:${dateString}`
|
||||||
if (queue.missing(key)) {
|
if (queue.missing(key)) {
|
||||||
queue.add(key, {
|
queue.add(key, {
|
||||||
channel,
|
channel,
|
||||||
date: dateString,
|
date: dateString,
|
||||||
config
|
config
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return queue
|
return queue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +1,56 @@
|
||||||
import { Collection } from '@freearhey/core'
|
import { Collection } from '@freearhey/core'
|
||||||
import { Channel } from 'epg-grabber'
|
import { Channel } from 'epg-grabber'
|
||||||
|
|
||||||
export class XML {
|
export class XML {
|
||||||
items: Collection
|
items: Collection
|
||||||
|
|
||||||
constructor(items: Collection) {
|
constructor(items: Collection) {
|
||||||
this.items = items
|
this.items = items
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
let output = '<?xml version="1.0" encoding="UTF-8"?>\r\n<channels>\r\n'
|
let output = '<?xml version="1.0" encoding="UTF-8"?>\r\n<channels>\r\n'
|
||||||
|
|
||||||
this.items.forEach((channel: Channel) => {
|
this.items.forEach((channel: Channel) => {
|
||||||
const logo = channel.logo ? ` logo="${channel.logo}"` : ''
|
const logo = channel.logo ? ` logo="${channel.logo}"` : ''
|
||||||
const xmltv_id = channel.xmltv_id || ''
|
const xmltv_id = channel.xmltv_id || ''
|
||||||
const lang = channel.lang || ''
|
const lang = channel.lang || ''
|
||||||
const site_id = channel.site_id || ''
|
const site_id = channel.site_id || ''
|
||||||
output += ` <channel site="${channel.site}" lang="${lang}" xmltv_id="${escapeString(
|
output += ` <channel site="${channel.site}" lang="${lang}" xmltv_id="${escapeString(
|
||||||
xmltv_id
|
xmltv_id
|
||||||
)}" site_id="${site_id}"${logo}>${escapeString(channel.name)}</channel>\r\n`
|
)}" site_id="${site_id}"${logo}>${escapeString(channel.name)}</channel>\r\n`
|
||||||
})
|
})
|
||||||
|
|
||||||
output += '</channels>\r\n'
|
output += '</channels>\r\n'
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeString(value: string, defaultValue: string = '') {
|
function escapeString(value: string, defaultValue: string = '') {
|
||||||
if (!value) return defaultValue
|
if (!value) return defaultValue
|
||||||
|
|
||||||
const regex = new RegExp(
|
const regex = new RegExp(
|
||||||
'((?:[\0-\x08\x0B\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))|([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|(?:\\uD83F[\\uDFFE\\uDFFF])|(?:\\uD87F[\\uDF' +
|
'((?:[\0-\x08\x0B\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))|([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|(?:\\uD83F[\\uDFFE\\uDFFF])|(?:\\uD87F[\\uDF' +
|
||||||
'FE\\uDFFF])|(?:\\uD8BF[\\uDFFE\\uDFFF])|(?:\\uD8FF[\\uDFFE\\uDFFF])|(?:\\uD93F[\\uDFFE\\uD' +
|
'FE\\uDFFF])|(?:\\uD8BF[\\uDFFE\\uDFFF])|(?:\\uD8FF[\\uDFFE\\uDFFF])|(?:\\uD93F[\\uDFFE\\uD' +
|
||||||
'FFF])|(?:\\uD97F[\\uDFFE\\uDFFF])|(?:\\uD9BF[\\uDFFE\\uDFFF])|(?:\\uD9FF[\\uDFFE\\uDFFF])' +
|
'FFF])|(?:\\uD97F[\\uDFFE\\uDFFF])|(?:\\uD9BF[\\uDFFE\\uDFFF])|(?:\\uD9FF[\\uDFFE\\uDFFF])' +
|
||||||
'|(?:\\uDA3F[\\uDFFE\\uDFFF])|(?:\\uDA7F[\\uDFFE\\uDFFF])|(?:\\uDABF[\\uDFFE\\uDFFF])|(?:\\' +
|
'|(?:\\uDA3F[\\uDFFE\\uDFFF])|(?:\\uDA7F[\\uDFFE\\uDFFF])|(?:\\uDABF[\\uDFFE\\uDFFF])|(?:\\' +
|
||||||
'uDAFF[\\uDFFE\\uDFFF])|(?:\\uDB3F[\\uDFFE\\uDFFF])|(?:\\uDB7F[\\uDFFE\\uDFFF])|(?:\\uDBBF' +
|
'uDAFF[\\uDFFE\\uDFFF])|(?:\\uDB3F[\\uDFFE\\uDFFF])|(?:\\uDB7F[\\uDFFE\\uDFFF])|(?:\\uDBBF' +
|
||||||
'[\\uDFFE\\uDFFF])|(?:\\uDBFF[\\uDFFE\\uDFFF])(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\' +
|
'[\\uDFFE\\uDFFF])|(?:\\uDBFF[\\uDFFE\\uDFFF])(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\' +
|
||||||
'uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|' +
|
'uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|' +
|
||||||
'(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))',
|
'(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))',
|
||||||
'g'
|
'g'
|
||||||
)
|
)
|
||||||
|
|
||||||
value = String(value || '').replace(regex, '')
|
value = String(value || '').replace(regex, '')
|
||||||
|
|
||||||
return value
|
return value
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
.replace(/'/g, ''')
|
.replace(/'/g, ''')
|
||||||
.replace(/\n|\r/g, ' ')
|
.replace(/\n|\r/g, ' ')
|
||||||
.replace(/ +/g, ' ')
|
.replace(/ +/g, ' ')
|
||||||
.trim()
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
import { DateTime, Collection } from '@freearhey/core'
|
import { DateTime, Collection } from '@freearhey/core'
|
||||||
import { generateXMLTV } from 'epg-grabber'
|
import { generateXMLTV } from 'epg-grabber'
|
||||||
|
|
||||||
type XMLTVProps = {
|
type XMLTVProps = {
|
||||||
channels: Collection
|
channels: Collection
|
||||||
programs: Collection
|
programs: Collection
|
||||||
date: DateTime
|
date: DateTime
|
||||||
}
|
}
|
||||||
|
|
||||||
export class XMLTV {
|
export class XMLTV {
|
||||||
channels: Collection
|
channels: Collection
|
||||||
programs: Collection
|
programs: Collection
|
||||||
date: DateTime
|
date: DateTime
|
||||||
|
|
||||||
constructor({ channels, programs, date }: XMLTVProps) {
|
constructor({ channels, programs, date }: XMLTVProps) {
|
||||||
this.channels = channels
|
this.channels = channels
|
||||||
this.programs = programs
|
this.programs = programs
|
||||||
this.date = date
|
this.date = date
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return generateXMLTV({
|
return generateXMLTV({
|
||||||
channels: this.channels.all(),
|
channels: this.channels.all(),
|
||||||
programs: this.programs.all(),
|
programs: this.programs.all(),
|
||||||
date: this.date.toJSON()
|
date: this.date.toJSON()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
scripts/types/langs.d.ts
vendored
2
scripts/types/langs.d.ts
vendored
|
@ -1 +1 @@
|
||||||
declare module 'langs'
|
declare module 'langs'
|
||||||
|
|
|
@ -1,69 +1,69 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: '9tv.co.il',
|
site: '9tv.co.il',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ date }) {
|
url: function ({ date }) {
|
||||||
return `https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=${date.format(
|
return `https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=${date.format(
|
||||||
'DD/MM/YYYY 00:00:00'
|
'DD/MM/YYYY 00:00:00'
|
||||||
)}`
|
)}`
|
||||||
},
|
},
|
||||||
parser: function ({ content, date }) {
|
parser: function ({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
const start = parseStart($item, date)
|
const start = parseStart($item, date)
|
||||||
if (prev) prev.stop = start
|
if (prev) prev.stop = start
|
||||||
const stop = start.add(1, 'h')
|
const stop = start.add(1, 'h')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle($item),
|
title: parseTitle($item),
|
||||||
icon: parseIcon($item),
|
icon: parseIcon($item),
|
||||||
description: parseDescription($item),
|
description: parseDescription($item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
let time = $item('a > div.guide_list_time').text().trim()
|
let time = $item('a > div.guide_list_time').text().trim()
|
||||||
|
|
||||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Jerusalem')
|
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Jerusalem')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon($item) {
|
function parseIcon($item) {
|
||||||
const backgroundImage = $item('a > div.guide_info_group > div.guide_info_pict').css(
|
const backgroundImage = $item('a > div.guide_info_group > div.guide_info_pict').css(
|
||||||
'background-image'
|
'background-image'
|
||||||
)
|
)
|
||||||
if (!backgroundImage) return null
|
if (!backgroundImage) return null
|
||||||
const [, relativePath] = backgroundImage.match(/url\((.*)\)/) || [null, null]
|
const [, relativePath] = backgroundImage.match(/url\((.*)\)/) || [null, null]
|
||||||
|
|
||||||
return relativePath ? `https://www.9tv.co.il${relativePath}` : null
|
return relativePath ? `https://www.9tv.co.il${relativePath}` : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription($item) {
|
function parseDescription($item) {
|
||||||
return $item('a > div.guide_info_group > div.guide_txt_group > div').text().trim()
|
return $item('a > div.guide_info_group > div.guide_txt_group > div').text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('a > div.guide_info_group > div.guide_txt_group > h3').text().trim()
|
return $item('a > div.guide_info_group > div.guide_txt_group > h3').text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
return $('li').toArray()
|
return $('li').toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +1,57 @@
|
||||||
// npm run grab -- --site=9tv.co.il
|
// npm run grab -- --site=9tv.co.il
|
||||||
|
|
||||||
const { parser, url } = require('./9tv.co.il.config.js')
|
const { parser, url } = require('./9tv.co.il.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '#',
|
site_id: '#',
|
||||||
xmltv_id: 'Channel9.il'
|
xmltv_id: 'Channel9.il'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ date })).toBe(
|
expect(url({ date })).toBe(
|
||||||
'https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=06/03/2022 00:00:00'
|
'https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=06/03/2022 00:00:00'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content =
|
const content =
|
||||||
'<li> <a href="#" class="guide_list_link w-inline-block"> <div class="guide_list_time">06:30</div><div class="guide_info_group"> <div class="guide_info_pict" style="background-image: url(/download/pictures/img_id=8484.jpg);"></div><div class="guide_txt_group"> <h3 class="guide_info_title">Слепая</h3> <div>Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы. </div></div></div></a></li><li> <a href="#" class="guide_list_link even w-inline-block"> <div class="guide_list_time">09:10</div><div class="guide_info_group"> <div class="guide_info_pict" style="background-image: url(/download/pictures/img_id=23694.jpg);"></div><div class="guide_txt_group"> <h3 class="guide_info_title">Орел и решка. Морской сезон</h3> <div>Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.</div></div></div></a></li>'
|
'<li> <a href="#" class="guide_list_link w-inline-block"> <div class="guide_list_time">06:30</div><div class="guide_info_group"> <div class="guide_info_pict" style="background-image: url(/download/pictures/img_id=8484.jpg);"></div><div class="guide_txt_group"> <h3 class="guide_info_title">Слепая</h3> <div>Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы. </div></div></div></a></li><li> <a href="#" class="guide_list_link even w-inline-block"> <div class="guide_list_time">09:10</div><div class="guide_info_group"> <div class="guide_info_pict" style="background-image: url(/download/pictures/img_id=23694.jpg);"></div><div class="guide_txt_group"> <h3 class="guide_info_title">Орел и решка. Морской сезон</h3> <div>Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.</div></div></div></a></li>'
|
||||||
const result = parser({ content, date }).map(p => {
|
const result = parser({ content, date }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2022-03-06T04:30:00.000Z',
|
start: '2022-03-06T04:30:00.000Z',
|
||||||
stop: '2022-03-06T07:10:00.000Z',
|
stop: '2022-03-06T07:10:00.000Z',
|
||||||
title: 'Слепая',
|
title: 'Слепая',
|
||||||
icon: 'https://www.9tv.co.il/download/pictures/img_id=8484.jpg',
|
icon: 'https://www.9tv.co.il/download/pictures/img_id=8484.jpg',
|
||||||
description:
|
description:
|
||||||
'Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы.'
|
'Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: '2022-03-06T07:10:00.000Z',
|
start: '2022-03-06T07:10:00.000Z',
|
||||||
stop: '2022-03-06T08:10:00.000Z',
|
stop: '2022-03-06T08:10:00.000Z',
|
||||||
icon: 'https://www.9tv.co.il/download/pictures/img_id=23694.jpg',
|
icon: 'https://www.9tv.co.il/download/pictures/img_id=23694.jpg',
|
||||||
title: 'Орел и решка. Морской сезон',
|
title: 'Орел и решка. Морской сезон',
|
||||||
description: 'Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.'
|
description: 'Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,77 +1,77 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'abc.net.au',
|
site: 'abc.net.au',
|
||||||
days: 3,
|
days: 3,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 60 * 60 * 1000 // 1 hour
|
ttl: 60 * 60 * 1000 // 1 hour
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url({ date }) {
|
url({ date }) {
|
||||||
return `https://epg.abctv.net.au/processed/Sydney_${date.format('YYYY-MM-DD')}.json`
|
return `https://epg.abctv.net.au/processed/Sydney_${date.format('YYYY-MM-DD')}.json`
|
||||||
},
|
},
|
||||||
parser({ content, channel }) {
|
parser({ content, channel }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
sub_title: item.episode_title,
|
sub_title: item.episode_title,
|
||||||
category: item.genres,
|
category: item.genres,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
season: parseSeason(item),
|
season: parseSeason(item),
|
||||||
episode: parseEpisode(item),
|
episode: parseEpisode(item),
|
||||||
rating: parseRating(item),
|
rating: parseRating(item),
|
||||||
icon: parseIcon(item),
|
icon: parseIcon(item),
|
||||||
start: parseTime(item.start_time),
|
start: parseTime(item.start_time),
|
||||||
stop: parseTime(item.end_time)
|
stop: parseTime(item.end_time)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data) return []
|
if (!data) return []
|
||||||
if (!Array.isArray(data.schedule)) return []
|
if (!Array.isArray(data.schedule)) return []
|
||||||
|
|
||||||
const channelData = data.schedule.find(i => i.channel == channel.site_id)
|
const channelData = data.schedule.find(i => i.channel == channel.site_id)
|
||||||
return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : []
|
return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : []
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSeason(item) {
|
function parseSeason(item) {
|
||||||
return item.series_num || null
|
return item.series_num || null
|
||||||
}
|
}
|
||||||
function parseEpisode(item) {
|
function parseEpisode(item) {
|
||||||
return item.episode_num || null
|
return item.episode_num || null
|
||||||
}
|
}
|
||||||
function parseTime(time) {
|
function parseTime(time) {
|
||||||
return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'Australia/Sydney')
|
return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'Australia/Sydney')
|
||||||
}
|
}
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
return item.image_file
|
return item.image_file
|
||||||
? `https://www.abc.net.au/tv/common/images/publicity/${item.image_file}`
|
? `https://www.abc.net.au/tv/common/images/publicity/${item.image_file}`
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
function parseRating(item) {
|
function parseRating(item) {
|
||||||
return item.rating
|
return item.rating
|
||||||
? {
|
? {
|
||||||
system: 'ACB',
|
system: 'ACB',
|
||||||
value: item.rating
|
value: item.rating
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +1,56 @@
|
||||||
// npm run grab -- --site=abc.net.au
|
// npm run grab -- --site=abc.net.au
|
||||||
|
|
||||||
const { parser, url } = require('./abc.net.au.config.js')
|
const { parser, url } = require('./abc.net.au.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2022-12-22', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-12-22', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'ABC1',
|
site_id: 'ABC1',
|
||||||
xmltv_id: 'ABCTV.au'
|
xmltv_id: 'ABCTV.au'
|
||||||
}
|
}
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ date })).toBe('https://epg.abctv.net.au/processed/Sydney_2022-12-22.json')
|
expect(url({ date })).toBe('https://epg.abctv.net.au/processed/Sydney_2022-12-22.json')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content =
|
const content =
|
||||||
'{"date":"2022-12-22","region":"Sydney","schedule":[{"channel":"ABC1","listing":[{"consumer_advice":"Adult Themes, Drug Use, Violence","rating":"M","show_id":912747,"repeat":true,"description":"When tragedy strikes close to home, it puts head teacher Noah Taylor on a collision course with the criminals responsible. Can the Lyell team help him stop the cycle of violence?","title":"Silent Witness","crid":"ZW2178A004S00","start_time":"2022-12-22T00:46:00","series-crid":"ZW2178A","live":false,"captioning":true,"show_type":"Episode","series_num":22,"episode_title":"Lift Up Your Hearts (part Two)","length":58,"onair_title":"Silent Witness","end_time":"2022-12-22T01:44:00","genres":["Entertainment"],"image_file":"ZW2178A004S00_460.jpg","prog_slug":"silent-witness","episode_num":4}]}]}'
|
'{"date":"2022-12-22","region":"Sydney","schedule":[{"channel":"ABC1","listing":[{"consumer_advice":"Adult Themes, Drug Use, Violence","rating":"M","show_id":912747,"repeat":true,"description":"When tragedy strikes close to home, it puts head teacher Noah Taylor on a collision course with the criminals responsible. Can the Lyell team help him stop the cycle of violence?","title":"Silent Witness","crid":"ZW2178A004S00","start_time":"2022-12-22T00:46:00","series-crid":"ZW2178A","live":false,"captioning":true,"show_type":"Episode","series_num":22,"episode_title":"Lift Up Your Hearts (part Two)","length":58,"onair_title":"Silent Witness","end_time":"2022-12-22T01:44:00","genres":["Entertainment"],"image_file":"ZW2178A004S00_460.jpg","prog_slug":"silent-witness","episode_num":4}]}]}'
|
||||||
|
|
||||||
const result = parser({ content, channel }).map(p => {
|
const result = parser({ content, channel }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
title: 'Silent Witness',
|
title: 'Silent Witness',
|
||||||
sub_title: 'Lift Up Your Hearts (part Two)',
|
sub_title: 'Lift Up Your Hearts (part Two)',
|
||||||
description:
|
description:
|
||||||
'When tragedy strikes close to home, it puts head teacher Noah Taylor on a collision course with the criminals responsible. Can the Lyell team help him stop the cycle of violence?',
|
'When tragedy strikes close to home, it puts head teacher Noah Taylor on a collision course with the criminals responsible. Can the Lyell team help him stop the cycle of violence?',
|
||||||
category: ['Entertainment'],
|
category: ['Entertainment'],
|
||||||
rating: {
|
rating: {
|
||||||
system: 'ACB',
|
system: 'ACB',
|
||||||
value: 'M'
|
value: 'M'
|
||||||
},
|
},
|
||||||
season: 22,
|
season: 22,
|
||||||
episode: 4,
|
episode: 4,
|
||||||
icon: 'https://www.abc.net.au/tv/common/images/publicity/ZW2178A004S00_460.jpg',
|
icon: 'https://www.abc.net.au/tv/common/images/publicity/ZW2178A004S00_460.jpg',
|
||||||
start: '2022-12-21T13:46:00.000Z',
|
start: '2022-12-21T13:46:00.000Z',
|
||||||
stop: '2022-12-21T14:44:00.000Z'
|
stop: '2022-12-21T14:44:00.000Z'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser(
|
const result = parser(
|
||||||
{
|
{
|
||||||
content:
|
content:
|
||||||
'<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Key>processed/Sydney_2023-01-17.json</Key><RequestId>6MRHX5TJ12X39B3Y</RequestId><HostId>59rH6XRMrmkFywg8Kv58iqpI6O1fuOCuEbKa1HRRYa4buByXMBTvAhz8zuAK7X5D+ZN9ZuWxyGs=</HostId></Error>'
|
'<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Key>processed/Sydney_2023-01-17.json</Key><RequestId>6MRHX5TJ12X39B3Y</RequestId><HostId>59rH6XRMrmkFywg8Kv58iqpI6O1fuOCuEbKa1HRRYa4buByXMBTvAhz8zuAK7X5D+ZN9ZuWxyGs=</HostId></Error>'
|
||||||
},
|
},
|
||||||
channel
|
channel
|
||||||
)
|
)
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,63 +1,63 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'allente.se',
|
site: 'allente.se',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ date, channel }) {
|
url({ date, channel }) {
|
||||||
const [country] = channel.site_id.split('#')
|
const [country] = channel.site_id.split('#')
|
||||||
|
|
||||||
return `https://cs-vcb.allente.${country}/epg/events?date=${date.format('YYYY-MM-DD')}`
|
return `https://cs-vcb.allente.${country}/epg/events?date=${date.format('YYYY-MM-DD')}`
|
||||||
},
|
},
|
||||||
parser({ content, channel }) {
|
parser({ content, channel }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
if (!item.details) return
|
if (!item.details) return
|
||||||
const start = dayjs(item.time)
|
const start = dayjs(item.time)
|
||||||
const stop = start.add(item.details.duration, 'm')
|
const stop = start.add(item.details.duration, 'm')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
category: item.details.categories,
|
category: item.details.categories,
|
||||||
description: item.details.description,
|
description: item.details.description,
|
||||||
icon: item.details.image,
|
icon: item.details.image,
|
||||||
season: parseSeason(item),
|
season: parseSeason(item),
|
||||||
episode: parseEpisode(item),
|
episode: parseEpisode(item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ country, lang }) {
|
async channels({ country, lang }) {
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(`https://cs-vcb.allente.${country}/epg/events?date=2021-11-17`)
|
.get(`https://cs-vcb.allente.${country}/epg/events?date=2021-11-17`)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
return data.channels.map(item => {
|
return data.channels.map(item => {
|
||||||
return {
|
return {
|
||||||
lang,
|
lang,
|
||||||
site_id: `${country}#${item.id}`,
|
site_id: `${country}#${item.id}`,
|
||||||
name: item.name
|
name: item.name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
const [, channelId] = channel.site_id.split('#')
|
const [, channelId] = channel.site_id.split('#')
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !Array.isArray(data.channels)) return []
|
if (!data || !Array.isArray(data.channels)) return []
|
||||||
const channelData = data.channels.find(i => i.id === channelId)
|
const channelData = data.channels.find(i => i.id === channelId)
|
||||||
|
|
||||||
return channelData && Array.isArray(channelData.events) ? channelData.events : []
|
return channelData && Array.isArray(channelData.events) ? channelData.events : []
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSeason(item) {
|
function parseSeason(item) {
|
||||||
return item.details.season || null
|
return item.details.season || null
|
||||||
}
|
}
|
||||||
function parseEpisode(item) {
|
function parseEpisode(item) {
|
||||||
return item.details.episode || null
|
return item.details.episode || null
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +1,62 @@
|
||||||
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_se.channels.xml --set=country:se --set=lang:sv
|
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_se.channels.xml --set=country:se --set=lang:sv
|
||||||
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_fi.channels.xml --set=country:fi --set=lang:fi
|
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_fi.channels.xml --set=country:fi --set=lang:fi
|
||||||
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_no.channels.xml --set=country:no --set=lang:no
|
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_no.channels.xml --set=country:no --set=lang:no
|
||||||
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_dk.channels.xml --set=country:dk --set=lang:da
|
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_dk.channels.xml --set=country:dk --set=lang:da
|
||||||
// npm run grab -- --site=allente.se
|
// npm run grab -- --site=allente.se
|
||||||
|
|
||||||
const { parser, url } = require('./allente.se.config.js')
|
const { parser, url } = require('./allente.se.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'se#0148',
|
site_id: 'se#0148',
|
||||||
xmltv_id: 'SVT1.se'
|
xmltv_id: 'SVT1.se'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ date, channel })).toBe('https://cs-vcb.allente.se/epg/events?date=2021-11-17')
|
expect(url({ date, channel })).toBe('https://cs-vcb.allente.se/epg/events?date=2021-11-17')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid url for different country', () => {
|
it('can generate valid url for different country', () => {
|
||||||
const dkChannel = { site_id: 'dk#0148' }
|
const dkChannel = { site_id: 'dk#0148' }
|
||||||
expect(url({ date, channel: dkChannel })).toBe(
|
expect(url({ date, channel: dkChannel })).toBe(
|
||||||
'https://cs-vcb.allente.dk/epg/events?date=2021-11-17'
|
'https://cs-vcb.allente.dk/epg/events?date=2021-11-17'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content =
|
const content =
|
||||||
'{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]}'
|
'{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]}'
|
||||||
const result = parser({ content, channel }).map(p => {
|
const result = parser({ content, channel }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2022-08-22T07:10:00.000Z',
|
start: '2022-08-22T07:10:00.000Z',
|
||||||
stop: '2022-08-22T07:30:00.000Z',
|
stop: '2022-08-22T07:30:00.000Z',
|
||||||
title: 'Hemmagympa med Sofia',
|
title: 'Hemmagympa med Sofia',
|
||||||
category: ['other'],
|
category: ['other'],
|
||||||
description:
|
description:
|
||||||
'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.',
|
'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.',
|
||||||
icon: 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440',
|
icon: 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440',
|
||||||
season: 4,
|
season: 4,
|
||||||
episode: 1
|
episode: 1
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content: '{"date":"2001-11-17","categories":[],"channels":[]}'
|
content: '{"date":"2001-11-17","categories":[],"channels":[]}'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,59 +1,59 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const { DateTime } = require('luxon')
|
const { DateTime } = require('luxon')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'andorradifusio.ad',
|
site: 'andorradifusio.ad',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel }) {
|
url({ channel }) {
|
||||||
return `https://www.andorradifusio.ad/programacio/${channel.site_id}`
|
return `https://www.andorradifusio.ad/programacio/${channel.site_id}`
|
||||||
},
|
},
|
||||||
parser({ content, date }) {
|
parser({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content, date)
|
const items = parseItems(content, date)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
let start = parseStart(item, date)
|
let start = parseStart(item, date)
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (start < prev.start) {
|
if (start < prev.start) {
|
||||||
start = start.plus({ days: 1 })
|
start = start.plus({ days: 1 })
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
prev.stop = start
|
prev.stop = start
|
||||||
}
|
}
|
||||||
const stop = start.plus({ hours: 1 })
|
const stop = start.plus({ hours: 1 })
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item, date) {
|
function parseStart(item, date) {
|
||||||
const dateString = `${date.format('MM/DD/YYYY')} ${item.time}`
|
const dateString = `${date.format('MM/DD/YYYY')} ${item.time}`
|
||||||
|
|
||||||
return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH:mm', { zone: 'Europe/Madrid' }).toUTC()
|
return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH:mm', { zone: 'Europe/Madrid' }).toUTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, date) {
|
function parseItems(content, date) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
const day = DateTime.fromMillis(date.valueOf()).setLocale('ca').toFormat('dd LLLL').toLowerCase()
|
const day = DateTime.fromMillis(date.valueOf()).setLocale('ca').toFormat('dd LLLL').toLowerCase()
|
||||||
const column = $('.programacio-dia > h3 > .dia')
|
const column = $('.programacio-dia > h3 > .dia')
|
||||||
.filter((i, el) => $(el).text() === day.slice(0, 6) + '.')
|
.filter((i, el) => $(el).text() === day.slice(0, 6) + '.')
|
||||||
.first()
|
.first()
|
||||||
.parent()
|
.parent()
|
||||||
.parent()
|
.parent()
|
||||||
const items = []
|
const items = []
|
||||||
const titles = column.find('p').toArray()
|
const titles = column.find('p').toArray()
|
||||||
column.find('h4').each((i, time) => {
|
column.find('h4').each((i, time) => {
|
||||||
items.push({
|
items.push({
|
||||||
time: $(time).text(),
|
time: $(time).text(),
|
||||||
title: $(titles[i]).text()
|
title: $(titles[i]).text()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
// npm run grab -- --site=andorradifusio.ad
|
// npm run grab -- --site=andorradifusio.ad
|
||||||
|
|
||||||
const { parser, url } = require('./andorradifusio.ad.config.js')
|
const { parser, url } = require('./andorradifusio.ad.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-06-07', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-06-07', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'atv',
|
site_id: 'atv',
|
||||||
xmltv_id: 'AndorraTV.ad'
|
xmltv_id: 'AndorraTV.ad'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel })).toBe('https://www.andorradifusio.ad/programacio/atv')
|
expect(url({ channel })).toBe('https://www.andorradifusio.ad/programacio/atv')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||||
const results = parser({ content, date }).map(p => {
|
const results = parser({ content, date }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-06-07T05:00:00.000Z',
|
start: '2023-06-07T05:00:00.000Z',
|
||||||
stop: '2023-06-07T06:00:00.000Z',
|
stop: '2023-06-07T06:00:00.000Z',
|
||||||
title: 'Club Piolet'
|
title: 'Club Piolet'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[20]).toMatchObject({
|
expect(results[20]).toMatchObject({
|
||||||
start: '2023-06-07T23:00:00.000Z',
|
start: '2023-06-07T23:00:00.000Z',
|
||||||
stop: '2023-06-08T00:00:00.000Z',
|
stop: '2023-06-08T00:00:00.000Z',
|
||||||
title: 'Àrea Andorra Difusió'
|
title: 'Àrea Andorra Difusió'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,82 +1,82 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'arianaafgtv.com',
|
site: 'arianaafgtv.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: 'https://www.arianaafgtv.com/index.html',
|
url: 'https://www.arianaafgtv.com/index.html',
|
||||||
parser({ content, date }) {
|
parser({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content, date)
|
const items = parseItems(content, date)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const title = item.title
|
const title = item.title
|
||||||
const start = parseStart(item, date)
|
const start = parseStart(item, date)
|
||||||
const stop = parseStop(item, date)
|
const stop = parseStop(item, date)
|
||||||
programs.push({
|
programs.push({
|
||||||
title,
|
title,
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item, date) {
|
function parseStop(item, date) {
|
||||||
const time = `${date.format('MM/DD/YYYY')} ${item.end.toUpperCase()}`
|
const time = `${date.format('MM/DD/YYYY')} ${item.end.toUpperCase()}`
|
||||||
|
|
||||||
return dayjs.tz(time, 'MM/DD/YYYY hh:mm A', 'Asia/Kabul')
|
return dayjs.tz(time, 'MM/DD/YYYY hh:mm A', 'Asia/Kabul')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item, date) {
|
function parseStart(item, date) {
|
||||||
const time = `${date.format('MM/DD/YYYY')} ${item.start.toUpperCase()}`
|
const time = `${date.format('MM/DD/YYYY')} ${item.start.toUpperCase()}`
|
||||||
|
|
||||||
return dayjs.tz(time, 'MM/DD/YYYY hh:mm A', 'Asia/Kabul')
|
return dayjs.tz(time, 'MM/DD/YYYY hh:mm A', 'Asia/Kabul')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, date) {
|
function parseItems(content, date) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
const dayOfWeek = date.format('dddd')
|
const dayOfWeek = date.format('dddd')
|
||||||
const column = $('.H4')
|
const column = $('.H4')
|
||||||
.filter((i, el) => {
|
.filter((i, el) => {
|
||||||
return $(el).text() === dayOfWeek
|
return $(el).text() === dayOfWeek
|
||||||
})
|
})
|
||||||
.first()
|
.first()
|
||||||
.parent()
|
.parent()
|
||||||
|
|
||||||
const rows = column
|
const rows = column
|
||||||
.find('.Paragraph')
|
.find('.Paragraph')
|
||||||
.map((i, el) => {
|
.map((i, el) => {
|
||||||
return $(el).html()
|
return $(el).html()
|
||||||
})
|
})
|
||||||
.toArray()
|
.toArray()
|
||||||
.map(r => (r === ' ' ? '|' : r))
|
.map(r => (r === ' ' ? '|' : r))
|
||||||
.join(' ')
|
.join(' ')
|
||||||
.split('|')
|
.split('|')
|
||||||
|
|
||||||
const items = []
|
const items = []
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
row = row.trim()
|
row = row.trim()
|
||||||
if (row) {
|
if (row) {
|
||||||
const found = row.match(/(\d+(|:\d+)(a|p)m-\d+(|:\d+)(a|p)m)/gi)
|
const found = row.match(/(\d+(|:\d+)(a|p)m-\d+(|:\d+)(a|p)m)/gi)
|
||||||
if (!found) return
|
if (!found) return
|
||||||
const time = found[0]
|
const time = found[0]
|
||||||
let start = time.match(/(\d+(|:\d+)(a|p)m)-/i)[1]
|
let start = time.match(/(\d+(|:\d+)(a|p)m)-/i)[1]
|
||||||
start = dayjs(start.toUpperCase(), ['hh:mmA', 'h:mmA', 'hA']).format('hh:mm A')
|
start = dayjs(start.toUpperCase(), ['hh:mmA', 'h:mmA', 'hA']).format('hh:mm A')
|
||||||
let end = time.match(/-(\d+(|:\d+)(a|p)m)/i)[1]
|
let end = time.match(/-(\d+(|:\d+)(a|p)m)/i)[1]
|
||||||
end = dayjs(end.toUpperCase(), ['hh:mmA', 'h:mmA', 'hA']).format('hh:mm A')
|
end = dayjs(end.toUpperCase(), ['hh:mmA', 'h:mmA', 'hA']).format('hh:mm A')
|
||||||
const title = row.replace(time, '').replace(' ', '').trim()
|
const title = row.replace(time, '').replace(' ', '').trim()
|
||||||
items.push({ start, end, title })
|
items.push({ start, end, title })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,60 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const { DateTime } = require('luxon')
|
const { DateTime } = require('luxon')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'arianatelevision.com',
|
site: 'arianatelevision.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: 'https://www.arianatelevision.com/program-schedule/',
|
url: 'https://www.arianatelevision.com/program-schedule/',
|
||||||
parser({ content, date }) {
|
parser({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content, date)
|
const items = parseItems(content, date)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
let start = parseStart(item, date)
|
let start = parseStart(item, date)
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (start < prev.start) {
|
if (start < prev.start) {
|
||||||
start = start.plus({ days: 1 })
|
start = start.plus({ days: 1 })
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
prev.stop = start
|
prev.stop = start
|
||||||
}
|
}
|
||||||
const stop = start.plus({ minutes: 30 })
|
const stop = start.plus({ minutes: 30 })
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item, date) {
|
function parseStart(item, date) {
|
||||||
const time = `${date.format('YYYY-MM-DD')} ${item.start}`
|
const time = `${date.format('YYYY-MM-DD')} ${item.start}`
|
||||||
|
|
||||||
return DateTime.fromFormat(time, 'yyyy-MM-dd H:mm', { zone: 'Asia/Kabul' }).toUTC()
|
return DateTime.fromFormat(time, 'yyyy-MM-dd H:mm', { zone: 'Asia/Kabul' }).toUTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, date) {
|
function parseItems(content, date) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
const settings = $('#jtrt_table_settings_508').text()
|
const settings = $('#jtrt_table_settings_508').text()
|
||||||
if (!settings) return []
|
if (!settings) return []
|
||||||
const data = JSON.parse(settings)
|
const data = JSON.parse(settings)
|
||||||
if (!data || !Array.isArray(data)) return []
|
if (!data || !Array.isArray(data)) return []
|
||||||
|
|
||||||
let rows = data[0]
|
let rows = data[0]
|
||||||
rows.shift()
|
rows.shift()
|
||||||
const output = []
|
const output = []
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
let day = date.day() + 2
|
let day = date.day() + 2
|
||||||
if (day > 7) day = 1
|
if (day > 7) day = 1
|
||||||
if (!row[0] || !row[day]) return
|
if (!row[0] || !row[day]) return
|
||||||
output.push({
|
output.push({
|
||||||
start: row[0].trim(),
|
start: row[0].trim(),
|
||||||
title: row[day].trim()
|
title: row[day].trim()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +1,61 @@
|
||||||
// npm run grab -- --site=arianatelevision.com
|
// npm run grab -- --site=arianatelevision.com
|
||||||
|
|
||||||
const { parser, url } = require('./arianatelevision.com.config.js')
|
const { parser, url } = require('./arianatelevision.com.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2021-11-27', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2021-11-27', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '#',
|
site_id: '#',
|
||||||
xmltv_id: 'ArianaTVNational.af'
|
xmltv_id: 'ArianaTVNational.af'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url).toBe('https://www.arianatelevision.com/program-schedule/')
|
expect(url).toBe('https://www.arianatelevision.com/program-schedule/')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content =
|
const content =
|
||||||
'<!DOCTYPE html><html><head></head><body><textarea data-jtrt-table-id="508" id="jtrt_table_settings_508" cols="30" rows="10">[[["Start","Saturday","Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","",""],["7:00","City Report","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","",""],["7:30","ICC T20 Highlights","Sport ","Sport ","Sport ","Sport ","Sport ","Sport ","",""],["15:00","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","",""],["6:30","Quran and Hadis ","Falah","Falah","Falah","Falah","Falah","Falah","",""],["","\\n","","","","","","","",""]]]</textarea></body></html>'
|
'<!DOCTYPE html><html><head></head><body><textarea data-jtrt-table-id="508" id="jtrt_table_settings_508" cols="30" rows="10">[[["Start","Saturday","Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","",""],["7:00","City Report","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","",""],["7:30","ICC T20 Highlights","Sport ","Sport ","Sport ","Sport ","Sport ","Sport ","",""],["15:00","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","",""],["6:30","Quran and Hadis ","Falah","Falah","Falah","Falah","Falah","Falah","",""],["","\\n","","","","","","","",""]]]</textarea></body></html>'
|
||||||
const result = parser({ content, date }).map(p => {
|
const result = parser({ content, date }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2021-11-27T02:30:00.000Z',
|
start: '2021-11-27T02:30:00.000Z',
|
||||||
stop: '2021-11-27T03:00:00.000Z',
|
stop: '2021-11-27T03:00:00.000Z',
|
||||||
title: 'City Report'
|
title: 'City Report'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: '2021-11-27T03:00:00.000Z',
|
start: '2021-11-27T03:00:00.000Z',
|
||||||
stop: '2021-11-27T10:30:00.000Z',
|
stop: '2021-11-27T10:30:00.000Z',
|
||||||
title: 'ICC T20 Highlights'
|
title: 'ICC T20 Highlights'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: '2021-11-27T10:30:00.000Z',
|
start: '2021-11-27T10:30:00.000Z',
|
||||||
stop: '2021-11-28T02:00:00.000Z',
|
stop: '2021-11-28T02:00:00.000Z',
|
||||||
title: 'ICC T20 World Cup'
|
title: 'ICC T20 World Cup'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: '2021-11-28T02:00:00.000Z',
|
start: '2021-11-28T02:00:00.000Z',
|
||||||
stop: '2021-11-28T02:30:00.000Z',
|
stop: '2021-11-28T02:30:00.000Z',
|
||||||
title: 'Quran and Hadis'
|
title: 'Quran and Hadis'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content:
|
content:
|
||||||
'<!DOCTYPE html><html><head></head><body><textarea data-jtrt-table-id="508" id="jtrt_table_settings_508" cols="30" rows="10"></textarea></body></html>'
|
'<!DOCTYPE html><html><head></head><body><textarea data-jtrt-table-id="508" id="jtrt_table_settings_508" cols="30" rows="10"></textarea></body></html>'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,153 +1,153 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'arirang.com',
|
site: 'arirang.com',
|
||||||
output: 'arirang.com.guide.xml',
|
output: 'arirang.com.guide.xml',
|
||||||
channels: 'arirang.com.channels.xml',
|
channels: 'arirang.com.channels.xml',
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
days: 7,
|
days: 7,
|
||||||
delay: 5000,
|
delay: 5000,
|
||||||
url: 'https://www.arirang.com/v1.0/open/external/proxy',
|
url: 'https://www.arirang.com/v1.0/open/external/proxy',
|
||||||
|
|
||||||
request: {
|
request: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
cache: { ttl: 60 * 60 * 1000 },
|
cache: { ttl: 60 * 60 * 1000 },
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json, text/plain, */*',
|
Accept: 'application/json, text/plain, */*',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Origin: 'https://www.arirang.com',
|
Origin: 'https://www.arirang.com',
|
||||||
Referer: 'https://www.arirang.com/schedule',
|
Referer: 'https://www.arirang.com/schedule',
|
||||||
'User-Agent':
|
'User-Agent':
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
|
||||||
},
|
},
|
||||||
data: function (context) {
|
data: function (context) {
|
||||||
const { channel, date } = context
|
const { channel, date } = context
|
||||||
return {
|
return {
|
||||||
address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
|
address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {
|
body: {
|
||||||
data: {
|
data: {
|
||||||
dmParam: {
|
dmParam: {
|
||||||
chanId: channel.site_id,
|
chanId: channel.site_id,
|
||||||
broadYmd: dayjs.tz(date, 'Asia/Seoul').format('YYYYMMDD'),
|
broadYmd: dayjs.tz(date, 'Asia/Seoul').format('YYYYMMDD'),
|
||||||
planNo: '1'
|
planNo: '1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
logo: function (context) {
|
logo: function (context) {
|
||||||
return context.channel.logo
|
return context.channel.logo
|
||||||
},
|
},
|
||||||
|
|
||||||
async parser(context) {
|
async parser(context) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(context.content)
|
const items = parseItems(context.content)
|
||||||
|
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
const programDetail = await parseProgramDetail(item)
|
const programDetail = await parseProgramDetail(item)
|
||||||
|
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.displayNm,
|
title: item.displayNm,
|
||||||
start: parseStart(item),
|
start: parseStart(item),
|
||||||
stop: parseStop(item),
|
stop: parseStop(item),
|
||||||
icon: parseIcon(programDetail),
|
icon: parseIcon(programDetail),
|
||||||
category: parseCategory(programDetail),
|
category: parseCategory(programDetail),
|
||||||
description: parseDescription(programDetail)
|
description: parseDescription(programDetail)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
if (content != '') {
|
if (content != '') {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
return !data || !data.responseBody || !Array.isArray(data.responseBody.dsSchWeek)
|
return !data || !data.responseBody || !Array.isArray(data.responseBody.dsSchWeek)
|
||||||
? []
|
? []
|
||||||
: data.responseBody.dsSchWeek
|
: data.responseBody.dsSchWeek
|
||||||
} else {
|
} else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
|
return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseStop(item) {
|
||||||
return dayjs
|
return dayjs
|
||||||
.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
|
.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
|
||||||
.add(item.broadRun, 'minute')
|
.add(item.broadRun, 'minute')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseProgramDetail(item) {
|
async function parseProgramDetail(item) {
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post(
|
||||||
'https://www.arirang.com/v1.0/open/program/detail',
|
'https://www.arirang.com/v1.0/open/program/detail',
|
||||||
{
|
{
|
||||||
bis_program_code: item.pgmCd
|
bis_program_code: item.pgmCd
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json, text/plain, */*',
|
Accept: 'application/json, text/plain, */*',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Origin: 'https://www.arirang.com',
|
Origin: 'https://www.arirang.com',
|
||||||
Referer: 'https://www.arirang.com/schedule',
|
Referer: 'https://www.arirang.com/schedule',
|
||||||
'User-Agent':
|
'User-Agent':
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
|
||||||
},
|
},
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
cache: { ttl: 60 * 1000 }
|
cache: { ttl: 60 * 1000 }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
return response.data
|
return response.data
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon(programDetail) {
|
function parseIcon(programDetail) {
|
||||||
if (programDetail && programDetail.image && programDetail.image[0].url) {
|
if (programDetail && programDetail.image && programDetail.image[0].url) {
|
||||||
return programDetail.image[0].url
|
return programDetail.image[0].url
|
||||||
} else {
|
} else {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory(programDetail) {
|
function parseCategory(programDetail) {
|
||||||
if (programDetail && programDetail.category_Info && programDetail.category_Info[0].title) {
|
if (programDetail && programDetail.category_Info && programDetail.category_Info[0].title) {
|
||||||
return programDetail.category_Info[0].title
|
return programDetail.category_Info[0].title
|
||||||
} else {
|
} else {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription(programDetail) {
|
function parseDescription(programDetail) {
|
||||||
if (
|
if (
|
||||||
programDetail &&
|
programDetail &&
|
||||||
programDetail.content &&
|
programDetail.content &&
|
||||||
programDetail.content[0] &&
|
programDetail.content[0] &&
|
||||||
programDetail.content[0].text
|
programDetail.content[0].text
|
||||||
) {
|
) {
|
||||||
let description = programDetail.content[0].text
|
let description = programDetail.content[0].text
|
||||||
let regex = /(<([^>]+)>)/gi
|
let regex = /(<([^>]+)>)/gi
|
||||||
return description.replace(regex, '')
|
return description.replace(regex, '')
|
||||||
} else {
|
} else {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +1,74 @@
|
||||||
// npm run grab -- --site=arirang.com
|
// npm run grab -- --site=arirang.com
|
||||||
// npx jest arirang.com.test.js
|
// npx jest arirang.com.test.js
|
||||||
|
|
||||||
const { url, parser } = require('./arirang.com.config.js')
|
const { url, parser } = require('./arirang.com.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const date = dayjs.tz('2023-08-25', 'Asia/Seoul').startOf('d')
|
const date = dayjs.tz('2023-08-25', 'Asia/Seoul').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
xmltv_id: 'ArirangWorld.kr',
|
xmltv_id: 'ArirangWorld.kr',
|
||||||
site_id: 'CH_W',
|
site_id: 'CH_W',
|
||||||
name: 'Arirang World',
|
name: 'Arirang World',
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
logo: 'https://i.imgur.com/5Aoithj.png'
|
logo: 'https://i.imgur.com/5Aoithj.png'
|
||||||
}
|
}
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json'), 'utf8')
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json'), 'utf8')
|
||||||
const programDetail = fs.readFileSync(path.resolve(__dirname, '__data__/detail.json'), 'utf8')
|
const programDetail = fs.readFileSync(path.resolve(__dirname, '__data__/detail.json'), 'utf8')
|
||||||
const context = { channel: channel, content: content, date: date }
|
const context = { channel: channel, content: content, date: date }
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url).toBe('https://www.arirang.com/v1.0/open/external/proxy')
|
expect(url).toBe('https://www.arirang.com/v1.0/open/external/proxy')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', async () => {
|
it('can handle empty guide', async () => {
|
||||||
const results = await parser({ channel: channel, content: '', date: date })
|
const results = await parser({ channel: channel, content: '', date: date })
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', async () => {
|
it('can parse response', async () => {
|
||||||
axios.post.mockImplementation((url, data) => {
|
axios.post.mockImplementation((url, data) => {
|
||||||
if (
|
if (
|
||||||
url === 'https://www.arirang.com/v1.0/open/external/proxy' &&
|
url === 'https://www.arirang.com/v1.0/open/external/proxy' &&
|
||||||
JSON.stringify(data) ===
|
JSON.stringify(data) ===
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
|
address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {},
|
headers: {},
|
||||||
body: { data: { dmParam: { chanId: 'CH_W', broadYmd: '20230825', planNo: '1' } } }
|
body: { data: { dmParam: { chanId: 'CH_W', broadYmd: '20230825', planNo: '1' } } }
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(content)
|
data: JSON.parse(content)
|
||||||
})
|
})
|
||||||
} else if (
|
} else if (
|
||||||
url === 'https://www.arirang.com/v1.0/open/program/detail' &&
|
url === 'https://www.arirang.com/v1.0/open/program/detail' &&
|
||||||
JSON.stringify(data) === JSON.stringify({ bis_program_code: '2023004T' })
|
JSON.stringify(data) === JSON.stringify({ bis_program_code: '2023004T' })
|
||||||
) {
|
) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(programDetail)
|
data: JSON.parse(programDetail)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: ''
|
data: ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const results = await parser(context)
|
const results = await parser(context)
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
title: 'WITHIN THE FRAME [R]',
|
title: 'WITHIN THE FRAME [R]',
|
||||||
start: dayjs.tz(date, 'Asia/Seoul'),
|
start: dayjs.tz(date, 'Asia/Seoul'),
|
||||||
stop: dayjs.tz(date, 'Asia/Seoul').add(30, 'minute'),
|
stop: dayjs.tz(date, 'Asia/Seoul').add(30, 'minute'),
|
||||||
icon: 'https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202308/2080840096998752900.png',
|
icon: 'https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202308/2080840096998752900.png',
|
||||||
description: 'NEWS',
|
description: 'NEWS',
|
||||||
category: 'Current Affairs'
|
category: 'Current Affairs'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,68 +1,68 @@
|
||||||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
|
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
|
||||||
|
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'artonline.tv',
|
site: 'artonline.tv',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ channel }) {
|
url: function ({ channel }) {
|
||||||
return `https://www.artonline.tv/Home/Tvlist${channel.site_id}`
|
return `https://www.artonline.tv/Home/Tvlist${channel.site_id}`
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/x-www-form-urlencoded'
|
'content-type': 'application/x-www-form-urlencoded'
|
||||||
},
|
},
|
||||||
data: function ({ date }) {
|
data: function ({ date }) {
|
||||||
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
params.append('objId', diff)
|
params.append('objId', diff)
|
||||||
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser: function ({ content }) {
|
parser: function ({ content }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
if (!content) return programs
|
if (!content) return programs
|
||||||
const items = JSON.parse(content)
|
const items = JSON.parse(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const icon = parseIcon(item)
|
const icon = parseIcon(item)
|
||||||
const start = parseStart(item)
|
const start = parseStart(item)
|
||||||
const duration = parseDuration(item)
|
const duration = parseDuration(item)
|
||||||
const stop = start.add(duration, 's')
|
const stop = start.add(duration, 's')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
icon,
|
icon,
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
const [, M, D, YYYY] = item.adddate.match(/(\d+)\/(\d+)\/(\d+) /)
|
const [, M, D, YYYY] = item.adddate.match(/(\d+)\/(\d+)\/(\d+) /)
|
||||||
const [HH, mm] = item.start_Time.split(':')
|
const [HH, mm] = item.start_Time.split(':')
|
||||||
|
|
||||||
return dayjs.tz(`${YYYY}-${M}-${D}T${HH}:${mm}:00`, 'YYYY-M-DTHH:mm:ss', 'Asia/Riyadh')
|
return dayjs.tz(`${YYYY}-${M}-${D}T${HH}:${mm}:00`, 'YYYY-M-DTHH:mm:ss', 'Asia/Riyadh')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDuration(item) {
|
function parseDuration(item) {
|
||||||
const [, HH, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)/)
|
const [, HH, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)/)
|
||||||
|
|
||||||
return parseInt(HH) * 3600 + parseInt(mm) * 60 + parseInt(ss)
|
return parseInt(HH) * 3600 + parseInt(mm) * 60 + parseInt(ss)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
return item.thumbnail ? `https://www.artonline.tv${item.thumbnail}` : null
|
return item.thumbnail ? `https://www.artonline.tv${item.thumbnail}` : null
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +1,67 @@
|
||||||
// npm run grab -- --site=artonline.tv
|
// npm run grab -- --site=artonline.tv
|
||||||
|
|
||||||
const { parser, url, request } = require('./artonline.tv.config.js')
|
const { parser, url, request } = require('./artonline.tv.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'Aflam2',
|
site_id: 'Aflam2',
|
||||||
xmltv_id: 'ARTAflam2.sa'
|
xmltv_id: 'ARTAflam2.sa'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel })).toBe('https://www.artonline.tv/Home/TvlistAflam2')
|
expect(url({ channel })).toBe('https://www.artonline.tv/Home/TvlistAflam2')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request method', () => {
|
it('can generate valid request method', () => {
|
||||||
expect(request.method).toBe('POST')
|
expect(request.method).toBe('POST')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request headers', () => {
|
it('can generate valid request headers', () => {
|
||||||
expect(request.headers).toMatchObject({
|
expect(request.headers).toMatchObject({
|
||||||
'content-type': 'application/x-www-form-urlencoded'
|
'content-type': 'application/x-www-form-urlencoded'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request data for today', () => {
|
it('can generate valid request data for today', () => {
|
||||||
const date = dayjs.utc().startOf('d')
|
const date = dayjs.utc().startOf('d')
|
||||||
const data = request.data({ date })
|
const data = request.data({ date })
|
||||||
expect(data.get('objId')).toBe('0')
|
expect(data.get('objId')).toBe('0')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request data for tomorrow', () => {
|
it('can generate valid request data for tomorrow', () => {
|
||||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||||
const data = request.data({ date })
|
const data = request.data({ date })
|
||||||
expect(data.get('objId')).toBe('1')
|
expect(data.get('objId')).toBe('1')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content =
|
const content =
|
||||||
'[{"id":158963,"eventid":null,"duration":"01:34:00","lang":"Arabic","title":"الراقصه و السياسي","description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","thumbnail":"/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg","image":"0","start_Time":"00:30","adddate":"3/4/2022 12:00:00 AM","repeat1":null,"iD_genre":0,"iD_Show_Type":0,"iD_Channel":77,"iD_country":0,"iD_rating":0,"end_time":"02:04","season_Number":0,"epoisode_Number":0,"hasCatchup":0,"cmsid":0,"containerID":0,"imagePath":"../../UploadImages/Channel/ARTAFLAM1/3/","youtube":"0","published_at":"0","directed_by":"0","composition":"0","cast":"0","timeShow":null,"short_description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","seOdescription":null,"tagseo":null,"channel_name":null,"pathimage":null,"pathThumbnail":null}]'
|
'[{"id":158963,"eventid":null,"duration":"01:34:00","lang":"Arabic","title":"الراقصه و السياسي","description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","thumbnail":"/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg","image":"0","start_Time":"00:30","adddate":"3/4/2022 12:00:00 AM","repeat1":null,"iD_genre":0,"iD_Show_Type":0,"iD_Channel":77,"iD_country":0,"iD_rating":0,"end_time":"02:04","season_Number":0,"epoisode_Number":0,"hasCatchup":0,"cmsid":0,"containerID":0,"imagePath":"../../UploadImages/Channel/ARTAFLAM1/3/","youtube":"0","published_at":"0","directed_by":"0","composition":"0","cast":"0","timeShow":null,"short_description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","seOdescription":null,"tagseo":null,"channel_name":null,"pathimage":null,"pathThumbnail":null}]'
|
||||||
const result = parser({ content }).map(p => {
|
const result = parser({ content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2022-03-03T21:30:00.000Z',
|
start: '2022-03-03T21:30:00.000Z',
|
||||||
stop: '2022-03-03T23:04:00.000Z',
|
stop: '2022-03-03T23:04:00.000Z',
|
||||||
title: 'الراقصه و السياسي',
|
title: 'الراقصه و السياسي',
|
||||||
description:
|
description:
|
||||||
'تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .',
|
'تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .',
|
||||||
icon: 'https://www.artonline.tv/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg'
|
icon: 'https://www.artonline.tv/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
content: ''
|
content: ''
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,123 +1,123 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const API_ENDPOINT = 'https://contenthub-api.eco.astro.com.my'
|
const API_ENDPOINT = 'https://contenthub-api.eco.astro.com.my'
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'astro.com.my',
|
site: 'astro.com.my',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ channel }) {
|
url: function ({ channel }) {
|
||||||
return `${API_ENDPOINT}/channel/${channel.site_id}.json`
|
return `${API_ENDPOINT}/channel/${channel.site_id}.json`
|
||||||
},
|
},
|
||||||
async parser({ content, date }) {
|
async parser({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content, date)
|
const items = parseItems(content, date)
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
const start = dayjs.utc(item.datetimeInUtc)
|
const start = dayjs.utc(item.datetimeInUtc)
|
||||||
const duration = parseDuration(item.duration)
|
const duration = parseDuration(item.duration)
|
||||||
const stop = start.add(duration, 's')
|
const stop = start.add(duration, 's')
|
||||||
const details = await loadProgramDetails(item)
|
const details = await loadProgramDetails(item)
|
||||||
programs.push({
|
programs.push({
|
||||||
title: details.title,
|
title: details.title,
|
||||||
sub_title: item.subtitles,
|
sub_title: item.subtitles,
|
||||||
description: details.longSynopsis || details.shortSynopsis,
|
description: details.longSynopsis || details.shortSynopsis,
|
||||||
actors: parseList(details.cast),
|
actors: parseList(details.cast),
|
||||||
directors: parseList(details.director),
|
directors: parseList(details.director),
|
||||||
icon: details.imageUrl,
|
icon: details.imageUrl,
|
||||||
rating: parseRating(details),
|
rating: parseRating(details),
|
||||||
categories: parseCategories(details),
|
categories: parseCategories(details),
|
||||||
episode: parseEpisode(item),
|
episode: parseEpisode(item),
|
||||||
season: parseSeason(details),
|
season: parseSeason(details),
|
||||||
start: start,
|
start: start,
|
||||||
stop: stop
|
stop: stop
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseEpisode(item) {
|
function parseEpisode(item) {
|
||||||
const [, number] = item.title.match(/Ep(\d+)$/) || [null, null]
|
const [, number] = item.title.match(/Ep(\d+)$/) || [null, null]
|
||||||
|
|
||||||
return number ? parseInt(number) : null
|
return number ? parseInt(number) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSeason(details) {
|
function parseSeason(details) {
|
||||||
const [, season] = details.title ? details.title.match(/ S(\d+)/) || [null, null] : [null, null]
|
const [, season] = details.title ? details.title.match(/ S(\d+)/) || [null, null] : [null, null]
|
||||||
|
|
||||||
return season ? parseInt(season) : null
|
return season ? parseInt(season) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseList(list) {
|
function parseList(list) {
|
||||||
return typeof list === 'string' ? list.split(',') : []
|
return typeof list === 'string' ? list.split(',') : []
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRating(details) {
|
function parseRating(details) {
|
||||||
return details.certification
|
return details.certification
|
||||||
? {
|
? {
|
||||||
system: 'LPF',
|
system: 'LPF',
|
||||||
value: details.certification
|
value: details.certification
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, date) {
|
function parseItems(content, date) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
const schedules = data.response.schedule
|
const schedules = data.response.schedule
|
||||||
|
|
||||||
return schedules[date.format('YYYY-MM-DD')] || []
|
return schedules[date.format('YYYY-MM-DD')] || []
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDuration(duration) {
|
function parseDuration(duration) {
|
||||||
const match = duration.match(/(\d{2}):(\d{2}):(\d{2})/)
|
const match = duration.match(/(\d{2}):(\d{2}):(\d{2})/)
|
||||||
const hours = parseInt(match[1])
|
const hours = parseInt(match[1])
|
||||||
const minutes = parseInt(match[2])
|
const minutes = parseInt(match[2])
|
||||||
const seconds = parseInt(match[3])
|
const seconds = parseInt(match[3])
|
||||||
|
|
||||||
return hours * 3600 + minutes * 60 + seconds
|
return hours * 3600 + minutes * 60 + seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategories(details) {
|
function parseCategories(details) {
|
||||||
const genres = {
|
const genres = {
|
||||||
'filter/2': 'Action',
|
'filter/2': 'Action',
|
||||||
'filter/4': 'Anime',
|
'filter/4': 'Anime',
|
||||||
'filter/12': 'Cartoons',
|
'filter/12': 'Cartoons',
|
||||||
'filter/16': 'Comedy',
|
'filter/16': 'Comedy',
|
||||||
'filter/19': 'Crime',
|
'filter/19': 'Crime',
|
||||||
'filter/24': 'Drama',
|
'filter/24': 'Drama',
|
||||||
'filter/25': 'Educational',
|
'filter/25': 'Educational',
|
||||||
'filter/36': 'Horror',
|
'filter/36': 'Horror',
|
||||||
'filter/39': 'Live Action',
|
'filter/39': 'Live Action',
|
||||||
'filter/55': 'Pre-school',
|
'filter/55': 'Pre-school',
|
||||||
'filter/56': 'Reality',
|
'filter/56': 'Reality',
|
||||||
'filter/60': 'Romance',
|
'filter/60': 'Romance',
|
||||||
'filter/68': 'Talk Show',
|
'filter/68': 'Talk Show',
|
||||||
'filter/69': 'Thriller',
|
'filter/69': 'Thriller',
|
||||||
'filter/72': 'Variety',
|
'filter/72': 'Variety',
|
||||||
'filter/75': 'Series',
|
'filter/75': 'Series',
|
||||||
'filter/100': 'Others (Children)'
|
'filter/100': 'Others (Children)'
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.isArray(details.subFilter)
|
return Array.isArray(details.subFilter)
|
||||||
? details.subFilter.map(g => genres[g.toLowerCase()]).filter(Boolean)
|
? details.subFilter.map(g => genres[g.toLowerCase()]).filter(Boolean)
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProgramDetails(item) {
|
async function loadProgramDetails(item) {
|
||||||
const url = `${API_ENDPOINT}/api/v1/linear-detail?siTrafficKey=${item.siTrafficKey}`
|
const url = `${API_ENDPOINT}/api/v1/linear-detail?siTrafficKey=${item.siTrafficKey}`
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(error => console.log(error.message))
|
.catch(error => console.log(error.message))
|
||||||
if (!data) return {}
|
if (!data) return {}
|
||||||
|
|
||||||
return data.response || {}
|
return data.response || {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +1,73 @@
|
||||||
// npm run grab -- --site=astro.com.my
|
// npm run grab -- --site=astro.com.my
|
||||||
|
|
||||||
const { parser, url } = require('./astro.com.my.config.js')
|
const { parser, url } = require('./astro.com.my.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const date = dayjs.utc('2022-10-31', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-10-31', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '425',
|
site_id: '425',
|
||||||
xmltv_id: 'TVBClassic.hk'
|
xmltv_id: 'TVBClassic.hk'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel })).toBe('https://contenthub-api.eco.astro.com.my/channel/425.json')
|
expect(url({ channel })).toBe('https://contenthub-api.eco.astro.com.my/channel/425.json')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', async () => {
|
it('can parse response', async () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
|
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
if (
|
if (
|
||||||
url ===
|
url ===
|
||||||
'https://contenthub-api.eco.astro.com.my/api/v1/linear-detail?siTrafficKey=1:10000526:47979653'
|
'https://contenthub-api.eco.astro.com.my/api/v1/linear-detail?siTrafficKey=1:10000526:47979653'
|
||||||
) {
|
) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json')))
|
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json')))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: '' })
|
return Promise.resolve({ data: '' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let results = await parser({ content, channel, date })
|
let results = await parser({ content, channel, date })
|
||||||
results = results.map(p => {
|
results = results.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results.length).toBe(31)
|
expect(results.length).toBe(31)
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-10-30T16:10:00.000Z',
|
start: '2022-10-30T16:10:00.000Z',
|
||||||
stop: '2022-10-30T17:02:00.000Z',
|
stop: '2022-10-30T17:02:00.000Z',
|
||||||
title: 'Triumph in the Skies S1 Ep06',
|
title: 'Triumph in the Skies S1 Ep06',
|
||||||
description:
|
description:
|
||||||
'This classic drama depicts the many aspects of two complicated relationships set against an airline company. Will those involved ever find true love?',
|
'This classic drama depicts the many aspects of two complicated relationships set against an airline company. Will those involved ever find true love?',
|
||||||
actors: ['Francis Ng Chun Yu', 'Joe Ma Tak Chung', 'Flora Chan Wai San'],
|
actors: ['Francis Ng Chun Yu', 'Joe Ma Tak Chung', 'Flora Chan Wai San'],
|
||||||
directors: ['Joe Ma Tak Chung'],
|
directors: ['Joe Ma Tak Chung'],
|
||||||
icon: 'https://s3-ap-southeast-1.amazonaws.com/ams-astro/production/images/1035X328883.jpg',
|
icon: 'https://s3-ap-southeast-1.amazonaws.com/ams-astro/production/images/1035X328883.jpg',
|
||||||
rating: {
|
rating: {
|
||||||
system: 'LPF',
|
system: 'LPF',
|
||||||
value: 'U'
|
value: 'U'
|
||||||
},
|
},
|
||||||
episode: 6,
|
episode: 6,
|
||||||
season: 1,
|
season: 1,
|
||||||
categories: ['Drama']
|
categories: ['Drama']
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', async () => {
|
it('can handle empty guide', async () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'))
|
||||||
const results = await parser({ date, content })
|
const results = await parser({ date, content })
|
||||||
|
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,79 +1,79 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const { DateTime } = require('luxon')
|
const { DateTime } = require('luxon')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'bein.com',
|
site: 'bein.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 60 * 60 * 1000 // 1 hour
|
ttl: 60 * 60 * 1000 // 1 hour
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url: function ({ date, channel }) {
|
url: function ({ date, channel }) {
|
||||||
const [category] = channel.site_id.split('#')
|
const [category] = channel.site_id.split('#')
|
||||||
const postid = channel.lang === 'ar' ? '25344' : '25356'
|
const postid = channel.lang === 'ar' ? '25344' : '25356'
|
||||||
|
|
||||||
return `https://www.bein.com/${
|
return `https://www.bein.com/${
|
||||||
channel.lang
|
channel.lang
|
||||||
}/epg-ajax-template/?action=epg_fetch&category=${category}&cdate=${date.format(
|
}/epg-ajax-template/?action=epg_fetch&category=${category}&cdate=${date.format(
|
||||||
'YYYY-MM-DD'
|
'YYYY-MM-DD'
|
||||||
)}&language=${channel.lang.toUpperCase()}&loadindex=0&mins=00&offset=0&postid=${postid}&serviceidentity=bein.net`
|
)}&language=${channel.lang.toUpperCase()}&loadindex=0&mins=00&offset=0&postid=${postid}&serviceidentity=bein.net`
|
||||||
},
|
},
|
||||||
parser: function ({ content, channel, date }) {
|
parser: function ({ content, channel, date }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
date = DateTime.fromMillis(date.valueOf()).minus({ days: 1 })
|
date = DateTime.fromMillis(date.valueOf()).minus({ days: 1 })
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
const title = parseTitle($item)
|
const title = parseTitle($item)
|
||||||
if (!title) return
|
if (!title) return
|
||||||
const category = parseCategory($item)
|
const category = parseCategory($item)
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
let start = parseTime($item, date)
|
let start = parseTime($item, date)
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (start < prev.start) {
|
if (start < prev.start) {
|
||||||
start = start.plus({ days: 1 })
|
start = start.plus({ days: 1 })
|
||||||
date = date.plus({ days: 1 })
|
date = date.plus({ days: 1 })
|
||||||
}
|
}
|
||||||
prev.stop = start
|
prev.stop = start
|
||||||
}
|
}
|
||||||
let stop = parseTime($item, start)
|
let stop = parseTime($item, start)
|
||||||
if (stop < start) {
|
if (stop < start) {
|
||||||
stop = stop.plus({ days: 1 })
|
stop = stop.plus({ days: 1 })
|
||||||
}
|
}
|
||||||
programs.push({
|
programs.push({
|
||||||
title,
|
title,
|
||||||
category,
|
category,
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('.title').text()
|
return $item('.title').text()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory($item) {
|
function parseCategory($item) {
|
||||||
return $item('.format').text()
|
return $item('.format').text()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTime($item, date) {
|
function parseTime($item, date) {
|
||||||
let [, time] = $item('.time')
|
let [, time] = $item('.time')
|
||||||
.text()
|
.text()
|
||||||
.match(/^(\d{2}:\d{2})/) || [null, null]
|
.match(/^(\d{2}:\d{2})/) || [null, null]
|
||||||
if (!time) return null
|
if (!time) return null
|
||||||
time = `${date.toFormat('yyyy-MM-dd')} ${time}`
|
time = `${date.toFormat('yyyy-MM-dd')} ${time}`
|
||||||
|
|
||||||
return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Asia/Qatar' }).toUTC()
|
return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Asia/Qatar' }).toUTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
const [, channelId] = channel.site_id.split('#')
|
const [, channelId] = channel.site_id.split('#')
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray()
|
return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,60 @@
|
||||||
// npm run grab -- --site=bein.com
|
// npm run grab -- --site=bein.com
|
||||||
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { parser, url } = require('./bein.com.config.js')
|
const { parser, url } = require('./bein.com.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = { site_id: 'entertainment#1', xmltv_id: 'beINMovies1Premiere.qa', lang: 'en' }
|
const channel = { site_id: 'entertainment#1', xmltv_id: 'beINMovies1Premiere.qa', lang: 'en' }
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
const result = url({ date, channel })
|
const result = url({ date, channel })
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'https://www.bein.com/en/epg-ajax-template/?action=epg_fetch&category=entertainment&cdate=2023-01-19&language=EN&loadindex=0&mins=00&offset=0&postid=25356&serviceidentity=bein.net'
|
'https://www.bein.com/en/epg-ajax-template/?action=epg_fetch&category=entertainment&cdate=2023-01-19&language=EN&loadindex=0&mins=00&offset=0&postid=25356&serviceidentity=bein.net'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve('sites/bein.com/__data__/content.html'))
|
const content = fs.readFileSync(path.resolve('sites/bein.com/__data__/content.html'))
|
||||||
const results = parser({ date, channel, content }).map(p => {
|
const results = parser({ date, channel, content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-01-18T20:15:00.000Z',
|
start: '2023-01-18T20:15:00.000Z',
|
||||||
stop: '2023-01-18T22:15:00.000Z',
|
stop: '2023-01-18T22:15:00.000Z',
|
||||||
title: 'The Walk',
|
title: 'The Walk',
|
||||||
category: 'Movies'
|
category: 'Movies'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[1]).toMatchObject({
|
expect(results[1]).toMatchObject({
|
||||||
start: '2023-01-18T22:15:00.000Z',
|
start: '2023-01-18T22:15:00.000Z',
|
||||||
stop: '2023-01-19T00:00:00.000Z',
|
stop: '2023-01-19T00:00:00.000Z',
|
||||||
title: 'Resident Evil: Welcome To Raccoon City',
|
title: 'Resident Evil: Welcome To Raccoon City',
|
||||||
category: 'Movies'
|
category: 'Movies'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[10]).toMatchObject({
|
expect(results[10]).toMatchObject({
|
||||||
start: '2023-01-19T15:30:00.000Z',
|
start: '2023-01-19T15:30:00.000Z',
|
||||||
stop: '2023-01-19T18:00:00.000Z',
|
stop: '2023-01-19T18:00:00.000Z',
|
||||||
title: 'Spider-Man: No Way Home',
|
title: 'Spider-Man: No Way Home',
|
||||||
category: 'Movies'
|
category: 'Movies'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const noContent = fs.readFileSync(path.resolve('sites/bein.com/__data__/no-content.html'))
|
const noContent = fs.readFileSync(path.resolve('sites/bein.com/__data__/no-content.html'))
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content: noContent
|
content: noContent
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,130 +1,130 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'beinsports.com',
|
site: 'beinsports.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 60 * 60 * 1000, // 1h
|
ttl: 60 * 60 * 1000, // 1h
|
||||||
interpretHeader: false
|
interpretHeader: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url: function ({ date, channel }) {
|
url: function ({ date, channel }) {
|
||||||
let [region] = channel.site_id.split('#')
|
let [region] = channel.site_id.split('#')
|
||||||
region = region ? `_${region}` : ''
|
region = region ? `_${region}` : ''
|
||||||
|
|
||||||
return `https://epg.beinsports.com/utctime${region}.php?mins=00&serviceidentity=beinsports.com&cdate=${date.format(
|
return `https://epg.beinsports.com/utctime${region}.php?mins=00&serviceidentity=beinsports.com&cdate=${date.format(
|
||||||
'YYYY-MM-DD'
|
'YYYY-MM-DD'
|
||||||
)}`
|
)}`
|
||||||
},
|
},
|
||||||
parser: function ({ content, channel, date }) {
|
parser: function ({ content, channel, date }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
let i = 0
|
let i = 0
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
const title = parseTitle($item)
|
const title = parseTitle($item)
|
||||||
if (!title) return
|
if (!title) return
|
||||||
const category = parseCategory($item)
|
const category = parseCategory($item)
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
let start = parseStart($item, date)
|
let start = parseStart($item, date)
|
||||||
if (i === 0 && start.hour() > 18) {
|
if (i === 0 && start.hour() > 18) {
|
||||||
date = date.subtract(1, 'd')
|
date = date.subtract(1, 'd')
|
||||||
start = start.subtract(1, 'd')
|
start = start.subtract(1, 'd')
|
||||||
}
|
}
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (start.isBefore(prev.start)) {
|
if (start.isBefore(prev.start)) {
|
||||||
start = start.add(1, 'd')
|
start = start.add(1, 'd')
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
prev.stop = start
|
prev.stop = start
|
||||||
}
|
}
|
||||||
let stop = parseStop($item, start)
|
let stop = parseStop($item, start)
|
||||||
if (stop.isBefore(start)) {
|
if (stop.isBefore(start)) {
|
||||||
stop = stop.add(1, 'd')
|
stop = stop.add(1, 'd')
|
||||||
}
|
}
|
||||||
|
|
||||||
programs.push({ title, category, start, stop })
|
programs.push({ title, category, start, stop })
|
||||||
i++
|
i++
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ region, lang }) {
|
async channels({ region, lang }) {
|
||||||
const suffix = region ? `_${region}` : ''
|
const suffix = region ? `_${region}` : ''
|
||||||
const content = await axios
|
const content = await axios
|
||||||
.get(
|
.get(
|
||||||
`https://epg.beinsports.com/utctime${suffix}.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08`
|
`https://epg.beinsports.com/utctime${suffix}.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08`
|
||||||
)
|
)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
const items = $('.container > div, #epg_div > div').toArray()
|
const items = $('.container > div, #epg_div > div').toArray()
|
||||||
return items
|
return items
|
||||||
.map(item => {
|
.map(item => {
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
const id = $item('*').attr('id')
|
const id = $item('*').attr('id')
|
||||||
if (!/^channels_[0-9]+$/.test(id)) return null
|
if (!/^channels_[0-9]+$/.test(id)) return null
|
||||||
const channelId = id.replace('channels_', '')
|
const channelId = id.replace('channels_', '')
|
||||||
const imgSrc = $item('img').attr('src')
|
const imgSrc = $item('img').attr('src')
|
||||||
const [, , name] = imgSrc.match(/(\/|)([a-z0-9-_.]+)(.png|.svg)$/i) || [null, null, '']
|
const [, , name] = imgSrc.match(/(\/|)([a-z0-9-_.]+)(.png|.svg)$/i) || [null, null, '']
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lang,
|
lang,
|
||||||
site_id: `${region}#${channelId}`,
|
site_id: `${region}#${channelId}`,
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(i => i)
|
.filter(i => i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('.title').text()
|
return $item('.title').text()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory($item) {
|
function parseCategory($item) {
|
||||||
return $item('.format')
|
return $item('.format')
|
||||||
.map(function () {
|
.map(function () {
|
||||||
return $item(this).text()
|
return $item(this).text()
|
||||||
})
|
})
|
||||||
.get()
|
.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
let time = $item('.time').text()
|
let time = $item('.time').text()
|
||||||
if (!time) return null
|
if (!time) return null
|
||||||
let [, start, period] = time.match(/^(\d{2}:\d{2})( AM| PM|)/) || [null, null, null]
|
let [, start, period] = time.match(/^(\d{2}:\d{2})( AM| PM|)/) || [null, null, null]
|
||||||
if (!start) return null
|
if (!start) return null
|
||||||
start = `${date.format('YYYY-MM-DD')} ${start}${period}`
|
start = `${date.format('YYYY-MM-DD')} ${start}${period}`
|
||||||
const format = period ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'
|
const format = period ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'
|
||||||
|
|
||||||
return dayjs.tz(start, format, 'Asia/Qatar')
|
return dayjs.tz(start, format, 'Asia/Qatar')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop($item, date) {
|
function parseStop($item, date) {
|
||||||
let time = $item('.time').text()
|
let time = $item('.time').text()
|
||||||
if (!time) return null
|
if (!time) return null
|
||||||
let [, stop, period] = time.match(/(\d{2}:\d{2})( AM| PM|)$/) || [null, null, null]
|
let [, stop, period] = time.match(/(\d{2}:\d{2})( AM| PM|)$/) || [null, null, null]
|
||||||
if (!stop) return null
|
if (!stop) return null
|
||||||
stop = `${date.format('YYYY-MM-DD')} ${stop}${period}`
|
stop = `${date.format('YYYY-MM-DD')} ${stop}${period}`
|
||||||
const format = period ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'
|
const format = period ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'
|
||||||
|
|
||||||
return dayjs.tz(stop, format, 'Asia/Qatar')
|
return dayjs.tz(stop, format, 'Asia/Qatar')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
const [, channelId] = channel.site_id.split('#')
|
const [, channelId] = channel.site_id.split('#')
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray()
|
return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,91 +1,91 @@
|
||||||
// npm run channels:parse -- --config=./sites/beinsports.com/beinsports.com.config.js --output=./sites/beinsports.com/beinsports.com_qa-ar.channels.xml --set=lang:ar --set=region:ar
|
// npm run channels:parse -- --config=./sites/beinsports.com/beinsports.com.config.js --output=./sites/beinsports.com/beinsports.com_qa-ar.channels.xml --set=lang:ar --set=region:ar
|
||||||
// npm run grab -- --site=beinsports.com
|
// npm run grab -- --site=beinsports.com
|
||||||
// npm run grab -- --site=beinsports.com
|
// npm run grab -- --site=beinsports.com
|
||||||
|
|
||||||
const { parser, url } = require('./beinsports.com.config.js')
|
const { parser, url } = require('./beinsports.com.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2022-05-08', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-05-08', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = { site_id: '#2', xmltv_id: 'BeINSports.qa' }
|
const channel = { site_id: '#2', xmltv_id: 'BeINSports.qa' }
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
const result = url({ date, channel })
|
const result = url({ date, channel })
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'https://epg.beinsports.com/utctime.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08'
|
'https://epg.beinsports.com/utctime.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid url for arabic guide', () => {
|
it('can generate valid url for arabic guide', () => {
|
||||||
const channel = { site_id: 'ar#1', xmltv_id: 'BeINSports.qa' }
|
const channel = { site_id: 'ar#1', xmltv_id: 'BeINSports.qa' }
|
||||||
const result = url({ date, channel })
|
const result = url({ date, channel })
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'https://epg.beinsports.com/utctime_ar.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08'
|
'https://epg.beinsports.com/utctime_ar.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/content.html'))
|
const content = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/content.html'))
|
||||||
const results = parser({ date, channel, content }).map(p => {
|
const results = parser({ date, channel, content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-05-07T19:30:00.000Z',
|
start: '2022-05-07T19:30:00.000Z',
|
||||||
stop: '2022-05-07T21:20:00.000Z',
|
stop: '2022-05-07T21:20:00.000Z',
|
||||||
title: 'Lorient vs Marseille',
|
title: 'Lorient vs Marseille',
|
||||||
category: ['Ligue 1 2021/22']
|
category: ['Ligue 1 2021/22']
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response for tomorrow', () => {
|
it('can parse response for tomorrow', () => {
|
||||||
const date = dayjs.utc('2022-05-09', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-05-09', 'YYYY-MM-DD').startOf('d')
|
||||||
const content = fs.readFileSync(
|
const content = fs.readFileSync(
|
||||||
path.resolve('sites/beinsports.com/__data__/content_tomorrow.html')
|
path.resolve('sites/beinsports.com/__data__/content_tomorrow.html')
|
||||||
)
|
)
|
||||||
const results = parser({ date, channel, content }).map(p => {
|
const results = parser({ date, channel, content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-05-08T21:20:00.000Z',
|
start: '2022-05-08T21:20:00.000Z',
|
||||||
stop: '2022-05-08T23:10:00.000Z',
|
stop: '2022-05-08T23:10:00.000Z',
|
||||||
title: 'Celtic vs Hearts',
|
title: 'Celtic vs Hearts',
|
||||||
category: ['SPFL Premiership 2021/22']
|
category: ['SPFL Premiership 2021/22']
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse US response', () => {
|
it('can parse US response', () => {
|
||||||
const content = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/content_us.html'))
|
const content = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/content_us.html'))
|
||||||
const results = parser({ date, channel, content }).map(p => {
|
const results = parser({ date, channel, content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-05-07T20:00:00.000Z',
|
start: '2022-05-07T20:00:00.000Z',
|
||||||
stop: '2022-05-07T22:00:00.000Z',
|
stop: '2022-05-07T22:00:00.000Z',
|
||||||
title: 'Basaksehir vs. Galatasaray',
|
title: 'Basaksehir vs. Galatasaray',
|
||||||
category: ['Fútbol Turco Superliga', 'Soccer']
|
category: ['Fútbol Turco Superliga', 'Soccer']
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const noContent = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/no-content.html'))
|
const noContent = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/no-content.html'))
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content: noContent
|
content: noContent
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,93 +1,93 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
dayjs.Ls.en.weekStart = 1
|
dayjs.Ls.en.weekStart = 1
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'berrymedia.co.kr',
|
site: 'berrymedia.co.kr',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel }) {
|
url({ channel }) {
|
||||||
return `http://www.berrymedia.co.kr/schedule_proc${channel.site_id}.php`
|
return `http://www.berrymedia.co.kr/schedule_proc${channel.site_id}.php`
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
},
|
},
|
||||||
data({ date }) {
|
data({ date }) {
|
||||||
let params = new URLSearchParams()
|
let params = new URLSearchParams()
|
||||||
let startOfWeek = date.startOf('week').format('YYYY-MM-DD')
|
let startOfWeek = date.startOf('week').format('YYYY-MM-DD')
|
||||||
let endOfWeek = date.endOf('week').format('YYYY-MM-DD')
|
let endOfWeek = date.endOf('week').format('YYYY-MM-DD')
|
||||||
|
|
||||||
params.append('week', `${startOfWeek}~${endOfWeek}`)
|
params.append('week', `${startOfWeek}~${endOfWeek}`)
|
||||||
params.append('day', date.format('YYYY-MM-DD'))
|
params.append('day', date.format('YYYY-MM-DD'))
|
||||||
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser({ content, date }) {
|
parser({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
let start = parseStart($item, date)
|
let start = parseStart($item, date)
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (start.isBefore(prev.start)) {
|
if (start.isBefore(prev.start)) {
|
||||||
start = start.add(1, 'd')
|
start = start.add(1, 'd')
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
prev.stop = start
|
prev.stop = start
|
||||||
}
|
}
|
||||||
const stop = start.add(30, 'm')
|
const stop = start.add(30, 'm')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle($item),
|
title: parseTitle($item),
|
||||||
category: parseCategory($item),
|
category: parseCategory($item),
|
||||||
rating: parseRating($item),
|
rating: parseRating($item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
const time = $item('span:nth-child(1)').text().trim()
|
const time = $item('span:nth-child(1)').text().trim()
|
||||||
|
|
||||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul')
|
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('span.sdfsdf').clone().children().remove().end().text().trim()
|
return $item('span.sdfsdf').clone().children().remove().end().text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory($item) {
|
function parseCategory($item) {
|
||||||
return $item('span:nth-child(2) > p').text().trim()
|
return $item('span:nth-child(2) > p').text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRating($item) {
|
function parseRating($item) {
|
||||||
const rating = $item('span:nth-child(5) > p:nth-child(1)').text().trim()
|
const rating = $item('span:nth-child(5) > p:nth-child(1)').text().trim()
|
||||||
|
|
||||||
return rating
|
return rating
|
||||||
? {
|
? {
|
||||||
system: 'KMRB',
|
system: 'KMRB',
|
||||||
value: rating
|
value: rating
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
return $('.sc_time dd').toArray()
|
return $('.sc_time dd').toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +1,79 @@
|
||||||
// npm run grab -- --site=berrymedia.co.kr
|
// npm run grab -- --site=berrymedia.co.kr
|
||||||
|
|
||||||
const { parser, url, request } = require('./berrymedia.co.kr.config.js')
|
const { parser, url, request } = require('./berrymedia.co.kr.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-01-26', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-01-26', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '',
|
site_id: '',
|
||||||
xmltv_id: 'GTV.kr'
|
xmltv_id: 'GTV.kr'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel })).toBe('http://www.berrymedia.co.kr/schedule_proc.php')
|
expect(url({ channel })).toBe('http://www.berrymedia.co.kr/schedule_proc.php')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate request method', () => {
|
it('can generate request method', () => {
|
||||||
expect(request.method).toBe('POST')
|
expect(request.method).toBe('POST')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request headers', () => {
|
it('can generate valid request headers', () => {
|
||||||
expect(request.headers).toMatchObject({
|
expect(request.headers).toMatchObject({
|
||||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request data', () => {
|
it('can generate valid request data', () => {
|
||||||
let params = request.data({ date })
|
let params = request.data({ date })
|
||||||
|
|
||||||
expect(params.get('week')).toBe('2023-01-23~2023-01-29')
|
expect(params.get('week')).toBe('2023-01-23~2023-01-29')
|
||||||
expect(params.get('day')).toBe('2023-01-26')
|
expect(params.get('day')).toBe('2023-01-26')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||||
let results = parser({ content, date })
|
let results = parser({ content, date })
|
||||||
results = results.map(p => {
|
results = results.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-01-25T15:00:00.000Z',
|
start: '2023-01-25T15:00:00.000Z',
|
||||||
stop: '2023-01-25T16:00:00.000Z',
|
stop: '2023-01-25T16:00:00.000Z',
|
||||||
title: '더트롯쇼',
|
title: '더트롯쇼',
|
||||||
category: '연예/오락',
|
category: '연예/오락',
|
||||||
rating: {
|
rating: {
|
||||||
system: 'KMRB',
|
system: 'KMRB',
|
||||||
value: '15'
|
value: '15'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[17]).toMatchObject({
|
expect(results[17]).toMatchObject({
|
||||||
start: '2023-01-26T13:50:00.000Z',
|
start: '2023-01-26T13:50:00.000Z',
|
||||||
stop: '2023-01-26T14:20:00.000Z',
|
stop: '2023-01-26T14:20:00.000Z',
|
||||||
title: '나는 자연인이다',
|
title: '나는 자연인이다',
|
||||||
category: '교양',
|
category: '교양',
|
||||||
rating: {
|
rating: {
|
||||||
system: 'KMRB',
|
system: 'KMRB',
|
||||||
value: 'ALL'
|
value: 'ALL'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const results = parser({
|
const results = parser({
|
||||||
date,
|
date,
|
||||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,54 +1,54 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'bt.com',
|
site: 'bt.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ date, channel }) {
|
url: function ({ date, channel }) {
|
||||||
return `https://voila.metabroadcast.com/4/schedules/${
|
return `https://voila.metabroadcast.com/4/schedules/${
|
||||||
channel.site_id
|
channel.site_id
|
||||||
}.json?key=b4d2edb68da14dfb9e47b5465e99b1b1&from=${date.utc().format()}&to=${date
|
}.json?key=b4d2edb68da14dfb9e47b5465e99b1b1&from=${date.utc().format()}&to=${date
|
||||||
.add(1, 'd')
|
.add(1, 'd')
|
||||||
.utc()
|
.utc()
|
||||||
.format()}&source=api.youview.tv&annotations=content.description`
|
.format()}&source=api.youview.tv&annotations=content.description`
|
||||||
},
|
},
|
||||||
parser: function ({ content }) {
|
parser: function ({ content }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.item.title,
|
title: item.item.title,
|
||||||
description: item.item.description,
|
description: item.item.description,
|
||||||
icon: parseIcon(item),
|
icon: parseIcon(item),
|
||||||
season: parseSeason(item),
|
season: parseSeason(item),
|
||||||
episode: parseEpisode(item),
|
episode: parseEpisode(item),
|
||||||
start: parseStart(item),
|
start: parseStart(item),
|
||||||
stop: parseStop(item)
|
stop: parseStop(item)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
return data && data.schedule.entries ? data.schedule.entries : []
|
return data && data.schedule.entries ? data.schedule.entries : []
|
||||||
}
|
}
|
||||||
function parseSeason(item) {
|
function parseSeason(item) {
|
||||||
if (item.item.type !== 'episode') return null
|
if (item.item.type !== 'episode') return null
|
||||||
return item.item.series_number || null
|
return item.item.series_number || null
|
||||||
}
|
}
|
||||||
function parseEpisode(item) {
|
function parseEpisode(item) {
|
||||||
if (item.item.type !== 'episode') return null
|
if (item.item.type !== 'episode') return null
|
||||||
return item.item.episode_number || null
|
return item.item.episode_number || null
|
||||||
}
|
}
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
return item.item.image || null
|
return item.item.image || null
|
||||||
}
|
}
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs(item.broadcast.transmission_time)
|
return dayjs(item.broadcast.transmission_time)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseStop(item) {
|
||||||
return dayjs(item.broadcast.transmission_end_time)
|
return dayjs(item.broadcast.transmission_end_time)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,52 @@
|
||||||
// npm run grab -- --site=bt.com
|
// npm run grab -- --site=bt.com
|
||||||
|
|
||||||
const { parser, url } = require('./bt.com.config.js')
|
const { parser, url } = require('./bt.com.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2022-03-20', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-03-20', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'hsxv',
|
site_id: 'hsxv',
|
||||||
xmltv_id: 'BBCOneHD.uk'
|
xmltv_id: 'BBCOneHD.uk'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ date, channel })).toBe(
|
expect(url({ date, channel })).toBe(
|
||||||
'https://voila.metabroadcast.com/4/schedules/hsxv.json?key=b4d2edb68da14dfb9e47b5465e99b1b1&from=2022-03-20T00:00:00Z&to=2022-03-21T00:00:00Z&source=api.youview.tv&annotations=content.description'
|
'https://voila.metabroadcast.com/4/schedules/hsxv.json?key=b4d2edb68da14dfb9e47b5465e99b1b1&from=2022-03-20T00:00:00Z&to=2022-03-21T00:00:00Z&source=api.youview.tv&annotations=content.description'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content =
|
const content =
|
||||||
'{"schedule":{"channel":{"title":"BBC One HD","id":"hsxv","uri":"http://api.youview.tv/channels/dvb://233a..4484","images":[{"uri":"https://images.metabroadcast.com?source=http%3A%2F%2Fimages-live.youview.tv%2Fimages%2Fentity%2F8c4c0357-d7ee-5d8a-8bc4-b177b6875128%2Fident%2F1_1024x532.png%3Fdefaultimg%3D0&ETag=r5vyecG6of%2BhCbHeEClx0Q%3D%3D","mime_type":"image/png","type":null,"color":"monochrome","theme":"light_monochrome","aspect_ratio":null,"availability_start":null,"availability_end":null,"width":1024,"height":532,"hasTitleArt":null,"source":null}],"available_from":[{"key":"api.youview.tv","name":"YouView JSON","country":"GB"}],"source":{"key":"api.youview.tv","name":"YouView JSON","country":"GB"},"same_as":[],"media_type":"video","broadcaster":null,"aliases":[{"namespace":"youview:serviceLocator","value":"dvb://233a..4484"},{"namespace":"youview:channel:id","value":"8c4c0357-d7ee-5d8a-8bc4-b177b6875128"}],"genres":[],"high_definition":true,"timeshifted":null,"regional":null,"related_links":[],"start_date":null,"advertised_from":null,"advertised_to":null,"short_description":null,"medium_description":null,"long_description":null,"region":null,"target_regions":[],"channel_type":"CHANNEL","interactive":false,"transmission_types":["DTT"],"quality":"HD","hdr":false},"source":"api.youview.tv","entries":[{"broadcast":{"aliases":[{"namespace":"api.youview.tv:slot","value":"dvb://233a..4484;76bc"},{"namespace":"dvb:event-locator","value":"dvb://233a..4484;76bc"},{"namespace":"dvb:pcrid","value":"crid://fp.bbc.co.uk/b/3Q30S2"},{"namespace":"youview:schedule_event:id","value":"79d318f3-b41a-582d-b089-7b0172538b42"}],"transmission_time":"2022-03-19T23:30:00.000Z","transmission_end_time":"2022-03-20T01:20:00.000Z","broadcast_duration":6600,"broadcast_on":"hsxv","schedule_date":null,"repeat":null,"subtitled":true,"signed":null,"audio_described":false,"high_definition":null,"widescreen":null,"surround":null,"live":null,"premiere":null,"continuation":null,"new_series":null,"new_episode":null,"new_one_off":null,"revised_repeat":null,"blackout_restriction":{"all":false}},"item":{"id":"n72nsw","type":"item","display_title":{"title":"The Finest Hours (2016)","subtitle":null},"year":null,"media_type":"video","specialization":"tv","source":{"key":"api.youview.tv","name":"YouView JSON","country":"GB"},"title":"The Finest Hours (2016)","description":"Drama based on a true story, recounting one of history\'s most daring coastguard rescue attempts. Stranded on a sinking oil tanker along with 30 other sailors, engineer Ray Sybert battles to buy his crew more time as Captain Bernie Webber and three of his colleagues tackle gigantic waves and gale-force winds in their astonishing bid to save the seamen.","image":"https://images.metabroadcast.com?source=http%3A%2F%2Fimages-live.youview.tv%2Fimages%2Fentity%2F52172983%2Fprimary%2F1_1024x576.jpg%3Fdefaultimg%3D0&ETag=z7ucT5kdAq7HuNQf%2FGTEJg%3D%3D","thumbnail":null,"duration":null,"container":null}}]},"terms_and_conditions":{"text":"Specific terms and conditions in your agreement with MetaBroadcast, and with any data provider, apply to your use of this data, and associated systems."},"results":1,"request":{"path":"/4/schedules/hsxv.json","parameters":{"annotations":"content.description","from":"2022-03-20T00:00:00Z","to":"2022-03-21T00:00:00Z","source":"api.youview.tv","key":"b4d2edb68da14dfb9e47b5465e99b1b1"}}}'
|
'{"schedule":{"channel":{"title":"BBC One HD","id":"hsxv","uri":"http://api.youview.tv/channels/dvb://233a..4484","images":[{"uri":"https://images.metabroadcast.com?source=http%3A%2F%2Fimages-live.youview.tv%2Fimages%2Fentity%2F8c4c0357-d7ee-5d8a-8bc4-b177b6875128%2Fident%2F1_1024x532.png%3Fdefaultimg%3D0&ETag=r5vyecG6of%2BhCbHeEClx0Q%3D%3D","mime_type":"image/png","type":null,"color":"monochrome","theme":"light_monochrome","aspect_ratio":null,"availability_start":null,"availability_end":null,"width":1024,"height":532,"hasTitleArt":null,"source":null}],"available_from":[{"key":"api.youview.tv","name":"YouView JSON","country":"GB"}],"source":{"key":"api.youview.tv","name":"YouView JSON","country":"GB"},"same_as":[],"media_type":"video","broadcaster":null,"aliases":[{"namespace":"youview:serviceLocator","value":"dvb://233a..4484"},{"namespace":"youview:channel:id","value":"8c4c0357-d7ee-5d8a-8bc4-b177b6875128"}],"genres":[],"high_definition":true,"timeshifted":null,"regional":null,"related_links":[],"start_date":null,"advertised_from":null,"advertised_to":null,"short_description":null,"medium_description":null,"long_description":null,"region":null,"target_regions":[],"channel_type":"CHANNEL","interactive":false,"transmission_types":["DTT"],"quality":"HD","hdr":false},"source":"api.youview.tv","entries":[{"broadcast":{"aliases":[{"namespace":"api.youview.tv:slot","value":"dvb://233a..4484;76bc"},{"namespace":"dvb:event-locator","value":"dvb://233a..4484;76bc"},{"namespace":"dvb:pcrid","value":"crid://fp.bbc.co.uk/b/3Q30S2"},{"namespace":"youview:schedule_event:id","value":"79d318f3-b41a-582d-b089-7b0172538b42"}],"transmission_time":"2022-03-19T23:30:00.000Z","transmission_end_time":"2022-03-20T01:20:00.000Z","broadcast_duration":6600,"broadcast_on":"hsxv","schedule_date":null,"repeat":null,"subtitled":true,"signed":null,"audio_described":false,"high_definition":null,"widescreen":null,"surround":null,"live":null,"premiere":null,"continuation":null,"new_series":null,"new_episode":null,"new_one_off":null,"revised_repeat":null,"blackout_restriction":{"all":false}},"item":{"id":"n72nsw","type":"item","display_title":{"title":"The Finest Hours (2016)","subtitle":null},"year":null,"media_type":"video","specialization":"tv","source":{"key":"api.youview.tv","name":"YouView JSON","country":"GB"},"title":"The Finest Hours (2016)","description":"Drama based on a true story, recounting one of history\'s most daring coastguard rescue attempts. Stranded on a sinking oil tanker along with 30 other sailors, engineer Ray Sybert battles to buy his crew more time as Captain Bernie Webber and three of his colleagues tackle gigantic waves and gale-force winds in their astonishing bid to save the seamen.","image":"https://images.metabroadcast.com?source=http%3A%2F%2Fimages-live.youview.tv%2Fimages%2Fentity%2F52172983%2Fprimary%2F1_1024x576.jpg%3Fdefaultimg%3D0&ETag=z7ucT5kdAq7HuNQf%2FGTEJg%3D%3D","thumbnail":null,"duration":null,"container":null}}]},"terms_and_conditions":{"text":"Specific terms and conditions in your agreement with MetaBroadcast, and with any data provider, apply to your use of this data, and associated systems."},"results":1,"request":{"path":"/4/schedules/hsxv.json","parameters":{"annotations":"content.description","from":"2022-03-20T00:00:00Z","to":"2022-03-21T00:00:00Z","source":"api.youview.tv","key":"b4d2edb68da14dfb9e47b5465e99b1b1"}}}'
|
||||||
|
|
||||||
const result = parser({ content }).map(p => {
|
const result = parser({ content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
title: 'The Finest Hours (2016)',
|
title: 'The Finest Hours (2016)',
|
||||||
description:
|
description:
|
||||||
"Drama based on a true story, recounting one of history's most daring coastguard rescue attempts. Stranded on a sinking oil tanker along with 30 other sailors, engineer Ray Sybert battles to buy his crew more time as Captain Bernie Webber and three of his colleagues tackle gigantic waves and gale-force winds in their astonishing bid to save the seamen.",
|
"Drama based on a true story, recounting one of history's most daring coastguard rescue attempts. Stranded on a sinking oil tanker along with 30 other sailors, engineer Ray Sybert battles to buy his crew more time as Captain Bernie Webber and three of his colleagues tackle gigantic waves and gale-force winds in their astonishing bid to save the seamen.",
|
||||||
icon: 'https://images.metabroadcast.com?source=http%3A%2F%2Fimages-live.youview.tv%2Fimages%2Fentity%2F52172983%2Fprimary%2F1_1024x576.jpg%3Fdefaultimg%3D0&ETag=z7ucT5kdAq7HuNQf%2FGTEJg%3D%3D',
|
icon: 'https://images.metabroadcast.com?source=http%3A%2F%2Fimages-live.youview.tv%2Fimages%2Fentity%2F52172983%2Fprimary%2F1_1024x576.jpg%3Fdefaultimg%3D0&ETag=z7ucT5kdAq7HuNQf%2FGTEJg%3D%3D',
|
||||||
season: null,
|
season: null,
|
||||||
episode: null,
|
episode: null,
|
||||||
start: '2022-03-19T23:30:00.000Z',
|
start: '2022-03-19T23:30:00.000Z',
|
||||||
stop: '2022-03-20T01:20:00.000Z'
|
stop: '2022-03-20T01:20:00.000Z'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
content:
|
content:
|
||||||
'{"schedule":{"channel":{"title":"BBC One HD","id":"hsxv","uri":"http://api.youview.tv/channels/dvb://233a..4484","images":[{"uri":"https://images.metabroadcast.com?source=http%3A%2F%2Fimages-live.youview.tv%2Fimages%2Fentity%2F8c4c0357-d7ee-5d8a-8bc4-b177b6875128%2Fident%2F1_1024x532.png%3Fdefaultimg%3D0&ETag=r5vyecG6of%2BhCbHeEClx0Q%3D%3D","mime_type":"image/png","type":null,"color":"monochrome","theme":"light_monochrome","aspect_ratio":null,"availability_start":null,"availability_end":null,"width":1024,"height":532,"hasTitleArt":null,"source":null}],"available_from":[{"key":"api.youview.tv","name":"YouView JSON","country":"GB"}],"source":{"key":"api.youview.tv","name":"YouView JSON","country":"GB"},"same_as":[],"media_type":"video","broadcaster":null,"aliases":[{"namespace":"youview:serviceLocator","value":"dvb://233a..4484"},{"namespace":"youview:channel:id","value":"8c4c0357-d7ee-5d8a-8bc4-b177b6875128"}],"genres":[],"high_definition":true,"timeshifted":null,"regional":null,"related_links":[],"start_date":null,"advertised_from":null,"advertised_to":null,"short_description":null,"medium_description":null,"long_description":null,"region":null,"target_regions":[],"channel_type":"CHANNEL","interactive":false,"transmission_types":["DTT"],"quality":"HD","hdr":false},"source":"api.youview.tv","entries":[]},"terms_and_conditions":{"text":"Specific terms and conditions in your agreement with MetaBroadcast, and with any data provider, apply to your use of this data, and associated systems."},"results":1,"request":{"path":"/4/schedules/hsxv.json","parameters":{"annotations":"content.description","from":"2022-03-20T00:00:00Z","to":"2022-03-21T00:00:00Z","source":"api.youview.tv","key":"b4d2edb68da14dfb9e47b5465e99b1b1"}}}'
|
'{"schedule":{"channel":{"title":"BBC One HD","id":"hsxv","uri":"http://api.youview.tv/channels/dvb://233a..4484","images":[{"uri":"https://images.metabroadcast.com?source=http%3A%2F%2Fimages-live.youview.tv%2Fimages%2Fentity%2F8c4c0357-d7ee-5d8a-8bc4-b177b6875128%2Fident%2F1_1024x532.png%3Fdefaultimg%3D0&ETag=r5vyecG6of%2BhCbHeEClx0Q%3D%3D","mime_type":"image/png","type":null,"color":"monochrome","theme":"light_monochrome","aspect_ratio":null,"availability_start":null,"availability_end":null,"width":1024,"height":532,"hasTitleArt":null,"source":null}],"available_from":[{"key":"api.youview.tv","name":"YouView JSON","country":"GB"}],"source":{"key":"api.youview.tv","name":"YouView JSON","country":"GB"},"same_as":[],"media_type":"video","broadcaster":null,"aliases":[{"namespace":"youview:serviceLocator","value":"dvb://233a..4484"},{"namespace":"youview:channel:id","value":"8c4c0357-d7ee-5d8a-8bc4-b177b6875128"}],"genres":[],"high_definition":true,"timeshifted":null,"regional":null,"related_links":[],"start_date":null,"advertised_from":null,"advertised_to":null,"short_description":null,"medium_description":null,"long_description":null,"region":null,"target_regions":[],"channel_type":"CHANNEL","interactive":false,"transmission_types":["DTT"],"quality":"HD","hdr":false},"source":"api.youview.tv","entries":[]},"terms_and_conditions":{"text":"Specific terms and conditions in your agreement with MetaBroadcast, and with any data provider, apply to your use of this data, and associated systems."},"results":1,"request":{"path":"/4/schedules/hsxv.json","parameters":{"annotations":"content.description","from":"2022-03-20T00:00:00Z","to":"2022-03-21T00:00:00Z","source":"api.youview.tv","key":"b4d2edb68da14dfb9e47b5465e99b1b1"}}}'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,109 +1,109 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'cablego.com.pe',
|
site: 'cablego.com.pe',
|
||||||
days: 2,
|
days: 2,
|
||||||
request: {
|
request: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'x-requested-with': 'XMLHttpRequest'
|
'x-requested-with': 'XMLHttpRequest'
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 60 * 60 * 1000 // 1 hour
|
ttl: 60 * 60 * 1000 // 1 hour
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url({ channel, date }) {
|
url({ channel, date }) {
|
||||||
const [page] = channel.site_id.split('#')
|
const [page] = channel.site_id.split('#')
|
||||||
|
|
||||||
return `https://cablego.com.pe/epg/default/${date.format(
|
return `https://cablego.com.pe/epg/default/${date.format(
|
||||||
'YYYY-MM-DD'
|
'YYYY-MM-DD'
|
||||||
)}?page=${page}&do=loadPage`
|
)}?page=${page}&do=loadPage`
|
||||||
},
|
},
|
||||||
parser: function ({ content, channel, date }) {
|
parser: function ({ content, channel, date }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
let start = parseStart($item, date)
|
let start = parseStart($item, date)
|
||||||
if (!start) return
|
if (!start) return
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (start.isBefore(prev.start)) {
|
if (start.isBefore(prev.start)) {
|
||||||
start = start.add(1, 'd')
|
start = start.add(1, 'd')
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
prev.stop = start
|
prev.stop = start
|
||||||
}
|
}
|
||||||
const stop = start.add(30, 'm')
|
const stop = start.add(30, 'm')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle($item),
|
title: parseTitle($item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const promises = [0, 1, 2, 3, 4].map(page => {
|
const promises = [0, 1, 2, 3, 4].map(page => {
|
||||||
return axios.post(
|
return axios.post(
|
||||||
`https://cablego.com.pe/epg/default/2022-11-28?page=${page}&do=loadPage`,
|
`https://cablego.com.pe/epg/default/2022-11-28?page=${page}&do=loadPage`,
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'x-requested-with': 'XMLHttpRequest'
|
'x-requested-with': 'XMLHttpRequest'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const channels = []
|
const channels = []
|
||||||
await Promise.allSettled(promises).then(results => {
|
await Promise.allSettled(promises).then(results => {
|
||||||
results.forEach((r, page) => {
|
results.forEach((r, page) => {
|
||||||
if (r.status === 'fulfilled') {
|
if (r.status === 'fulfilled') {
|
||||||
const html = r.value.data.snippets['snippet--channelGrid']
|
const html = r.value.data.snippets['snippet--channelGrid']
|
||||||
const $ = cheerio.load(html)
|
const $ = cheerio.load(html)
|
||||||
$('.epg-channel-strip').each((i, el) => {
|
$('.epg-channel-strip').each((i, el) => {
|
||||||
const channelId = $(el).find('.epg-channel-logo').attr('id')
|
const channelId = $(el).find('.epg-channel-logo').attr('id')
|
||||||
channels.push({
|
channels.push({
|
||||||
lang: 'es',
|
lang: 'es',
|
||||||
site_id: `${page}#${channelId}`,
|
site_id: `${page}#${channelId}`,
|
||||||
name: $(el).find('img').attr('alt')
|
name: $(el).find('img').attr('alt')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('span:nth-child(2) > a').text().trim()
|
return $item('span:nth-child(2) > a').text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
const time = $item('.epg-show-start').text().trim()
|
const time = $item('.epg-show-start').text().trim()
|
||||||
|
|
||||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'America/Lima')
|
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'America/Lima')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
const [, channelId] = channel.site_id.split('#')
|
const [, channelId] = channel.site_id.split('#')
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !data.snippets || !data.snippets['snippet--channelGrid']) return []
|
if (!data || !data.snippets || !data.snippets['snippet--channelGrid']) return []
|
||||||
const html = data.snippets['snippet--channelGrid']
|
const html = data.snippets['snippet--channelGrid']
|
||||||
const $ = cheerio.load(html)
|
const $ = cheerio.load(html)
|
||||||
|
|
||||||
return $(`#${channelId}`).parent().find('.epg-show').toArray()
|
return $(`#${channelId}`).parent().find('.epg-show').toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,54 @@
|
||||||
// npm run channels:parse -- --config=./sites/cablego.com.pe/cablego.com.pe.config.js --output=./sites/cablego.com.pe/cablego.com.pe.channels.xml
|
// npm run channels:parse -- --config=./sites/cablego.com.pe/cablego.com.pe.config.js --output=./sites/cablego.com.pe/cablego.com.pe.channels.xml
|
||||||
// npm run grab -- --site=cablego.com.pe
|
// npm run grab -- --site=cablego.com.pe
|
||||||
|
|
||||||
const { parser, url, request } = require('./cablego.com.pe.config.js')
|
const { parser, url, request } = require('./cablego.com.pe.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2022-11-28', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-11-28', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '0#LATINA',
|
site_id: '0#LATINA',
|
||||||
xmltv_id: 'Latina.pe'
|
xmltv_id: 'Latina.pe'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'https://cablego.com.pe/epg/default/2022-11-28?page=0&do=loadPage'
|
'https://cablego.com.pe/epg/default/2022-11-28?page=0&do=loadPage'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request method', () => {
|
it('can generate valid request method', () => {
|
||||||
expect(request.method).toBe('POST')
|
expect(request.method).toBe('POST')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request headers', () => {
|
it('can generate valid request headers', () => {
|
||||||
expect(request.headers).toMatchObject({
|
expect(request.headers).toMatchObject({
|
||||||
'x-requested-with': 'XMLHttpRequest'
|
'x-requested-with': 'XMLHttpRequest'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
let results = parser({ content, channel, date }).map(p => {
|
let results = parser({ content, channel, date }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-11-28T05:00:00.000Z',
|
start: '2022-11-28T05:00:00.000Z',
|
||||||
stop: '2022-11-28T06:30:00.000Z',
|
stop: '2022-11-28T06:30:00.000Z',
|
||||||
title: 'Especiales Qatar'
|
title: 'Especiales Qatar'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||||
const result = parser({ content, channel, date })
|
const result = parser({ content, channel, date })
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,133 +1,133 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
const API_ENDPOINT = 'https://www.reportv.com.ar/finder'
|
const API_ENDPOINT = 'https://www.reportv.com.ar/finder'
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'cableplus.com.uy',
|
site: 'cableplus.com.uy',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: `${API_ENDPOINT}/channel`,
|
url: `${API_ENDPOINT}/channel`,
|
||||||
request: {
|
request: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||||
},
|
},
|
||||||
data({ date, channel }) {
|
data({ date, channel }) {
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
params.append('idAlineacion', '3017')
|
params.append('idAlineacion', '3017')
|
||||||
params.append('idSenial', channel.site_id)
|
params.append('idSenial', channel.site_id)
|
||||||
params.append('fecha', date.format('YYYY-MM-DD'))
|
params.append('fecha', date.format('YYYY-MM-DD'))
|
||||||
params.append('hora', '00:00')
|
params.append('hora', '00:00')
|
||||||
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser({ content, date }) {
|
parser({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content, date)
|
const items = parseItems(content, date)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
let start = parseStart($item, date)
|
let start = parseStart($item, date)
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (start.isBefore(prev.start)) {
|
if (start.isBefore(prev.start)) {
|
||||||
start = start.add(1, 'd')
|
start = start.add(1, 'd')
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
prev.stop = start
|
prev.stop = start
|
||||||
}
|
}
|
||||||
const stop = start.add(30, 'm')
|
const stop = start.add(30, 'm')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle($item),
|
title: parseTitle($item),
|
||||||
categories: parseCategories($item),
|
categories: parseCategories($item),
|
||||||
icon: parseIcon($item),
|
icon: parseIcon($item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const params = new URLSearchParams({ idAlineacion: '3017' })
|
const params = new URLSearchParams({ idAlineacion: '3017' })
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.post(`${API_ENDPOINT}/channelGrid`, params, {
|
.post(`${API_ENDPOINT}/channelGrid`, params, {
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }
|
||||||
})
|
})
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
const $ = cheerio.load(data)
|
const $ = cheerio.load(data)
|
||||||
|
|
||||||
return $('.senial')
|
return $('.senial')
|
||||||
.map(function () {
|
.map(function () {
|
||||||
return {
|
return {
|
||||||
lang: 'es',
|
lang: 'es',
|
||||||
site_id: $(this).attr('id'),
|
site_id: $(this).attr('id'),
|
||||||
name: $(this).find('img').attr('alt')
|
name: $(this).find('img').attr('alt')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.get()
|
.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('p.evento_titulo.texto_a_continuacion.dotdotdot,.programa-titulo > span:first-child')
|
return $item('p.evento_titulo.texto_a_continuacion.dotdotdot,.programa-titulo > span:first-child')
|
||||||
.text()
|
.text()
|
||||||
.trim()
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon($item) {
|
function parseIcon($item) {
|
||||||
return $item('img').data('src') || $item('img').attr('src') || null
|
return $item('img').data('src') || $item('img').attr('src') || null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategories($item) {
|
function parseCategories($item) {
|
||||||
return $item('p.evento_genero')
|
return $item('p.evento_genero')
|
||||||
.map(function () {
|
.map(function () {
|
||||||
return $item(this).text().trim()
|
return $item(this).text().trim()
|
||||||
})
|
})
|
||||||
.toArray()
|
.toArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
let time = $item('.grid_fecha_hora').text().trim()
|
let time = $item('.grid_fecha_hora').text().trim()
|
||||||
|
|
||||||
if (time) {
|
if (time) {
|
||||||
return dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD-MM HH:mm[hs.]', 'America/Montevideo')
|
return dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD-MM HH:mm[hs.]', 'America/Montevideo')
|
||||||
}
|
}
|
||||||
|
|
||||||
time = $item('.fechaHora').text().trim()
|
time = $item('.fechaHora').text().trim()
|
||||||
|
|
||||||
return time
|
return time
|
||||||
? dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD/MM HH:mm[hs.]', 'America/Montevideo')
|
? dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD/MM HH:mm[hs.]', 'America/Montevideo')
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, date) {
|
function parseItems(content, date) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
let featuredItems = $('.vista-pc > .programacion-fila > .channel-programa')
|
let featuredItems = $('.vista-pc > .programacion-fila > .channel-programa')
|
||||||
.filter(function () {
|
.filter(function () {
|
||||||
return $(this).find('.grid_fecha_hora').text().indexOf(date.format('DD-MM')) > -1
|
return $(this).find('.grid_fecha_hora').text().indexOf(date.format('DD-MM')) > -1
|
||||||
})
|
})
|
||||||
.toArray()
|
.toArray()
|
||||||
let otherItems = $('#owl-pc > .item-program')
|
let otherItems = $('#owl-pc > .item-program')
|
||||||
.filter(function () {
|
.filter(function () {
|
||||||
return (
|
return (
|
||||||
$(this)
|
$(this)
|
||||||
.find('.evento_titulo > .horario > p.fechaHora')
|
.find('.evento_titulo > .horario > p.fechaHora')
|
||||||
.text()
|
.text()
|
||||||
.indexOf(date.format('DD/MM')) > -1
|
.indexOf(date.format('DD/MM')) > -1
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.toArray()
|
.toArray()
|
||||||
|
|
||||||
return featuredItems.concat(otherItems)
|
return featuredItems.concat(otherItems)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +1,76 @@
|
||||||
// npm run channels:parse -- --config=./sites/cableplus.com.uy/cableplus.com.uy.config.js --output=./sites/cableplus.com.uy/cableplus.com.uy.channels.xml
|
// npm run channels:parse -- --config=./sites/cableplus.com.uy/cableplus.com.uy.config.js --output=./sites/cableplus.com.uy/cableplus.com.uy.channels.xml
|
||||||
// npm run grab -- --site=cableplus.com.uy
|
// npm run grab -- --site=cableplus.com.uy
|
||||||
|
|
||||||
const { parser, url, request } = require('./cableplus.com.uy.config.js')
|
const { parser, url, request } = require('./cableplus.com.uy.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-02-12', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-02-12', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '2035',
|
site_id: '2035',
|
||||||
xmltv_id: 'APlusV.uy'
|
xmltv_id: 'APlusV.uy'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url).toBe('https://www.reportv.com.ar/finder/channel')
|
expect(url).toBe('https://www.reportv.com.ar/finder/channel')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request method', () => {
|
it('can generate valid request method', () => {
|
||||||
expect(request.method).toBe('POST')
|
expect(request.method).toBe('POST')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request headers', () => {
|
it('can generate valid request headers', () => {
|
||||||
expect(request.headers).toMatchObject({
|
expect(request.headers).toMatchObject({
|
||||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request data', () => {
|
it('can generate valid request data', () => {
|
||||||
const params = request.data({ date, channel })
|
const params = request.data({ date, channel })
|
||||||
|
|
||||||
expect(params.get('idAlineacion')).toBe('3017')
|
expect(params.get('idAlineacion')).toBe('3017')
|
||||||
expect(params.get('idSenial')).toBe('2035')
|
expect(params.get('idSenial')).toBe('2035')
|
||||||
expect(params.get('fecha')).toBe('2023-02-12')
|
expect(params.get('fecha')).toBe('2023-02-12')
|
||||||
expect(params.get('hora')).toBe('00:00')
|
expect(params.get('hora')).toBe('00:00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||||
let results = parser({ content, date })
|
let results = parser({ content, date })
|
||||||
results = results.map(p => {
|
results = results.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results.length).toBe(21)
|
expect(results.length).toBe(21)
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-02-12T09:30:00.000Z',
|
start: '2023-02-12T09:30:00.000Z',
|
||||||
stop: '2023-02-12T10:30:00.000Z',
|
stop: '2023-02-12T10:30:00.000Z',
|
||||||
title: 'Revista agropecuaria',
|
title: 'Revista agropecuaria',
|
||||||
icon: 'https://www.reportv.com.ar/buscador/img/Programas/2797844.jpg',
|
icon: 'https://www.reportv.com.ar/buscador/img/Programas/2797844.jpg',
|
||||||
categories: []
|
categories: []
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[4]).toMatchObject({
|
expect(results[4]).toMatchObject({
|
||||||
start: '2023-02-12T12:30:00.000Z',
|
start: '2023-02-12T12:30:00.000Z',
|
||||||
stop: '2023-02-12T13:30:00.000Z',
|
stop: '2023-02-12T13:30:00.000Z',
|
||||||
title: 'De pago en pago',
|
title: 'De pago en pago',
|
||||||
icon: 'https://www.reportv.com.ar/buscador/img/Programas/3772835.jpg',
|
icon: 'https://www.reportv.com.ar/buscador/img/Programas/3772835.jpg',
|
||||||
categories: ['Cultural']
|
categories: ['Cultural']
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,93 +1,93 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'canalplus-caraibes.com',
|
site: 'canalplus-caraibes.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ channel, date }) {
|
url: function ({ channel, date }) {
|
||||||
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
||||||
|
|
||||||
return `https://service.canal-overseas.com/ott-frontend/vector/53001/channel/${channel.site_id}/events?filter.day=${diff}`
|
return `https://service.canal-overseas.com/ott-frontend/vector/53001/channel/${channel.site_id}/events?filter.day=${diff}`
|
||||||
},
|
},
|
||||||
async parser({ content }) {
|
async parser({ content }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
if (item.title === 'Fin des programmes') return
|
if (item.title === 'Fin des programmes') return
|
||||||
const detail = await loadProgramDetails(item)
|
const detail = await loadProgramDetails(item)
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
description: parseDescription(detail),
|
description: parseDescription(detail),
|
||||||
category: parseCategory(detail),
|
category: parseCategory(detail),
|
||||||
icon: parseIcon(item),
|
icon: parseIcon(item),
|
||||||
start: parseStart(item),
|
start: parseStart(item),
|
||||||
stop: parseStop(item)
|
stop: parseStop(item)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const html = await axios
|
const html = await axios
|
||||||
.get('https://www.canalplus-caraibes.com/bl/guide-tv-ce-soir')
|
.get('https://www.canalplus-caraibes.com/bl/guide-tv-ce-soir')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
const $ = cheerio.load(html)
|
const $ = cheerio.load(html)
|
||||||
const script = $('body > script:nth-child(2)').html()
|
const script = $('body > script:nth-child(2)').html()
|
||||||
const [, json] = script.match(/window.APP_STATE=(.*);/) || [null, null]
|
const [, json] = script.match(/window.APP_STATE=(.*);/) || [null, null]
|
||||||
const data = JSON.parse(json)
|
const data = JSON.parse(json)
|
||||||
const items = data.tvGuide.channels.byZapNumber
|
const items = data.tvGuide.channels.byZapNumber
|
||||||
|
|
||||||
return Object.values(items).map(item => {
|
return Object.values(items).map(item => {
|
||||||
return {
|
return {
|
||||||
lang: 'fr',
|
lang: 'fr',
|
||||||
site_id: item.epgID,
|
site_id: item.epgID,
|
||||||
name: item.name
|
name: item.name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProgramDetails(item) {
|
async function loadProgramDetails(item) {
|
||||||
if (!item.onClick.URLPage) return {}
|
if (!item.onClick.URLPage) return {}
|
||||||
const url = item.onClick.URLPage
|
const url = item.onClick.URLPage
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
return data || {}
|
return data || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription(detail) {
|
function parseDescription(detail) {
|
||||||
return detail.detail.informations.summary || null
|
return detail.detail.informations.summary || null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory(detail) {
|
function parseCategory(detail) {
|
||||||
return detail.detail.informations.subGenre || null
|
return detail.detail.informations.subGenre || null
|
||||||
}
|
}
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
return item.URLImage || item.URLImageDefault
|
return item.URLImage || item.URLImageDefault
|
||||||
}
|
}
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs.unix(item.startTime)
|
return dayjs.unix(item.startTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseStop(item) {
|
||||||
return dayjs.unix(item.endTime)
|
return dayjs.unix(item.endTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !data.timeSlices) return []
|
if (!data || !data.timeSlices) return []
|
||||||
const items = data.timeSlices.reduce((acc, curr) => {
|
const items = data.timeSlices.reduce((acc, curr) => {
|
||||||
acc = acc.concat(curr.contents)
|
acc = acc.concat(curr.contents)
|
||||||
return acc
|
return acc
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,137 +1,137 @@
|
||||||
// [Geo-blocked] node ./scripts/channels.js --config=./sites/canalplus-caraibes.com/canalplus-caraibes.com.config.js --output=./sites/canalplus-caraibes.com/canalplus-caraibes.com.channels.xml
|
// [Geo-blocked] node ./scripts/channels.js --config=./sites/canalplus-caraibes.com/canalplus-caraibes.com.config.js --output=./sites/canalplus-caraibes.com/canalplus-caraibes.com.channels.xml
|
||||||
// npm run grab -- --site=canalplus-caraibes.com
|
// npm run grab -- --site=canalplus-caraibes.com
|
||||||
|
|
||||||
const { parser, url } = require('./canalplus-caraibes.com.config.js')
|
const { parser, url } = require('./canalplus-caraibes.com.config.js')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '50115',
|
site_id: '50115',
|
||||||
xmltv_id: 'beINSports1France.fr'
|
xmltv_id: 'beINSports1France.fr'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url for today', () => {
|
it('can generate valid url for today', () => {
|
||||||
const date = dayjs.utc().startOf('d')
|
const date = dayjs.utc().startOf('d')
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'https://service.canal-overseas.com/ott-frontend/vector/53001/channel/50115/events?filter.day=0'
|
'https://service.canal-overseas.com/ott-frontend/vector/53001/channel/50115/events?filter.day=0'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid url for tomorrow', () => {
|
it('can generate valid url for tomorrow', () => {
|
||||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'https://service.canal-overseas.com/ott-frontend/vector/53001/channel/50115/events?filter.day=1'
|
'https://service.canal-overseas.com/ott-frontend/vector/53001/channel/50115/events?filter.day=1'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', done => {
|
it('can parse response', done => {
|
||||||
const content =
|
const content =
|
||||||
'{"timeSlices":[{"contents":[{"title":"Rugby - Leinster / La Rochelle","subtitle":"Rugby","thirdTitle":"BEIN SPORTS 1 HD","startTime":1660815000,"endTime":1660816800,"onClick":{"displayTemplate":"miniDetail","displayName":"Rugby - Leinster / La Rochelle","URLPage":"https://service.canal-overseas.com/ott-frontend/vector/53001/event/140377765","URLVitrine":"https://service.canal-overseas.com/ott-frontend/vector/53001/program/224515801/recommendations"},"programID":224515801,"diffusionID":"140377765","URLImageDefault":"https://service.canal-overseas.com/image-api/v1/image/75fca4586fdc3458930dd1ab6fc2e643","URLImage":"https://service.canal-overseas.com/image-api/v1/image/7854e20fb6efecd398598653c57cc771"}],"timeSlice":"4"}]}'
|
'{"timeSlices":[{"contents":[{"title":"Rugby - Leinster / La Rochelle","subtitle":"Rugby","thirdTitle":"BEIN SPORTS 1 HD","startTime":1660815000,"endTime":1660816800,"onClick":{"displayTemplate":"miniDetail","displayName":"Rugby - Leinster / La Rochelle","URLPage":"https://service.canal-overseas.com/ott-frontend/vector/53001/event/140377765","URLVitrine":"https://service.canal-overseas.com/ott-frontend/vector/53001/program/224515801/recommendations"},"programID":224515801,"diffusionID":"140377765","URLImageDefault":"https://service.canal-overseas.com/image-api/v1/image/75fca4586fdc3458930dd1ab6fc2e643","URLImage":"https://service.canal-overseas.com/image-api/v1/image/7854e20fb6efecd398598653c57cc771"}],"timeSlice":"4"}]}'
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/53001/event/140377765') {
|
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/53001/event/140377765') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(`{
|
data: JSON.parse(`{
|
||||||
"currentPage": {
|
"currentPage": {
|
||||||
"displayName": "Rugby - Leinster / La Rochelle",
|
"displayName": "Rugby - Leinster / La Rochelle",
|
||||||
"displayTemplate": "detailPage",
|
"displayTemplate": "detailPage",
|
||||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53001/program/224515801/recommendations"
|
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53001/program/224515801/recommendations"
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"informations": {
|
"informations": {
|
||||||
"programmeType": "EPG",
|
"programmeType": "EPG",
|
||||||
"isInOffer": false,
|
"isInOffer": false,
|
||||||
"isInOfferOnDevice": false,
|
"isInOfferOnDevice": false,
|
||||||
"isInOfferForD2G": false,
|
"isInOfferForD2G": false,
|
||||||
"availableInVoDOnDevice": false,
|
"availableInVoDOnDevice": false,
|
||||||
"availableInVoDOnG5": false,
|
"availableInVoDOnG5": false,
|
||||||
"availableInD2GOnDevice": false,
|
"availableInD2GOnDevice": false,
|
||||||
"availableInLiveOnDevice": false,
|
"availableInLiveOnDevice": false,
|
||||||
"rediffusions": true,
|
"rediffusions": true,
|
||||||
"canBeRecorded": false,
|
"canBeRecorded": false,
|
||||||
"channelName": "BEIN SPORTS 1 HD",
|
"channelName": "BEIN SPORTS 1 HD",
|
||||||
"startTime": 1660815000,
|
"startTime": 1660815000,
|
||||||
"endTime": 1660816800,
|
"endTime": 1660816800,
|
||||||
"title": "Rugby - Leinster / La Rochelle",
|
"title": "Rugby - Leinster / La Rochelle",
|
||||||
"subtitle": "Rugby",
|
"subtitle": "Rugby",
|
||||||
"thirdTitle": "BEIN SPORTS 1 HD",
|
"thirdTitle": "BEIN SPORTS 1 HD",
|
||||||
"genre": "Sport",
|
"genre": "Sport",
|
||||||
"subGenre": "Rugby",
|
"subGenre": "Rugby",
|
||||||
"editorialTitle": "Sport, France, 0h30",
|
"editorialTitle": "Sport, France, 0h30",
|
||||||
"audioLanguage": "VF",
|
"audioLanguage": "VF",
|
||||||
"summary": "Retransmission d'un match de Champions Cup de rugby à XV. L'European Rugby Champions Cup est une compétition annuelle interclubs de rugby à XV disputée par les meilleures équipes en Europe. Jusqu'en 2014, cette compétition s'appelait Heineken Cup, ou H Cup, et était sous l'égide de l'ERC, et depuis cette date l'EPRC lui a succédé. La première édition s'est déroulée en 1995.",
|
"summary": "Retransmission d'un match de Champions Cup de rugby à XV. L'European Rugby Champions Cup est une compétition annuelle interclubs de rugby à XV disputée par les meilleures équipes en Europe. Jusqu'en 2014, cette compétition s'appelait Heineken Cup, ou H Cup, et était sous l'égide de l'ERC, et depuis cette date l'EPRC lui a succédé. La première édition s'est déroulée en 1995.",
|
||||||
"summaryMedium": "Retransmission d'un match de Champions Cup de rugby à XV. L'European Rugby Champions Cup est une compétition annuelle interclubs de rugby à XV disputée par les meilleures équipes en Europe. Jusqu'en 2014, cette compétition s'appelait Heineken Cup, ou H Cup, et était sous l'égide de l'ERC, et depuis cette date l'EPRC lui a succédé. La première édition s'est déroulée en 1995.",
|
"summaryMedium": "Retransmission d'un match de Champions Cup de rugby à XV. L'European Rugby Champions Cup est une compétition annuelle interclubs de rugby à XV disputée par les meilleures équipes en Europe. Jusqu'en 2014, cette compétition s'appelait Heineken Cup, ou H Cup, et était sous l'égide de l'ERC, et depuis cette date l'EPRC lui a succédé. La première édition s'est déroulée en 1995.",
|
||||||
"programID": 224515801,
|
"programID": 224515801,
|
||||||
"sharingURL": "https://www.canalplus-caraibes.com/grille-tv/event/140377765-rugby-leinster-la-rochelle.html",
|
"sharingURL": "https://www.canalplus-caraibes.com/grille-tv/event/140377765-rugby-leinster-la-rochelle.html",
|
||||||
"EpgId": 50115,
|
"EpgId": 50115,
|
||||||
"CSA": 1,
|
"CSA": 1,
|
||||||
"HD": false,
|
"HD": false,
|
||||||
"3D": false,
|
"3D": false,
|
||||||
"diffusionID": "140377765",
|
"diffusionID": "140377765",
|
||||||
"duration": "1800",
|
"duration": "1800",
|
||||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/75fca4586fdc3458930dd1ab6fc2e643",
|
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/75fca4586fdc3458930dd1ab6fc2e643",
|
||||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/7854e20fb6efecd398598653c57cc771",
|
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/7854e20fb6efecd398598653c57cc771",
|
||||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e",
|
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e",
|
||||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e",
|
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e",
|
||||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53001/program/224515801/recommendations"
|
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53001/program/224515801/recommendations"
|
||||||
},
|
},
|
||||||
"diffusions": [
|
"diffusions": [
|
||||||
{
|
{
|
||||||
"diffusionDateUTC": 1660815000,
|
"diffusionDateUTC": 1660815000,
|
||||||
"sharingUrl": "https://www.canalplus-caraibes.com/grille-tv/event/140377765-rugby-leinster-la-rochelle.html",
|
"sharingUrl": "https://www.canalplus-caraibes.com/grille-tv/event/140377765-rugby-leinster-la-rochelle.html",
|
||||||
"broadcastId": "140377765",
|
"broadcastId": "140377765",
|
||||||
"name": "BEIN SPORTS 1 HD",
|
"name": "BEIN SPORTS 1 HD",
|
||||||
"epgID": "50115",
|
"epgID": "50115",
|
||||||
"ZapNumber": "191",
|
"ZapNumber": "191",
|
||||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e",
|
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e",
|
||||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e"
|
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`)
|
}`)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: '' })
|
return Promise.resolve({ data: '' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
parser({ content })
|
parser({ content })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
result = result.map(p => {
|
result = result.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2022-08-18T09:30:00.000Z',
|
start: '2022-08-18T09:30:00.000Z',
|
||||||
stop: '2022-08-18T10:00:00.000Z',
|
stop: '2022-08-18T10:00:00.000Z',
|
||||||
title: 'Rugby - Leinster / La Rochelle',
|
title: 'Rugby - Leinster / La Rochelle',
|
||||||
icon: 'https://service.canal-overseas.com/image-api/v1/image/7854e20fb6efecd398598653c57cc771',
|
icon: 'https://service.canal-overseas.com/image-api/v1/image/7854e20fb6efecd398598653c57cc771',
|
||||||
category: 'Rugby',
|
category: 'Rugby',
|
||||||
description:
|
description:
|
||||||
"Retransmission d'un match de Champions Cup de rugby à XV. L'European Rugby Champions Cup est une compétition annuelle interclubs de rugby à XV disputée par les meilleures équipes en Europe. Jusqu'en 2014, cette compétition s'appelait Heineken Cup, ou H Cup, et était sous l'égide de l'ERC, et depuis cette date l'EPRC lui a succédé. La première édition s'est déroulée en 1995."
|
"Retransmission d'un match de Champions Cup de rugby à XV. L'European Rugby Champions Cup est une compétition annuelle interclubs de rugby à XV disputée par les meilleures équipes en Europe. Jusqu'en 2014, cette compétition s'appelait Heineken Cup, ou H Cup, et était sous l'égide de l'ERC, et depuis cette date l'EPRC lui a succédé. La première édition s'est déroulée en 1995."
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', done => {
|
it('can handle empty guide', done => {
|
||||||
parser({
|
parser({
|
||||||
content:
|
content:
|
||||||
'{"currentPage":{"displayTemplate":"error","BOName":"Page introuvable"},"title":"Page introuvable","text":"La page que vous demandez est introuvable. Si le problème persiste, vous pouvez contacter l\'assistance de CANAL+/CANALSAT.","code":404}'
|
'{"currentPage":{"displayTemplate":"error","BOName":"Page introuvable"},"title":"Page introuvable","text":"La page que vous demandez est introuvable. Si le problème persiste, vous pouvez contacter l\'assistance de CANAL+/CANALSAT.","code":404}'
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,94 +1,94 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'canalplus-haiti.com',
|
site: 'canalplus-haiti.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ channel, date }) {
|
url: function ({ channel, date }) {
|
||||||
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
||||||
|
|
||||||
return `https://service.canal-overseas.com/ott-frontend/vector/53101/channel/${channel.site_id}/events?filter.day=${diff}`
|
return `https://service.canal-overseas.com/ott-frontend/vector/53101/channel/${channel.site_id}/events?filter.day=${diff}`
|
||||||
},
|
},
|
||||||
async parser({ content }) {
|
async parser({ content }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
if (item.title === 'Fin des programmes') return
|
if (item.title === 'Fin des programmes') return
|
||||||
const detail = await loadProgramDetails(item)
|
const detail = await loadProgramDetails(item)
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
description: parseDescription(detail),
|
description: parseDescription(detail),
|
||||||
category: parseCategory(detail),
|
category: parseCategory(detail),
|
||||||
icon: parseIcon(item),
|
icon: parseIcon(item),
|
||||||
start: parseStart(item),
|
start: parseStart(item),
|
||||||
stop: parseStop(item)
|
stop: parseStop(item)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const html = await axios
|
const html = await axios
|
||||||
.get('https://www.canalplus-haiti.com/guide-tv-ce-soir')
|
.get('https://www.canalplus-haiti.com/guide-tv-ce-soir')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
const $ = cheerio.load(html)
|
const $ = cheerio.load(html)
|
||||||
const script = $('body > script:nth-child(2)').html()
|
const script = $('body > script:nth-child(2)').html()
|
||||||
const [, json] = script.match(/window.APP_STATE=(.*);/) || [null, null]
|
const [, json] = script.match(/window.APP_STATE=(.*);/) || [null, null]
|
||||||
const data = JSON.parse(json)
|
const data = JSON.parse(json)
|
||||||
const items = data.tvGuide.channels.byZapNumber
|
const items = data.tvGuide.channels.byZapNumber
|
||||||
|
|
||||||
return Object.values(items).map(item => {
|
return Object.values(items).map(item => {
|
||||||
return {
|
return {
|
||||||
lang: 'fr',
|
lang: 'fr',
|
||||||
site_id: item.epgID,
|
site_id: item.epgID,
|
||||||
name: item.name
|
name: item.name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProgramDetails(item) {
|
async function loadProgramDetails(item) {
|
||||||
if (!item.onClick.URLPage) return {}
|
if (!item.onClick.URLPage) return {}
|
||||||
const url = item.onClick.URLPage
|
const url = item.onClick.URLPage
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
return data || {}
|
return data || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription(detail) {
|
function parseDescription(detail) {
|
||||||
return detail.detail.informations.summary || null
|
return detail.detail.informations.summary || null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory(detail) {
|
function parseCategory(detail) {
|
||||||
return detail.detail.informations.subGenre || null
|
return detail.detail.informations.subGenre || null
|
||||||
}
|
}
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
return item.URLImage || item.URLImageDefault
|
return item.URLImage || item.URLImageDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs.unix(item.startTime)
|
return dayjs.unix(item.startTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseStop(item) {
|
||||||
return dayjs.unix(item.endTime)
|
return dayjs.unix(item.endTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !data.timeSlices) return []
|
if (!data || !data.timeSlices) return []
|
||||||
const items = data.timeSlices.reduce((acc, curr) => {
|
const items = data.timeSlices.reduce((acc, curr) => {
|
||||||
acc = acc.concat(curr.contents)
|
acc = acc.concat(curr.contents)
|
||||||
return acc
|
return acc
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,176 +1,176 @@
|
||||||
// [Geo-blocked] npm run channels:parse -- --config=./sites/canalplus-haiti.com/canalplus-haiti.com.config.js --output=./sites/canalplus-haiti.com/canalplus-haiti.com.channels.xml
|
// [Geo-blocked] npm run channels:parse -- --config=./sites/canalplus-haiti.com/canalplus-haiti.com.config.js --output=./sites/canalplus-haiti.com/canalplus-haiti.com.channels.xml
|
||||||
// npm run grab -- --site=canalplus-haiti.com
|
// npm run grab -- --site=canalplus-haiti.com
|
||||||
|
|
||||||
const { parser, url } = require('./canalplus-haiti.com.config.js')
|
const { parser, url } = require('./canalplus-haiti.com.config.js')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '51006',
|
site_id: '51006',
|
||||||
xmltv_id: 'ViaATV.mq'
|
xmltv_id: 'ViaATV.mq'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url for today', () => {
|
it('can generate valid url for today', () => {
|
||||||
const date = dayjs.utc().startOf('d')
|
const date = dayjs.utc().startOf('d')
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'https://service.canal-overseas.com/ott-frontend/vector/53101/channel/51006/events?filter.day=0'
|
'https://service.canal-overseas.com/ott-frontend/vector/53101/channel/51006/events?filter.day=0'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid url for tomorrow', () => {
|
it('can generate valid url for tomorrow', () => {
|
||||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'https://service.canal-overseas.com/ott-frontend/vector/53101/channel/51006/events?filter.day=1'
|
'https://service.canal-overseas.com/ott-frontend/vector/53101/channel/51006/events?filter.day=1'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', done => {
|
it('can parse response', done => {
|
||||||
const content = `{
|
const content = `{
|
||||||
"timeSlices": [
|
"timeSlices": [
|
||||||
{
|
{
|
||||||
"contents": [
|
"contents": [
|
||||||
{
|
{
|
||||||
"title": "New Amsterdam - S3 - Ep7",
|
"title": "New Amsterdam - S3 - Ep7",
|
||||||
"subtitle": "Episode 7 - Le mur de la honte",
|
"subtitle": "Episode 7 - Le mur de la honte",
|
||||||
"thirdTitle": "viaATV",
|
"thirdTitle": "viaATV",
|
||||||
"startTime": 1660780500,
|
"startTime": 1660780500,
|
||||||
"endTime": 1660783200,
|
"endTime": 1660783200,
|
||||||
"onClick": {
|
"onClick": {
|
||||||
"displayTemplate": "miniDetail",
|
"displayTemplate": "miniDetail",
|
||||||
"displayName": "New Amsterdam - S3 - Ep7",
|
"displayName": "New Amsterdam - S3 - Ep7",
|
||||||
"URLPage": "https://service.canal-overseas.com/ott-frontend/vector/53101/event/140952809",
|
"URLPage": "https://service.canal-overseas.com/ott-frontend/vector/53101/event/140952809",
|
||||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53101/program/187882282/recommendations"
|
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53101/program/187882282/recommendations"
|
||||||
},
|
},
|
||||||
"programID": 187882282,
|
"programID": 187882282,
|
||||||
"diffusionID": "140952809",
|
"diffusionID": "140952809",
|
||||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/generic",
|
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/generic",
|
||||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e"
|
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timeSlice": "2"
|
"timeSlice": "2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}`
|
}`
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/53101/event/140952809') {
|
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/53101/event/140952809') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(`{
|
data: JSON.parse(`{
|
||||||
"currentPage": {
|
"currentPage": {
|
||||||
"displayName": "New Amsterdam - S3 - Ep7",
|
"displayName": "New Amsterdam - S3 - Ep7",
|
||||||
"displayTemplate": "detailPage",
|
"displayTemplate": "detailPage",
|
||||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53101/program/187882282/recommendations"
|
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53101/program/187882282/recommendations"
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"informations": {
|
"informations": {
|
||||||
"programmeType": "EPG",
|
"programmeType": "EPG",
|
||||||
"isInOffer": false,
|
"isInOffer": false,
|
||||||
"isInOfferOnDevice": false,
|
"isInOfferOnDevice": false,
|
||||||
"isInOfferForD2G": false,
|
"isInOfferForD2G": false,
|
||||||
"availableInVoDOnDevice": false,
|
"availableInVoDOnDevice": false,
|
||||||
"availableInVoDOnG5": false,
|
"availableInVoDOnG5": false,
|
||||||
"availableInD2GOnDevice": false,
|
"availableInD2GOnDevice": false,
|
||||||
"availableInLiveOnDevice": false,
|
"availableInLiveOnDevice": false,
|
||||||
"rediffusions": true,
|
"rediffusions": true,
|
||||||
"canBeRecorded": false,
|
"canBeRecorded": false,
|
||||||
"channelName": "viaATV",
|
"channelName": "viaATV",
|
||||||
"startTime": 1660780500,
|
"startTime": 1660780500,
|
||||||
"endTime": 1660783200,
|
"endTime": 1660783200,
|
||||||
"title": "New Amsterdam - S3 - Ep7",
|
"title": "New Amsterdam - S3 - Ep7",
|
||||||
"subtitle": "Episode 7 - Le mur de la honte",
|
"subtitle": "Episode 7 - Le mur de la honte",
|
||||||
"thirdTitle": "viaATV",
|
"thirdTitle": "viaATV",
|
||||||
"genre": "Séries",
|
"genre": "Séries",
|
||||||
"subGenre": "Série Hôpital",
|
"subGenre": "Série Hôpital",
|
||||||
"editorialTitle": "Séries, Etats-Unis, 2020, 0h45",
|
"editorialTitle": "Séries, Etats-Unis, 2020, 0h45",
|
||||||
"audioLanguage": "VF",
|
"audioLanguage": "VF",
|
||||||
"personnalities": [
|
"personnalities": [
|
||||||
{
|
{
|
||||||
"prefix": "De :",
|
"prefix": "De :",
|
||||||
"content": "Darnell Martin"
|
"content": "Darnell Martin"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"prefix": "Avec :",
|
"prefix": "Avec :",
|
||||||
"content": "André De Shields, Anna Suzuki, Anupam Kher, Baylen Thomas, Christine Chang, Craig Wedren, Daniel Dae Kim, Dierdre Friel, Em Grosland, Emma Ramos, Freema Agyeman, Gina Gershon, Graham Norris, Jamie Ann Romero, Janet Montgomery, Jefferson Friedman, Joshua Gitta, Kerry Flanagan, Larry Bryggman, Mike Doyle, Nora Clow, Opal Clow, Ryan Eggold, Simone Policano, Stephen Spinella, Tyler Labine"
|
"content": "André De Shields, Anna Suzuki, Anupam Kher, Baylen Thomas, Christine Chang, Craig Wedren, Daniel Dae Kim, Dierdre Friel, Em Grosland, Emma Ramos, Freema Agyeman, Gina Gershon, Graham Norris, Jamie Ann Romero, Janet Montgomery, Jefferson Friedman, Joshua Gitta, Kerry Flanagan, Larry Bryggman, Mike Doyle, Nora Clow, Opal Clow, Ryan Eggold, Simone Policano, Stephen Spinella, Tyler Labine"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"summary": "C'est la journée nationale de dépistage du VIH et Max offre des soins gratuits à tous les malades séropositifs qui se présentent à New Amsterdam.",
|
"summary": "C'est la journée nationale de dépistage du VIH et Max offre des soins gratuits à tous les malades séropositifs qui se présentent à New Amsterdam.",
|
||||||
"summaryMedium": "C'est la journée nationale de dépistage du VIH et Max offre des soins gratuits à tous les malades séropositifs qui se présentent à New Amsterdam.",
|
"summaryMedium": "C'est la journée nationale de dépistage du VIH et Max offre des soins gratuits à tous les malades séropositifs qui se présentent à New Amsterdam.",
|
||||||
"programID": 187882282,
|
"programID": 187882282,
|
||||||
"sharingURL": "https://www.canalplus-haiti.com/grille-tv/event/140952809-new-amsterdam-s3-ep7.html",
|
"sharingURL": "https://www.canalplus-haiti.com/grille-tv/event/140952809-new-amsterdam-s3-ep7.html",
|
||||||
"labels": {
|
"labels": {
|
||||||
"allocine": false,
|
"allocine": false,
|
||||||
"telerama": false,
|
"telerama": false,
|
||||||
"sensCritique": false
|
"sensCritique": false
|
||||||
},
|
},
|
||||||
"EpgId": 51006,
|
"EpgId": 51006,
|
||||||
"CSA": 1,
|
"CSA": 1,
|
||||||
"HD": false,
|
"HD": false,
|
||||||
"3D": false,
|
"3D": false,
|
||||||
"diffusionID": "140952809",
|
"diffusionID": "140952809",
|
||||||
"duration": "2700",
|
"duration": "2700",
|
||||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/generic",
|
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/generic",
|
||||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e",
|
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e",
|
||||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce",
|
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce",
|
||||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce",
|
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce",
|
||||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53101/program/187882282/recommendations"
|
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53101/program/187882282/recommendations"
|
||||||
},
|
},
|
||||||
"diffusions": [
|
"diffusions": [
|
||||||
{
|
{
|
||||||
"diffusionDateUTC": 1660780500,
|
"diffusionDateUTC": 1660780500,
|
||||||
"sharingUrl": "https://www.canalplus-haiti.com/grille-tv/event/140952809-new-amsterdam.html",
|
"sharingUrl": "https://www.canalplus-haiti.com/grille-tv/event/140952809-new-amsterdam.html",
|
||||||
"broadcastId": "140952809",
|
"broadcastId": "140952809",
|
||||||
"name": "viaATV",
|
"name": "viaATV",
|
||||||
"epgID": "51006",
|
"epgID": "51006",
|
||||||
"ZapNumber": "28",
|
"ZapNumber": "28",
|
||||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce",
|
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce",
|
||||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce"
|
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`)
|
}`)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: '' })
|
return Promise.resolve({ data: '' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
parser({ content })
|
parser({ content })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
result = result.map(p => {
|
result = result.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2022-08-17T23:55:00.000Z',
|
start: '2022-08-17T23:55:00.000Z',
|
||||||
stop: '2022-08-18T00:40:00.000Z',
|
stop: '2022-08-18T00:40:00.000Z',
|
||||||
title: 'New Amsterdam - S3 - Ep7',
|
title: 'New Amsterdam - S3 - Ep7',
|
||||||
icon: 'https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e',
|
icon: 'https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e',
|
||||||
category: 'Série Hôpital',
|
category: 'Série Hôpital',
|
||||||
description:
|
description:
|
||||||
"C'est la journée nationale de dépistage du VIH et Max offre des soins gratuits à tous les malades séropositifs qui se présentent à New Amsterdam."
|
"C'est la journée nationale de dépistage du VIH et Max offre des soins gratuits à tous les malades séropositifs qui se présentent à New Amsterdam."
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', done => {
|
it('can handle empty guide', done => {
|
||||||
parser({
|
parser({
|
||||||
content:
|
content:
|
||||||
'{"currentPage":{"displayTemplate":"error","BOName":"Page introuvable"},"title":"Page introuvable","text":"La page que vous demandez est introuvable. Si le problème persiste, vous pouvez contacter l\'assistance de CANAL+/CANALSAT.","code":404}'
|
'{"currentPage":{"displayTemplate":"error","BOName":"Page introuvable"},"title":"Page introuvable","text":"La page que vous demandez est introuvable. Si le problème persiste, vous pouvez contacter l\'assistance de CANAL+/CANALSAT.","code":404}'
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,72 +1,72 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'canalplus-reunion.com',
|
site: 'canalplus-reunion.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ channel, date }) {
|
url: function ({ channel, date }) {
|
||||||
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
||||||
|
|
||||||
return `https://service.canal-overseas.com/ott-frontend/vector/63001/channel/${channel.site_id}/events?filter.day=${diff}`
|
return `https://service.canal-overseas.com/ott-frontend/vector/63001/channel/${channel.site_id}/events?filter.day=${diff}`
|
||||||
},
|
},
|
||||||
async parser({ content }) {
|
async parser({ content }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
if (item.title === 'Fin des programmes') return
|
if (item.title === 'Fin des programmes') return
|
||||||
const detail = await loadProgramDetails(item)
|
const detail = await loadProgramDetails(item)
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
description: parseDescription(detail),
|
description: parseDescription(detail),
|
||||||
category: parseCategory(detail),
|
category: parseCategory(detail),
|
||||||
icon: parseIcon(item),
|
icon: parseIcon(item),
|
||||||
start: parseStart(item),
|
start: parseStart(item),
|
||||||
stop: parseStop(item)
|
stop: parseStop(item)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProgramDetails(item) {
|
async function loadProgramDetails(item) {
|
||||||
if (!item.onClick.URLPage) return {}
|
if (!item.onClick.URLPage) return {}
|
||||||
const url = item.onClick.URLPage
|
const url = item.onClick.URLPage
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
return data || {}
|
return data || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription(detail) {
|
function parseDescription(detail) {
|
||||||
return detail.detail.informations.summary || null
|
return detail.detail.informations.summary || null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory(detail) {
|
function parseCategory(detail) {
|
||||||
return detail.detail.informations.subGenre || null
|
return detail.detail.informations.subGenre || null
|
||||||
}
|
}
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
return item.URLImage || item.URLImageDefault
|
return item.URLImage || item.URLImageDefault
|
||||||
}
|
}
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs.unix(item.startTime)
|
return dayjs.unix(item.startTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseStop(item) {
|
||||||
return dayjs.unix(item.endTime)
|
return dayjs.unix(item.endTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !data.timeSlices) return []
|
if (!data || !data.timeSlices) return []
|
||||||
const items = data.timeSlices.reduce((acc, curr) => {
|
const items = data.timeSlices.reduce((acc, curr) => {
|
||||||
acc = acc.concat(curr.contents)
|
acc = acc.concat(curr.contents)
|
||||||
return acc
|
return acc
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,160 +1,160 @@
|
||||||
// npm run grab -- --site=canalplus-reunion.com
|
// npm run grab -- --site=canalplus-reunion.com
|
||||||
|
|
||||||
const { parser, url } = require('./canalplus-reunion.com.config.js')
|
const { parser, url } = require('./canalplus-reunion.com.config.js')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '60243',
|
site_id: '60243',
|
||||||
xmltv_id: 'beINSports2France.fr'
|
xmltv_id: 'beINSports2France.fr'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url for today', () => {
|
it('can generate valid url for today', () => {
|
||||||
const date = dayjs.utc().startOf('d')
|
const date = dayjs.utc().startOf('d')
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'https://service.canal-overseas.com/ott-frontend/vector/63001/channel/60243/events?filter.day=0'
|
'https://service.canal-overseas.com/ott-frontend/vector/63001/channel/60243/events?filter.day=0'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid url for tomorrow', () => {
|
it('can generate valid url for tomorrow', () => {
|
||||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'https://service.canal-overseas.com/ott-frontend/vector/63001/channel/60243/events?filter.day=1'
|
'https://service.canal-overseas.com/ott-frontend/vector/63001/channel/60243/events?filter.day=1'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', done => {
|
it('can parse response', done => {
|
||||||
const content = `{
|
const content = `{
|
||||||
"timeSlices": [
|
"timeSlices": [
|
||||||
{
|
{
|
||||||
"contents": [
|
"contents": [
|
||||||
{
|
{
|
||||||
"title": "Almeria / Real Madrid",
|
"title": "Almeria / Real Madrid",
|
||||||
"subtitle": "Football",
|
"subtitle": "Football",
|
||||||
"thirdTitle": "BEIN SPORTS 2 HD",
|
"thirdTitle": "BEIN SPORTS 2 HD",
|
||||||
"startTime": 1660780800,
|
"startTime": 1660780800,
|
||||||
"endTime": 1660788000,
|
"endTime": 1660788000,
|
||||||
"onClick": {
|
"onClick": {
|
||||||
"displayTemplate": "miniDetail",
|
"displayTemplate": "miniDetail",
|
||||||
"displayName": "Almeria / Real Madrid",
|
"displayName": "Almeria / Real Madrid",
|
||||||
"URLPage": "https://service.canal-overseas.com/ott-frontend/vector/63001/event/140382363",
|
"URLPage": "https://service.canal-overseas.com/ott-frontend/vector/63001/event/140382363",
|
||||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/63001/program/224523053/recommendations"
|
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/63001/program/224523053/recommendations"
|
||||||
},
|
},
|
||||||
"programID": 224523053,
|
"programID": 224523053,
|
||||||
"diffusionID": "140382363",
|
"diffusionID": "140382363",
|
||||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/a6b640e16608ffa3d862e2bd8a4b3e4c",
|
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/a6b640e16608ffa3d862e2bd8a4b3e4c",
|
||||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20"
|
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timeSlice": "4"
|
"timeSlice": "4"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}`
|
}`
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/63001/event/140382363') {
|
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/63001/event/140382363') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(`{
|
data: JSON.parse(`{
|
||||||
"currentPage": {
|
"currentPage": {
|
||||||
"displayName": "Almeria / Real Madrid",
|
"displayName": "Almeria / Real Madrid",
|
||||||
"displayTemplate": "detailPage",
|
"displayTemplate": "detailPage",
|
||||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/63001/program/224523053/recommendations"
|
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/63001/program/224523053/recommendations"
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"informations": {
|
"informations": {
|
||||||
"programmeType": "EPG",
|
"programmeType": "EPG",
|
||||||
"isInOffer": false,
|
"isInOffer": false,
|
||||||
"isInOfferOnDevice": false,
|
"isInOfferOnDevice": false,
|
||||||
"isInOfferForD2G": false,
|
"isInOfferForD2G": false,
|
||||||
"availableInVoDOnDevice": false,
|
"availableInVoDOnDevice": false,
|
||||||
"availableInVoDOnG5": false,
|
"availableInVoDOnG5": false,
|
||||||
"availableInD2GOnDevice": false,
|
"availableInD2GOnDevice": false,
|
||||||
"availableInLiveOnDevice": false,
|
"availableInLiveOnDevice": false,
|
||||||
"rediffusions": true,
|
"rediffusions": true,
|
||||||
"canBeRecorded": false,
|
"canBeRecorded": false,
|
||||||
"channelName": "BEIN SPORTS 2 HD",
|
"channelName": "BEIN SPORTS 2 HD",
|
||||||
"startTime": 1660780800,
|
"startTime": 1660780800,
|
||||||
"endTime": 1660788000,
|
"endTime": 1660788000,
|
||||||
"title": "Almeria / Real Madrid",
|
"title": "Almeria / Real Madrid",
|
||||||
"subtitle": "Football",
|
"subtitle": "Football",
|
||||||
"thirdTitle": "BEIN SPORTS 2 HD",
|
"thirdTitle": "BEIN SPORTS 2 HD",
|
||||||
"genre": "Sport",
|
"genre": "Sport",
|
||||||
"subGenre": "Football",
|
"subGenre": "Football",
|
||||||
"editorialTitle": "Sport, Espagne, 2h00",
|
"editorialTitle": "Sport, Espagne, 2h00",
|
||||||
"audioLanguage": "VF",
|
"audioLanguage": "VF",
|
||||||
"summary": "Diffusion d'un match de LaLiga Santander, championnat d'Espagne de football, la plus haute compétition de football d'Espagne. Cette compétition professionnelle, placée sous la supervision de la Fédération espagnole de football, a été fondée en 1928 et s'appelle Primera Division jusqu'en 2008. Elle se nomme ensuite Liga BBVA jusqu'en 2016 puis LaLiga Santander depuis cette date.",
|
"summary": "Diffusion d'un match de LaLiga Santander, championnat d'Espagne de football, la plus haute compétition de football d'Espagne. Cette compétition professionnelle, placée sous la supervision de la Fédération espagnole de football, a été fondée en 1928 et s'appelle Primera Division jusqu'en 2008. Elle se nomme ensuite Liga BBVA jusqu'en 2016 puis LaLiga Santander depuis cette date.",
|
||||||
"summaryMedium": "Diffusion d'un match de LaLiga Santander, championnat d'Espagne de football, la plus haute compétition de football d'Espagne. Cette compétition professionnelle, placée sous la supervision de la Fédération espagnole de football, a été fondée en 1928 et s'appelle Primera Division jusqu'en 2008. Elle se nomme ensuite Liga BBVA jusqu'en 2016 puis LaLiga Santander depuis cette date.",
|
"summaryMedium": "Diffusion d'un match de LaLiga Santander, championnat d'Espagne de football, la plus haute compétition de football d'Espagne. Cette compétition professionnelle, placée sous la supervision de la Fédération espagnole de football, a été fondée en 1928 et s'appelle Primera Division jusqu'en 2008. Elle se nomme ensuite Liga BBVA jusqu'en 2016 puis LaLiga Santander depuis cette date.",
|
||||||
"programID": 224523053,
|
"programID": 224523053,
|
||||||
"sharingURL": "https://www.canalplus-reunion.com/grille-tv/event/140382363-almeria-real-madrid.html",
|
"sharingURL": "https://www.canalplus-reunion.com/grille-tv/event/140382363-almeria-real-madrid.html",
|
||||||
"EpgId": 60243,
|
"EpgId": 60243,
|
||||||
"CSA": 1,
|
"CSA": 1,
|
||||||
"HD": false,
|
"HD": false,
|
||||||
"3D": false,
|
"3D": false,
|
||||||
"diffusionID": "140382363",
|
"diffusionID": "140382363",
|
||||||
"duration": "7200",
|
"duration": "7200",
|
||||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/a6b640e16608ffa3d862e2bd8a4b3e4c",
|
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/a6b640e16608ffa3d862e2bd8a4b3e4c",
|
||||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20",
|
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20",
|
||||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9",
|
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9",
|
||||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9",
|
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9",
|
||||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/63001/program/224523053/recommendations"
|
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/63001/program/224523053/recommendations"
|
||||||
},
|
},
|
||||||
"diffusions": [
|
"diffusions": [
|
||||||
{
|
{
|
||||||
"diffusionDateUTC": 1660780800,
|
"diffusionDateUTC": 1660780800,
|
||||||
"sharingUrl": "https://www.canalplus-reunion.com/grille-tv/event/140382363-almeria-real-madrid.html",
|
"sharingUrl": "https://www.canalplus-reunion.com/grille-tv/event/140382363-almeria-real-madrid.html",
|
||||||
"broadcastId": "140382363",
|
"broadcastId": "140382363",
|
||||||
"name": "BEIN SPORTS 2 HD",
|
"name": "BEIN SPORTS 2 HD",
|
||||||
"epgID": "60243",
|
"epgID": "60243",
|
||||||
"ZapNumber": "96",
|
"ZapNumber": "96",
|
||||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9",
|
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9",
|
||||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9"
|
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}`)
|
}`)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: '' })
|
return Promise.resolve({ data: '' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
parser({ content })
|
parser({ content })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
result = result.map(p => {
|
result = result.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2022-08-18T00:00:00.000Z',
|
start: '2022-08-18T00:00:00.000Z',
|
||||||
stop: '2022-08-18T02:00:00.000Z',
|
stop: '2022-08-18T02:00:00.000Z',
|
||||||
title: 'Almeria / Real Madrid',
|
title: 'Almeria / Real Madrid',
|
||||||
icon: 'https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20',
|
icon: 'https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20',
|
||||||
category: 'Football',
|
category: 'Football',
|
||||||
description:
|
description:
|
||||||
"Diffusion d'un match de LaLiga Santander, championnat d'Espagne de football, la plus haute compétition de football d'Espagne. Cette compétition professionnelle, placée sous la supervision de la Fédération espagnole de football, a été fondée en 1928 et s'appelle Primera Division jusqu'en 2008. Elle se nomme ensuite Liga BBVA jusqu'en 2016 puis LaLiga Santander depuis cette date."
|
"Diffusion d'un match de LaLiga Santander, championnat d'Espagne de football, la plus haute compétition de football d'Espagne. Cette compétition professionnelle, placée sous la supervision de la Fédération espagnole de football, a été fondée en 1928 et s'appelle Primera Division jusqu'en 2008. Elle se nomme ensuite Liga BBVA jusqu'en 2016 puis LaLiga Santander depuis cette date."
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', done => {
|
it('can handle empty guide', done => {
|
||||||
parser({
|
parser({
|
||||||
content:
|
content:
|
||||||
'{"currentPage":{"displayTemplate":"error","BOName":"Page introuvable"},"title":"Page introuvable","text":"La page que vous demandez est introuvable. Si le problème persiste, vous pouvez contacter l\'assistance de CANAL+/CANALSAT.","code":404}'
|
'{"currentPage":{"displayTemplate":"error","BOName":"Page introuvable"},"title":"Page introuvable","text":"La page que vous demandez est introuvable. Si le problème persiste, vous pouvez contacter l\'assistance de CANAL+/CANALSAT.","code":404}'
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,185 +1,185 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'canalplus.com',
|
site: 'canalplus.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: async function ({ channel, date }) {
|
url: async function ({ channel, date }) {
|
||||||
const [region, site_id] = channel.site_id.split('#')
|
const [region, site_id] = channel.site_id.split('#')
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(`https://www.canalplus.com/${region}/programme-tv/`)
|
.get(`https://www.canalplus.com/${region}/programme-tv/`)
|
||||||
.then(r => r.data.toString())
|
.then(r => r.data.toString())
|
||||||
.catch(err => console.log(err))
|
.catch(err => console.log(err))
|
||||||
const token = parseToken(data)
|
const token = parseToken(data)
|
||||||
|
|
||||||
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
||||||
|
|
||||||
return `https://hodor.canalplus.pro/api/v2/mycanal/channels/${token}/${site_id}/broadcasts/day/${diff}`
|
return `https://hodor.canalplus.pro/api/v2/mycanal/channels/${token}/${site_id}/broadcasts/day/${diff}`
|
||||||
},
|
},
|
||||||
async parser({ content }) {
|
async parser({ content }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
const details = await loadProgramDetails(item)
|
const details = await loadProgramDetails(item)
|
||||||
const info = parseInfo(details)
|
const info = parseInfo(details)
|
||||||
const start = parseStart(item)
|
const start = parseStart(item)
|
||||||
if (prev) prev.stop = start
|
if (prev) prev.stop = start
|
||||||
const stop = start.add(1, 'h')
|
const stop = start.add(1, 'h')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
description: parseDescription(info),
|
description: parseDescription(info),
|
||||||
icon: parseIcon(info),
|
icon: parseIcon(info),
|
||||||
actors: parseCast(info, 'Avec :'),
|
actors: parseCast(info, 'Avec :'),
|
||||||
director: parseCast(info, 'De :'),
|
director: parseCast(info, 'De :'),
|
||||||
writer: parseCast(info, 'Scénario :'),
|
writer: parseCast(info, 'Scénario :'),
|
||||||
composer: parseCast(info, 'Musique :'),
|
composer: parseCast(info, 'Musique :'),
|
||||||
presenter: parseCast(info, 'Présenté par :'),
|
presenter: parseCast(info, 'Présenté par :'),
|
||||||
date: parseDate(info),
|
date: parseDate(info),
|
||||||
rating: parseRating(info),
|
rating: parseRating(info),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const endpoints = {
|
const endpoints = {
|
||||||
ad: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ad/all/v2.2/globalchannels.json',
|
ad: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ad/all/v2.2/globalchannels.json',
|
||||||
bf: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/bf/all/v2.2/globalchannels.json',
|
bf: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/bf/all/v2.2/globalchannels.json',
|
||||||
bi: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/bi/all/v2.2/globalchannels.json',
|
bi: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/bi/all/v2.2/globalchannels.json',
|
||||||
bj: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/bj/all/v2.2/globalchannels.json',
|
bj: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/bj/all/v2.2/globalchannels.json',
|
||||||
bl: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/bl/all/v2.2/globalchannels.json',
|
bl: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/bl/all/v2.2/globalchannels.json',
|
||||||
cd: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cd/all/v2.2/globalchannels.json',
|
cd: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cd/all/v2.2/globalchannels.json',
|
||||||
cf: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cf/all/v2.2/globalchannels.json',
|
cf: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cf/all/v2.2/globalchannels.json',
|
||||||
cg: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cg/all/v2.2/globalchannels.json',
|
cg: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cg/all/v2.2/globalchannels.json',
|
||||||
ch: 'https://secure-webtv-static.canal-plus.com/metadata/cpche/all/v2.2/globalchannels.json',
|
ch: 'https://secure-webtv-static.canal-plus.com/metadata/cpche/all/v2.2/globalchannels.json',
|
||||||
ci: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ci/all/v2.2/globalchannels.json',
|
ci: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ci/all/v2.2/globalchannels.json',
|
||||||
cm: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cm/all/v2.2/globalchannels.json',
|
cm: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cm/all/v2.2/globalchannels.json',
|
||||||
cv: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cv/all/v2.2/globalchannels.json',
|
cv: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cv/all/v2.2/globalchannels.json',
|
||||||
dj: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/dj/all/v2.2/globalchannels.json',
|
dj: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/dj/all/v2.2/globalchannels.json',
|
||||||
fr: 'https://secure-webtv-static.canal-plus.com/metadata/cpfra/all/v2.2/globalchannels.json',
|
fr: 'https://secure-webtv-static.canal-plus.com/metadata/cpfra/all/v2.2/globalchannels.json',
|
||||||
ga: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ga/all/v2.2/globalchannels.json',
|
ga: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ga/all/v2.2/globalchannels.json',
|
||||||
gf: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/gf/all/v2.2/globalchannels.json',
|
gf: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/gf/all/v2.2/globalchannels.json',
|
||||||
gh: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gh/all/v2.2/globalchannels.json',
|
gh: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gh/all/v2.2/globalchannels.json',
|
||||||
gm: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gm/all/v2.2/globalchannels.json',
|
gm: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gm/all/v2.2/globalchannels.json',
|
||||||
gn: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gn/all/v2.2/globalchannels.json',
|
gn: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gn/all/v2.2/globalchannels.json',
|
||||||
gp: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gp/all/v2.2/globalchannels.json',
|
gp: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gp/all/v2.2/globalchannels.json',
|
||||||
gw: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gw/all/v2.2/globalchannels.json',
|
gw: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gw/all/v2.2/globalchannels.json',
|
||||||
mf: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/mf/all/v2.2/globalchannels.json',
|
mf: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/mf/all/v2.2/globalchannels.json',
|
||||||
mg: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/mg/all/v2.2/globalchannels.json',
|
mg: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/mg/all/v2.2/globalchannels.json',
|
||||||
ml: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ml/all/v2.2/globalchannels.json',
|
ml: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ml/all/v2.2/globalchannels.json',
|
||||||
mq: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/mq/all/v2.2/globalchannels.json',
|
mq: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/mq/all/v2.2/globalchannels.json',
|
||||||
mr: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/mr/all/v2.2/globalchannels.json',
|
mr: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/mr/all/v2.2/globalchannels.json',
|
||||||
mu: 'https://secure-webtv-static.canal-plus.com/metadata/cpmus/mu/all/v2.2/globalchannels.json',
|
mu: 'https://secure-webtv-static.canal-plus.com/metadata/cpmus/mu/all/v2.2/globalchannels.json',
|
||||||
nc: 'https://secure-webtv-static.canal-plus.com/metadata/cpncl/nc/all/v2.2/globalchannels.json',
|
nc: 'https://secure-webtv-static.canal-plus.com/metadata/cpncl/nc/all/v2.2/globalchannels.json',
|
||||||
ne: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ne/all/v2.2/globalchannels.json',
|
ne: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ne/all/v2.2/globalchannels.json',
|
||||||
pl: 'https://secure-webtv-static.canal-plus.com/metadata/cppol/all/v2.2/globalchannels.json',
|
pl: 'https://secure-webtv-static.canal-plus.com/metadata/cppol/all/v2.2/globalchannels.json',
|
||||||
re: 'https://secure-webtv-static.canal-plus.com/metadata/cpreu/re/all/v2.2/globalchannels.json',
|
re: 'https://secure-webtv-static.canal-plus.com/metadata/cpreu/re/all/v2.2/globalchannels.json',
|
||||||
rw: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/rw/all/v2.2/globalchannels.json',
|
rw: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/rw/all/v2.2/globalchannels.json',
|
||||||
sl: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/sl/all/v2.2/globalchannels.json',
|
sl: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/sl/all/v2.2/globalchannels.json',
|
||||||
sn: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/sn/all/v2.2/globalchannels.json',
|
sn: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/sn/all/v2.2/globalchannels.json',
|
||||||
td: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/td/all/v2.2/globalchannels.json',
|
td: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/td/all/v2.2/globalchannels.json',
|
||||||
tg: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/tg/all/v2.2/globalchannels.json',
|
tg: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/tg/all/v2.2/globalchannels.json',
|
||||||
wf: 'https://secure-webtv-static.canal-plus.com/metadata/cpncl/wf/all/v2.2/globalchannels.json',
|
wf: 'https://secure-webtv-static.canal-plus.com/metadata/cpncl/wf/all/v2.2/globalchannels.json',
|
||||||
yt: 'https://secure-webtv-static.canal-plus.com/metadata/cpreu/yt/all/v2.2/globalchannels.json'
|
yt: 'https://secure-webtv-static.canal-plus.com/metadata/cpreu/yt/all/v2.2/globalchannels.json'
|
||||||
}
|
}
|
||||||
|
|
||||||
let channels = []
|
let channels = []
|
||||||
for (let [region, url] of Object.entries(endpoints)) {
|
for (let [region, url] of Object.entries(endpoints)) {
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
data.channels.forEach(channel => {
|
data.channels.forEach(channel => {
|
||||||
const site_id = region === 'fr' ? `#${channel.id}` : `${region}#${channel.id}`
|
const site_id = region === 'fr' ? `#${channel.id}` : `${region}#${channel.id}`
|
||||||
|
|
||||||
if (channel.name === '.') return
|
if (channel.name === '.') return
|
||||||
|
|
||||||
channels.push({
|
channels.push({
|
||||||
lang: 'fr',
|
lang: 'fr',
|
||||||
site_id,
|
site_id,
|
||||||
name: channel.name
|
name: channel.name
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseToken(data) {
|
function parseToken(data) {
|
||||||
const [, token] = data.match(/"token":"([^"]+)/) || [null, null]
|
const [, token] = data.match(/"token":"([^"]+)/) || [null, null]
|
||||||
|
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return item && item.startTime ? dayjs(item.startTime) : null
|
return item && item.startTime ? dayjs(item.startTime) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon(info) {
|
function parseIcon(info) {
|
||||||
return info ? info.URLImage : null
|
return info ? info.URLImage : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription(info) {
|
function parseDescription(info) {
|
||||||
return info ? info.summary : null
|
return info ? info.summary : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseInfo(data) {
|
function parseInfo(data) {
|
||||||
if (!data || !data.detail || !data.detail.informations) return null
|
if (!data || !data.detail || !data.detail.informations) return null
|
||||||
|
|
||||||
return data.detail.informations
|
return data.detail.informations
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProgramDetails(item) {
|
async function loadProgramDetails(item) {
|
||||||
if (!item.onClick || !item.onClick.URLPage) return {}
|
if (!item.onClick || !item.onClick.URLPage) return {}
|
||||||
|
|
||||||
return await axios
|
return await axios
|
||||||
.get(item.onClick.URLPage)
|
.get(item.onClick.URLPage)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !Array.isArray(data.timeSlices)) return []
|
if (!data || !Array.isArray(data.timeSlices)) return []
|
||||||
|
|
||||||
return data.timeSlices.reduce((acc, curr) => {
|
return data.timeSlices.reduce((acc, curr) => {
|
||||||
acc = acc.concat(curr.contents)
|
acc = acc.concat(curr.contents)
|
||||||
return acc
|
return acc
|
||||||
}, [])
|
}, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCast(info, type) {
|
function parseCast(info, type) {
|
||||||
let people = []
|
let people = []
|
||||||
if (info && info.personnalities) {
|
if (info && info.personnalities) {
|
||||||
const personnalities = info.personnalities.find(i => i.prefix == type)
|
const personnalities = info.personnalities.find(i => i.prefix == type)
|
||||||
if (!personnalities) return people
|
if (!personnalities) return people
|
||||||
for (let person of personnalities.personnalitiesList) {
|
for (let person of personnalities.personnalitiesList) {
|
||||||
people.push(person.title)
|
people.push(person.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return people
|
return people
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDate(info) {
|
function parseDate(info) {
|
||||||
return info && info.productionYear ? info.productionYear : null
|
return info && info.productionYear ? info.productionYear : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRating(info) {
|
function parseRating(info) {
|
||||||
if (!info || !info.parentalRatings) return null
|
if (!info || !info.parentalRatings) return null
|
||||||
let rating = info.parentalRatings.find(i => i.authority === 'CSA')
|
let rating = info.parentalRatings.find(i => i.authority === 'CSA')
|
||||||
if (!rating || Array.isArray(rating)) return null
|
if (!rating || Array.isArray(rating)) return null
|
||||||
if (rating.value === '1') return null
|
if (rating.value === '1') return null
|
||||||
if (rating.value === '2') rating.value = '-10'
|
if (rating.value === '2') rating.value = '-10'
|
||||||
if (rating.value === '3') rating.value = '-12'
|
if (rating.value === '3') rating.value = '-12'
|
||||||
if (rating.value === '4') rating.value = '-16'
|
if (rating.value === '4') rating.value = '-16'
|
||||||
if (rating.value === '5') rating.value = '-18'
|
if (rating.value === '5') rating.value = '-18'
|
||||||
return {
|
return {
|
||||||
system: rating.authority,
|
system: rating.authority,
|
||||||
value: rating.value
|
value: rating.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,147 +1,147 @@
|
||||||
// npm run channels:parse -- --config=./sites/canalplus.com/canalplus.com.config.js --output=./sites/canalplus.com/canalplus.com.channels.xml
|
// npm run channels:parse -- --config=./sites/canalplus.com/canalplus.com.config.js --output=./sites/canalplus.com/canalplus.com.channels.xml
|
||||||
// npm run grab -- --site=canalplus.com
|
// npm run grab -- --site=canalplus.com
|
||||||
|
|
||||||
const { parser, url } = require('./canalplus.com.config.js')
|
const { parser, url } = require('./canalplus.com.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'bi#198',
|
site_id: 'bi#198',
|
||||||
xmltv_id: 'CanalPlusCinemaFrance.fr'
|
xmltv_id: 'CanalPlusCinemaFrance.fr'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url for today', done => {
|
it('can generate valid url for today', done => {
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
if (url === 'https://www.canalplus.com/bi/programme-tv/') {
|
if (url === 'https://www.canalplus.com/bi/programme-tv/') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html'))
|
data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html'))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: '' })
|
return Promise.resolve({ data: '' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const today = dayjs.utc().startOf('d')
|
const today = dayjs.utc().startOf('d')
|
||||||
url({ channel, date: today })
|
url({ channel, date: today })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/0'
|
'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/0'
|
||||||
)
|
)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid url for tomorrow', done => {
|
it('can generate valid url for tomorrow', done => {
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
if (url === 'https://www.canalplus.com/bi/programme-tv/') {
|
if (url === 'https://www.canalplus.com/bi/programme-tv/') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html'))
|
data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html'))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: '' })
|
return Promise.resolve({ data: '' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const tomorrow = dayjs.utc().startOf('d').add(1, 'd')
|
const tomorrow = dayjs.utc().startOf('d').add(1, 'd')
|
||||||
url({ channel, date: tomorrow })
|
url({ channel, date: tomorrow })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/1'
|
'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/1'
|
||||||
)
|
)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', done => {
|
it('can parse response', done => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
|
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
if (
|
if (
|
||||||
url ===
|
url ===
|
||||||
'https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/6564630_50001.json?detailType=detailSeason&objectType=season&broadcastID=PLM_1196447642&episodeId=20482220_50001&brandID=4501558_50001&fromDiff=true'
|
'https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/6564630_50001.json?detailType=detailSeason&objectType=season&broadcastID=PLM_1196447642&episodeId=20482220_50001&brandID=4501558_50001&fromDiff=true'
|
||||||
) {
|
) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
|
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
|
||||||
})
|
})
|
||||||
} else if (
|
} else if (
|
||||||
url ===
|
url ===
|
||||||
'https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/17230453_50001.json?detailType=detailPage&objectType=unit&broadcastID=PLM_1196447637&fromDiff=true'
|
'https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/17230453_50001.json?detailType=detailPage&objectType=unit&broadcastID=PLM_1196447637&fromDiff=true'
|
||||||
) {
|
) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))
|
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: '' })
|
return Promise.resolve({ data: '' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
parser({ content })
|
parser({ content })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
result.map(p => {
|
result.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2023-01-12T06:28:00.000Z',
|
start: '2023-01-12T06:28:00.000Z',
|
||||||
stop: '2023-01-12T12:06:00.000Z',
|
stop: '2023-01-12T12:06:00.000Z',
|
||||||
title: 'Le cercle',
|
title: 'Le cercle',
|
||||||
description:
|
description:
|
||||||
"Tant qu'il y aura du cinéma, LE CERCLE sera là. C'est la seule émission télévisée de débats critiques 100% consacrée au cinéma et elle rentre dans sa 18e saison. Chaque semaine, elle offre des joutes enflammées, joyeuses et sans condescendance, sur les films à l'affiche ; et invite avec \"Le questionnaire du CERCLE\" les réalisatrices et réalisateurs à venir partager leur passion cinéphile.",
|
"Tant qu'il y aura du cinéma, LE CERCLE sera là. C'est la seule émission télévisée de débats critiques 100% consacrée au cinéma et elle rentre dans sa 18e saison. Chaque semaine, elle offre des joutes enflammées, joyeuses et sans condescendance, sur les films à l'affiche ; et invite avec \"Le questionnaire du CERCLE\" les réalisatrices et réalisateurs à venir partager leur passion cinéphile.",
|
||||||
icon: 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107297573',
|
icon: 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107297573',
|
||||||
presenter: ['Lily Bloom'],
|
presenter: ['Lily Bloom'],
|
||||||
rating: {
|
rating: {
|
||||||
system: 'CSA',
|
system: 'CSA',
|
||||||
value: '-10'
|
value: '-10'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: '2023-01-12T12:06:00.000Z',
|
start: '2023-01-12T12:06:00.000Z',
|
||||||
stop: '2023-01-12T13:06:00.000Z',
|
stop: '2023-01-12T13:06:00.000Z',
|
||||||
title: 'Illusions perdues',
|
title: 'Illusions perdues',
|
||||||
description:
|
description:
|
||||||
"Pendant la Restauration, Lucien de Rubempré, jeune provincial d'Angoulême, se rêve poète. Il débarque à Paris en quête de gloire. Il a le soutien de Louise de Bargeton, une aristocrate qui croit en son talent. Pour gagner sa vie, Lucien trouve un emploi dans le journal dirigé par le peu scrupuleux Etienne Lousteau...",
|
"Pendant la Restauration, Lucien de Rubempré, jeune provincial d'Angoulême, se rêve poète. Il débarque à Paris en quête de gloire. Il a le soutien de Louise de Bargeton, une aristocrate qui croit en son talent. Pour gagner sa vie, Lucien trouve un emploi dans le journal dirigé par le peu scrupuleux Etienne Lousteau...",
|
||||||
icon: 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107356485',
|
icon: 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107356485',
|
||||||
director: ['Xavier Giannoli'],
|
director: ['Xavier Giannoli'],
|
||||||
actors: [
|
actors: [
|
||||||
'Benjamin Voisin',
|
'Benjamin Voisin',
|
||||||
'Cécile de France',
|
'Cécile de France',
|
||||||
'Vincent Lacoste',
|
'Vincent Lacoste',
|
||||||
'Xavier Dolan',
|
'Xavier Dolan',
|
||||||
'Gérard Depardieu',
|
'Gérard Depardieu',
|
||||||
'Salomé Dewaels',
|
'Salomé Dewaels',
|
||||||
'Jeanne Balibar',
|
'Jeanne Balibar',
|
||||||
'Louis-Do de Lencquesaing',
|
'Louis-Do de Lencquesaing',
|
||||||
'Alexis Barbosa',
|
'Alexis Barbosa',
|
||||||
'Jean-François Stévenin',
|
'Jean-François Stévenin',
|
||||||
'André Marcon',
|
'André Marcon',
|
||||||
'Marie Cornillon'
|
'Marie Cornillon'
|
||||||
],
|
],
|
||||||
writer: ['Xavier Giannoli'],
|
writer: ['Xavier Giannoli'],
|
||||||
rating: {
|
rating: {
|
||||||
system: 'CSA',
|
system: 'CSA',
|
||||||
value: '-10'
|
value: '-10'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', async () => {
|
it('can handle empty guide', async () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||||
const result = await parser({ content })
|
const result = await parser({ content })
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,92 +1,92 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'cgates.lt',
|
site: 'cgates.lt',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ channel }) {
|
url: function ({ channel }) {
|
||||||
return `https://www.cgates.lt/tv-kanalai/${channel.site_id}/`
|
return `https://www.cgates.lt/tv-kanalai/${channel.site_id}/`
|
||||||
},
|
},
|
||||||
parser: function ({ content, date }) {
|
parser: function ({ content, date }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, date)
|
const items = parseItems(content, date)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
let start = parseStart($item, date)
|
let start = parseStart($item, date)
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (start.isBefore(prev.start)) {
|
if (start.isBefore(prev.start)) {
|
||||||
start = start.add(1, 'd')
|
start = start.add(1, 'd')
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
prev.stop = start
|
prev.stop = start
|
||||||
}
|
}
|
||||||
const stop = start.add(30, 'm')
|
const stop = start.add(30, 'm')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle($item),
|
title: parseTitle($item),
|
||||||
description: parseDescription($item),
|
description: parseDescription($item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
let html = await axios
|
let html = await axios
|
||||||
.get('https://www.cgates.lt/televizija/tv-programa-savaitei/')
|
.get('https://www.cgates.lt/televizija/tv-programa-savaitei/')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
let $ = cheerio.load(html)
|
let $ = cheerio.load(html)
|
||||||
const items = $('.kanalas_wrap').toArray()
|
const items = $('.kanalas_wrap').toArray()
|
||||||
|
|
||||||
return items.map(item => {
|
return items.map(item => {
|
||||||
const name = $(item).find('h6').text().trim()
|
const name = $(item).find('h6').text().trim()
|
||||||
const link = $(item).find('a').attr('href')
|
const link = $(item).find('a').attr('href')
|
||||||
const [, site_id] = link.match(/\/tv-kanalai\/(.*)\//) || [null, null]
|
const [, site_id] = link.match(/\/tv-kanalai\/(.*)\//) || [null, null]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lang: 'lt',
|
lang: 'lt',
|
||||||
site_id,
|
site_id,
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
const title = $item('td:nth-child(2) > .vc_toggle > .vc_toggle_title').text().trim()
|
const title = $item('td:nth-child(2) > .vc_toggle > .vc_toggle_title').text().trim()
|
||||||
|
|
||||||
return title || $item('td:nth-child(2)').text().trim()
|
return title || $item('td:nth-child(2)').text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription($item) {
|
function parseDescription($item) {
|
||||||
return $item('.vc_toggle_content > p').text().trim()
|
return $item('.vc_toggle_content > p').text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
const time = $item('.laikas')
|
const time = $item('.laikas')
|
||||||
|
|
||||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Vilnius')
|
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Vilnius')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, date) {
|
function parseItems(content, date) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
const section = $(
|
const section = $(
|
||||||
'article > div:nth-child(2) > div.vc_row.wpb_row.vc_row-fluid > div > div > div > div > div'
|
'article > div:nth-child(2) > div.vc_row.wpb_row.vc_row-fluid > div > div > div > div > div'
|
||||||
)
|
)
|
||||||
.filter(function () {
|
.filter(function () {
|
||||||
return $(`.dt-fancy-title:contains("${date.format('YYYY-MM-DD')}")`, this).length === 1
|
return $(`.dt-fancy-title:contains("${date.format('YYYY-MM-DD')}")`, this).length === 1
|
||||||
})
|
})
|
||||||
.first()
|
.first()
|
||||||
|
|
||||||
return $('.tv_programa tr', section).toArray()
|
return $('.tv_programa tr', section).toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,52 @@
|
||||||
// npm run channels:parse -- --config=./sites/cgates.lt/cgates.lt.config.js --output=./sites/cgates.lt/cgates.lt.channels.xml
|
// npm run channels:parse -- --config=./sites/cgates.lt/cgates.lt.config.js --output=./sites/cgates.lt/cgates.lt.channels.xml
|
||||||
// npm run grab -- --site=cgates.lt
|
// npm run grab -- --site=cgates.lt
|
||||||
|
|
||||||
const { parser, url } = require('./cgates.lt.config.js')
|
const { parser, url } = require('./cgates.lt.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2022-08-30', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-08-30', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'lrt-televizija-hd',
|
site_id: 'lrt-televizija-hd',
|
||||||
xmltv_id: 'LRTTV.lt'
|
xmltv_id: 'LRTTV.lt'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe('https://www.cgates.lt/tv-kanalai/lrt-televizija-hd/')
|
expect(url({ channel, date })).toBe('https://www.cgates.lt/tv-kanalai/lrt-televizija-hd/')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||||
const results = parser({ content, date }).map(p => {
|
const results = parser({ content, date }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results.length).toBe(35)
|
expect(results.length).toBe(35)
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-08-29T21:05:00.000Z',
|
start: '2022-08-29T21:05:00.000Z',
|
||||||
stop: '2022-08-29T21:30:00.000Z',
|
stop: '2022-08-29T21:30:00.000Z',
|
||||||
title: '31-oji nuovada (District 31), Drama, 2016',
|
title: '31-oji nuovada (District 31), Drama, 2016',
|
||||||
description:
|
description:
|
||||||
'Seriale pasakojama apie kasdienius policijos išbandymus ir sunkumus. Vadovybė pertvarko Monrealio miesto policijos struktūrą: išskirsto į 36 policijos nuovadas, kad šios būtų arčiau gyventojų. 31-osios nuovados darbuotojams tenka kone sunkiausias darbas: šiame miesto rajone gyvena socialiai remtinos šeimos, nuolat kovojančios su turtingųjų klase, įsipliekia ir rasinių konfliktų. Be to, čia akivaizdus kartų atotrūkis, o tapti nusikalstamo pasaulio dalimi labai lengva. Serialo siužetas – intensyvus, nauji nusikaltimai tiriami kiekvieną savaitę. Čia vaizduojamas nepagražintas nusikalstamas pasaulis, jo poveikis rajono gyventojams. Policijos nuovados darbuotojai narplios įvairiausių nusikaltimų schemas. Tai ir pagrobimai, įsilaužimai, žmogžudystės, smurtas artimoje aplinkoje, lytiniai nusikaltimai, prekyba narkotikais, teroristinių išpuolių grėsmė ir pan. Šis serialas leis žiūrovui įsigilinti į policijos pareigūnų realybę, pateiks skirtingą požiūrį į kiekvieną nusikaltimą.'
|
'Seriale pasakojama apie kasdienius policijos išbandymus ir sunkumus. Vadovybė pertvarko Monrealio miesto policijos struktūrą: išskirsto į 36 policijos nuovadas, kad šios būtų arčiau gyventojų. 31-osios nuovados darbuotojams tenka kone sunkiausias darbas: šiame miesto rajone gyvena socialiai remtinos šeimos, nuolat kovojančios su turtingųjų klase, įsipliekia ir rasinių konfliktų. Be to, čia akivaizdus kartų atotrūkis, o tapti nusikalstamo pasaulio dalimi labai lengva. Serialo siužetas – intensyvus, nauji nusikaltimai tiriami kiekvieną savaitę. Čia vaizduojamas nepagražintas nusikalstamas pasaulis, jo poveikis rajono gyventojams. Policijos nuovados darbuotojai narplios įvairiausių nusikaltimų schemas. Tai ir pagrobimai, įsilaužimai, žmogžudystės, smurtas artimoje aplinkoje, lytiniai nusikaltimai, prekyba narkotikais, teroristinių išpuolių grėsmė ir pan. Šis serialas leis žiūrovui įsigilinti į policijos pareigūnų realybę, pateiks skirtingą požiūrį į kiekvieną nusikaltimą.'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[34]).toMatchObject({
|
expect(results[34]).toMatchObject({
|
||||||
start: '2022-08-30T20:45:00.000Z',
|
start: '2022-08-30T20:45:00.000Z',
|
||||||
stop: '2022-08-30T21:15:00.000Z',
|
stop: '2022-08-30T21:15:00.000Z',
|
||||||
title: '31-oji nuovada (District 31), Drama, 2016!'
|
title: '31-oji nuovada (District 31), Drama, 2016!'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
content: ''
|
content: ''
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,47 +1,47 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'chaines-tv.orange.fr',
|
site: 'chaines-tv.orange.fr',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel, date }) {
|
url({ channel, date }) {
|
||||||
return `https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?groupBy=channel&includeEmptyChannels=false&period=${date.valueOf()},${date
|
return `https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?groupBy=channel&includeEmptyChannels=false&period=${date.valueOf()},${date
|
||||||
.add(1, 'd')
|
.add(1, 'd')
|
||||||
.valueOf()}&after=${channel.site_id}&limit=1`
|
.valueOf()}&after=${channel.site_id}&limit=1`
|
||||||
},
|
},
|
||||||
parser: function ({ content, channel }) {
|
parser: function ({ content, channel }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const start = parseStart(item)
|
const start = parseStart(item)
|
||||||
const stop = parseStop(item, start)
|
const stop = parseStop(item, start)
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
category: item.genreDetailed,
|
category: item.genreDetailed,
|
||||||
description: item.synopsis,
|
description: item.synopsis,
|
||||||
icon: parseIcon(item),
|
icon: parseIcon(item),
|
||||||
start: start.toJSON(),
|
start: start.toJSON(),
|
||||||
stop: stop.toJSON()
|
stop: stop.toJSON()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
return item.covers && item.covers.length ? item.covers[0].url : null
|
return item.covers && item.covers.length ? item.covers[0].url : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs.unix(item.diffusionDate)
|
return dayjs.unix(item.diffusionDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item, start) {
|
function parseStop(item, start) {
|
||||||
return start.add(item.duration, 's')
|
return start.add(item.duration, 's')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
|
|
||||||
return data && data[channel.site_id] ? data[channel.site_id] : []
|
return data && data[channel.site_id] ? data[channel.site_id] : []
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,48 @@
|
||||||
// npm run grab -- --site=chaines-tv.orange.fr
|
// npm run grab -- --site=chaines-tv.orange.fr
|
||||||
|
|
||||||
const { parser, url } = require('./chaines-tv.orange.fr.config.js')
|
const { parser, url } = require('./chaines-tv.orange.fr.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2021-11-08', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2021-11-08', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '192',
|
site_id: '192',
|
||||||
xmltv_id: 'TF1.fr'
|
xmltv_id: 'TF1.fr'
|
||||||
}
|
}
|
||||||
const content =
|
const content =
|
||||||
'{"192":[{"id":1635062528017,"programType":"EPISODE","title":"Tête de liste","channelId":"192","channelZappingNumber":11,"covers":[{"format":"RATIO_16_9","url":"https://proxymedia.woopic.com/340/p/169_EMI_9697669.jpg"},{"format":"RATIO_4_3","url":"https://proxymedia.woopic.com/340/p/43_EMI_9697669.jpg"}],"diffusionDate":1636328100,"duration":2700,"csa":2,"synopsis":"Un tueur en série prend un plaisir pervers à prévenir les autorités de Tallahassee avant chaque nouveau meurtre. Rossi apprend le décès d\'un de ses vieux amis.","languageVersion":"VM","hearingImpaired":true,"audioDescription":false,"season":{"number":10,"episodesCount":23,"serie":{"title":"Esprits criminels"}},"episodeNumber":12,"definition":"SD","links":[{"rel":"SELF","href":"https://rp-live.orange.fr/live-webapp/v3/applications/STB4PC/programs/1635062528017"}],"dayPart":"OTHER","catchupId":null,"genre":"Série","genreDetailed":"Série Suspense"}]}'
|
'{"192":[{"id":1635062528017,"programType":"EPISODE","title":"Tête de liste","channelId":"192","channelZappingNumber":11,"covers":[{"format":"RATIO_16_9","url":"https://proxymedia.woopic.com/340/p/169_EMI_9697669.jpg"},{"format":"RATIO_4_3","url":"https://proxymedia.woopic.com/340/p/43_EMI_9697669.jpg"}],"diffusionDate":1636328100,"duration":2700,"csa":2,"synopsis":"Un tueur en série prend un plaisir pervers à prévenir les autorités de Tallahassee avant chaque nouveau meurtre. Rossi apprend le décès d\'un de ses vieux amis.","languageVersion":"VM","hearingImpaired":true,"audioDescription":false,"season":{"number":10,"episodesCount":23,"serie":{"title":"Esprits criminels"}},"episodeNumber":12,"definition":"SD","links":[{"rel":"SELF","href":"https://rp-live.orange.fr/live-webapp/v3/applications/STB4PC/programs/1635062528017"}],"dayPart":"OTHER","catchupId":null,"genre":"Série","genreDetailed":"Série Suspense"}]}'
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
const result = url({ channel, date })
|
const result = url({ channel, date })
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?groupBy=channel&includeEmptyChannels=false&period=1636329600000,1636416000000&after=192&limit=1'
|
'https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?groupBy=channel&includeEmptyChannels=false&period=1636329600000,1636416000000&after=192&limit=1'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const result = parser({ date, channel, content })
|
const result = parser({ date, channel, content })
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2021-11-07T23:35:00.000Z',
|
start: '2021-11-07T23:35:00.000Z',
|
||||||
stop: '2021-11-08T00:20:00.000Z',
|
stop: '2021-11-08T00:20:00.000Z',
|
||||||
title: 'Tête de liste',
|
title: 'Tête de liste',
|
||||||
description:
|
description:
|
||||||
"Un tueur en série prend un plaisir pervers à prévenir les autorités de Tallahassee avant chaque nouveau meurtre. Rossi apprend le décès d'un de ses vieux amis.",
|
"Un tueur en série prend un plaisir pervers à prévenir les autorités de Tallahassee avant chaque nouveau meurtre. Rossi apprend le décès d'un de ses vieux amis.",
|
||||||
category: 'Série Suspense',
|
category: 'Série Suspense',
|
||||||
icon: 'https://proxymedia.woopic.com/340/p/169_EMI_9697669.jpg'
|
icon: 'https://proxymedia.woopic.com/340/p/169_EMI_9697669.jpg'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content:
|
content:
|
||||||
'{"code":60,"message":"Resource not found","param":{},"description":"L\'URI demandé ou la ressource demandée n\'existe pas.","stackTrace":null}'
|
'{"code":60,"message":"Resource not found","param":{},"description":"L\'URI demandé ou la ressource demandée n\'existe pas.","stackTrace":null}'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,99 +1,99 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const { DateTime } = require('luxon')
|
const { DateTime } = require('luxon')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'clickthecity.com',
|
site: 'clickthecity.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel }) {
|
url({ channel }) {
|
||||||
return `https://www.clickthecity.com/tv/channels/?netid=${channel.site_id}`
|
return `https://www.clickthecity.com/tv/channels/?netid=${channel.site_id}`
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/x-www-form-urlencoded'
|
'content-type': 'application/x-www-form-urlencoded'
|
||||||
},
|
},
|
||||||
data({ date }) {
|
data({ date }) {
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
params.append(
|
params.append(
|
||||||
'optDate',
|
'optDate',
|
||||||
DateTime.fromMillis(date.valueOf()).setZone('Asia/Manila').toFormat('yyyy-MM-dd')
|
DateTime.fromMillis(date.valueOf()).setZone('Asia/Manila').toFormat('yyyy-MM-dd')
|
||||||
)
|
)
|
||||||
params.append('optTime', '00:00:00')
|
params.append('optTime', '00:00:00')
|
||||||
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser({ content, date }) {
|
parser({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
let start = parseStart($item, date)
|
let start = parseStart($item, date)
|
||||||
let stop = parseStop($item, date)
|
let stop = parseStop($item, date)
|
||||||
if (!start || !stop) return
|
if (!start || !stop) return
|
||||||
if (start > stop) {
|
if (start > stop) {
|
||||||
stop = stop.plus({ days: 1 })
|
stop = stop.plus({ days: 1 })
|
||||||
}
|
}
|
||||||
|
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle($item),
|
title: parseTitle($item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const html = await axios
|
const html = await axios
|
||||||
.get('https://www.clickthecity.com/tv/channels/')
|
.get('https://www.clickthecity.com/tv/channels/')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
const $ = cheerio.load(html)
|
const $ = cheerio.load(html)
|
||||||
const items = $('#channels .col').toArray()
|
const items = $('#channels .col').toArray()
|
||||||
|
|
||||||
return items.map(item => {
|
return items.map(item => {
|
||||||
const name = $(item).find('.card-body').text().trim()
|
const name = $(item).find('.card-body').text().trim()
|
||||||
const url = $(item).find('a').attr('href')
|
const url = $(item).find('a').attr('href')
|
||||||
const [, site_id] = url.match(/netid=(\d+)/) || [null, null]
|
const [, site_id] = url.match(/netid=(\d+)/) || [null, null]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
site_id,
|
site_id,
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('td > a').text().trim()
|
return $item('td > a').text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
const url = $item('td.cPrg > a').attr('href') || ''
|
const url = $item('td.cPrg > a').attr('href') || ''
|
||||||
let [, time] = url.match(/starttime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null]
|
let [, time] = url.match(/starttime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null]
|
||||||
if (!time) return null
|
if (!time) return null
|
||||||
time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}`
|
time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}`
|
||||||
|
|
||||||
return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC()
|
return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop($item, date) {
|
function parseStop($item, date) {
|
||||||
const url = $item('td.cPrg > a').attr('href') || ''
|
const url = $item('td.cPrg > a').attr('href') || ''
|
||||||
let [, time] = url.match(/endtime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null]
|
let [, time] = url.match(/endtime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null]
|
||||||
if (!time) return null
|
if (!time) return null
|
||||||
time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}`
|
time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}`
|
||||||
|
|
||||||
return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC()
|
return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
return $('#tvlistings > tbody > tr')
|
return $('#tvlistings > tbody > tr')
|
||||||
.filter(function () {
|
.filter(function () {
|
||||||
return $(this).find('td.cPrg').length
|
return $(this).find('td.cPrg').length
|
||||||
})
|
})
|
||||||
.toArray()
|
.toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +1,70 @@
|
||||||
// npm run channels:parse -- --config=./sites/clickthecity.com/clickthecity.com.config.js --output=./sites/clickthecity.com/clickthecity.com.channels.xml
|
// npm run channels:parse -- --config=./sites/clickthecity.com/clickthecity.com.config.js --output=./sites/clickthecity.com/clickthecity.com.channels.xml
|
||||||
// npm run grab -- --site=clickthecity.com
|
// npm run grab -- --site=clickthecity.com
|
||||||
|
|
||||||
const { parser, url, request } = require('./clickthecity.com.config.js')
|
const { parser, url, request } = require('./clickthecity.com.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-06-12', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-06-12', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '5',
|
site_id: '5',
|
||||||
xmltv_id: 'TV5.ph'
|
xmltv_id: 'TV5.ph'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel })).toBe('https://www.clickthecity.com/tv/channels/?netid=5')
|
expect(url({ channel })).toBe('https://www.clickthecity.com/tv/channels/?netid=5')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request method', () => {
|
it('can generate valid request method', () => {
|
||||||
expect(request.method).toBe('POST')
|
expect(request.method).toBe('POST')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request headers', () => {
|
it('can generate valid request headers', () => {
|
||||||
expect(request.headers).toMatchObject({
|
expect(request.headers).toMatchObject({
|
||||||
'content-type': 'application/x-www-form-urlencoded'
|
'content-type': 'application/x-www-form-urlencoded'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request data', () => {
|
it('can generate valid request data', () => {
|
||||||
const result = request.data({ date })
|
const result = request.data({ date })
|
||||||
expect(result.get('optDate')).toBe('2023-06-12')
|
expect(result.get('optDate')).toBe('2023-06-12')
|
||||||
expect(result.get('optTime')).toBe('00:00:00')
|
expect(result.get('optTime')).toBe('00:00:00')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||||
const results = parser({ content, date }).map(p => {
|
const results = parser({ content, date }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results.length).toBe(20)
|
expect(results.length).toBe(20)
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-06-11T21:00:00.000Z',
|
start: '2023-06-11T21:00:00.000Z',
|
||||||
stop: '2023-06-11T22:00:00.000Z',
|
stop: '2023-06-11T22:00:00.000Z',
|
||||||
title: 'Word Of God'
|
title: 'Word Of God'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[19]).toMatchObject({
|
expect(results[19]).toMatchObject({
|
||||||
start: '2023-06-12T15:30:00.000Z',
|
start: '2023-06-12T15:30:00.000Z',
|
||||||
stop: '2023-06-12T16:00:00.000Z',
|
stop: '2023-06-12T16:00:00.000Z',
|
||||||
title: 'La Suerte De Loli'
|
title: 'La Suerte De Loli'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content:
|
content:
|
||||||
'<!DOCTYPE html><html class="html" lang="en-US" prefix="og: https://ogp.me/ns#"><head></head><body></body></html>'
|
'<!DOCTYPE html><html class="html" lang="en-US" prefix="og: https://ogp.me/ns#"><head></head><body></body></html>'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
const parser = require('epg-parser')
|
const parser = require('epg-parser')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'compulms.com',
|
site: 'compulms.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 60 * 60 * 1000 // 1 hour
|
ttl: 60 * 60 * 1000 // 1 hour
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url: 'https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml',
|
url: 'https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml',
|
||||||
parser: function ({ content, channel, date }) {
|
parser: function ({ content, channel, date }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel, date)
|
const items = parseItems(content, channel, date)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title?.[0].value,
|
title: item.title?.[0].value,
|
||||||
description: item.desc?.[0].value,
|
description: item.desc?.[0].value,
|
||||||
icon: item.icon?.[0],
|
icon: item.icon?.[0],
|
||||||
start: item.start,
|
start: item.start,
|
||||||
stop: item.stop
|
stop: item.stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel, date) {
|
function parseItems(content, channel, date) {
|
||||||
const { programs } = parser.parse(content)
|
const { programs } = parser.parse(content)
|
||||||
|
|
||||||
return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day'))
|
return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day'))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
// npm run grab -- --site=compulms.com
|
// npm run grab -- --site=compulms.com
|
||||||
|
|
||||||
const { parser, url } = require('./compulms.com.config.js')
|
const { parser, url } = require('./compulms.com.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2022-11-29', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-11-29', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'EnerGeek Retro',
|
site_id: 'EnerGeek Retro',
|
||||||
xmltv_id: 'EnerGeekRetro.cl'
|
xmltv_id: 'EnerGeekRetro.cl'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url).toBe('https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml')
|
expect(url).toBe('https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'))
|
||||||
let results = parser({ content, channel, date })
|
let results = parser({ content, channel, date })
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-11-29T03:00:00.000Z',
|
start: '2022-11-29T03:00:00.000Z',
|
||||||
stop: '2022-11-29T03:30:00.000Z',
|
stop: '2022-11-29T03:30:00.000Z',
|
||||||
title: 'Noir',
|
title: 'Noir',
|
||||||
description:
|
description:
|
||||||
'Kirika Yuumura es una adolescente japonesa que no recuerda nada de su pasado, salvo la palabra NOIR, por lo que decidirá contactar con Mireille Bouquet, una asesina profesional para que la ayude a investigar. Ambas forman un equipo muy eficiente, que resuelve un trabajo tras otro con gran éxito, hasta que aparece un grupo conocido como "Les Soldats", relacionados con el pasado de Kirika. Estos tratarán de eliminar a las dos chicas, antes de que indaguen más hondo sobre la verdad acerca de Noir',
|
'Kirika Yuumura es una adolescente japonesa que no recuerda nada de su pasado, salvo la palabra NOIR, por lo que decidirá contactar con Mireille Bouquet, una asesina profesional para que la ayude a investigar. Ambas forman un equipo muy eficiente, que resuelve un trabajo tras otro con gran éxito, hasta que aparece un grupo conocido como "Les Soldats", relacionados con el pasado de Kirika. Estos tratarán de eliminar a las dos chicas, antes de que indaguen más hondo sobre la verdad acerca de Noir',
|
||||||
icon: 'https://pics.filmaffinity.com/nowaru_noir_tv_series-225888552-mmed.jpg'
|
icon: 'https://pics.filmaffinity.com/nowaru_noir_tv_series-225888552-mmed.jpg'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({ content: '', channel, date })
|
const result = parser({ content: '', channel, date })
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,68 +1,68 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'comteco.com.bo',
|
site: 'comteco.com.bo',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ channel }) {
|
url: function ({ channel }) {
|
||||||
return `https://comteco.com.bo/pages/canales-y-programacion-tv/paquete-oro/${channel.site_id}`
|
return `https://comteco.com.bo/pages/canales-y-programacion-tv/paquete-oro/${channel.site_id}`
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
},
|
},
|
||||||
data: function ({ date }) {
|
data: function ({ date }) {
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
params.append('_method', 'POST')
|
params.append('_method', 'POST')
|
||||||
params.append('fechaini', date.format('D/M/YYYY'))
|
params.append('fechaini', date.format('D/M/YYYY'))
|
||||||
params.append('fechafin', date.format('D/M/YYYY'))
|
params.append('fechafin', date.format('D/M/YYYY'))
|
||||||
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser: function ({ content, date }) {
|
parser: function ({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
let start = parseStart($item, date)
|
let start = parseStart($item, date)
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (start.isBefore(prev.start)) {
|
if (start.isBefore(prev.start)) {
|
||||||
start = start.add(1, 'd')
|
start = start.add(1, 'd')
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
prev.stop = start
|
prev.stop = start
|
||||||
}
|
}
|
||||||
const stop = start.add(30, 'm')
|
const stop = start.add(30, 'm')
|
||||||
programs.push({ title: parseTitle($item), start, stop })
|
programs.push({ title: parseTitle($item), start, stop })
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
const timeString = $item('div > div.col-xs-11 > p > span').text().trim()
|
const timeString = $item('div > div.col-xs-11 > p > span').text().trim()
|
||||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||||
|
|
||||||
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm:ss', 'America/La_Paz')
|
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm:ss', 'America/La_Paz')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('div > div.col-xs-11 > p > strong').text().trim()
|
return $item('div > div.col-xs-11 > p > strong').text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
return $('#datosasociados > div > .list-group-item').toArray()
|
return $('#datosasociados > div > .list-group-item').toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +1,74 @@
|
||||||
// npm run grab -- --site=comteco.com.bo
|
// npm run grab -- --site=comteco.com.bo
|
||||||
|
|
||||||
const { parser, url, request } = require('./comteco.com.bo.config.js')
|
const { parser, url, request } = require('./comteco.com.bo.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2021-11-25', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2021-11-25', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'ABYA YALA',
|
site_id: 'ABYA YALA',
|
||||||
xmltv_id: 'AbyaYalaTV.bo'
|
xmltv_id: 'AbyaYalaTV.bo'
|
||||||
}
|
}
|
||||||
const content =
|
const content =
|
||||||
'<!DOCTYPE html><html dir="ltr" lang="es"> <head></head> <body class=""> <div id="wrapper" class="clearfix"> <div class="main-content"> <section class="rubroguias"> <div class="container pt-70 pb-40"> <div class="section-content"> <form method="post" accept-charset="utf-8" class="reservation-form mb-0" role="form" id="myform" action="/pages/canales-y-programacion-tv/paquete-oro/ABYA%20YALA" > <div style="display: none"><input type="hidden" name="_method" value="POST"/></div><div class="row"> <div class="col-sm-5"> <div class="col-xs-5 col-sm-7"> <img src="/img/upload/canales/abya-yala.png" alt="" class="img-responsive"/> </div><div class="col-xs-7 col-sm-5 mt-sm-50 mt-lg-50 mt-md-50 mt-xs-20"> <p><strong>Canal Analógico:</strong> 48</p></div></div></div></form> <div class="row"> <div class="col-sm-12"> <div class="row mt-0"> <div class="single-service"> <h3 class=" text-theme-colored line-bottom text-theme-colored mt-0 text-uppercase " > ABYA YALA </h3> <div id="datosasociados"> <div class="list-group"> <div href="#" class="list-group-item bg-white-f1"> <div class="row"> <div class="col-xs-11"> <p class="mb-0"> <span class="text-red mr-15">00:00:00</span> <strong>Abya Yala noticias - 3ra edición</strong> </p></div></div></div><div href="#" class="list-group-item bg-white-f1"> <div class="row"> <div class="col-xs-11"> <p class="mb-0"> <span class="text-red mr-15">01:00:00</span> <strong>Cierre de emisión</strong> </p></div></div></div><div href="#" class="list-group-item bg-white-f1"> <div class="row"> <div class="col-xs-11"> <p class="mb-0"> <span class="text-red mr-15">23:00:00</span> <strong>Referentes</strong> </p></div></div></div><p class="mt-20"> <a href="/pages/canales-y-programacion-tv" class="btn btn-border btn-gray btn-transparent btn-circled" >Regresar a canales</a > </p></div></div></div></div></div></div></div></div></section> </div></div></body></html>'
|
'<!DOCTYPE html><html dir="ltr" lang="es"> <head></head> <body class=""> <div id="wrapper" class="clearfix"> <div class="main-content"> <section class="rubroguias"> <div class="container pt-70 pb-40"> <div class="section-content"> <form method="post" accept-charset="utf-8" class="reservation-form mb-0" role="form" id="myform" action="/pages/canales-y-programacion-tv/paquete-oro/ABYA%20YALA" > <div style="display: none"><input type="hidden" name="_method" value="POST"/></div><div class="row"> <div class="col-sm-5"> <div class="col-xs-5 col-sm-7"> <img src="/img/upload/canales/abya-yala.png" alt="" class="img-responsive"/> </div><div class="col-xs-7 col-sm-5 mt-sm-50 mt-lg-50 mt-md-50 mt-xs-20"> <p><strong>Canal Analógico:</strong> 48</p></div></div></div></form> <div class="row"> <div class="col-sm-12"> <div class="row mt-0"> <div class="single-service"> <h3 class=" text-theme-colored line-bottom text-theme-colored mt-0 text-uppercase " > ABYA YALA </h3> <div id="datosasociados"> <div class="list-group"> <div href="#" class="list-group-item bg-white-f1"> <div class="row"> <div class="col-xs-11"> <p class="mb-0"> <span class="text-red mr-15">00:00:00</span> <strong>Abya Yala noticias - 3ra edición</strong> </p></div></div></div><div href="#" class="list-group-item bg-white-f1"> <div class="row"> <div class="col-xs-11"> <p class="mb-0"> <span class="text-red mr-15">01:00:00</span> <strong>Cierre de emisión</strong> </p></div></div></div><div href="#" class="list-group-item bg-white-f1"> <div class="row"> <div class="col-xs-11"> <p class="mb-0"> <span class="text-red mr-15">23:00:00</span> <strong>Referentes</strong> </p></div></div></div><p class="mt-20"> <a href="/pages/canales-y-programacion-tv" class="btn btn-border btn-gray btn-transparent btn-circled" >Regresar a canales</a > </p></div></div></div></div></div></div></div></div></section> </div></div></body></html>'
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel })).toBe(
|
expect(url({ channel })).toBe(
|
||||||
'https://comteco.com.bo/pages/canales-y-programacion-tv/paquete-oro/ABYA YALA'
|
'https://comteco.com.bo/pages/canales-y-programacion-tv/paquete-oro/ABYA YALA'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request method', () => {
|
it('can generate valid request method', () => {
|
||||||
expect(request.method).toBe('POST')
|
expect(request.method).toBe('POST')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request headers', () => {
|
it('can generate valid request headers', () => {
|
||||||
expect(request.headers).toMatchObject({
|
expect(request.headers).toMatchObject({
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request data', () => {
|
it('can generate valid request data', () => {
|
||||||
const result = request.data({ date })
|
const result = request.data({ date })
|
||||||
expect(result.get('_method')).toBe('POST')
|
expect(result.get('_method')).toBe('POST')
|
||||||
expect(result.get('fechaini')).toBe('25/11/2021')
|
expect(result.get('fechaini')).toBe('25/11/2021')
|
||||||
expect(result.get('fechafin')).toBe('25/11/2021')
|
expect(result.get('fechafin')).toBe('25/11/2021')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const result = parser({ content, channel, date }).map(p => {
|
const result = parser({ content, channel, date }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2021-11-25T04:00:00.000Z',
|
start: '2021-11-25T04:00:00.000Z',
|
||||||
stop: '2021-11-25T05:00:00.000Z',
|
stop: '2021-11-25T05:00:00.000Z',
|
||||||
title: 'Abya Yala noticias - 3ra edición'
|
title: 'Abya Yala noticias - 3ra edición'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: '2021-11-25T05:00:00.000Z',
|
start: '2021-11-25T05:00:00.000Z',
|
||||||
stop: '2021-11-26T03:00:00.000Z',
|
stop: '2021-11-26T03:00:00.000Z',
|
||||||
title: 'Cierre de emisión'
|
title: 'Cierre de emisión'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: '2021-11-26T03:00:00.000Z',
|
start: '2021-11-26T03:00:00.000Z',
|
||||||
stop: '2021-11-26T03:30:00.000Z',
|
stop: '2021-11-26T03:30:00.000Z',
|
||||||
title: 'Referentes'
|
title: 'Referentes'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,78 +1,78 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const { DateTime } = require('luxon')
|
const { DateTime } = require('luxon')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'cosmote.gr',
|
site: 'cosmote.gr',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ date, channel }) {
|
url: function ({ date, channel }) {
|
||||||
return `https://www.cosmotetv.gr/portal/residential/program/epg/programchannel?p_p_id=channelprogram_WAR_OTETVportlet&p_p_lifecycle=0&_channelprogram_WAR_OTETVportlet_platform=IPTV&_channelprogram_WAR_OTETVportlet_date=${date.format(
|
return `https://www.cosmotetv.gr/portal/residential/program/epg/programchannel?p_p_id=channelprogram_WAR_OTETVportlet&p_p_lifecycle=0&_channelprogram_WAR_OTETVportlet_platform=IPTV&_channelprogram_WAR_OTETVportlet_date=${date.format(
|
||||||
'DD-MM-YYYY'
|
'DD-MM-YYYY'
|
||||||
)}&_channelprogram_WAR_OTETVportlet_articleTitleUrl=${channel.site_id}`
|
)}&_channelprogram_WAR_OTETVportlet_articleTitleUrl=${channel.site_id}`
|
||||||
},
|
},
|
||||||
parser: function ({ date, content }) {
|
parser: function ({ date, content }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
items.forEach((item, i) => {
|
items.forEach((item, i) => {
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
let start = parseStart($item, date)
|
let start = parseStart($item, date)
|
||||||
if (i === 0 && start.hour > 12 && start.hour < 21) {
|
if (i === 0 && start.hour > 12 && start.hour < 21) {
|
||||||
date = date.subtract(1, 'd')
|
date = date.subtract(1, 'd')
|
||||||
start = start.minus({ days: 1 })
|
start = start.minus({ days: 1 })
|
||||||
}
|
}
|
||||||
if (prev && start < prev.start) {
|
if (prev && start < prev.start) {
|
||||||
start = start.plus({ days: 1 })
|
start = start.plus({ days: 1 })
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
let stop = parseStop($item, date)
|
let stop = parseStop($item, date)
|
||||||
if (stop < start) {
|
if (stop < start) {
|
||||||
stop = stop.plus({ days: 1 })
|
stop = stop.plus({ days: 1 })
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle($item),
|
title: parseTitle($item),
|
||||||
category: parseCategory($item),
|
category: parseCategory($item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('.channel_program-table--program > a').text()
|
return $item('.channel_program-table--program > a').text()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory($item) {
|
function parseCategory($item) {
|
||||||
const typeString = $item('.channel_program-table--program_type')
|
const typeString = $item('.channel_program-table--program_type')
|
||||||
.children()
|
.children()
|
||||||
.remove()
|
.remove()
|
||||||
.end()
|
.end()
|
||||||
.text()
|
.text()
|
||||||
.trim()
|
.trim()
|
||||||
const [, category] = typeString.match(/\| (.*)/) || [null, null]
|
const [, category] = typeString.match(/\| (.*)/) || [null, null]
|
||||||
|
|
||||||
return category
|
return category
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
const timeString = $item('span.start-time').text()
|
const timeString = $item('span.start-time').text()
|
||||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||||
|
|
||||||
return DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Athens' }).toUTC()
|
return DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Athens' }).toUTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop($item, date) {
|
function parseStop($item, date) {
|
||||||
const timeString = $item('span.end-time').text()
|
const timeString = $item('span.end-time').text()
|
||||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||||
|
|
||||||
return DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Athens' }).toUTC()
|
return DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Athens' }).toUTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
return $('#_channelprogram_WAR_OTETVportlet_programs > tr.d-sm-table-row').toArray()
|
return $('#_channelprogram_WAR_OTETVportlet_programs > tr.d-sm-table-row').toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +1,79 @@
|
||||||
// npm run grab -- --site=cosmote.gr
|
// npm run grab -- --site=cosmote.gr
|
||||||
|
|
||||||
const { parser, url } = require('./cosmote.gr.config.js')
|
const { parser, url } = require('./cosmote.gr.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-06-08', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-06-08', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '4e',
|
site_id: '4e',
|
||||||
xmltv_id: '4E.gr'
|
xmltv_id: '4E.gr'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'https://www.cosmotetv.gr/portal/residential/program/epg/programchannel?p_p_id=channelprogram_WAR_OTETVportlet&p_p_lifecycle=0&_channelprogram_WAR_OTETVportlet_platform=IPTV&_channelprogram_WAR_OTETVportlet_date=08-06-2023&_channelprogram_WAR_OTETVportlet_articleTitleUrl=4e'
|
'https://www.cosmotetv.gr/portal/residential/program/epg/programchannel?p_p_id=channelprogram_WAR_OTETVportlet&p_p_lifecycle=0&_channelprogram_WAR_OTETVportlet_platform=IPTV&_channelprogram_WAR_OTETVportlet_date=08-06-2023&_channelprogram_WAR_OTETVportlet_articleTitleUrl=4e'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content1.html'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content1.html'))
|
||||||
const results = parser({ content, date }).map(p => {
|
const results = parser({ content, date }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-06-07T20:30:00.000Z',
|
start: '2023-06-07T20:30:00.000Z',
|
||||||
stop: '2023-06-07T21:45:00.000Z',
|
stop: '2023-06-07T21:45:00.000Z',
|
||||||
title: 'Τηλεφημερίδα',
|
title: 'Τηλεφημερίδα',
|
||||||
category: 'Εκπομπή - Μαγκαζίνο'
|
category: 'Εκπομπή - Μαγκαζίνο'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[30]).toMatchObject({
|
expect(results[30]).toMatchObject({
|
||||||
start: '2023-06-08T19:45:00.000Z',
|
start: '2023-06-08T19:45:00.000Z',
|
||||||
stop: '2023-06-08T20:30:00.000Z',
|
stop: '2023-06-08T20:30:00.000Z',
|
||||||
title: 'Μικρό Απόδειπνο',
|
title: 'Μικρό Απόδειπνο',
|
||||||
category: 'Special'
|
category: 'Special'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response when the guide starting before midnight', () => {
|
it('can parse response when the guide starting before midnight', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content2.html'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content2.html'))
|
||||||
const results = parser({ content, date }).map(p => {
|
const results = parser({ content, date }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-06-07T21:30:00.000Z',
|
start: '2023-06-07T21:30:00.000Z',
|
||||||
stop: '2023-06-07T22:30:00.000Z',
|
stop: '2023-06-07T22:30:00.000Z',
|
||||||
title: 'Καλύτερα Αργά',
|
title: 'Καλύτερα Αργά',
|
||||||
category: 'Ψυχαγωγική Εκπομπή'
|
category: 'Ψυχαγωγική Εκπομπή'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[22]).toMatchObject({
|
expect(results[22]).toMatchObject({
|
||||||
start: '2023-06-08T19:00:00.000Z',
|
start: '2023-06-08T19:00:00.000Z',
|
||||||
stop: '2023-06-08T21:30:00.000Z',
|
stop: '2023-06-08T21:30:00.000Z',
|
||||||
title: 'Πίσω Από Τις Γραμμές',
|
title: 'Πίσω Από Τις Γραμμές',
|
||||||
category: 'Εκπομπή - Μαγκαζίνο'
|
category: 'Εκπομπή - Μαγκαζίνο'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,70 +1,70 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'delta.nl',
|
site: 'delta.nl',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ channel, date }) {
|
url: function ({ channel, date }) {
|
||||||
return `https://clientapi.tv.delta.nl/guide/channels/list?start=${date.unix()}&end=${date
|
return `https://clientapi.tv.delta.nl/guide/channels/list?start=${date.unix()}&end=${date
|
||||||
.add(1, 'd')
|
.add(1, 'd')
|
||||||
.unix()}&includeDetails=true&channels=${channel.site_id}`
|
.unix()}&includeDetails=true&channels=${channel.site_id}`
|
||||||
},
|
},
|
||||||
async parser({ content, channel }) {
|
async parser({ content, channel }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
const details = await loadProgramDetails(item)
|
const details = await loadProgramDetails(item)
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
icon: item.images.thumbnail.url,
|
icon: item.images.thumbnail.url,
|
||||||
description: details.description,
|
description: details.description,
|
||||||
start: parseStart(item).toJSON(),
|
start: parseStart(item).toJSON(),
|
||||||
stop: parseStop(item).toJSON()
|
stop: parseStop(item).toJSON()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const items = await axios
|
const items = await axios
|
||||||
.get('https://clientapi.tv.delta.nl/channels/list')
|
.get('https://clientapi.tv.delta.nl/channels/list')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
return items
|
return items
|
||||||
.filter(i => i.type === 'TV')
|
.filter(i => i.type === 'TV')
|
||||||
.map(item => {
|
.map(item => {
|
||||||
return {
|
return {
|
||||||
lang: 'nl',
|
lang: 'nl',
|
||||||
site_id: item['ID'],
|
site_id: item['ID'],
|
||||||
name: item.name
|
name: item.name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProgramDetails(item) {
|
async function loadProgramDetails(item) {
|
||||||
if (!item.ID) return {}
|
if (!item.ID) return {}
|
||||||
const url = `https://clientapi.tv.delta.nl/guide/4/details/${item.ID}?X-Response-Version=4.5`
|
const url = `https://clientapi.tv.delta.nl/guide/4/details/${item.ID}?X-Response-Version=4.5`
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
return data || {}
|
return data || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs.unix(item.start)
|
return dayjs.unix(item.start)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseStop(item) {
|
||||||
return dayjs.unix(item.end)
|
return dayjs.unix(item.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data) return []
|
if (!data) return []
|
||||||
|
|
||||||
return data[channel.site_id] || []
|
return data[channel.site_id] || []
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +1,70 @@
|
||||||
// npm run channels:parse -- --config=./sites/delta.nl/delta.nl.config.js --output=./sites/delta.nl/delta.nl.channels.xml
|
// npm run channels:parse -- --config=./sites/delta.nl/delta.nl.config.js --output=./sites/delta.nl/delta.nl.channels.xml
|
||||||
// npm run grab -- --site=delta.nl
|
// npm run grab -- --site=delta.nl
|
||||||
|
|
||||||
const { parser, url } = require('./delta.nl.config.js')
|
const { parser, url } = require('./delta.nl.config.js')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const date = dayjs.utc('2021-11-12', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2021-11-12', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '1',
|
site_id: '1',
|
||||||
xmltv_id: 'NPO1.nl'
|
xmltv_id: 'NPO1.nl'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'https://clientapi.tv.delta.nl/guide/channels/list?start=1636675200&end=1636761600&includeDetails=true&channels=1'
|
'https://clientapi.tv.delta.nl/guide/channels/list?start=1636675200&end=1636761600&includeDetails=true&channels=1'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', done => {
|
it('can parse response', done => {
|
||||||
axios.get.mockImplementation(() =>
|
axios.get.mockImplementation(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
data: JSON.parse(
|
data: JSON.parse(
|
||||||
'{"ID":"P~945cb98e-3d19-11ec-8456-953363d7a344","seriesID":"S~d37c4626-b691-11ea-ba69-255835135f02","channelID":"1","start":1636674960,"end":1636676520,"catchupAvailableUntil":1637279760,"title":"Eigen Huis & Tuin: Lekker Leven","description":"Nederlands lifestyleprogramma uit 2022 (ook in HD) met dagelijkse inspiratie voor een lekker leven in en om het huis.\\nPresentatrice Froukje de Both, kok Hugo Kennis en een team van experts, onder wie tuinman Tom Groot, geven praktische tips op het gebied van wonen, lifestyle, tuinieren en koken. Daarmee kun je zelf direct aan de slag om je leven leuker én gezonder te maken. Afl. 15 van seizoen 4.","images":{"thumbnail":{"url":"https://cdn.gvidi.tv/img/booxmedia/b291/561946.jpg"}},"additionalInformation":{"metadataID":"M~c512c206-95e5-11ec-87d8-494f70130311","externalMetadataID":"E~RTL4-89d99356_6599_4b65_a7a0_a93f39019645"},"parentalGuidance":{"kijkwijzer":["AL"]},"restrictions":{"startoverDisabled":false,"catchupDisabled":false,"recordingDisabled":false},"isFiller":false}'
|
'{"ID":"P~945cb98e-3d19-11ec-8456-953363d7a344","seriesID":"S~d37c4626-b691-11ea-ba69-255835135f02","channelID":"1","start":1636674960,"end":1636676520,"catchupAvailableUntil":1637279760,"title":"Eigen Huis & Tuin: Lekker Leven","description":"Nederlands lifestyleprogramma uit 2022 (ook in HD) met dagelijkse inspiratie voor een lekker leven in en om het huis.\\nPresentatrice Froukje de Both, kok Hugo Kennis en een team van experts, onder wie tuinman Tom Groot, geven praktische tips op het gebied van wonen, lifestyle, tuinieren en koken. Daarmee kun je zelf direct aan de slag om je leven leuker én gezonder te maken. Afl. 15 van seizoen 4.","images":{"thumbnail":{"url":"https://cdn.gvidi.tv/img/booxmedia/b291/561946.jpg"}},"additionalInformation":{"metadataID":"M~c512c206-95e5-11ec-87d8-494f70130311","externalMetadataID":"E~RTL4-89d99356_6599_4b65_a7a0_a93f39019645"},"parentalGuidance":{"kijkwijzer":["AL"]},"restrictions":{"startoverDisabled":false,"catchupDisabled":false,"recordingDisabled":false},"isFiller":false}'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const content =
|
const content =
|
||||||
'{"1":[{"ID":"P~945cb98e-3d19-11ec-8456-953363d7a344","seriesID":"S~d37c4626-b691-11ea-ba69-255835135f02","channelID":"1","start":1636674960,"end":1636676520,"catchupAvailableUntil":1637279760,"title":"NOS Journaal","images":{"thumbnail":{"url":"https://cdn.gvidi.tv/img/booxmedia/e19c/static/NOS%20Journaal5.jpg"}},"additionalInformation":{"metadataID":"M~944f3c6e-3d19-11ec-9faf-2735f2e98d2a","externalMetadataID":"E~TV01-2026117420668"},"parentalGuidance":{"kijkwijzer":["AL"]},"restrictions":{"startoverDisabled":false,"catchupDisabled":false,"recordingDisabled":false},"isFiller":false}]}'
|
'{"1":[{"ID":"P~945cb98e-3d19-11ec-8456-953363d7a344","seriesID":"S~d37c4626-b691-11ea-ba69-255835135f02","channelID":"1","start":1636674960,"end":1636676520,"catchupAvailableUntil":1637279760,"title":"NOS Journaal","images":{"thumbnail":{"url":"https://cdn.gvidi.tv/img/booxmedia/e19c/static/NOS%20Journaal5.jpg"}},"additionalInformation":{"metadataID":"M~944f3c6e-3d19-11ec-9faf-2735f2e98d2a","externalMetadataID":"E~TV01-2026117420668"},"parentalGuidance":{"kijkwijzer":["AL"]},"restrictions":{"startoverDisabled":false,"catchupDisabled":false,"recordingDisabled":false},"isFiller":false}]}'
|
||||||
|
|
||||||
parser({ date, channel, content })
|
parser({ date, channel, content })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2021-11-11T23:56:00.000Z',
|
start: '2021-11-11T23:56:00.000Z',
|
||||||
stop: '2021-11-12T00:22:00.000Z',
|
stop: '2021-11-12T00:22:00.000Z',
|
||||||
title: 'NOS Journaal',
|
title: 'NOS Journaal',
|
||||||
description:
|
description:
|
||||||
'Nederlands lifestyleprogramma uit 2022 (ook in HD) met dagelijkse inspiratie voor een lekker leven in en om het huis.\nPresentatrice Froukje de Both, kok Hugo Kennis en een team van experts, onder wie tuinman Tom Groot, geven praktische tips op het gebied van wonen, lifestyle, tuinieren en koken. Daarmee kun je zelf direct aan de slag om je leven leuker én gezonder te maken. Afl. 15 van seizoen 4.',
|
'Nederlands lifestyleprogramma uit 2022 (ook in HD) met dagelijkse inspiratie voor een lekker leven in en om het huis.\nPresentatrice Froukje de Both, kok Hugo Kennis en een team van experts, onder wie tuinman Tom Groot, geven praktische tips op het gebied van wonen, lifestyle, tuinieren en koken. Daarmee kun je zelf direct aan de slag om je leven leuker én gezonder te maken. Afl. 15 van seizoen 4.',
|
||||||
icon: 'https://cdn.gvidi.tv/img/booxmedia/e19c/static/NOS%20Journaal5.jpg'
|
icon: 'https://cdn.gvidi.tv/img/booxmedia/e19c/static/NOS%20Journaal5.jpg'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
done(error)
|
done(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', done => {
|
it('can handle empty guide', done => {
|
||||||
parser({
|
parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content: '{"code":500,"message":"Error retrieving guide"}'
|
content: '{"code":500,"message":"Error retrieving guide"}'
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
done(error)
|
done(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,77 +1,77 @@
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
// category list is not complete
|
// category list is not complete
|
||||||
// const categories = {
|
// const categories = {
|
||||||
// '00': 'Diğer',
|
// '00': 'Diğer',
|
||||||
// E0: 'Romantik Komedi',
|
// E0: 'Romantik Komedi',
|
||||||
// E1: 'Aksiyon',
|
// E1: 'Aksiyon',
|
||||||
// E4: 'Macera',
|
// E4: 'Macera',
|
||||||
// E5: 'Dram',
|
// E5: 'Dram',
|
||||||
// E6: 'Fantastik',
|
// E6: 'Fantastik',
|
||||||
// E7: 'Komedi',
|
// E7: 'Komedi',
|
||||||
// E8: 'Korku',
|
// E8: 'Korku',
|
||||||
// EB: 'Polisiye',
|
// EB: 'Polisiye',
|
||||||
// EF: 'Western',
|
// EF: 'Western',
|
||||||
// FA: 'Macera',
|
// FA: 'Macera',
|
||||||
// FB: 'Yarışma',
|
// FB: 'Yarışma',
|
||||||
// FC: 'Eğlence',
|
// FC: 'Eğlence',
|
||||||
// F0: 'Reality-Show',
|
// F0: 'Reality-Show',
|
||||||
// F2: 'Haberler',
|
// F2: 'Haberler',
|
||||||
// F4: 'Belgesel',
|
// F4: 'Belgesel',
|
||||||
// F6: 'Eğitim',
|
// F6: 'Eğitim',
|
||||||
// F7: 'Sanat ve Kültür',
|
// F7: 'Sanat ve Kültür',
|
||||||
// F9: 'Life Style'
|
// F9: 'Life Style'
|
||||||
// }
|
// }
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'digiturk.com.tr',
|
site: 'digiturk.com.tr',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ date, channel }) {
|
url: function ({ date, channel }) {
|
||||||
return `https://www.digiturk.com.tr/_Ajax/getBroadcast.aspx?channelNo=${
|
return `https://www.digiturk.com.tr/_Ajax/getBroadcast.aspx?channelNo=${
|
||||||
channel.site_id
|
channel.site_id
|
||||||
}&date=${date.format('DD.MM.YYYY')}&tomorrow=false&primetime=false`
|
}&date=${date.format('DD.MM.YYYY')}&tomorrow=false&primetime=false`
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Referer: 'https://www.digiturk.com.tr/'
|
Referer: 'https://www.digiturk.com.tr/'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser: function ({ content }) {
|
parser: function ({ content }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.PName,
|
title: item.PName,
|
||||||
// description: item.LongDescription,
|
// description: item.LongDescription,
|
||||||
// category: parseCategory(item),
|
// category: parseCategory(item),
|
||||||
start: parseTime(item.PStartTime),
|
start: parseTime(item.PStartTime),
|
||||||
stop: parseTime(item.PEndTime)
|
stop: parseTime(item.PEndTime)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
programs = _.sortBy(programs, 'start')
|
programs = _.sortBy(programs, 'start')
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTime(time) {
|
function parseTime(time) {
|
||||||
let timestamp = parseInt(time.replace('/Date(', '').replace('+0300)/', ''))
|
let timestamp = parseInt(time.replace('/Date(', '').replace('+0300)/', ''))
|
||||||
return dayjs(timestamp)
|
return dayjs(timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// function parseCategory(item) {
|
// function parseCategory(item) {
|
||||||
// return (item.PGenre) ? categories[item.PGenre] : null
|
// return (item.PGenre) ? categories[item.PGenre] : null
|
||||||
// }
|
// }
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
if (!content) return []
|
if (!content) return []
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
return data && data.BChannels && data.BChannels[0].CPrograms ? data.BChannels[0].CPrograms : []
|
return data && data.BChannels && data.BChannels[0].CPrograms ? data.BChannels[0].CPrograms : []
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
// npm run grab -- --site=digiturk.com.tr
|
// npm run grab -- --site=digiturk.com.tr
|
||||||
|
|
||||||
const { parser, url } = require('./digiturk.com.tr.config.js')
|
const { parser, url } = require('./digiturk.com.tr.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '14',
|
site_id: '14',
|
||||||
xmltv_id: 'beINMovies2Action.qa'
|
xmltv_id: 'beINMovies2Action.qa'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
const result = url({ date, channel })
|
const result = url({ date, channel })
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'https://www.digiturk.com.tr/_Ajax/getBroadcast.aspx?channelNo=14&date=19.01.2023&tomorrow=false&primetime=false'
|
'https://www.digiturk.com.tr/_Ajax/getBroadcast.aspx?channelNo=14&date=19.01.2023&tomorrow=false&primetime=false'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
const results = parser({ content }).map(p => {
|
const results = parser({ content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-01-18T20:40:00.000Z',
|
start: '2023-01-18T20:40:00.000Z',
|
||||||
stop: '2023-01-18T22:32:00.000Z',
|
stop: '2023-01-18T22:32:00.000Z',
|
||||||
title: 'PARÇALANMIŞ'
|
title: 'PARÇALANMIŞ'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[10]).toMatchObject({
|
expect(results[10]).toMatchObject({
|
||||||
start: '2023-01-19T05:04:00.000Z',
|
start: '2023-01-19T05:04:00.000Z',
|
||||||
stop: '2023-01-19T06:42:00.000Z',
|
stop: '2023-01-19T06:42:00.000Z',
|
||||||
title: 'HIZLI VE ÖFKELİ: TOKYO YARIŞI'
|
title: 'HIZLI VE ÖFKELİ: TOKYO YARIŞI'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({ content: '' })
|
const result = parser({ content: '' })
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,100 +1,100 @@
|
||||||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
|
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'directv.com.ar',
|
site: 'directv.com.ar',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: 'https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming',
|
url: 'https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming',
|
||||||
request: {
|
request: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;',
|
Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;',
|
||||||
Accept: '*/*',
|
Accept: '*/*',
|
||||||
'Accept-Language': 'es-419,es;q=0.9',
|
'Accept-Language': 'es-419,es;q=0.9',
|
||||||
Connection: 'keep-alive',
|
Connection: 'keep-alive',
|
||||||
'Content-Type': 'application/json; charset=UTF-8',
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
Origin: 'https://www.directv.com.ar',
|
Origin: 'https://www.directv.com.ar',
|
||||||
Referer: 'https://www.directv.com.ar/guia/ChannelDetail.aspx?id=1740&name=TLCHD',
|
Referer: 'https://www.directv.com.ar/guia/ChannelDetail.aspx?id=1740&name=TLCHD',
|
||||||
'Sec-Fetch-Dest': 'empty',
|
'Sec-Fetch-Dest': 'empty',
|
||||||
'Sec-Fetch-Mode': 'cors',
|
'Sec-Fetch-Mode': 'cors',
|
||||||
'Sec-Fetch-Site': 'same-origin',
|
'Sec-Fetch-Site': 'same-origin',
|
||||||
'User-Agent':
|
'User-Agent':
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
|
||||||
'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
|
'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
|
||||||
'sec-ch-ua-mobile': '?0',
|
'sec-ch-ua-mobile': '?0',
|
||||||
'sec-ch-ua-platform': '"Windows"'
|
'sec-ch-ua-platform': '"Windows"'
|
||||||
},
|
},
|
||||||
data({ channel, date }) {
|
data({ channel, date }) {
|
||||||
const [channelNum, channelName] = channel.site_id.split('#')
|
const [channelNum, channelName] = channel.site_id.split('#')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filterParameters: {
|
filterParameters: {
|
||||||
day: date.date(),
|
day: date.date(),
|
||||||
time: 0,
|
time: 0,
|
||||||
minute: 0,
|
minute: 0,
|
||||||
month: date.month() + 1,
|
month: date.month() + 1,
|
||||||
year: date.year(),
|
year: date.year(),
|
||||||
offSetValue: 0,
|
offSetValue: 0,
|
||||||
homeScreenFilter: '',
|
homeScreenFilter: '',
|
||||||
filtersScreenFilters: [''],
|
filtersScreenFilters: [''],
|
||||||
isHd: '',
|
isHd: '',
|
||||||
isChannelDetails: 'Y',
|
isChannelDetails: 'Y',
|
||||||
channelNum,
|
channelNum,
|
||||||
channelName: channelName.replace('&', '&')
|
channelName: channelName.replace('&', '&')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser({ content, channel }) {
|
parser({ content, channel }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
rating: parseRating(item),
|
rating: parseRating(item),
|
||||||
start: parseStart(item),
|
start: parseStart(item),
|
||||||
stop: parseStop(item)
|
stop: parseStop(item)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRating(item) {
|
function parseRating(item) {
|
||||||
return item.rating
|
return item.rating
|
||||||
? {
|
? {
|
||||||
system: 'MPA',
|
system: 'MPA',
|
||||||
value: item.rating
|
value: item.rating
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires')
|
return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseStop(item) {
|
||||||
return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires')
|
return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
if (!content) return []
|
if (!content) return []
|
||||||
let [ChannelNumber, ChannelName] = channel.site_id.split('#')
|
let [ChannelNumber, ChannelName] = channel.site_id.split('#')
|
||||||
ChannelName = ChannelName.replace('&', '&')
|
ChannelName = ChannelName.replace('&', '&')
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !Array.isArray(data.d)) return []
|
if (!data || !Array.isArray(data.d)) return []
|
||||||
const channelData = data.d.find(
|
const channelData = data.d.find(
|
||||||
c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName
|
c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName
|
||||||
)
|
)
|
||||||
|
|
||||||
return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : []
|
return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : []
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +1,79 @@
|
||||||
// npm run grab -- --site=directv.com.ar
|
// npm run grab -- --site=directv.com.ar
|
||||||
|
|
||||||
const { parser, url, request } = require('./directv.com.ar.config.js')
|
const { parser, url, request } = require('./directv.com.ar.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2022-06-19', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-06-19', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '207#A&EHD',
|
site_id: '207#A&EHD',
|
||||||
xmltv_id: 'AEHDSouth.us'
|
xmltv_id: 'AEHDSouth.us'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url).toBe('https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming')
|
expect(url).toBe('https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request method', () => {
|
it('can generate valid request method', () => {
|
||||||
expect(request.method).toBe('POST')
|
expect(request.method).toBe('POST')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request headers', () => {
|
it('can generate valid request headers', () => {
|
||||||
expect(request.headers).toMatchObject({
|
expect(request.headers).toMatchObject({
|
||||||
'Content-Type': 'application/json; charset=UTF-8',
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;'
|
Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request data', () => {
|
it('can generate valid request data', () => {
|
||||||
expect(request.data({ channel, date })).toMatchObject({
|
expect(request.data({ channel, date })).toMatchObject({
|
||||||
filterParameters: {
|
filterParameters: {
|
||||||
day: 19,
|
day: 19,
|
||||||
time: 0,
|
time: 0,
|
||||||
minute: 0,
|
minute: 0,
|
||||||
month: 6,
|
month: 6,
|
||||||
year: 2022,
|
year: 2022,
|
||||||
offSetValue: 0,
|
offSetValue: 0,
|
||||||
filtersScreenFilters: [''],
|
filtersScreenFilters: [''],
|
||||||
isHd: '',
|
isHd: '',
|
||||||
isChannelDetails: 'Y',
|
isChannelDetails: 'Y',
|
||||||
channelNum: '207',
|
channelNum: '207',
|
||||||
channelName: 'A&EHD'
|
channelName: 'A&EHD'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content =
|
const content =
|
||||||
'{"d":[{"ChannelSection":"","ChannelFullName":"A&E HD","IsFavorite":false,"ChannelName":"A&EHD","ChannelNumber":207,"ProgramList":[{"_channelSection":"","eventId":"120289890767","titleId":"SH0110397700000001","title":"Chicas guapas","programId":null,"description":"Un espacio destinado a la belleza y los distintos estilos de vida, que muestra el trabajo inspiracional de la moda latinoamericana.","episodeTitle":null,"channelNumber":120,"channelName":"AME2","channelFullName":"América TV (ARG)","channelSection":"","contentChannelID":120,"startTime":"/Date(-62135578800000)/","endTime":"/Date(-62135578800000)/","GMTstartTime":"/Date(-62135578800000)/","GMTendTime":"/Date(-62135578800000)/","css":16,"language":null,"tmsId":"SH0110397700000001","rating":"NR","categoryId":"Tipos de Programas","categoryName":0,"subCategoryId":0,"subCategoryName":"Series","serviceExpiration":"/Date(-62135578800000)/","crId":null,"promoUrl1":null,"promoUrl2":null,"price":0,"isPurchasable":"N","videoUrl":"","imageUrl":"https://dnqt2wx2urq99.cloudfront.net/ondirectv/LOGOS/Canales/AR/120.png","titleSecond":"Chicas guapas","isHD":"N","DetailsURL":null,"BuyURL":null,"ProgramServiceId":null,"SearchDateTime":null,"startTimeString":"6/19/2022 12:00:00 AM","endTimeString":"6/19/2022 12:15:00 AM","DurationInMinutes":null,"castDetails":null,"scheduleDetails":null,"seriesDetails":null,"processedSeasonDetails":null}]}]}'
|
'{"d":[{"ChannelSection":"","ChannelFullName":"A&E HD","IsFavorite":false,"ChannelName":"A&EHD","ChannelNumber":207,"ProgramList":[{"_channelSection":"","eventId":"120289890767","titleId":"SH0110397700000001","title":"Chicas guapas","programId":null,"description":"Un espacio destinado a la belleza y los distintos estilos de vida, que muestra el trabajo inspiracional de la moda latinoamericana.","episodeTitle":null,"channelNumber":120,"channelName":"AME2","channelFullName":"América TV (ARG)","channelSection":"","contentChannelID":120,"startTime":"/Date(-62135578800000)/","endTime":"/Date(-62135578800000)/","GMTstartTime":"/Date(-62135578800000)/","GMTendTime":"/Date(-62135578800000)/","css":16,"language":null,"tmsId":"SH0110397700000001","rating":"NR","categoryId":"Tipos de Programas","categoryName":0,"subCategoryId":0,"subCategoryName":"Series","serviceExpiration":"/Date(-62135578800000)/","crId":null,"promoUrl1":null,"promoUrl2":null,"price":0,"isPurchasable":"N","videoUrl":"","imageUrl":"https://dnqt2wx2urq99.cloudfront.net/ondirectv/LOGOS/Canales/AR/120.png","titleSecond":"Chicas guapas","isHD":"N","DetailsURL":null,"BuyURL":null,"ProgramServiceId":null,"SearchDateTime":null,"startTimeString":"6/19/2022 12:00:00 AM","endTimeString":"6/19/2022 12:15:00 AM","DurationInMinutes":null,"castDetails":null,"scheduleDetails":null,"seriesDetails":null,"processedSeasonDetails":null}]}]}'
|
||||||
const result = parser({ content, channel }).map(p => {
|
const result = parser({ content, channel }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2022-06-19T03:00:00.000Z',
|
start: '2022-06-19T03:00:00.000Z',
|
||||||
stop: '2022-06-19T03:15:00.000Z',
|
stop: '2022-06-19T03:15:00.000Z',
|
||||||
title: 'Chicas guapas',
|
title: 'Chicas guapas',
|
||||||
description:
|
description:
|
||||||
'Un espacio destinado a la belleza y los distintos estilos de vida, que muestra el trabajo inspiracional de la moda latinoamericana.',
|
'Un espacio destinado a la belleza y los distintos estilos de vida, que muestra el trabajo inspiracional de la moda latinoamericana.',
|
||||||
rating: {
|
rating: {
|
||||||
system: 'MPA',
|
system: 'MPA',
|
||||||
value: 'NR'
|
value: 'NR'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
content: '',
|
content: '',
|
||||||
channel
|
channel
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,85 +1,85 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'directv.com.uy',
|
site: 'directv.com.uy',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: 'https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming',
|
url: 'https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming',
|
||||||
request: {
|
request: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json; charset=UTF-8',
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;'
|
Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;'
|
||||||
},
|
},
|
||||||
data({ channel, date }) {
|
data({ channel, date }) {
|
||||||
const [channelNum, channelName] = channel.site_id.split('#')
|
const [channelNum, channelName] = channel.site_id.split('#')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filterParameters: {
|
filterParameters: {
|
||||||
day: date.date(),
|
day: date.date(),
|
||||||
time: 0,
|
time: 0,
|
||||||
minute: 0,
|
minute: 0,
|
||||||
month: date.month() + 1,
|
month: date.month() + 1,
|
||||||
year: date.year(),
|
year: date.year(),
|
||||||
offSetValue: 0,
|
offSetValue: 0,
|
||||||
filtersScreenFilters: [''],
|
filtersScreenFilters: [''],
|
||||||
isHd: '',
|
isHd: '',
|
||||||
isChannelDetails: 'Y',
|
isChannelDetails: 'Y',
|
||||||
channelNum,
|
channelNum,
|
||||||
channelName: channelName.replace('&', '&')
|
channelName: channelName.replace('&', '&')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser({ content, channel }) {
|
parser({ content, channel }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
rating: parseRating(item),
|
rating: parseRating(item),
|
||||||
start: parseStart(item),
|
start: parseStart(item),
|
||||||
stop: parseStop(item)
|
stop: parseStop(item)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRating(item) {
|
function parseRating(item) {
|
||||||
return item.rating
|
return item.rating
|
||||||
? {
|
? {
|
||||||
system: 'MPA',
|
system: 'MPA',
|
||||||
value: item.rating
|
value: item.rating
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo')
|
return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseStop(item) {
|
||||||
return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo')
|
return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
if (!content) return []
|
if (!content) return []
|
||||||
let [ChannelNumber, ChannelName] = channel.site_id.split('#')
|
let [ChannelNumber, ChannelName] = channel.site_id.split('#')
|
||||||
ChannelName = ChannelName.replace('&', '&')
|
ChannelName = ChannelName.replace('&', '&')
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !Array.isArray(data.d)) return []
|
if (!data || !Array.isArray(data.d)) return []
|
||||||
const channelData = data.d.find(
|
const channelData = data.d.find(
|
||||||
c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName
|
c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName
|
||||||
)
|
)
|
||||||
|
|
||||||
return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : []
|
return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : []
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +1,78 @@
|
||||||
// npm run grab -- --site=directv.com.uy
|
// npm run grab -- --site=directv.com.uy
|
||||||
|
|
||||||
const { parser, url, request } = require('./directv.com.uy.config.js')
|
const { parser, url, request } = require('./directv.com.uy.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2022-08-29', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-08-29', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '184#VTV',
|
site_id: '184#VTV',
|
||||||
xmltv_id: 'VTV.uy'
|
xmltv_id: 'VTV.uy'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url).toBe('https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming')
|
expect(url).toBe('https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request method', () => {
|
it('can generate valid request method', () => {
|
||||||
expect(request.method).toBe('POST')
|
expect(request.method).toBe('POST')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request headers', () => {
|
it('can generate valid request headers', () => {
|
||||||
expect(request.headers).toMatchObject({
|
expect(request.headers).toMatchObject({
|
||||||
'Content-Type': 'application/json; charset=UTF-8',
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;'
|
Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request data', () => {
|
it('can generate valid request data', () => {
|
||||||
expect(request.data({ channel, date })).toMatchObject({
|
expect(request.data({ channel, date })).toMatchObject({
|
||||||
filterParameters: {
|
filterParameters: {
|
||||||
day: 29,
|
day: 29,
|
||||||
time: 0,
|
time: 0,
|
||||||
minute: 0,
|
minute: 0,
|
||||||
month: 8,
|
month: 8,
|
||||||
year: 2022,
|
year: 2022,
|
||||||
offSetValue: 0,
|
offSetValue: 0,
|
||||||
filtersScreenFilters: [''],
|
filtersScreenFilters: [''],
|
||||||
isHd: '',
|
isHd: '',
|
||||||
isChannelDetails: 'Y',
|
isChannelDetails: 'Y',
|
||||||
channelNum: '184',
|
channelNum: '184',
|
||||||
channelName: 'VTV'
|
channelName: 'VTV'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
const results = parser({ content, channel }).map(p => {
|
const results = parser({ content, channel }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-08-29T03:00:00.000Z',
|
start: '2022-08-29T03:00:00.000Z',
|
||||||
stop: '2022-08-29T05:00:00.000Z',
|
stop: '2022-08-29T05:00:00.000Z',
|
||||||
title: 'Peñarol vs. Danubio : Fútbol Uruguayo Primera División - Peñarol vs. Danubio',
|
title: 'Peñarol vs. Danubio : Fútbol Uruguayo Primera División - Peñarol vs. Danubio',
|
||||||
description:
|
description:
|
||||||
'Jornada 5 del Torneo Clausura 2022. Peñarol recibe a Danubio en el estadio Campeón del Siglo. Los carboneros llevan 3 partidos sin caer (2PG 1PE), mientras que los franjeados acumulan 6 juegos sin derrotas (4PG 2PE).',
|
'Jornada 5 del Torneo Clausura 2022. Peñarol recibe a Danubio en el estadio Campeón del Siglo. Los carboneros llevan 3 partidos sin caer (2PG 1PE), mientras que los franjeados acumulan 6 juegos sin derrotas (4PG 2PE).',
|
||||||
rating: {
|
rating: {
|
||||||
system: 'MPA',
|
system: 'MPA',
|
||||||
value: 'NR'
|
value: 'NR'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
content: '',
|
content: '',
|
||||||
channel
|
channel
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,113 +1,113 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'directv.com',
|
site: 'directv.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 60 * 60 * 1000 // 1 hour
|
ttl: 60 * 60 * 1000 // 1 hour
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
'Accept-Language': 'en-US,en;q=0.5',
|
'Accept-Language': 'en-US,en;q=0.5',
|
||||||
Connection: 'keep-alive'
|
Connection: 'keep-alive'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url({ date, channel }) {
|
url({ date, channel }) {
|
||||||
const [channelId, childId] = channel.site_id.split('#')
|
const [channelId, childId] = channel.site_id.split('#')
|
||||||
return `https://www.directv.com/json/channelschedule?channels=${channelId}&startTime=${date.format()}&hours=24&chId=${childId}`
|
return `https://www.directv.com/json/channelschedule?channels=${channelId}&startTime=${date.format()}&hours=24&chId=${childId}`
|
||||||
},
|
},
|
||||||
async parser({ content, channel }) {
|
async parser({ content, channel }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
if (item.programID === '-1') continue
|
if (item.programID === '-1') continue
|
||||||
const detail = await loadProgramDetail(item.programID)
|
const detail = await loadProgramDetail(item.programID)
|
||||||
const start = parseStart(item)
|
const start = parseStart(item)
|
||||||
const stop = start.add(item.duration, 'm')
|
const stop = start.add(item.duration, 'm')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
sub_title: item.episodeTitle,
|
sub_title: item.episodeTitle,
|
||||||
description: parseDescription(detail),
|
description: parseDescription(detail),
|
||||||
rating: parseRating(item),
|
rating: parseRating(item),
|
||||||
date: parseYear(detail),
|
date: parseYear(detail),
|
||||||
category: item.subcategoryList,
|
category: item.subcategoryList,
|
||||||
season: item.seasonNumber,
|
season: item.seasonNumber,
|
||||||
episode: item.episodeNumber,
|
episode: item.episodeNumber,
|
||||||
icon: parseIcon(item),
|
icon: parseIcon(item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ zip }) {
|
async channels({ zip }) {
|
||||||
const html = await axios
|
const html = await axios
|
||||||
.get('https://www.directv.com/guide', {
|
.get('https://www.directv.com/guide', {
|
||||||
headers: {
|
headers: {
|
||||||
cookie: `dtve-prospect-zip=${zip}`
|
cookie: `dtve-prospect-zip=${zip}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
const $ = cheerio.load(html)
|
const $ = cheerio.load(html)
|
||||||
const script = $('#dtvClientData').html()
|
const script = $('#dtvClientData').html()
|
||||||
const [, json] = script.match(/var dtvClientData = (.*);/) || [null, null]
|
const [, json] = script.match(/var dtvClientData = (.*);/) || [null, null]
|
||||||
const data = JSON.parse(json)
|
const data = JSON.parse(json)
|
||||||
|
|
||||||
let items = data.guideData.channels
|
let items = data.guideData.channels
|
||||||
|
|
||||||
return items.map(item => {
|
return items.map(item => {
|
||||||
return {
|
return {
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
site_id: item.chNum,
|
site_id: item.chNum,
|
||||||
name: item.chName
|
name: item.chName
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription(detail) {
|
function parseDescription(detail) {
|
||||||
return detail ? detail.description : null
|
return detail ? detail.description : null
|
||||||
}
|
}
|
||||||
function parseYear(detail) {
|
function parseYear(detail) {
|
||||||
return detail ? detail.releaseYear : null
|
return detail ? detail.releaseYear : null
|
||||||
}
|
}
|
||||||
function parseRating(item) {
|
function parseRating(item) {
|
||||||
return item.rating
|
return item.rating
|
||||||
? {
|
? {
|
||||||
system: 'MPA',
|
system: 'MPA',
|
||||||
value: item.rating
|
value: item.rating
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
return item.primaryImageUrl ? `https://www.directv.com${item.primaryImageUrl}` : null
|
return item.primaryImageUrl ? `https://www.directv.com${item.primaryImageUrl}` : null
|
||||||
}
|
}
|
||||||
function loadProgramDetail(programID) {
|
function loadProgramDetail(programID) {
|
||||||
return axios
|
return axios
|
||||||
.get(`https://www.directv.com/json/program/flip/${programID}`)
|
.get(`https://www.directv.com/json/program/flip/${programID}`)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.then(d => d.programDetail)
|
.then(d => d.programDetail)
|
||||||
.catch(console.err)
|
.catch(console.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs.utc(item.airTime)
|
return dayjs.utc(item.airTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data) return []
|
if (!data) return []
|
||||||
if (!Array.isArray(data.schedule)) return []
|
if (!Array.isArray(data.schedule)) return []
|
||||||
|
|
||||||
const [, childId] = channel.site_id.split('#')
|
const [, childId] = channel.site_id.split('#')
|
||||||
const channelData = data.schedule.find(i => i.chId == childId)
|
const channelData = data.schedule.find(i => i.chId == childId)
|
||||||
return channelData.schedules && Array.isArray(channelData.schedules) ? channelData.schedules : []
|
return channelData.schedules && Array.isArray(channelData.schedules) ? channelData.schedules : []
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,98 +1,98 @@
|
||||||
// node ./scripts/commands/parse-channels.js --config=./sites/directv.com/directv.com.config.js --output=./sites/directv.com/directv.com.channels.xml --set=zip:10001
|
// node ./scripts/commands/parse-channels.js --config=./sites/directv.com/directv.com.config.js --output=./sites/directv.com/directv.com.channels.xml --set=zip:10001
|
||||||
// npm run grab -- --site=directv.com
|
// npm run grab -- --site=directv.com
|
||||||
|
|
||||||
const { parser, url } = require('./directv.com.config.js')
|
const { parser, url } = require('./directv.com.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const date = dayjs.utc('2023-01-15', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-01-15', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '249#249',
|
site_id: '249#249',
|
||||||
xmltv_id: 'ComedyCentralEast.us'
|
xmltv_id: 'ComedyCentralEast.us'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
const result = url({ date, channel })
|
const result = url({ date, channel })
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'https://www.directv.com/json/channelschedule?channels=249&startTime=2023-01-15T00:00:00Z&hours=24&chId=249'
|
'https://www.directv.com/json/channelschedule?channels=249&startTime=2023-01-15T00:00:00Z&hours=24&chId=249'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', done => {
|
it('can parse response', done => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
|
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
if (url === 'https://www.directv.com/json/program/flip/MV001173520000') {
|
if (url === 'https://www.directv.com/json/program/flip/MV001173520000') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
|
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
|
||||||
})
|
})
|
||||||
} else if (url === 'https://www.directv.com/json/program/flip/EP002298270445') {
|
} else if (url === 'https://www.directv.com/json/program/flip/EP002298270445') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))
|
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: '' })
|
return Promise.resolve({ data: '' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
parser({ content, channel })
|
parser({ content, channel })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
result = result.map(p => {
|
result = result.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2023-01-14T23:00:00.000Z',
|
start: '2023-01-14T23:00:00.000Z',
|
||||||
stop: '2023-01-15T01:00:00.000Z',
|
stop: '2023-01-15T01:00:00.000Z',
|
||||||
title: 'Men in Black II',
|
title: 'Men in Black II',
|
||||||
description:
|
description:
|
||||||
'Kay (Tommy Lee Jones) and Jay (Will Smith) reunite to provide our best line of defense against a seductress who levels the toughest challenge yet to the MIBs mission statement: protecting the earth from the scum of the universe. While investigating a routine crime, Jay uncovers a plot masterminded by Serleena (Boyle), a Kylothian monster who disguises herself as a lingerie model. When Serleena takes the MIB building hostage, there is only one person Jay can turn to -- his former MIB partner.',
|
'Kay (Tommy Lee Jones) and Jay (Will Smith) reunite to provide our best line of defense against a seductress who levels the toughest challenge yet to the MIBs mission statement: protecting the earth from the scum of the universe. While investigating a routine crime, Jay uncovers a plot masterminded by Serleena (Boyle), a Kylothian monster who disguises herself as a lingerie model. When Serleena takes the MIB building hostage, there is only one person Jay can turn to -- his former MIB partner.',
|
||||||
date: '2002',
|
date: '2002',
|
||||||
icon: 'https://www.directv.com/db_photos/movies/AllPhotosAPGI/29160/29160_aa.jpg',
|
icon: 'https://www.directv.com/db_photos/movies/AllPhotosAPGI/29160/29160_aa.jpg',
|
||||||
category: ['Comedy', 'Movies Anywhere', 'Action/Adventure', 'Science Fiction'],
|
category: ['Comedy', 'Movies Anywhere', 'Action/Adventure', 'Science Fiction'],
|
||||||
rating: {
|
rating: {
|
||||||
system: 'MPA',
|
system: 'MPA',
|
||||||
value: 'TV14'
|
value: 'TV14'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: '2023-01-15T06:00:00.000Z',
|
start: '2023-01-15T06:00:00.000Z',
|
||||||
stop: '2023-01-15T06:30:00.000Z',
|
stop: '2023-01-15T06:30:00.000Z',
|
||||||
title: 'South Park',
|
title: 'South Park',
|
||||||
sub_title: 'Goth Kids 3: Dawn of the Posers',
|
sub_title: 'Goth Kids 3: Dawn of the Posers',
|
||||||
description: 'The goth kids are sent to a camp for troubled children.',
|
description: 'The goth kids are sent to a camp for troubled children.',
|
||||||
icon: 'https://www.directv.com/db_photos/showcards/v5/AllPhotos/184338/p184338_b_v5_aa.jpg',
|
icon: 'https://www.directv.com/db_photos/showcards/v5/AllPhotos/184338/p184338_b_v5_aa.jpg',
|
||||||
category: ['Series', 'Animation', 'Comedy'],
|
category: ['Series', 'Animation', 'Comedy'],
|
||||||
season: 17,
|
season: 17,
|
||||||
episode: 4,
|
episode: 4,
|
||||||
rating: {
|
rating: {
|
||||||
system: 'MPA',
|
system: 'MPA',
|
||||||
value: 'TVMA'
|
value: 'TVMA'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', done => {
|
it('can handle empty guide', done => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.json'))
|
||||||
parser({ content, channel })
|
parser({ content, channel })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,145 +1,145 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'dishtv.in',
|
site: 'dishtv.in',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: 'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram',
|
url: 'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram',
|
||||||
request: {
|
request: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data({ channel, date }) {
|
data({ channel, date }) {
|
||||||
return {
|
return {
|
||||||
Channelarr: channel.site_id,
|
Channelarr: channel.site_id,
|
||||||
fromdate: date.format('YYYYMMDDHHmm'),
|
fromdate: date.format('YYYYMMDDHHmm'),
|
||||||
todate: date.add(1, 'd').format('YYYYMMDDHHmm')
|
todate: date.add(1, 'd').format('YYYYMMDDHHmm')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser: function ({ content, date }) {
|
parser: function ({ content, date }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const data = parseContent(content)
|
const data = parseContent(content)
|
||||||
const items = parseItems(data)
|
const items = parseItems(data)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const title = parseTitle(item)
|
const title = parseTitle(item)
|
||||||
const start = parseStart(item, date)
|
const start = parseStart(item, date)
|
||||||
const stop = parseStop(item, start)
|
const stop = parseStop(item, start)
|
||||||
if (title === 'No Information Available') return
|
if (title === 'No Information Available') return
|
||||||
|
|
||||||
programs.push({
|
programs.push({
|
||||||
title,
|
title,
|
||||||
start: start.toString(),
|
start: start.toString(),
|
||||||
stop: stop.toString()
|
stop: stop.toString()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const channelguide = await axios
|
const channelguide = await axios
|
||||||
.get('https://www.dishtv.in/channelguide/')
|
.get('https://www.dishtv.in/channelguide/')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
const $channelguide = cheerio.load(channelguide)
|
const $channelguide = cheerio.load(channelguide)
|
||||||
|
|
||||||
let ids = []
|
let ids = []
|
||||||
$channelguide('#MainContent_recordPagging li').each((i, item) => {
|
$channelguide('#MainContent_recordPagging li').each((i, item) => {
|
||||||
const onclick = $channelguide(item).find('a').attr('onclick')
|
const onclick = $channelguide(item).find('a').attr('onclick')
|
||||||
const [, list] = onclick.match(/ShowNextPageResult\('([^']+)/) || [null, null]
|
const [, list] = onclick.match(/ShowNextPageResult\('([^']+)/) || [null, null]
|
||||||
|
|
||||||
ids = ids.concat(list.split(','))
|
ids = ids.concat(list.split(','))
|
||||||
})
|
})
|
||||||
ids = ids.filter(Boolean)
|
ids = ids.filter(Boolean)
|
||||||
|
|
||||||
const channels = {}
|
const channels = {}
|
||||||
const channelList = await axios
|
const channelList = await axios
|
||||||
.post('https://www.dishtv.in/WebServiceMethod.aspx/GetChannelListFromMobileAPI', {
|
.post('https://www.dishtv.in/WebServiceMethod.aspx/GetChannelListFromMobileAPI', {
|
||||||
strChannel: ''
|
strChannel: ''
|
||||||
})
|
})
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
const $channelList = cheerio.load(channelList.d)
|
const $channelList = cheerio.load(channelList.d)
|
||||||
$channelList('#tblpackChnl > div').each((i, item) => {
|
$channelList('#tblpackChnl > div').each((i, item) => {
|
||||||
let num = $channelList(item).find('p:nth-child(2)').text().trim()
|
let num = $channelList(item).find('p:nth-child(2)').text().trim()
|
||||||
const name = $channelList(item).find('p').first().text().trim()
|
const name = $channelList(item).find('p').first().text().trim()
|
||||||
|
|
||||||
if (num === '') return
|
if (num === '') return
|
||||||
|
|
||||||
channels[parseInt(num)] = {
|
channels[parseInt(num)] = {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const date = dayjs().add(1, 'd')
|
const date = dayjs().add(1, 'd')
|
||||||
const promises = []
|
const promises = []
|
||||||
for (let id of ids) {
|
for (let id of ids) {
|
||||||
const promise = axios
|
const promise = axios
|
||||||
.post(
|
.post(
|
||||||
'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram',
|
'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram',
|
||||||
{
|
{
|
||||||
Channelarr: id,
|
Channelarr: id,
|
||||||
fromdate: date.format('YYYYMMDD[0000]'),
|
fromdate: date.format('YYYYMMDD[0000]'),
|
||||||
todate: date.format('YYYYMMDD[2300]')
|
todate: date.format('YYYYMMDD[2300]')
|
||||||
},
|
},
|
||||||
{ timeout: 5000 }
|
{ timeout: 5000 }
|
||||||
)
|
)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const $channelGuide = cheerio.load(data.d)
|
const $channelGuide = cheerio.load(data.d)
|
||||||
|
|
||||||
const num = $channelGuide('.cnl-fav > a > span').text().trim()
|
const num = $channelGuide('.cnl-fav > a > span').text().trim()
|
||||||
|
|
||||||
if (channels[num]) {
|
if (channels[num]) {
|
||||||
channels[num].site_id = id
|
channels[num].site_id = id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
promises.push(promise)
|
promises.push(promise)
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.allSettled(promises)
|
await Promise.allSettled(promises)
|
||||||
|
|
||||||
return Object.values(channels)
|
return Object.values(channels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle(item) {
|
function parseTitle(item) {
|
||||||
const $ = cheerio.load(item)
|
const $ = cheerio.load(item)
|
||||||
|
|
||||||
return $('a').text()
|
return $('a').text()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
const $ = cheerio.load(item)
|
const $ = cheerio.load(item)
|
||||||
const onclick = $('i.fa-circle').attr('onclick')
|
const onclick = $('i.fa-circle').attr('onclick')
|
||||||
const [, time] = onclick.match(/RecordingEnteryOpen\('.*','.*','(.*)','.*',.*\)/)
|
const [, time] = onclick.match(/RecordingEnteryOpen\('.*','.*','(.*)','.*',.*\)/)
|
||||||
|
|
||||||
return dayjs.tz(time, 'YYYYMMDDHHmm', 'Asia/Kolkata')
|
return dayjs.tz(time, 'YYYYMMDDHHmm', 'Asia/Kolkata')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item, start) {
|
function parseStop(item, start) {
|
||||||
const $ = cheerio.load(item)
|
const $ = cheerio.load(item)
|
||||||
const duration = $('*').data('time')
|
const duration = $('*').data('time')
|
||||||
|
|
||||||
return start.add(duration, 'm')
|
return start.add(duration, 'm')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseContent(content) {
|
function parseContent(content) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
|
|
||||||
return data.d
|
return data.d
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(data) {
|
function parseItems(data) {
|
||||||
const $ = cheerio.load(data)
|
const $ = cheerio.load(data)
|
||||||
|
|
||||||
return $('.datatime').toArray()
|
return $('.datatime').toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,45 @@
|
||||||
// npm run channels:parse -- --config=./sites/dishtv.in/dishtv.in.config.js --output=./sites/dishtv.in/dishtv.in.channels.xml
|
// npm run channels:parse -- --config=./sites/dishtv.in/dishtv.in.config.js --output=./sites/dishtv.in/dishtv.in.channels.xml
|
||||||
// npm run grab -- --site=dishtv.in
|
// npm run grab -- --site=dishtv.in
|
||||||
|
|
||||||
const { parser, url, request } = require('./dishtv.in.config.js')
|
const { parser, url, request } = require('./dishtv.in.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2021-11-05', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2021-11-05', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = { site_id: '10000000075992337', xmltv_id: 'WomensActive.in' }
|
const channel = { site_id: '10000000075992337', xmltv_id: 'WomensActive.in' }
|
||||||
const content =
|
const content =
|
||||||
'{"d":"\\u003cdiv class=\\"pgrid\\"\\u003e\\u003cdiv class=\\"img sm-30 grid\\"\\u003e\\u003cimg class=\\"chnl-logo\\" src=\\"http://imagesdishtvd2h.whatsonindia.com/dasimages/channel/landscape/360x270/hiyj8ndf.png\\" onclick=\\"ShowChannelGuid(\\u0027womens-active\\u0027,\\u002710000000075992337\\u0027);\\" /\\u003e\\u003cdiv class=\\"cnl-fav\\"\\u003e\\u003ca href=\\"javascript:;\\"\\u003e\\u003cem\\u003ech. no\\u003c/em\\u003e\\u003cspan\\u003e117\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/div\\u003e\\u003ci class=\\"fa fa-heart Set_Favourite_Channel\\" aria-hidden=\\"true\\" title=\\"Set womens active channel as your favourite channel\\" onclick=\\"SetFavouriteChannel();\\"\\u003e\\u003c/i\\u003e\\u003c/div\\u003e\\u003cdiv class=\\"grid-wrap\\"\\u003e\\u003cdiv class=\\"sm-30 grid datatime\\" data-time=\\"24\\" data-starttime=\\"12:00 AM\\" data-endttime=\\"12:24 AM\\" data-reamintime=\\"0\\"\\u003e\\u003ca title=\\"Event Name: Cynthia Williams - Diwali Look Part 01\\r\\nStart Time: 12:00 AM\\r\\nDuration: 24min\\r\\nSynopsis: Learn diwali look by cynthia williams p1\\r\\n\\" href=\\"javascript:;\\" onclick=\\"ShowCurrentTime(\\u002730000000550913679\\u0027,\\u002710000000075992337\\u0027,\\u0027202111051200\\u0027)\\"\\u003eCynthia Williams - Diwali Look Part 01\\u003c/a\\u003e\\u003cdiv class=\\"cnlSerialIcon\\"\\u003e\\u003ci class=\\"fa fa-heart\\" aria-hidden=\\"true\\" title=\\"Set Favourite Serial\\" onclick=\\"SetFavouriteShow();\\"\\u003e\\u003c/i\\u003e\\u003ci class=\\"fa fa-clock-o\\" aria-hidden=\\"true\\" title=\\"Reminder Serial\\" onclick=\\"ReminderEnteryOpen(\\u002730000000550913679\\u0027,\\u002710000000075992337\\u0027,\\u0027202111050000\\u0027,\\u0027117\\u0027)\\"\\u003e\\u003c/i\\u003e\\u003ci class=\\"fa fa-circle\\" aria-hidden=\\"true\\" title=\\"Record Serial\\" onclick=\\"RecordingEnteryOpen(\\u002730000000550913679\\u0027,\\u002710000000075992337\\u0027,\\u0027202111050000\\u0027,\\u0027117\\u0027,30000000550913679)\\"\\u003e\\u003c/i\\u003e\\u003c/div\\u003e\\u003c/div\\u003e\\u003c/div\\u003e\\u003c/div\\u003e"}'
|
'{"d":"\\u003cdiv class=\\"pgrid\\"\\u003e\\u003cdiv class=\\"img sm-30 grid\\"\\u003e\\u003cimg class=\\"chnl-logo\\" src=\\"http://imagesdishtvd2h.whatsonindia.com/dasimages/channel/landscape/360x270/hiyj8ndf.png\\" onclick=\\"ShowChannelGuid(\\u0027womens-active\\u0027,\\u002710000000075992337\\u0027);\\" /\\u003e\\u003cdiv class=\\"cnl-fav\\"\\u003e\\u003ca href=\\"javascript:;\\"\\u003e\\u003cem\\u003ech. no\\u003c/em\\u003e\\u003cspan\\u003e117\\u003c/span\\u003e\\u003c/a\\u003e\\u003c/div\\u003e\\u003ci class=\\"fa fa-heart Set_Favourite_Channel\\" aria-hidden=\\"true\\" title=\\"Set womens active channel as your favourite channel\\" onclick=\\"SetFavouriteChannel();\\"\\u003e\\u003c/i\\u003e\\u003c/div\\u003e\\u003cdiv class=\\"grid-wrap\\"\\u003e\\u003cdiv class=\\"sm-30 grid datatime\\" data-time=\\"24\\" data-starttime=\\"12:00 AM\\" data-endttime=\\"12:24 AM\\" data-reamintime=\\"0\\"\\u003e\\u003ca title=\\"Event Name: Cynthia Williams - Diwali Look Part 01\\r\\nStart Time: 12:00 AM\\r\\nDuration: 24min\\r\\nSynopsis: Learn diwali look by cynthia williams p1\\r\\n\\" href=\\"javascript:;\\" onclick=\\"ShowCurrentTime(\\u002730000000550913679\\u0027,\\u002710000000075992337\\u0027,\\u0027202111051200\\u0027)\\"\\u003eCynthia Williams - Diwali Look Part 01\\u003c/a\\u003e\\u003cdiv class=\\"cnlSerialIcon\\"\\u003e\\u003ci class=\\"fa fa-heart\\" aria-hidden=\\"true\\" title=\\"Set Favourite Serial\\" onclick=\\"SetFavouriteShow();\\"\\u003e\\u003c/i\\u003e\\u003ci class=\\"fa fa-clock-o\\" aria-hidden=\\"true\\" title=\\"Reminder Serial\\" onclick=\\"ReminderEnteryOpen(\\u002730000000550913679\\u0027,\\u002710000000075992337\\u0027,\\u0027202111050000\\u0027,\\u0027117\\u0027)\\"\\u003e\\u003c/i\\u003e\\u003ci class=\\"fa fa-circle\\" aria-hidden=\\"true\\" title=\\"Record Serial\\" onclick=\\"RecordingEnteryOpen(\\u002730000000550913679\\u0027,\\u002710000000075992337\\u0027,\\u0027202111050000\\u0027,\\u0027117\\u0027,30000000550913679)\\"\\u003e\\u003c/i\\u003e\\u003c/div\\u003e\\u003c/div\\u003e\\u003c/div\\u003e\\u003c/div\\u003e"}'
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url).toBe(
|
expect(url).toBe(
|
||||||
'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram'
|
'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request data', () => {
|
it('can generate valid request data', () => {
|
||||||
const result = request.data({ channel, date })
|
const result = request.data({ channel, date })
|
||||||
expect(result).toMatchObject({
|
expect(result).toMatchObject({
|
||||||
Channelarr: '10000000075992337',
|
Channelarr: '10000000075992337',
|
||||||
fromdate: '202111050000',
|
fromdate: '202111050000',
|
||||||
todate: '202111060000'
|
todate: '202111060000'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const result = parser({ date, channel, content })
|
const result = parser({ date, channel, content })
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
start: 'Thu, 04 Nov 2021 18:30:00 GMT',
|
start: 'Thu, 04 Nov 2021 18:30:00 GMT',
|
||||||
stop: 'Thu, 04 Nov 2021 18:54:00 GMT',
|
stop: 'Thu, 04 Nov 2021 18:54:00 GMT',
|
||||||
title: 'Cynthia Williams - Diwali Look Part 01'
|
title: 'Cynthia Williams - Diwali Look Part 01'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({ date, channel, content: '{"d":""}' })
|
const result = parser({ date, channel, content: '{"d":""}' })
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,104 +1,104 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
const API_ENDPOINT = 'https://www.dsmart.com.tr/api/v1/public/epg/schedules'
|
const API_ENDPOINT = 'https://www.dsmart.com.tr/api/v1/public/epg/schedules'
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'dsmart.com.tr',
|
site: 'dsmart.com.tr',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ date, channel }) {
|
url({ date, channel }) {
|
||||||
const [page] = channel.site_id.split('#')
|
const [page] = channel.site_id.split('#')
|
||||||
|
|
||||||
return `${API_ENDPOINT}?page=${page}&limit=1&day=${date.format('YYYY-MM-DD')}`
|
return `${API_ENDPOINT}?page=${page}&limit=1&day=${date.format('YYYY-MM-DD')}`
|
||||||
},
|
},
|
||||||
parser: function ({ content, channel }) {
|
parser: function ({ content, channel }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
let start
|
let start
|
||||||
if (prev) {
|
if (prev) {
|
||||||
start = parseStart(item, prev.stop)
|
start = parseStart(item, prev.stop)
|
||||||
} else {
|
} else {
|
||||||
start = parseStart(item, dayjs.utc(item.day))
|
start = parseStart(item, dayjs.utc(item.day))
|
||||||
}
|
}
|
||||||
let duration = parseDuration(item)
|
let duration = parseDuration(item)
|
||||||
let stop = start.add(duration, 's')
|
let stop = start.add(duration, 's')
|
||||||
|
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.program_name,
|
title: item.program_name,
|
||||||
category: parseCategory(item),
|
category: parseCategory(item),
|
||||||
description: item.description.trim(),
|
description: item.description.trim(),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const perPage = 1
|
const perPage = 1
|
||||||
const totalChannels = 210
|
const totalChannels = 210
|
||||||
const pages = Math.ceil(totalChannels / perPage)
|
const pages = Math.ceil(totalChannels / perPage)
|
||||||
|
|
||||||
const channels = []
|
const channels = []
|
||||||
for (let i in Array(pages).fill(0)) {
|
for (let i in Array(pages).fill(0)) {
|
||||||
const page = parseInt(i) + 1
|
const page = parseInt(i) + 1
|
||||||
const url = `${API_ENDPOINT}?page=${page}&limit=${perPage}&day=${dayjs().format(
|
const url = `${API_ENDPOINT}?page=${page}&limit=${perPage}&day=${dayjs().format(
|
||||||
'YYYY-MM-DD'
|
'YYYY-MM-DD'
|
||||||
)}`
|
)}`
|
||||||
let offset = i * perPage
|
let offset = i * perPage
|
||||||
await axios
|
await axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
offset++
|
offset++
|
||||||
if (data && data.data && Array.isArray(data.data.channels)) {
|
if (data && data.data && Array.isArray(data.data.channels)) {
|
||||||
data.data.channels.forEach((item, j) => {
|
data.data.channels.forEach((item, j) => {
|
||||||
const index = offset + j
|
const index = offset + j
|
||||||
channels.push({
|
channels.push({
|
||||||
lang: 'tr',
|
lang: 'tr',
|
||||||
name: item.channel_name,
|
name: item.channel_name,
|
||||||
site_id: index + '#' + item._id
|
site_id: index + '#' + item._id
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.log(err.message)
|
console.log(err.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory(item) {
|
function parseCategory(item) {
|
||||||
return item.genre !== '0' ? item.genre : null
|
return item.genre !== '0' ? item.genre : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item, date) {
|
function parseStart(item, date) {
|
||||||
const time = dayjs.utc(item.start_date)
|
const time = dayjs.utc(item.start_date)
|
||||||
|
|
||||||
return dayjs.utc(`${date.format('YYYY-MM-DD')} ${time.format('HH:mm:ss')}`, 'YYYY-MM-DD HH:mm:ss')
|
return dayjs.utc(`${date.format('YYYY-MM-DD')} ${time.format('HH:mm:ss')}`, 'YYYY-MM-DD HH:mm:ss')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDuration(item) {
|
function parseDuration(item) {
|
||||||
const [, H, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)$/)
|
const [, H, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)$/)
|
||||||
|
|
||||||
return parseInt(H) * 3600 + parseInt(mm) * 60 + parseInt(ss)
|
return parseInt(H) * 3600 + parseInt(mm) * 60 + parseInt(ss)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
const [, channelId] = channel.site_id.split('#')
|
const [, channelId] = channel.site_id.split('#')
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !data.data || !Array.isArray(data.data.channels)) return null
|
if (!data || !data.data || !Array.isArray(data.data.channels)) return null
|
||||||
const channelData = data.data.channels.find(i => i._id == channelId)
|
const channelData = data.data.channels.find(i => i._id == channelId)
|
||||||
|
|
||||||
return channelData && Array.isArray(channelData.schedule) ? channelData.schedule : []
|
return channelData && Array.isArray(channelData.schedule) ? channelData.schedule : []
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +1,68 @@
|
||||||
// npm run channels:parse -- --config=./sites/dsmart.com.tr/dsmart.com.tr.config.js --output=./sites/dsmart.com.tr/dsmart.com.tr.channels.xml
|
// npm run channels:parse -- --config=./sites/dsmart.com.tr/dsmart.com.tr.config.js --output=./sites/dsmart.com.tr/dsmart.com.tr.channels.xml
|
||||||
// npm run grab -- --site=dsmart.com.tr
|
// npm run grab -- --site=dsmart.com.tr
|
||||||
|
|
||||||
const { parser, url } = require('./dsmart.com.tr.config.js')
|
const { parser, url } = require('./dsmart.com.tr.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-01-16', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-01-16', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '3#5fe07d7acfef0b1593275751',
|
site_id: '3#5fe07d7acfef0b1593275751',
|
||||||
xmltv_id: 'SinemaTV.tr'
|
xmltv_id: 'SinemaTV.tr'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ date, channel })).toBe(
|
expect(url({ date, channel })).toBe(
|
||||||
'https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=3&limit=1&day=2023-01-16'
|
'https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=3&limit=1&day=2023-01-16'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
const results = parser({ channel, content }).map(p => {
|
const results = parser({ channel, content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-01-15T22:00:00.000Z',
|
start: '2023-01-15T22:00:00.000Z',
|
||||||
stop: '2023-01-15T23:45:00.000Z',
|
stop: '2023-01-15T23:45:00.000Z',
|
||||||
title: 'Bizi Ayıran Her Şey',
|
title: 'Bizi Ayıran Her Şey',
|
||||||
category: 'sinema/genel',
|
category: 'sinema/genel',
|
||||||
description:
|
description:
|
||||||
'Issızlığın ortasında yer alan orta sınıf bir evde bir anne kız yaşamaktadır. Çevrelerindeki taşları insanlarla yaşadıkları çatışmalar, anne-kızın hayatını olumsuz yönde etkilemektedir. Kızının ansızın ortadan kaybolması, bu çatışmaların seviyesini artıracak ve anne, kızını bulmak için her türlü yola başvuracaktır.'
|
'Issızlığın ortasında yer alan orta sınıf bir evde bir anne kız yaşamaktadır. Çevrelerindeki taşları insanlarla yaşadıkları çatışmalar, anne-kızın hayatını olumsuz yönde etkilemektedir. Kızının ansızın ortadan kaybolması, bu çatışmaların seviyesini artıracak ve anne, kızını bulmak için her türlü yola başvuracaktır.'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[1]).toMatchObject({
|
expect(results[1]).toMatchObject({
|
||||||
start: '2023-01-15T23:45:00.000Z',
|
start: '2023-01-15T23:45:00.000Z',
|
||||||
stop: '2023-01-16T01:30:00.000Z',
|
stop: '2023-01-16T01:30:00.000Z',
|
||||||
title: 'Pixie',
|
title: 'Pixie',
|
||||||
category: 'sinema/genel',
|
category: 'sinema/genel',
|
||||||
description:
|
description:
|
||||||
'Annesinin intikamını almak isteyen Pixie, dahiyane bir soygun planlar. Fakat işler planladığı gibi gitmeyince kendini İrlanda’nın vahşi gangsterleri tarafından kovalanan iki adamla birlikte kaçarken bulur.'
|
'Annesinin intikamını almak isteyen Pixie, dahiyane bir soygun planlar. Fakat işler planladığı gibi gitmeyince kendini İrlanda’nın vahşi gangsterleri tarafından kovalanan iki adamla birlikte kaçarken bulur.'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[12]).toMatchObject({
|
expect(results[12]).toMatchObject({
|
||||||
start: '2023-01-16T20:30:00.000Z',
|
start: '2023-01-16T20:30:00.000Z',
|
||||||
stop: '2023-01-16T22:30:00.000Z',
|
stop: '2023-01-16T22:30:00.000Z',
|
||||||
title: 'Seberg',
|
title: 'Seberg',
|
||||||
category: 'sinema/genel',
|
category: 'sinema/genel',
|
||||||
description:
|
description:
|
||||||
'Başrolünde ünlü yıldız Kristen Stewart’ın yer aldığı politik gerilim, 1960’ların sonunda insan hakları aktivisti Hakim Jamal ile yaşadığı politik ve romantik ilişki sebebiyle FBI tarafından hedef alınan, Fransız Yeni Dalgası’nın sevilen yüzü ve Serseri Aşıklar’ın yıldızı Jean Seberg’ün çarpıcı hikayesini anlatıyor.'
|
'Başrolünde ünlü yıldız Kristen Stewart’ın yer aldığı politik gerilim, 1960’ların sonunda insan hakları aktivisti Hakim Jamal ile yaşadığı politik ve romantik ilişki sebebiyle FBI tarafından hedef alınan, Fransız Yeni Dalgası’nın sevilen yüzü ve Serseri Aşıklar’ın yıldızı Jean Seberg’ün çarpıcı hikayesini anlatıyor.'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const results = parser({
|
const results = parser({
|
||||||
channel,
|
channel,
|
||||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,101 +1,101 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide'
|
const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide'
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'dstv.com',
|
site: 'dstv.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 3 * 60 * 60 * 1000, // 3h
|
ttl: 3 * 60 * 60 * 1000, // 3h
|
||||||
interpretHeader: false
|
interpretHeader: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url: function ({ channel, date }) {
|
url: function ({ channel, date }) {
|
||||||
const [region] = channel.site_id.split('#')
|
const [region] = channel.site_id.split('#')
|
||||||
const packageName = region === 'nga' ? '&package=DStv%20Premium' : ''
|
const packageName = region === 'nga' ? '&package=DStv%20Premium' : ''
|
||||||
|
|
||||||
return `${API_ENDPOINT}/GetProgrammes?d=${date.format(
|
return `${API_ENDPOINT}/GetProgrammes?d=${date.format(
|
||||||
'YYYY-MM-DD'
|
'YYYY-MM-DD'
|
||||||
)}${packageName}&country=${region}`
|
)}${packageName}&country=${region}`
|
||||||
},
|
},
|
||||||
async parser({ content, channel }) {
|
async parser({ content, channel }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const details = await loadProgramDetails(item)
|
const details = await loadProgramDetails(item)
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.Title,
|
title: item.Title,
|
||||||
description: parseDescription(details),
|
description: parseDescription(details),
|
||||||
icon: parseIcon(details),
|
icon: parseIcon(details),
|
||||||
category: parseCategory(details),
|
category: parseCategory(details),
|
||||||
start: parseTime(item.StartTime, channel),
|
start: parseTime(item.StartTime, channel),
|
||||||
stop: parseTime(item.EndTime, channel)
|
stop: parseTime(item.EndTime, channel)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ country }) {
|
async channels({ country }) {
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(`${API_ENDPOINT}/GetProgrammes?d=2022-03-10&package=DStv%20Premium&country=${country}`)
|
.get(`${API_ENDPOINT}/GetProgrammes?d=2022-03-10&package=DStv%20Premium&country=${country}`)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
return data.Channels.map(item => {
|
return data.Channels.map(item => {
|
||||||
return {
|
return {
|
||||||
site_id: `${country}#${item.Number}`,
|
site_id: `${country}#${item.Number}`,
|
||||||
name: item.Name
|
name: item.Name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTime(time, channel) {
|
function parseTime(time, channel) {
|
||||||
const [region] = channel.site_id.split('#')
|
const [region] = channel.site_id.split('#')
|
||||||
const tz = {
|
const tz = {
|
||||||
zaf: 'Africa/Johannesburg',
|
zaf: 'Africa/Johannesburg',
|
||||||
nga: 'Africa/Lagos'
|
nga: 'Africa/Lagos'
|
||||||
}
|
}
|
||||||
|
|
||||||
return dayjs.tz(time, 'YYYY-MM-DDTHH:mm:ss', tz[region])
|
return dayjs.tz(time, 'YYYY-MM-DDTHH:mm:ss', tz[region])
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription(details) {
|
function parseDescription(details) {
|
||||||
return details ? details.Synopsis : null
|
return details ? details.Synopsis : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon(details) {
|
function parseIcon(details) {
|
||||||
return details ? details.ThumbnailUri : null
|
return details ? details.ThumbnailUri : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory(details) {
|
function parseCategory(details) {
|
||||||
return details ? details.SubGenres : null
|
return details ? details.SubGenres : null
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProgramDetails(item) {
|
async function loadProgramDetails(item) {
|
||||||
const url = `${API_ENDPOINT}/GetProgramme?id=${item.Id}`
|
const url = `${API_ENDPOINT}/GetProgramme?id=${item.Id}`
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
const [, channelId] = channel.site_id.split('#')
|
const [, channelId] = channel.site_id.split('#')
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !Array.isArray(data.Channels)) return []
|
if (!data || !Array.isArray(data.Channels)) return []
|
||||||
const channelData = data.Channels.find(c => c.Number === channelId)
|
const channelData = data.Channels.find(c => c.Number === channelId)
|
||||||
if (!channelData || !Array.isArray(channelData.Programmes)) return []
|
if (!channelData || !Array.isArray(channelData.Programmes)) return []
|
||||||
|
|
||||||
return channelData.Programmes
|
return channelData.Programmes
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,112 +1,112 @@
|
||||||
// npm run channels:parse -- --config=./sites/dstv.com/dstv.com.config.js --output=./sites/dstv.com/dstv.com.channels.xml --set=country:zaf
|
// npm run channels:parse -- --config=./sites/dstv.com/dstv.com.config.js --output=./sites/dstv.com/dstv.com.channels.xml --set=country:zaf
|
||||||
// npm run grab -- --site=dstv.com
|
// npm run grab -- --site=dstv.com
|
||||||
|
|
||||||
const { parser, url } = require('./dstv.com.config.js')
|
const { parser, url } = require('./dstv.com.config.js')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide'
|
const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide'
|
||||||
|
|
||||||
const date = dayjs.utc('2022-11-22', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-11-22', 'YYYY-MM-DD').startOf('d')
|
||||||
const channelZA = {
|
const channelZA = {
|
||||||
site_id: 'zaf#201',
|
site_id: 'zaf#201',
|
||||||
xmltv_id: 'SuperSportGrandstand.za'
|
xmltv_id: 'SuperSportGrandstand.za'
|
||||||
}
|
}
|
||||||
const channelNG = {
|
const channelNG = {
|
||||||
site_id: 'nga#201',
|
site_id: 'nga#201',
|
||||||
xmltv_id: 'SuperSportGrandstand.za'
|
xmltv_id: 'SuperSportGrandstand.za'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url for zaf', () => {
|
it('can generate valid url for zaf', () => {
|
||||||
expect(url({ channel: channelZA, date })).toBe(
|
expect(url({ channel: channelZA, date })).toBe(
|
||||||
`${API_ENDPOINT}/GetProgrammes?d=2022-11-22&country=zaf`
|
`${API_ENDPOINT}/GetProgrammes?d=2022-11-22&country=zaf`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid url for nga', () => {
|
it('can generate valid url for nga', () => {
|
||||||
expect(url({ channel: channelNG, date })).toBe(
|
expect(url({ channel: channelNG, date })).toBe(
|
||||||
`${API_ENDPOINT}/GetProgrammes?d=2022-11-22&package=DStv%20Premium&country=nga`
|
`${API_ENDPOINT}/GetProgrammes?d=2022-11-22&package=DStv%20Premium&country=nga`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response for ZA', async () => {
|
it('can parse response for ZA', async () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_zaf.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_zaf.json'))
|
||||||
|
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
if (url === `${API_ENDPOINT}/GetProgramme?id=8b237235-aa17-4bb8-9ea6-097e7a813336`) {
|
if (url === `${API_ENDPOINT}/GetProgramme?id=8b237235-aa17-4bb8-9ea6-097e7a813336`) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_zaf.json')))
|
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_zaf.json')))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: '' })
|
return Promise.resolve({ data: '' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let results = await parser({ content, channel: channelZA })
|
let results = await parser({ content, channel: channelZA })
|
||||||
results = results.map(p => {
|
results = results.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[1]).toMatchObject({
|
expect(results[1]).toMatchObject({
|
||||||
start: '2022-11-21T23:00:00.000Z',
|
start: '2022-11-21T23:00:00.000Z',
|
||||||
stop: '2022-11-22T00:00:00.000Z',
|
stop: '2022-11-22T00:00:00.000Z',
|
||||||
title: 'UFC FN HL: Nzechukwu v Cutelaba',
|
title: 'UFC FN HL: Nzechukwu v Cutelaba',
|
||||||
description:
|
description:
|
||||||
"'UFC Fight Night Highlights - Heavyweight Bout: Kennedy Nzechukwu vs Ion Cutelaba'. From The UFC APEX Center - Las Vegas, USA.",
|
"'UFC Fight Night Highlights - Heavyweight Bout: Kennedy Nzechukwu vs Ion Cutelaba'. From The UFC APEX Center - Las Vegas, USA.",
|
||||||
icon: 'https://03mcdecdnimagerepository.blob.core.windows.net/epguideimage/img/271546_UFC Fight Night.png',
|
icon: 'https://03mcdecdnimagerepository.blob.core.windows.net/epguideimage/img/271546_UFC Fight Night.png',
|
||||||
category: ['All Sport', 'Mixed Martial Arts']
|
category: ['All Sport', 'Mixed Martial Arts']
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response for NG', async () => {
|
it('can parse response for NG', async () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_nga.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_nga.json'))
|
||||||
|
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
if (url === `${API_ENDPOINT}/GetProgramme?id=6d58931e-2192-486a-a202-14720136d204`) {
|
if (url === `${API_ENDPOINT}/GetProgramme?id=6d58931e-2192-486a-a202-14720136d204`) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_nga.json')))
|
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_nga.json')))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({ data: '' })
|
return Promise.resolve({ data: '' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let results = await parser({ content, channel: channelNG })
|
let results = await parser({ content, channel: channelNG })
|
||||||
results = results.map(p => {
|
results = results.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-11-21T23:00:00.000Z',
|
start: '2022-11-21T23:00:00.000Z',
|
||||||
stop: '2022-11-22T00:00:00.000Z',
|
stop: '2022-11-22T00:00:00.000Z',
|
||||||
title: 'UFC FN HL: Nzechukwu v Cutelaba',
|
title: 'UFC FN HL: Nzechukwu v Cutelaba',
|
||||||
description:
|
description:
|
||||||
"'UFC Fight Night Highlights - Heavyweight Bout: Kennedy Nzechukwu vs Ion Cutelaba'. From The UFC APEX Center - Las Vegas, USA.",
|
"'UFC Fight Night Highlights - Heavyweight Bout: Kennedy Nzechukwu vs Ion Cutelaba'. From The UFC APEX Center - Las Vegas, USA.",
|
||||||
icon: 'https://03mcdecdnimagerepository.blob.core.windows.net/epguideimage/img/271546_UFC Fight Night.png',
|
icon: 'https://03mcdecdnimagerepository.blob.core.windows.net/epguideimage/img/271546_UFC Fight Night.png',
|
||||||
category: ['All Sport', 'Mixed Martial Arts']
|
category: ['All Sport', 'Mixed Martial Arts']
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', done => {
|
it('can handle empty guide', done => {
|
||||||
parser({
|
parser({
|
||||||
content: '{"Total":0,"Channels":[]}',
|
content: '{"Total":0,"Channels":[]}',
|
||||||
channel: channelZA
|
channel: channelZA
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,118 +1,118 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
require('dayjs/locale/ar')
|
require('dayjs/locale/ar')
|
||||||
|
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'elcinema.com',
|
site: 'elcinema.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel }) {
|
url({ channel }) {
|
||||||
const lang = channel.lang === 'en' ? 'en/' : '/'
|
const lang = channel.lang === 'en' ? 'en/' : '/'
|
||||||
|
|
||||||
return `https://elcinema.com/${lang}tvguide/${channel.site_id}/`
|
return `https://elcinema.com/${lang}tvguide/${channel.site_id}/`
|
||||||
},
|
},
|
||||||
parser({ content, channel, date }) {
|
parser({ content, channel, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content, channel, date)
|
const items = parseItems(content, channel, date)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const start = parseStart(item, date)
|
const start = parseStart(item, date)
|
||||||
const duration = parseDuration(item)
|
const duration = parseDuration(item)
|
||||||
const stop = start.add(duration, 'm')
|
const stop = start.add(duration, 'm')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle(item),
|
title: parseTitle(item),
|
||||||
description: parseDescription(item),
|
description: parseDescription(item),
|
||||||
category: parseCategory(item),
|
category: parseCategory(item),
|
||||||
icon: parseIcon(item),
|
icon: parseIcon(item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
const $ = cheerio.load(item)
|
const $ = cheerio.load(item)
|
||||||
const imgSrc =
|
const imgSrc =
|
||||||
$('.row > div.columns.small-3.large-1 > a > img').data('src') ||
|
$('.row > div.columns.small-3.large-1 > a > img').data('src') ||
|
||||||
$('.row > div.columns.small-5.large-1 > img').data('src')
|
$('.row > div.columns.small-5.large-1 > img').data('src')
|
||||||
|
|
||||||
return imgSrc || null
|
return imgSrc || null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory(item) {
|
function parseCategory(item) {
|
||||||
const $ = cheerio.load(item)
|
const $ = cheerio.load(item)
|
||||||
const category = $('.row > div.columns.small-6.large-3 > ul > li:nth-child(2)').text()
|
const category = $('.row > div.columns.small-6.large-3 > ul > li:nth-child(2)').text()
|
||||||
|
|
||||||
return category.replace(/\(\d+\)/, '').trim() || null
|
return category.replace(/\(\d+\)/, '').trim() || null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDuration(item) {
|
function parseDuration(item) {
|
||||||
const $ = cheerio.load(item)
|
const $ = cheerio.load(item)
|
||||||
const duration =
|
const duration =
|
||||||
$('.row > div.columns.small-3.large-2 > ul > li:nth-child(2) > span').text() ||
|
$('.row > div.columns.small-3.large-2 > ul > li:nth-child(2) > span').text() ||
|
||||||
$('.row > div.columns.small-7.large-11 > ul > li:nth-child(2) > span').text()
|
$('.row > div.columns.small-7.large-11 > ul > li:nth-child(2) > span').text()
|
||||||
|
|
||||||
return duration.replace(/\D/g, '') || ''
|
return duration.replace(/\D/g, '') || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item, initDate) {
|
function parseStart(item, initDate) {
|
||||||
const $ = cheerio.load(item)
|
const $ = cheerio.load(item)
|
||||||
let time =
|
let time =
|
||||||
$('.row > div.columns.small-3.large-2 > ul > li:nth-child(1)').text() ||
|
$('.row > div.columns.small-3.large-2 > ul > li:nth-child(1)').text() ||
|
||||||
$('.row > div.columns.small-7.large-11 > ul > li:nth-child(2)').text() ||
|
$('.row > div.columns.small-7.large-11 > ul > li:nth-child(2)').text() ||
|
||||||
''
|
''
|
||||||
|
|
||||||
time = time
|
time = time
|
||||||
.replace(/\[.*\]/, '')
|
.replace(/\[.*\]/, '')
|
||||||
.replace('مساءً', 'PM')
|
.replace('مساءً', 'PM')
|
||||||
.replace('صباحًا', 'AM')
|
.replace('صباحًا', 'AM')
|
||||||
.trim()
|
.trim()
|
||||||
|
|
||||||
time = `${initDate.format('YYYY-MM-DD')} ${time}`
|
time = `${initDate.format('YYYY-MM-DD')} ${time}`
|
||||||
|
|
||||||
return dayjs.tz(time, 'YYYY-MM-DD hh:mm A', dayjs.tz.guess())
|
return dayjs.tz(time, 'YYYY-MM-DD hh:mm A', dayjs.tz.guess())
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle(item) {
|
function parseTitle(item) {
|
||||||
const $ = cheerio.load(item)
|
const $ = cheerio.load(item)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
$('.row > div.columns.small-6.large-3 > ul > li:nth-child(1) > a').text() ||
|
$('.row > div.columns.small-6.large-3 > ul > li:nth-child(1) > a').text() ||
|
||||||
$('.row > div.columns.small-7.large-11 > ul > li:nth-child(1)').text() ||
|
$('.row > div.columns.small-7.large-11 > ul > li:nth-child(1)').text() ||
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription(item) {
|
function parseDescription(item) {
|
||||||
const $ = cheerio.load(item)
|
const $ = cheerio.load(item)
|
||||||
const excerpt = $('.row > div.columns.small-12.large-6 > ul > li:nth-child(3)').text() || ''
|
const excerpt = $('.row > div.columns.small-12.large-6 > ul > li:nth-child(3)').text() || ''
|
||||||
|
|
||||||
return excerpt.replace('...اقرأ المزيد', '').replace('...Read more', '')
|
return excerpt.replace('...اقرأ المزيد', '').replace('...Read more', '')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel, date) {
|
function parseItems(content, channel, date) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
const dateString = date.locale(channel.lang).format('dddd D')
|
const dateString = date.locale(channel.lang).format('dddd D')
|
||||||
|
|
||||||
const list = $('.dates')
|
const list = $('.dates')
|
||||||
.filter((i, el) => {
|
.filter((i, el) => {
|
||||||
let parsedDateString = $(el).text().trim()
|
let parsedDateString = $(el).text().trim()
|
||||||
parsedDateString = parsedDateString.replace(/\s\s+/g, ' ')
|
parsedDateString = parsedDateString.replace(/\s\s+/g, ' ')
|
||||||
|
|
||||||
return parsedDateString.includes(dateString)
|
return parsedDateString.includes(dateString)
|
||||||
})
|
})
|
||||||
.first()
|
.first()
|
||||||
.parent()
|
.parent()
|
||||||
.next()
|
.next()
|
||||||
|
|
||||||
return $('.padded-half', list).toArray()
|
return $('.padded-half', list).toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +1,69 @@
|
||||||
// npm run grab -- --site=elcinema.com
|
// npm run grab -- --site=elcinema.com
|
||||||
|
|
||||||
const { parser, url } = require('./elcinema.com.config.js')
|
const { parser, url } = require('./elcinema.com.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2022-08-28', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-08-28', 'YYYY-MM-DD').startOf('d')
|
||||||
const channelAR = {
|
const channelAR = {
|
||||||
lang: 'ar',
|
lang: 'ar',
|
||||||
site_id: '1254',
|
site_id: '1254',
|
||||||
xmltv_id: 'OSNSeries.ae'
|
xmltv_id: 'OSNSeries.ae'
|
||||||
}
|
}
|
||||||
const channelEN = {
|
const channelEN = {
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
site_id: '1254',
|
site_id: '1254',
|
||||||
xmltv_id: 'OSNSeries.ae'
|
xmltv_id: 'OSNSeries.ae'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel: channelEN })).toBe('https://elcinema.com/en/tvguide/1254/')
|
expect(url({ channel: channelEN })).toBe('https://elcinema.com/en/tvguide/1254/')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response (en)', () => {
|
it('can parse response (en)', () => {
|
||||||
const contentEN = fs.readFileSync(path.resolve(__dirname, '__data__/content.en.html'))
|
const contentEN = fs.readFileSync(path.resolve(__dirname, '__data__/content.en.html'))
|
||||||
const results = parser({ date, channel: channelEN, content: contentEN }).map(p => {
|
const results = parser({ date, channel: channelEN, content: contentEN }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-08-27T14:25:00.000Z',
|
start: '2022-08-27T14:25:00.000Z',
|
||||||
stop: '2022-08-27T15:15:00.000Z',
|
stop: '2022-08-27T15:15:00.000Z',
|
||||||
title: 'Station 19 S5',
|
title: 'Station 19 S5',
|
||||||
icon: 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg',
|
icon: 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg',
|
||||||
category: 'Series'
|
category: 'Series'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response (ar)', () => {
|
it('can parse response (ar)', () => {
|
||||||
const contentAR = fs.readFileSync(path.resolve(__dirname, '__data__/content.ar.html'))
|
const contentAR = fs.readFileSync(path.resolve(__dirname, '__data__/content.ar.html'))
|
||||||
const results = parser({ date, channel: channelAR, content: contentAR }).map(p => {
|
const results = parser({ date, channel: channelAR, content: contentAR }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-08-27T14:25:00.000Z',
|
start: '2022-08-27T14:25:00.000Z',
|
||||||
stop: '2022-08-27T15:15:00.000Z',
|
stop: '2022-08-27T15:15:00.000Z',
|
||||||
title: 'Station 19 S5',
|
title: 'Station 19 S5',
|
||||||
icon: 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg',
|
icon: 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg',
|
||||||
category: 'مسلسل'
|
category: 'مسلسل'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel: channelEN,
|
channel: channelEN,
|
||||||
content: '<!DOCTYPE html><html lang="ar" dir="rtl"><head></head><body></body></html>'
|
content: '<!DOCTYPE html><html lang="ar" dir="rtl"><head></head><body></body></html>'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,68 +1,68 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'ena.skylifetv.co.kr',
|
site: 'ena.skylifetv.co.kr',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel, date }) {
|
url({ channel, date }) {
|
||||||
return `http://ena.skylifetv.co.kr/${channel.site_id}/?day=${date.format('YYYYMMDD')}&sc_dvsn=U`
|
return `http://ena.skylifetv.co.kr/${channel.site_id}/?day=${date.format('YYYYMMDD')}&sc_dvsn=U`
|
||||||
},
|
},
|
||||||
parser({ content, date }) {
|
parser({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content, date)
|
const items = parseItems(content, date)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
const start = parseStart($item, date)
|
const start = parseStart($item, date)
|
||||||
const duration = parseDuration($item)
|
const duration = parseDuration($item)
|
||||||
const stop = start.add(duration, 'm')
|
const stop = start.add(duration, 'm')
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle($item),
|
title: parseTitle($item),
|
||||||
rating: parseRating($item),
|
rating: parseRating($item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('.col2 > .tit').text().trim()
|
return $item('.col2 > .tit').text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRating($item) {
|
function parseRating($item) {
|
||||||
const rating = $item('.col4').text().trim()
|
const rating = $item('.col4').text().trim()
|
||||||
|
|
||||||
return rating
|
return rating
|
||||||
? {
|
? {
|
||||||
system: 'KMRB',
|
system: 'KMRB',
|
||||||
value: rating
|
value: rating
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDuration($item) {
|
function parseDuration($item) {
|
||||||
const duration = $item('.col5').text().trim()
|
const duration = $item('.col5').text().trim()
|
||||||
|
|
||||||
return duration ? parseInt(duration) : 30
|
return duration ? parseInt(duration) : 30
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
const time = $item('.col1').text().trim()
|
const time = $item('.col1').text().trim()
|
||||||
|
|
||||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul')
|
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
return $('.tbl_schedule > tbody > tr').toArray()
|
return $('.tbl_schedule > tbody > tr').toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +1,59 @@
|
||||||
// npm run grab -- --site=ena.skylifetv.co.kr
|
// npm run grab -- --site=ena.skylifetv.co.kr
|
||||||
|
|
||||||
const { parser, url } = require('./ena.skylifetv.co.kr.config.js')
|
const { parser, url } = require('./ena.skylifetv.co.kr.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-01-27', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-01-27', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'ENA',
|
site_id: 'ENA',
|
||||||
xmltv_id: 'ENA.kr'
|
xmltv_id: 'ENA.kr'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe('http://ena.skylifetv.co.kr/ENA/?day=20230127&sc_dvsn=U')
|
expect(url({ channel, date })).toBe('http://ena.skylifetv.co.kr/ENA/?day=20230127&sc_dvsn=U')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||||
let results = parser({ content, date })
|
let results = parser({ content, date })
|
||||||
results = results.map(p => {
|
results = results.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-01-26T16:05:00.000Z',
|
start: '2023-01-26T16:05:00.000Z',
|
||||||
stop: '2023-01-26T17:20:00.000Z',
|
stop: '2023-01-26T17:20:00.000Z',
|
||||||
title: '법쩐 6화',
|
title: '법쩐 6화',
|
||||||
rating: {
|
rating: {
|
||||||
system: 'KMRB',
|
system: 'KMRB',
|
||||||
value: '15'
|
value: '15'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[17]).toMatchObject({
|
expect(results[17]).toMatchObject({
|
||||||
start: '2023-01-27T14:10:00.000Z',
|
start: '2023-01-27T14:10:00.000Z',
|
||||||
stop: '2023-01-27T15:25:00.000Z',
|
stop: '2023-01-27T15:25:00.000Z',
|
||||||
title: '남이 될 수 있을까 4화',
|
title: '남이 될 수 있을까 4화',
|
||||||
rating: {
|
rating: {
|
||||||
system: 'KMRB',
|
system: 'KMRB',
|
||||||
value: '15'
|
value: '15'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const results = parser({
|
const results = parser({
|
||||||
date,
|
date,
|
||||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,95 +1,95 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const { DateTime } = require('luxon')
|
const { DateTime } = require('luxon')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'entertainment.ie',
|
site: 'entertainment.ie',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({ date, channel }) {
|
url: function ({ date, channel }) {
|
||||||
return `https://entertainment.ie/tv/${channel.site_id}/?date=${date.format(
|
return `https://entertainment.ie/tv/${channel.site_id}/?date=${date.format(
|
||||||
'DD-MM-YYYY'
|
'DD-MM-YYYY'
|
||||||
)}&time=all-day`
|
)}&time=all-day`
|
||||||
},
|
},
|
||||||
parser: function ({ content, date }) {
|
parser: function ({ content, date }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
const $item = cheerio.load(item)
|
const $item = cheerio.load(item)
|
||||||
let start = parseStart($item, date)
|
let start = parseStart($item, date)
|
||||||
if (!start) return
|
if (!start) return
|
||||||
if (prev && start < prev.start) {
|
if (prev && start < prev.start) {
|
||||||
start = start.plus({ days: 1 })
|
start = start.plus({ days: 1 })
|
||||||
}
|
}
|
||||||
const duration = parseDuration($item)
|
const duration = parseDuration($item)
|
||||||
const stop = start.plus({ minutes: duration })
|
const stop = start.plus({ minutes: duration })
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle($item),
|
title: parseTitle($item),
|
||||||
description: parseDescription($item),
|
description: parseDescription($item),
|
||||||
categories: parseCategories($item),
|
categories: parseCategories($item),
|
||||||
icon: parseIcon($item),
|
icon: parseIcon($item),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get('https://entertainment.ie/tv/all-channels/')
|
.get('https://entertainment.ie/tv/all-channels/')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
const $ = cheerio.load(data)
|
const $ = cheerio.load(data)
|
||||||
let channels = $('.tv-filter-container > tv-filter').attr(':channels')
|
let channels = $('.tv-filter-container > tv-filter').attr(':channels')
|
||||||
channels = JSON.parse(channels)
|
channels = JSON.parse(channels)
|
||||||
|
|
||||||
return channels.map(c => {
|
return channels.map(c => {
|
||||||
return {
|
return {
|
||||||
site_id: c.slug,
|
site_id: c.slug,
|
||||||
name: c.name
|
name: c.name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon($item) {
|
function parseIcon($item) {
|
||||||
return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('img')
|
return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('img')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle($item) {
|
function parseTitle($item) {
|
||||||
return $item('.text-holder h3').text().trim()
|
return $item('.text-holder h3').text().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription($item) {
|
function parseDescription($item) {
|
||||||
return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('description')
|
return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('description')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategories($item) {
|
function parseCategories($item) {
|
||||||
const genres = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('genres')
|
const genres = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('genres')
|
||||||
|
|
||||||
return genres ? genres.split(', ') : []
|
return genres ? genres.split(', ') : []
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseStart($item, date) {
|
||||||
let d = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('time')
|
let d = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('time')
|
||||||
let [, time] = d ? d.split(', ') : [null, null]
|
let [, time] = d ? d.split(', ') : [null, null]
|
||||||
|
|
||||||
return time
|
return time
|
||||||
? DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', {
|
? DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', {
|
||||||
zone: 'UTC'
|
zone: 'UTC'
|
||||||
}).toUTC()
|
}).toUTC()
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDuration($item) {
|
function parseDuration($item) {
|
||||||
const duration = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('duration')
|
const duration = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('duration')
|
||||||
|
|
||||||
return parseInt(duration)
|
return parseInt(duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
return $('.info-list > li').toArray()
|
return $('.info-list > li').toArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +1,59 @@
|
||||||
// npm run channels:parse -- --config=./sites/entertainment.ie/entertainment.ie.config.js --output=./sites/entertainment.ie/entertainment.ie.channels.xml
|
// npm run channels:parse -- --config=./sites/entertainment.ie/entertainment.ie.config.js --output=./sites/entertainment.ie/entertainment.ie.channels.xml
|
||||||
// npm run grab -- --site=entertainment.ie
|
// npm run grab -- --site=entertainment.ie
|
||||||
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { parser, url } = require('./entertainment.ie.config.js')
|
const { parser, url } = require('./entertainment.ie.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-06-29', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-06-29', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = { site_id: 'rte2', xmltv_id: 'RTE2.ie' }
|
const channel = { site_id: 'rte2', xmltv_id: 'RTE2.ie' }
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ date, channel })).toBe(
|
expect(url({ date, channel })).toBe(
|
||||||
'https://entertainment.ie/tv/rte2/?date=29-06-2023&time=all-day'
|
'https://entertainment.ie/tv/rte2/?date=29-06-2023&time=all-day'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||||
const results = parser({ date, content }).map(p => {
|
const results = parser({ date, content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results.length).toBe(51)
|
expect(results.length).toBe(51)
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-06-29T06:00:00.000Z',
|
start: '2023-06-29T06:00:00.000Z',
|
||||||
stop: '2023-06-29T08:00:00.000Z',
|
stop: '2023-06-29T08:00:00.000Z',
|
||||||
title: 'EuroNews',
|
title: 'EuroNews',
|
||||||
description: 'European and international headlines live via satellite',
|
description: 'European and international headlines live via satellite',
|
||||||
icon: 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg',
|
icon: 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg',
|
||||||
categories: ['Factual']
|
categories: ['Factual']
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[50]).toMatchObject({
|
expect(results[50]).toMatchObject({
|
||||||
start: '2023-06-30T02:25:00.000Z',
|
start: '2023-06-30T02:25:00.000Z',
|
||||||
stop: '2023-06-30T06:00:00.000Z',
|
stop: '2023-06-30T06:00:00.000Z',
|
||||||
title: 'EuroNews',
|
title: 'EuroNews',
|
||||||
description: 'European and international headlines live via satellite',
|
description: 'European and international headlines live via satellite',
|
||||||
icon: 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg',
|
icon: 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg',
|
||||||
categories: ['Factual']
|
categories: ['Factual']
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html'))
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content
|
content
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,89 +1,89 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const { DateTime } = require('luxon')
|
const { DateTime } = require('luxon')
|
||||||
|
|
||||||
const API_ENDPOINT = 'http://epg.i-cable.com/ci/channel'
|
const API_ENDPOINT = 'http://epg.i-cable.com/ci/channel'
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'epg.i-cable.com',
|
site: 'epg.i-cable.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 60 * 60 * 1000 // 1h
|
ttl: 60 * 60 * 1000 // 1h
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url: function ({ channel, date }) {
|
url: function ({ channel, date }) {
|
||||||
return `${API_ENDPOINT}/epg/${channel.site_id}/${date.format('YYYY-MM-DD')}?api=api`
|
return `${API_ENDPOINT}/epg/${channel.site_id}/${date.format('YYYY-MM-DD')}?api=api`
|
||||||
},
|
},
|
||||||
parser({ content, channel, date }) {
|
parser({ content, channel, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const items = parseItems(content, date)
|
const items = parseItems(content, date)
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
const prev = programs[programs.length - 1]
|
const prev = programs[programs.length - 1]
|
||||||
let start = parseStart(item, date)
|
let start = parseStart(item, date)
|
||||||
const stop = start.plus({ minutes: 30 })
|
const stop = start.plus({ minutes: 30 })
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (start < prev.start) {
|
if (start < prev.start) {
|
||||||
start = start.plus({ days: 1 })
|
start = start.plus({ days: 1 })
|
||||||
date = date.add(1, 'd')
|
date = date.add(1, 'd')
|
||||||
}
|
}
|
||||||
prev.stop = start
|
prev.stop = start
|
||||||
}
|
}
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle(item, channel),
|
title: parseTitle(item, channel),
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ lang }) {
|
async channels({ lang }) {
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(`${API_ENDPOINT}/category/0?api=api`)
|
.get(`${API_ENDPOINT}/category/0?api=api`)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
let channels = []
|
let channels = []
|
||||||
const promises = data.cates.map(c => axios.get(`${API_ENDPOINT}/category/${c.cate_id}?api=api`))
|
const promises = data.cates.map(c => axios.get(`${API_ENDPOINT}/category/${c.cate_id}?api=api`))
|
||||||
await Promise.allSettled(promises).then(results => {
|
await Promise.allSettled(promises).then(results => {
|
||||||
results.forEach(r => {
|
results.forEach(r => {
|
||||||
if (r.status === 'fulfilled') {
|
if (r.status === 'fulfilled') {
|
||||||
channels = channels.concat(r.value.data.chs)
|
channels = channels.concat(r.value.data.chs)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return channels.map(c => {
|
return channels.map(c => {
|
||||||
let name = lang === 'zh' ? c.channel_name : c.channel_name_en
|
let name = lang === 'zh' ? c.channel_name : c.channel_name_en
|
||||||
name = c.remark_id == 3 ? `${name} [HD]` : name
|
name = c.remark_id == 3 ? `${name} [HD]` : name
|
||||||
|
|
||||||
return {
|
return {
|
||||||
site_id: c.channel_no,
|
site_id: c.channel_no,
|
||||||
name,
|
name,
|
||||||
lang
|
lang
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle(item, channel) {
|
function parseTitle(item, channel) {
|
||||||
return channel.lang === 'en' ? item.programme_name_eng : item.programme_name_chi
|
return channel.lang === 'en' ? item.programme_name_eng : item.programme_name_chi
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item, date) {
|
function parseStart(item, date) {
|
||||||
let meridiem = item.session_mark === 'PM' ? 'PM' : 'AM'
|
let meridiem = item.session_mark === 'PM' ? 'PM' : 'AM'
|
||||||
return DateTime.fromFormat(
|
return DateTime.fromFormat(
|
||||||
`${date.format('YYYY-MM-DD')} ${item.time} ${meridiem}`,
|
`${date.format('YYYY-MM-DD')} ${item.time} ${meridiem}`,
|
||||||
'yyyy-MM-dd hh:mm a',
|
'yyyy-MM-dd hh:mm a',
|
||||||
{
|
{
|
||||||
zone: 'Asia/Hong_Kong'
|
zone: 'Asia/Hong_Kong'
|
||||||
}
|
}
|
||||||
).toUTC()
|
).toUTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data || !Array.isArray(data.epgs)) return []
|
if (!data || !Array.isArray(data.epgs)) return []
|
||||||
|
|
||||||
return data.epgs
|
return data.epgs
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +1,73 @@
|
||||||
// npm run channels:parse -- --config=./sites/epg.i-cable.com/epg.i-cable.com.config.js --output=./sites/epg.i-cable.com/epg.i-cable.com.channels.xml --set=lang:zh
|
// npm run channels:parse -- --config=./sites/epg.i-cable.com/epg.i-cable.com.config.js --output=./sites/epg.i-cable.com/epg.i-cable.com.channels.xml --set=lang:zh
|
||||||
// npm run grab -- --site=epg.i-cable.com
|
// npm run grab -- --site=epg.i-cable.com
|
||||||
|
|
||||||
const { parser, url } = require('./epg.i-cable.com.config.js')
|
const { parser, url } = require('./epg.i-cable.com.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const date = dayjs.utc('2022-11-15', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2022-11-15', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '003',
|
site_id: '003',
|
||||||
xmltv_id: 'HOYTV.hk',
|
xmltv_id: 'HOYTV.hk',
|
||||||
lang: 'zh'
|
lang: 'zh'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'http://epg.i-cable.com/ci/channel/epg/003/2022-11-15?api=api'
|
'http://epg.i-cable.com/ci/channel/epg/003/2022-11-15?api=api'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
let results = parser({ content, channel, date })
|
let results = parser({ content, channel, date })
|
||||||
results = results.map(p => {
|
results = results.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-11-14T22:00:00.000Z',
|
start: '2022-11-14T22:00:00.000Z',
|
||||||
stop: '2022-11-14T23:00:00.000Z',
|
stop: '2022-11-14T23:00:00.000Z',
|
||||||
title: 'Bloomberg 時段'
|
title: 'Bloomberg 時段'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[31]).toMatchObject({
|
expect(results[31]).toMatchObject({
|
||||||
start: '2022-11-15T21:00:00.000Z',
|
start: '2022-11-15T21:00:00.000Z',
|
||||||
stop: '2022-11-15T21:30:00.000Z',
|
stop: '2022-11-15T21:30:00.000Z',
|
||||||
title: 'Bloomberg 時段'
|
title: 'Bloomberg 時段'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response in English', () => {
|
it('can parse response in English', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
const channelEN = { ...channel, lang: 'en' }
|
const channelEN = { ...channel, lang: 'en' }
|
||||||
let results = parser({ content, channel: channelEN, date })
|
let results = parser({ content, channel: channelEN, date })
|
||||||
results = results.map(p => {
|
results = results.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-11-14T22:00:00.000Z',
|
start: '2022-11-14T22:00:00.000Z',
|
||||||
stop: '2022-11-14T23:00:00.000Z',
|
stop: '2022-11-14T23:00:00.000Z',
|
||||||
title: 'Bloomberg Hour'
|
title: 'Bloomberg Hour'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||||
const results = parser({ date, channel, content })
|
const results = parser({ date, channel, content })
|
||||||
|
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,52 +1,52 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'firstmedia.com',
|
site: 'firstmedia.com',
|
||||||
days: 1,
|
days: 1,
|
||||||
url: function ({ channel, date }) {
|
url: function ({ channel, date }) {
|
||||||
return `https://www.firstmedia.com/ajax/schedule?date=${date.format('DD/MM/YYYY')}&channel=${
|
return `https://www.firstmedia.com/ajax/schedule?date=${date.format('DD/MM/YYYY')}&channel=${
|
||||||
channel.site_id
|
channel.site_id
|
||||||
}&start_time=1&end_time=24&need_channels=0`
|
}&start_time=1&end_time=24&need_channels=0`
|
||||||
},
|
},
|
||||||
parser: function ({ content, channel }) {
|
parser: function ({ content, channel }) {
|
||||||
if (!content || !channel) return []
|
if (!content || !channel) return []
|
||||||
|
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel.site_id)
|
const items = parseItems(content, channel.site_id)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: parseTitle(item),
|
title: parseTitle(item),
|
||||||
description: parseDescription(item),
|
description: parseDescription(item),
|
||||||
start: parseStart(item).toISOString(),
|
start: parseStart(item).toISOString(),
|
||||||
stop: parseStop(item).toISOString()
|
stop: parseStop(item).toISOString()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
return JSON.parse(content.trim()).entries[channel]
|
return JSON.parse(content.trim()).entries[channel]
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle(item) {
|
function parseTitle(item) {
|
||||||
return item.title
|
return item.title
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription(item) {
|
function parseDescription(item) {
|
||||||
return item.long_description
|
return item.long_description
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs.tz(item.start_time, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
|
return dayjs.tz(item.start_time, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseStop(item) {
|
||||||
return dayjs.tz(item.end_time, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
|
return dayjs.tz(item.end_time, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
const { url, parser } = require('./firstmedia.com.config.js')
|
const { url, parser } = require('./firstmedia.com.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-06-18', 'DD/MM/YYYY').startOf('d')
|
const date = dayjs.utc('2023-06-18', 'DD/MM/YYYY').startOf('d')
|
||||||
const channel = { site_id: '251', xmltv_id: 'ABCAustralia.au', lang: 'id' }
|
const channel = { site_id: '251', xmltv_id: 'ABCAustralia.au', lang: 'id' }
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe(
|
||||||
'https://www.firstmedia.com/ajax/schedule?date=18/06/2023&channel=251&start_time=1&end_time=24&need_channels=0'
|
'https://www.firstmedia.com/ajax/schedule?date=18/06/2023&channel=251&start_time=1&end_time=24&need_channels=0'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content =
|
const content =
|
||||||
'{"entries":{"251":[{"logo":"files/images/d/new-logo/channels/11-NEWS/ABC Australia SD-FirstMedia-Chl-251.jpg","name":"ABC Australia","id":"2a800e8a-fdcc-47b3-a4a6-58d1d122b326","channel_id":"a1840c59-6c92-8233-3a02-230246aae0c4","channel_no":251,"programme_id":null,"episode":null,"title":"China Tonight","slug":null,"date":"2023-06-13 00:00:00","start_time":"2023-06-13 10:55:00","end_time":"2023-06-13 11:30:00","length":2100,"description":"China Tonight","long_description":"China is a superpower that dominates global news but it\'s also home to 1.4 billion stories. Sam Yang is back for a new season, hearing from the people who make this extraordinary nation what it is today.","status":"0","created_by":null,"updated_by":null,"created_at":"2023-06-13 00:20:24","updated_at":"2023-06-13 00:20:24"}]}}'
|
'{"entries":{"251":[{"logo":"files/images/d/new-logo/channels/11-NEWS/ABC Australia SD-FirstMedia-Chl-251.jpg","name":"ABC Australia","id":"2a800e8a-fdcc-47b3-a4a6-58d1d122b326","channel_id":"a1840c59-6c92-8233-3a02-230246aae0c4","channel_no":251,"programme_id":null,"episode":null,"title":"China Tonight","slug":null,"date":"2023-06-13 00:00:00","start_time":"2023-06-13 10:55:00","end_time":"2023-06-13 11:30:00","length":2100,"description":"China Tonight","long_description":"China is a superpower that dominates global news but it\'s also home to 1.4 billion stories. Sam Yang is back for a new season, hearing from the people who make this extraordinary nation what it is today.","status":"0","created_by":null,"updated_by":null,"created_at":"2023-06-13 00:20:24","updated_at":"2023-06-13 00:20:24"}]}}'
|
||||||
const results = parser({ content, channel })
|
const results = parser({ content, channel })
|
||||||
|
|
||||||
expect(results).toMatchObject([
|
expect(results).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2023-06-13T03:55:00.000Z',
|
start: '2023-06-13T03:55:00.000Z',
|
||||||
stop: '2023-06-13T04:30:00.000Z',
|
stop: '2023-06-13T04:30:00.000Z',
|
||||||
title: 'China Tonight',
|
title: 'China Tonight',
|
||||||
description:
|
description:
|
||||||
"China is a superpower that dominates global news but it's also home to 1.4 billion stories. Sam Yang is back for a new season, hearing from the people who make this extraordinary nation what it is today."
|
"China is a superpower that dominates global news but it's also home to 1.4 billion stories. Sam Yang is back for a new season, hearing from the people who make this extraordinary nation what it is today."
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const results = parser({ content: '' })
|
const results = parser({ content: '' })
|
||||||
|
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,45 +1,45 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'flixed.io',
|
site: 'flixed.io',
|
||||||
days: 1, // NOTE: changing the date in a request does not change the response
|
days: 1, // NOTE: changing the date in a request does not change the response
|
||||||
url: function ({ date, channel }) {
|
url: function ({ date, channel }) {
|
||||||
return `https://tv-guide.vercel.app/api/stationAirings?stationId=${
|
return `https://tv-guide.vercel.app/api/stationAirings?stationId=${
|
||||||
channel.site_id
|
channel.site_id
|
||||||
}&startDateTime=${date.toJSON()}`
|
}&startDateTime=${date.toJSON()}`
|
||||||
},
|
},
|
||||||
parser({ content }) {
|
parser({ content }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
let items = parseItems(content)
|
let items = parseItems(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.program.title,
|
title: item.program.title,
|
||||||
description: item.program.longDescription,
|
description: item.program.longDescription,
|
||||||
category: item.program.subType,
|
category: item.program.subType,
|
||||||
icon: parseIcon(item),
|
icon: parseIcon(item),
|
||||||
start: parseStart(item),
|
start: parseStart(item),
|
||||||
stop: parseStop(item)
|
stop: parseStop(item)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
const uri = item.program.preferredImage.uri
|
const uri = item.program.preferredImage.uri
|
||||||
|
|
||||||
return uri ? `https://adma.tmsimg.com/assets/${uri}` : null
|
return uri ? `https://adma.tmsimg.com/assets/${uri}` : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseStart(item) {
|
||||||
return dayjs(item.startTime)
|
return dayjs(item.startTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseStop(item) {
|
||||||
return dayjs(item.endTime)
|
return dayjs(item.endTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
return JSON.parse(content)
|
return JSON.parse(content)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
// npm run grab -- --site=flixed.io
|
// npm run grab -- --site=flixed.io
|
||||||
|
|
||||||
const { parser, url } = require('./flixed.io.config.js')
|
const { parser, url } = require('./flixed.io.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '108970',
|
site_id: '108970',
|
||||||
xmltv_id: 'VSiN.us'
|
xmltv_id: 'VSiN.us'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ date, channel })).toBe(
|
expect(url({ date, channel })).toBe(
|
||||||
'https://tv-guide.vercel.app/api/stationAirings?stationId=108970&startDateTime=2023-01-19T00:00:00.000Z'
|
'https://tv-guide.vercel.app/api/stationAirings?stationId=108970&startDateTime=2023-01-19T00:00:00.000Z'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
let results = parser({ content, channel, date })
|
let results = parser({ content, channel, date })
|
||||||
results = results.map(p => {
|
results = results.map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-01-19T05:00:00.000Z',
|
start: '2023-01-19T05:00:00.000Z',
|
||||||
stop: '2023-01-19T06:00:00.000Z',
|
stop: '2023-01-19T06:00:00.000Z',
|
||||||
title: 'The Greg Peterson Experience',
|
title: 'The Greg Peterson Experience',
|
||||||
category: 'Sports non-event',
|
category: 'Sports non-event',
|
||||||
icon: 'https://adma.tmsimg.com/assets/assets/p20628892_b_v13_aa.jpg?w=270&h=360',
|
icon: 'https://adma.tmsimg.com/assets/assets/p20628892_b_v13_aa.jpg?w=270&h=360',
|
||||||
description: 'A different kind of sports betting.'
|
description: 'A different kind of sports betting.'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const results = parser({
|
const results = parser({
|
||||||
content: '[]'
|
content: '[]'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,42 +1,42 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'foxsports.com.au',
|
site: 'foxsports.com.au',
|
||||||
days: 3,
|
days: 3,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 60 * 60 * 1000 // 1 hour
|
ttl: 60 * 60 * 1000 // 1 hour
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url({ date }) {
|
url({ date }) {
|
||||||
return `https://tvguide.foxsports.com.au/granite-api/programmes.json?from=${date.format(
|
return `https://tvguide.foxsports.com.au/granite-api/programmes.json?from=${date.format(
|
||||||
'YYYY-MM-DD'
|
'YYYY-MM-DD'
|
||||||
)}&to=${date.add(1, 'd').format('YYYY-MM-DD')}`
|
)}&to=${date.add(1, 'd').format('YYYY-MM-DD')}`
|
||||||
},
|
},
|
||||||
parser({ content, channel }) {
|
parser({ content, channel }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content, channel)
|
const items = parseItems(content, channel)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.programmeTitle,
|
title: item.programmeTitle,
|
||||||
sub_title: item.title,
|
sub_title: item.title,
|
||||||
category: item.genreTitle,
|
category: item.genreTitle,
|
||||||
description: item.synopsis,
|
description: item.synopsis,
|
||||||
start: dayjs.utc(item.startTime),
|
start: dayjs.utc(item.startTime),
|
||||||
stop: dayjs.utc(item.endTime)
|
stop: dayjs.utc(item.endTime)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel) {
|
function parseItems(content, channel) {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
if (!data) return []
|
if (!data) return []
|
||||||
const programmes = data['channel-programme']
|
const programmes = data['channel-programme']
|
||||||
if (!Array.isArray(programmes)) return []
|
if (!Array.isArray(programmes)) return []
|
||||||
|
|
||||||
const channelData = programmes.filter(i => i.channelId == channel.site_id)
|
const channelData = programmes.filter(i => i.channelId == channel.site_id)
|
||||||
return channelData && Array.isArray(channelData) ? channelData : []
|
return channelData && Array.isArray(channelData) ? channelData : []
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue