mirror of
https://github.com/iptv-org/epg.git
synced 2025-05-09 00:20:08 -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 { ApiClient } from '../../core'
|
||||
|
||||
async function main() {
|
||||
const logger = new Logger()
|
||||
const client = new ApiClient({ logger })
|
||||
|
||||
const requests = [
|
||||
client.download('channels.json'),
|
||||
client.download('countries.json'),
|
||||
client.download('regions.json'),
|
||||
client.download('subdivisions.json')
|
||||
]
|
||||
|
||||
await Promise.all(requests)
|
||||
}
|
||||
|
||||
main()
|
||||
import { Logger } from '@freearhey/core'
|
||||
import { ApiClient } from '../../core'
|
||||
|
||||
async function main() {
|
||||
const logger = new Logger()
|
||||
const client = new ApiClient({ logger })
|
||||
|
||||
const requests = [
|
||||
client.download('channels.json'),
|
||||
client.download('countries.json'),
|
||||
client.download('regions.json'),
|
||||
client.download('subdivisions.json')
|
||||
]
|
||||
|
||||
await Promise.all(requests)
|
||||
}
|
||||
|
||||
main()
|
||||
|
|
|
@ -1,180 +1,180 @@
|
|||
import { DATA_DIR } from '../../constants'
|
||||
import { Storage, Collection, Dictionary, Logger } from '@freearhey/core'
|
||||
import { ChannelsParser, XML, ApiChannel } from '../../core'
|
||||
import { Channel } from 'epg-grabber'
|
||||
import { transliterate } from 'transliteration'
|
||||
import nodeCleanup from 'node-cleanup'
|
||||
import { program } from 'commander'
|
||||
import inquirer, { QuestionCollection } from 'inquirer'
|
||||
|
||||
program
|
||||
.argument('<filepath>', 'Path to *.channels.xml file to edit')
|
||||
.option('-c, --country <name>', 'Default country (ISO 3166 code)', 'US')
|
||||
.parse(process.argv)
|
||||
|
||||
const filepath = program.args[0]
|
||||
const programOptions = program.opts()
|
||||
const defaultCountry = programOptions.country.toLowerCase()
|
||||
const newLabel = ` [new]`
|
||||
|
||||
let options = new Collection()
|
||||
|
||||
async function main() {
|
||||
const storage = new Storage()
|
||||
|
||||
if (!(await storage.exists(filepath))) {
|
||||
throw new Error(`File "${filepath}" does not exists`)
|
||||
}
|
||||
|
||||
const parser = new ChannelsParser({ storage })
|
||||
|
||||
const parsedChannels = await parser.parse(filepath)
|
||||
options = parsedChannels.map((channel: Channel) => {
|
||||
return {
|
||||
channel,
|
||||
delete: false
|
||||
}
|
||||
})
|
||||
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
const channelsContent = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
||||
|
||||
const buffer = new Dictionary()
|
||||
options.forEach(async (option: { channel: Channel; delete: boolean }) => {
|
||||
const channel = option.channel
|
||||
if (channel.xmltv_id) {
|
||||
if (channel.xmltv_id !== '-') {
|
||||
buffer.set(`${channel.xmltv_id}/${channel.lang}`, true)
|
||||
}
|
||||
return
|
||||
}
|
||||
let choices = getOptions(channels, channel)
|
||||
const question: QuestionCollection = {
|
||||
name: 'option',
|
||||
message: `Choose an option:`,
|
||||
type: 'list',
|
||||
choices,
|
||||
pageSize: 10
|
||||
}
|
||||
|
||||
await inquirer.prompt(question).then(async selected => {
|
||||
switch (selected.option) {
|
||||
case 'Overwrite':
|
||||
const input = await getInput(channel)
|
||||
channel.xmltv_id = input.xmltv_id
|
||||
break
|
||||
case 'Skip':
|
||||
channel.xmltv_id = '-'
|
||||
break
|
||||
default:
|
||||
const [, xmltv_id] = selected.option
|
||||
.replace(/ \[.*\]/, '')
|
||||
.split('|')
|
||||
.map((i: string) => i.trim().replace(newLabel, ''))
|
||||
channel.xmltv_id = xmltv_id
|
||||
break
|
||||
}
|
||||
|
||||
const found = buffer.has(`${channel.xmltv_id}/${channel.lang}`)
|
||||
if (found) {
|
||||
const question: QuestionCollection = {
|
||||
name: 'option',
|
||||
message: `"${channel.xmltv_id}" already on the list. Choose an option:`,
|
||||
type: 'list',
|
||||
choices: ['Skip', 'Add', 'Delete'],
|
||||
pageSize: 5
|
||||
}
|
||||
await inquirer.prompt(question).then(async selected => {
|
||||
switch (selected.option) {
|
||||
case 'Skip':
|
||||
channel.xmltv_id = '-'
|
||||
break
|
||||
case 'Delete':
|
||||
option.delete = true
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (channel.xmltv_id !== '-') {
|
||||
buffer.set(`${channel.xmltv_id}/${channel.lang}`, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
function save() {
|
||||
const logger = new Logger()
|
||||
const storage = new Storage()
|
||||
|
||||
if (!storage.existsSync(filepath)) return
|
||||
|
||||
const channels = options
|
||||
.filter((option: { channel: Channel; delete: boolean }) => !option.delete)
|
||||
.map((option: { channel: Channel; delete: boolean }) => option.channel)
|
||||
|
||||
const xml = new XML(channels)
|
||||
|
||||
storage.saveSync(filepath, xml.toString())
|
||||
|
||||
logger.info(`\nFile '${filepath}' successfully saved`)
|
||||
}
|
||||
|
||||
nodeCleanup(() => {
|
||||
save()
|
||||
})
|
||||
|
||||
async function getInput(channel: Channel) {
|
||||
const name = channel.name.trim()
|
||||
const input = await inquirer.prompt([
|
||||
{
|
||||
name: 'xmltv_id',
|
||||
message: ' ID:',
|
||||
type: 'input',
|
||||
default: generateCode(name, defaultCountry)
|
||||
}
|
||||
])
|
||||
|
||||
return { name, xmltv_id: input['xmltv_id'] }
|
||||
}
|
||||
|
||||
function getOptions(channels: Collection, channel: Channel) {
|
||||
const channelId = generateCode(channel.name, defaultCountry)
|
||||
const similar = getSimilar(channels, channelId)
|
||||
|
||||
const variants = new Collection()
|
||||
variants.add(`${channel.name.trim()} | ${channelId}${newLabel}`)
|
||||
similar.forEach((_channel: ApiChannel) => {
|
||||
const altNames = _channel.altNames.notEmpty() ? ` (${_channel.altNames.join(',')})` : ''
|
||||
const closed = _channel.closed ? `[closed:${_channel.closed}]` : ``
|
||||
const replacedBy = _channel.replacedBy ? `[replaced_by:${_channel.replacedBy}]` : ''
|
||||
|
||||
variants.add(`${_channel.name}${altNames} | ${_channel.id} ${closed}${replacedBy}[api]`)
|
||||
})
|
||||
variants.add(`Overwrite`)
|
||||
variants.add(`Skip`)
|
||||
|
||||
return variants.all()
|
||||
}
|
||||
|
||||
function getSimilar(channels: Collection, channelId: string) {
|
||||
const normChannelId = channelId.split('.')[0].slice(0, 8).toLowerCase()
|
||||
|
||||
return channels.filter((channel: ApiChannel) =>
|
||||
channel.id.split('.')[0].toLowerCase().startsWith(normChannelId)
|
||||
)
|
||||
}
|
||||
|
||||
function generateCode(name: string, country: string) {
|
||||
const channelId: string = transliterate(name)
|
||||
.replace(/\+/gi, 'Plus')
|
||||
.replace(/^\&/gi, 'And')
|
||||
.replace(/[^a-z\d]+/gi, '')
|
||||
|
||||
return `${channelId}.${country}`
|
||||
}
|
||||
import { DATA_DIR } from '../../constants'
|
||||
import { Storage, Collection, Dictionary, Logger } from '@freearhey/core'
|
||||
import { ChannelsParser, XML, ApiChannel } from '../../core'
|
||||
import { Channel } from 'epg-grabber'
|
||||
import { transliterate } from 'transliteration'
|
||||
import nodeCleanup from 'node-cleanup'
|
||||
import { program } from 'commander'
|
||||
import inquirer, { QuestionCollection } from 'inquirer'
|
||||
|
||||
program
|
||||
.argument('<filepath>', 'Path to *.channels.xml file to edit')
|
||||
.option('-c, --country <name>', 'Default country (ISO 3166 code)', 'US')
|
||||
.parse(process.argv)
|
||||
|
||||
const filepath = program.args[0]
|
||||
const programOptions = program.opts()
|
||||
const defaultCountry = programOptions.country.toLowerCase()
|
||||
const newLabel = ' [new]'
|
||||
|
||||
let options = new Collection()
|
||||
|
||||
async function main() {
|
||||
const storage = new Storage()
|
||||
|
||||
if (!(await storage.exists(filepath))) {
|
||||
throw new Error(`File "${filepath}" does not exists`)
|
||||
}
|
||||
|
||||
const parser = new ChannelsParser({ storage })
|
||||
|
||||
const parsedChannels = await parser.parse(filepath)
|
||||
options = parsedChannels.map((channel: Channel) => {
|
||||
return {
|
||||
channel,
|
||||
delete: false
|
||||
}
|
||||
})
|
||||
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
const channelsContent = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
||||
|
||||
const buffer = new Dictionary()
|
||||
options.forEach(async (option: { channel: Channel; delete: boolean }) => {
|
||||
const channel = option.channel
|
||||
if (channel.xmltv_id) {
|
||||
if (channel.xmltv_id !== '-') {
|
||||
buffer.set(`${channel.xmltv_id}/${channel.lang}`, true)
|
||||
}
|
||||
return
|
||||
}
|
||||
const choices = getOptions(channels, channel)
|
||||
const question: QuestionCollection = {
|
||||
name: 'option',
|
||||
message: 'Choose an option:',
|
||||
type: 'list',
|
||||
choices,
|
||||
pageSize: 10
|
||||
}
|
||||
|
||||
await inquirer.prompt(question).then(async selected => {
|
||||
switch (selected.option) {
|
||||
case 'Overwrite':
|
||||
const input = await getInput(channel)
|
||||
channel.xmltv_id = input.xmltv_id
|
||||
break
|
||||
case 'Skip':
|
||||
channel.xmltv_id = '-'
|
||||
break
|
||||
default:
|
||||
const [, xmltv_id] = selected.option
|
||||
.replace(/ \[.*\]/, '')
|
||||
.split('|')
|
||||
.map((i: string) => i.trim().replace(newLabel, ''))
|
||||
channel.xmltv_id = xmltv_id
|
||||
break
|
||||
}
|
||||
|
||||
const found = buffer.has(`${channel.xmltv_id}/${channel.lang}`)
|
||||
if (found) {
|
||||
const question: QuestionCollection = {
|
||||
name: 'option',
|
||||
message: `"${channel.xmltv_id}" already on the list. Choose an option:`,
|
||||
type: 'list',
|
||||
choices: ['Skip', 'Add', 'Delete'],
|
||||
pageSize: 5
|
||||
}
|
||||
await inquirer.prompt(question).then(async selected => {
|
||||
switch (selected.option) {
|
||||
case 'Skip':
|
||||
channel.xmltv_id = '-'
|
||||
break
|
||||
case 'Delete':
|
||||
option.delete = true
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (channel.xmltv_id !== '-') {
|
||||
buffer.set(`${channel.xmltv_id}/${channel.lang}`, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
function save() {
|
||||
const logger = new Logger()
|
||||
const storage = new Storage()
|
||||
|
||||
if (!storage.existsSync(filepath)) return
|
||||
|
||||
const channels = options
|
||||
.filter((option: { channel: Channel; delete: boolean }) => !option.delete)
|
||||
.map((option: { channel: Channel; delete: boolean }) => option.channel)
|
||||
|
||||
const xml = new XML(channels)
|
||||
|
||||
storage.saveSync(filepath, xml.toString())
|
||||
|
||||
logger.info(`\nFile '${filepath}' successfully saved`)
|
||||
}
|
||||
|
||||
nodeCleanup(() => {
|
||||
save()
|
||||
})
|
||||
|
||||
async function getInput(channel: Channel) {
|
||||
const name = channel.name.trim()
|
||||
const input = await inquirer.prompt([
|
||||
{
|
||||
name: 'xmltv_id',
|
||||
message: ' ID:',
|
||||
type: 'input',
|
||||
default: generateCode(name, defaultCountry)
|
||||
}
|
||||
])
|
||||
|
||||
return { name, xmltv_id: input['xmltv_id'] }
|
||||
}
|
||||
|
||||
function getOptions(channels: Collection, channel: Channel) {
|
||||
const channelId = generateCode(channel.name, defaultCountry)
|
||||
const similar = getSimilar(channels, channelId)
|
||||
|
||||
const variants = new Collection()
|
||||
variants.add(`${channel.name.trim()} | ${channelId}${newLabel}`)
|
||||
similar.forEach((_channel: ApiChannel) => {
|
||||
const altNames = _channel.altNames.notEmpty() ? ` (${_channel.altNames.join(',')})` : ''
|
||||
const closed = _channel.closed ? `[closed:${_channel.closed}]` : ''
|
||||
const replacedBy = _channel.replacedBy ? `[replaced_by:${_channel.replacedBy}]` : ''
|
||||
|
||||
variants.add(`${_channel.name}${altNames} | ${_channel.id} ${closed}${replacedBy}[api]`)
|
||||
})
|
||||
variants.add('Overwrite')
|
||||
variants.add('Skip')
|
||||
|
||||
return variants.all()
|
||||
}
|
||||
|
||||
function getSimilar(channels: Collection, channelId: string) {
|
||||
const normChannelId = channelId.split('.')[0].slice(0, 8).toLowerCase()
|
||||
|
||||
return channels.filter((channel: ApiChannel) =>
|
||||
channel.id.split('.')[0].toLowerCase().startsWith(normChannelId)
|
||||
)
|
||||
}
|
||||
|
||||
function generateCode(name: string, country: string) {
|
||||
const channelId: string = transliterate(name)
|
||||
.replace(/\+/gi, 'Plus')
|
||||
.replace(/^&/gi, 'And')
|
||||
.replace(/[^a-z\d]+/gi, '')
|
||||
|
||||
return `${channelId}.${country}`
|
||||
}
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
import chalk from 'chalk'
|
||||
import libxml, { ValidationError } from 'libxmljs2'
|
||||
import { program } from 'commander'
|
||||
import { Logger, Storage, File } from '@freearhey/core'
|
||||
|
||||
const xsd = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
|
||||
<xs:element name="channels">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="channel"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="channel">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute name="site" 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="xmltv_id" use="required" type="xs:string"/>
|
||||
<xs:attribute name="logo" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>`
|
||||
|
||||
program
|
||||
.option(
|
||||
'-c, --channels <path>',
|
||||
'Path to channels.xml file to validate',
|
||||
'sites/**/*.channels.xml'
|
||||
)
|
||||
.parse(process.argv)
|
||||
|
||||
const options = program.opts()
|
||||
|
||||
async function main() {
|
||||
const logger = new Logger()
|
||||
const storage = new Storage()
|
||||
|
||||
logger.info('options:')
|
||||
logger.tree(options)
|
||||
|
||||
let errors: ValidationError[] = []
|
||||
|
||||
let files: string[] = await storage.list(options.channels)
|
||||
for (const filepath of files) {
|
||||
const file = new File(filepath)
|
||||
if (file.extension() !== 'xml') continue
|
||||
|
||||
const xml = await storage.load(filepath)
|
||||
|
||||
let localErrors: ValidationError[] = []
|
||||
|
||||
const xsdDoc = libxml.parseXml(xsd)
|
||||
const doc = libxml.parseXml(xml)
|
||||
|
||||
if (!doc.validate(xsdDoc)) {
|
||||
localErrors = doc.validationErrors
|
||||
}
|
||||
|
||||
if (localErrors.length) {
|
||||
console.log(`\n${chalk.underline(filepath)}`)
|
||||
localErrors.forEach((error: ValidationError) => {
|
||||
const position = `${error.line}:${error.column}`
|
||||
console.log(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`)
|
||||
})
|
||||
|
||||
errors = errors.concat(localErrors)
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
console.log(chalk.red(`\n${errors.length} error(s)`))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
import chalk from 'chalk'
|
||||
import libxml, { ValidationError } from 'libxmljs2'
|
||||
import { program } from 'commander'
|
||||
import { Logger, Storage, File } from '@freearhey/core'
|
||||
|
||||
const xsd = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
|
||||
<xs:element name="channels">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="channel"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="channel">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute name="site" 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="xmltv_id" use="required" type="xs:string"/>
|
||||
<xs:attribute name="logo" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>`
|
||||
|
||||
program
|
||||
.option(
|
||||
'-c, --channels <path>',
|
||||
'Path to channels.xml file to validate',
|
||||
'sites/**/*.channels.xml'
|
||||
)
|
||||
.parse(process.argv)
|
||||
|
||||
const options = program.opts()
|
||||
|
||||
async function main() {
|
||||
const logger = new Logger()
|
||||
const storage = new Storage()
|
||||
|
||||
logger.info('options:')
|
||||
logger.tree(options)
|
||||
|
||||
let errors: ValidationError[] = []
|
||||
|
||||
const files: string[] = await storage.list(options.channels)
|
||||
for (const filepath of files) {
|
||||
const file = new File(filepath)
|
||||
if (file.extension() !== 'xml') continue
|
||||
|
||||
const xml = await storage.load(filepath)
|
||||
|
||||
let localErrors: ValidationError[] = []
|
||||
|
||||
const xsdDoc = libxml.parseXml(xsd)
|
||||
const doc = libxml.parseXml(xml)
|
||||
|
||||
if (!doc.validate(xsdDoc)) {
|
||||
localErrors = doc.validationErrors
|
||||
}
|
||||
|
||||
if (localErrors.length) {
|
||||
console.log(`\n${chalk.underline(filepath)}`)
|
||||
localErrors.forEach((error: ValidationError) => {
|
||||
const position = `${error.line}:${error.column}`
|
||||
console.log(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`)
|
||||
})
|
||||
|
||||
errors = errors.concat(localErrors)
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
console.log(chalk.red(`\n${errors.length} error(s)`))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
|
|
@ -1,81 +1,85 @@
|
|||
import { Logger, File, Collection, Storage } from '@freearhey/core'
|
||||
import { ChannelsParser, XML } from '../../core'
|
||||
import { Channel } from 'epg-grabber'
|
||||
import { Command, OptionValues } from 'commander'
|
||||
import path from 'path'
|
||||
|
||||
const program = new Command()
|
||||
program
|
||||
.requiredOption('-c, --config <config>', 'Config file')
|
||||
.option('-s, --set [args...]', 'Set custom arguments')
|
||||
.option('-o, --output <output>', 'Output file')
|
||||
.option('--clean', 'Delete the previous *.channels.xml if exists')
|
||||
.parse(process.argv)
|
||||
|
||||
type ParseOptions = {
|
||||
config: string
|
||||
set?: string
|
||||
output?: string
|
||||
clean?: boolean
|
||||
}
|
||||
|
||||
const options: ParseOptions = program.opts()
|
||||
|
||||
async function main() {
|
||||
const storage = new Storage()
|
||||
const parser = new ChannelsParser({ storage })
|
||||
const logger = new Logger()
|
||||
const file = new File(options.config)
|
||||
const dir = file.dirname()
|
||||
const config = require(path.resolve(options.config))
|
||||
const outputFilepath = options.output || `${dir}/${config.site}.channels.xml`
|
||||
|
||||
let channels = new Collection()
|
||||
if (!options.clean && (await storage.exists(outputFilepath))) {
|
||||
channels = await parser.parse(outputFilepath)
|
||||
}
|
||||
|
||||
const args: {
|
||||
[key: string]: any
|
||||
} = {}
|
||||
|
||||
if (Array.isArray(options.set)) {
|
||||
options.set.forEach((arg: string) => {
|
||||
const [key, value] = arg.split(':')
|
||||
args[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
let parsedChannels = config.channels(args)
|
||||
if (isPromise(parsedChannels)) {
|
||||
parsedChannels = await parsedChannels
|
||||
}
|
||||
parsedChannels = parsedChannels.map((channel: Channel) => {
|
||||
channel.site = config.site
|
||||
|
||||
return channel
|
||||
})
|
||||
|
||||
channels = channels
|
||||
.mergeBy(
|
||||
new Collection(parsedChannels),
|
||||
(channel: Channel) => channel.site_id.toString() + channel.lang
|
||||
)
|
||||
.orderBy([
|
||||
(channel: Channel) => channel.lang,
|
||||
(channel: Channel) => (channel.xmltv_id ? channel.xmltv_id.toLowerCase() : '_'),
|
||||
(channel: Channel) => channel.site_id
|
||||
])
|
||||
|
||||
const xml = new XML(channels)
|
||||
|
||||
await storage.save(outputFilepath, xml.toString())
|
||||
|
||||
logger.info(`File '${outputFilepath}' successfully saved`)
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
function isPromise(promise: any) {
|
||||
return !!promise && typeof promise.then === 'function'
|
||||
}
|
||||
import { Logger, File, Collection, Storage } from '@freearhey/core'
|
||||
import { ChannelsParser, XML } from '../../core'
|
||||
import { Channel } from 'epg-grabber'
|
||||
import { Command } from 'commander'
|
||||
import path from 'path'
|
||||
|
||||
const program = new Command()
|
||||
program
|
||||
.requiredOption('-c, --config <config>', 'Config file')
|
||||
.option('-s, --set [args...]', 'Set custom arguments')
|
||||
.option('-o, --output <output>', 'Output file')
|
||||
.option('--clean', 'Delete the previous *.channels.xml if exists')
|
||||
.parse(process.argv)
|
||||
|
||||
type ParseOptions = {
|
||||
config: string
|
||||
set?: string
|
||||
output?: string
|
||||
clean?: boolean
|
||||
}
|
||||
|
||||
const options: ParseOptions = program.opts()
|
||||
|
||||
async function main() {
|
||||
const storage = new Storage()
|
||||
const parser = new ChannelsParser({ storage })
|
||||
const logger = new Logger()
|
||||
const file = new File(options.config)
|
||||
const dir = file.dirname()
|
||||
const config = require(path.resolve(options.config))
|
||||
const outputFilepath = options.output || `${dir}/${config.site}.channels.xml`
|
||||
|
||||
let channels = new Collection()
|
||||
if (!options.clean && (await storage.exists(outputFilepath))) {
|
||||
channels = await parser.parse(outputFilepath)
|
||||
}
|
||||
|
||||
const args: {
|
||||
[key: string]: string
|
||||
} = {}
|
||||
|
||||
if (Array.isArray(options.set)) {
|
||||
options.set.forEach((arg: string) => {
|
||||
const [key, value] = arg.split(':')
|
||||
args[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
let parsedChannels = config.channels(args)
|
||||
if (isPromise(parsedChannels)) {
|
||||
parsedChannels = await parsedChannels
|
||||
}
|
||||
parsedChannels = parsedChannels.map((channel: Channel) => {
|
||||
channel.site = config.site
|
||||
|
||||
return channel
|
||||
})
|
||||
|
||||
channels = channels
|
||||
.mergeBy(
|
||||
new Collection(parsedChannels),
|
||||
(channel: Channel) => channel.site_id.toString() + channel.lang
|
||||
)
|
||||
.orderBy([
|
||||
(channel: Channel) => channel.lang,
|
||||
(channel: Channel) => (channel.xmltv_id ? channel.xmltv_id.toLowerCase() : '_'),
|
||||
(channel: Channel) => channel.site_id
|
||||
])
|
||||
|
||||
const xml = new XML(channels)
|
||||
|
||||
await storage.save(outputFilepath, xml.toString())
|
||||
|
||||
logger.info(`File '${outputFilepath}' successfully saved`)
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
function isPromise(promise: object[] | Promise<object[]>) {
|
||||
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 { ChannelsParser, ApiChannel } from '../../core'
|
||||
import { program } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
import langs from 'langs'
|
||||
import { DATA_DIR } from '../../constants'
|
||||
import { Channel } from 'epg-grabber'
|
||||
|
||||
program
|
||||
.option(
|
||||
'-c, --channels <path>',
|
||||
'Path to channels.xml file to validate',
|
||||
'sites/**/*.channels.xml'
|
||||
)
|
||||
.parse(process.argv)
|
||||
|
||||
const options = program.opts()
|
||||
|
||||
type ValidationError = {
|
||||
type: 'duplicate' | 'wrong_xmltv_id' | 'wrong_lang'
|
||||
name: string
|
||||
lang?: string
|
||||
xmltv_id?: string
|
||||
site_id?: string
|
||||
logo?: string
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const logger = new Logger()
|
||||
|
||||
logger.info('options:')
|
||||
logger.tree(options)
|
||||
|
||||
const parser = new ChannelsParser({ storage: new Storage() })
|
||||
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
const channelsContent = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
||||
|
||||
let totalFiles = 0
|
||||
let totalErrors = 0
|
||||
const storage = new Storage()
|
||||
let files: string[] = await storage.list(options.channels)
|
||||
for (const filepath of files) {
|
||||
const file = new File(filepath)
|
||||
if (file.extension() !== 'xml') continue
|
||||
|
||||
const parsedChannels = await parser.parse(filepath)
|
||||
|
||||
const bufferById = new Dictionary()
|
||||
const bufferBySiteId = new Dictionary()
|
||||
const errors: ValidationError[] = []
|
||||
parsedChannels.forEach((channel: Channel) => {
|
||||
const bufferId: string = `${channel.xmltv_id}:${channel.lang}`
|
||||
if (bufferById.missing(bufferId)) {
|
||||
bufferById.set(bufferId, true)
|
||||
} else {
|
||||
errors.push({ type: 'duplicate', ...channel })
|
||||
totalErrors++
|
||||
}
|
||||
|
||||
const bufferSiteId: string = `${channel.site_id}:${channel.lang}`
|
||||
if (bufferBySiteId.missing(bufferSiteId)) {
|
||||
bufferBySiteId.set(bufferSiteId, true)
|
||||
} else {
|
||||
errors.push({ type: 'duplicate', ...channel })
|
||||
totalErrors++
|
||||
}
|
||||
|
||||
if (channels.missing((_channel: ApiChannel) => _channel.id === channel.xmltv_id)) {
|
||||
errors.push({ type: 'wrong_xmltv_id', ...channel })
|
||||
totalErrors++
|
||||
}
|
||||
|
||||
if (!langs.where('1', channel.lang)) {
|
||||
errors.push({ type: 'wrong_lang', ...channel })
|
||||
totalErrors++
|
||||
}
|
||||
})
|
||||
|
||||
if (errors.length) {
|
||||
console.log(chalk.underline(filepath))
|
||||
console.table(errors, ['type', 'lang', 'xmltv_id', 'site_id', 'name'])
|
||||
console.log()
|
||||
totalFiles++
|
||||
}
|
||||
}
|
||||
|
||||
if (totalErrors > 0) {
|
||||
console.log(chalk.red(`${totalErrors} error(s) in ${totalFiles} file(s)`))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
import { Storage, Collection, Dictionary, File, Logger } from '@freearhey/core'
|
||||
import { ChannelsParser, ApiChannel } from '../../core'
|
||||
import { program } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
import langs from 'langs'
|
||||
import { DATA_DIR } from '../../constants'
|
||||
import { Channel } from 'epg-grabber'
|
||||
|
||||
program
|
||||
.option(
|
||||
'-c, --channels <path>',
|
||||
'Path to channels.xml file to validate',
|
||||
'sites/**/*.channels.xml'
|
||||
)
|
||||
.parse(process.argv)
|
||||
|
||||
const options = program.opts()
|
||||
|
||||
type ValidationError = {
|
||||
type: 'duplicate' | 'wrong_xmltv_id' | 'wrong_lang'
|
||||
name: string
|
||||
lang?: string
|
||||
xmltv_id?: string
|
||||
site_id?: string
|
||||
logo?: string
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const logger = new Logger()
|
||||
|
||||
logger.info('options:')
|
||||
logger.tree(options)
|
||||
|
||||
const parser = new ChannelsParser({ storage: new Storage() })
|
||||
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
const channelsContent = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
||||
|
||||
let totalFiles = 0
|
||||
let totalErrors = 0
|
||||
const storage = new Storage()
|
||||
const files: string[] = await storage.list(options.channels)
|
||||
for (const filepath of files) {
|
||||
const file = new File(filepath)
|
||||
if (file.extension() !== 'xml') continue
|
||||
|
||||
const parsedChannels = await parser.parse(filepath)
|
||||
|
||||
const bufferById = new Dictionary()
|
||||
const bufferBySiteId = new Dictionary()
|
||||
const errors: ValidationError[] = []
|
||||
parsedChannels.forEach((channel: Channel) => {
|
||||
const bufferId: string = `${channel.xmltv_id}:${channel.lang}`
|
||||
if (bufferById.missing(bufferId)) {
|
||||
bufferById.set(bufferId, true)
|
||||
} else {
|
||||
errors.push({ type: 'duplicate', ...channel })
|
||||
totalErrors++
|
||||
}
|
||||
|
||||
const bufferSiteId: string = `${channel.site_id}:${channel.lang}`
|
||||
if (bufferBySiteId.missing(bufferSiteId)) {
|
||||
bufferBySiteId.set(bufferSiteId, true)
|
||||
} else {
|
||||
errors.push({ type: 'duplicate', ...channel })
|
||||
totalErrors++
|
||||
}
|
||||
|
||||
if (channels.missing((_channel: ApiChannel) => _channel.id === channel.xmltv_id)) {
|
||||
errors.push({ type: 'wrong_xmltv_id', ...channel })
|
||||
totalErrors++
|
||||
}
|
||||
|
||||
if (!langs.where('1', channel.lang)) {
|
||||
errors.push({ type: 'wrong_lang', ...channel })
|
||||
totalErrors++
|
||||
}
|
||||
})
|
||||
|
||||
if (errors.length) {
|
||||
console.log(chalk.underline(filepath))
|
||||
console.table(errors, ['type', 'lang', 'xmltv_id', 'site_id', 'name'])
|
||||
console.log()
|
||||
totalFiles++
|
||||
}
|
||||
}
|
||||
|
||||
if (totalErrors > 0) {
|
||||
console.log(chalk.red(`${totalErrors} error(s) in ${totalFiles} file(s)`))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
|
|
@ -1,117 +1,117 @@
|
|||
import { Logger, Timer, Storage, Collection } from '@freearhey/core'
|
||||
import { program } from 'commander'
|
||||
import { CronJob } from 'cron'
|
||||
import { QueueCreator, Job, ChannelsParser } from '../../core'
|
||||
import { Channel } from 'epg-grabber'
|
||||
import path from 'path'
|
||||
import { SITES_DIR } from '../../constants'
|
||||
|
||||
program
|
||||
.option('-s, --site <name>', 'Name of the site to parse')
|
||||
.option(
|
||||
'-c, --channels <path>',
|
||||
'Path to *.channels.xml file (required if the "--site" attribute is not specified)'
|
||||
)
|
||||
.option('-o, --output <path>', 'Path to output file', 'guide.xml')
|
||||
.option('-l, --lang <code>', 'Filter channels by language (ISO 639-2 code)')
|
||||
.option('-t, --timeout <milliseconds>', 'Override the default timeout for each request')
|
||||
.option(
|
||||
'--days <days>',
|
||||
'Override the number of days for which the program will be loaded (defaults to the value from the site config)',
|
||||
value => parseInt(value)
|
||||
)
|
||||
.option(
|
||||
'--maxConnections <number>',
|
||||
'Limit on the number of concurrent requests',
|
||||
value => parseInt(value),
|
||||
1
|
||||
)
|
||||
.option('--cron <expression>', 'Schedule a script run (example: "0 0 * * *")')
|
||||
.option('--gzip', 'Create a compressed version of the guide as well', false)
|
||||
.parse(process.argv)
|
||||
|
||||
export type GrabOptions = {
|
||||
site?: string
|
||||
channels?: string
|
||||
output: string
|
||||
gzip: boolean
|
||||
maxConnections: number
|
||||
timeout?: string
|
||||
lang?: string
|
||||
days?: number
|
||||
cron?: string
|
||||
}
|
||||
|
||||
const options: GrabOptions = program.opts()
|
||||
|
||||
async function main() {
|
||||
if (!options.site && !options.channels)
|
||||
throw new Error('One of the arguments must be presented: `--site` or `--channels`')
|
||||
|
||||
const logger = new Logger()
|
||||
|
||||
logger.start('staring...')
|
||||
|
||||
logger.info('config:')
|
||||
logger.tree(options)
|
||||
|
||||
logger.info(`loading channels...`)
|
||||
const storage = new Storage()
|
||||
const parser = new ChannelsParser({ storage })
|
||||
|
||||
let files: string[] = []
|
||||
if (options.site) {
|
||||
let pattern = path.join(SITES_DIR, options.site, '*.channels.xml')
|
||||
pattern = pattern.replace(/\\/g, '/')
|
||||
files = await storage.list(pattern)
|
||||
} else if (options.channels) {
|
||||
files = await storage.list(options.channels)
|
||||
}
|
||||
|
||||
let parsedChannels = new Collection()
|
||||
for (let filepath of files) {
|
||||
parsedChannels = parsedChannels.concat(await parser.parse(filepath))
|
||||
}
|
||||
if (options.lang) {
|
||||
parsedChannels = parsedChannels.filter((channel: Channel) => channel.lang === options.lang)
|
||||
}
|
||||
logger.info(` found ${parsedChannels.count()} channels`)
|
||||
|
||||
logger.info('creating queue...')
|
||||
const queueCreator = new QueueCreator({
|
||||
parsedChannels,
|
||||
logger,
|
||||
options
|
||||
})
|
||||
const queue = await queueCreator.create()
|
||||
logger.info(` added ${queue.size()} items`)
|
||||
|
||||
const job = new Job({
|
||||
queue,
|
||||
logger,
|
||||
options
|
||||
})
|
||||
|
||||
let runIndex = 1
|
||||
if (options.cron) {
|
||||
const cronJob = new CronJob(options.cron, async () => {
|
||||
logger.info(`run #${runIndex}:`)
|
||||
const timer = new Timer()
|
||||
timer.start()
|
||||
await job.run()
|
||||
runIndex++
|
||||
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
|
||||
})
|
||||
cronJob.start()
|
||||
} else {
|
||||
logger.info(`run #${runIndex}:`)
|
||||
const timer = new Timer()
|
||||
timer.start()
|
||||
await job.run()
|
||||
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
|
||||
}
|
||||
|
||||
logger.info('finished')
|
||||
}
|
||||
|
||||
main()
|
||||
import { Logger, Timer, Storage, Collection } from '@freearhey/core'
|
||||
import { program } from 'commander'
|
||||
import { CronJob } from 'cron'
|
||||
import { QueueCreator, Job, ChannelsParser } from '../../core'
|
||||
import { Channel } from 'epg-grabber'
|
||||
import path from 'path'
|
||||
import { SITES_DIR } from '../../constants'
|
||||
|
||||
program
|
||||
.option('-s, --site <name>', 'Name of the site to parse')
|
||||
.option(
|
||||
'-c, --channels <path>',
|
||||
'Path to *.channels.xml file (required if the "--site" attribute is not specified)'
|
||||
)
|
||||
.option('-o, --output <path>', 'Path to output file', 'guide.xml')
|
||||
.option('-l, --lang <code>', 'Filter channels by language (ISO 639-2 code)')
|
||||
.option('-t, --timeout <milliseconds>', 'Override the default timeout for each request')
|
||||
.option(
|
||||
'--days <days>',
|
||||
'Override the number of days for which the program will be loaded (defaults to the value from the site config)',
|
||||
value => parseInt(value)
|
||||
)
|
||||
.option(
|
||||
'--maxConnections <number>',
|
||||
'Limit on the number of concurrent requests',
|
||||
value => parseInt(value),
|
||||
1
|
||||
)
|
||||
.option('--cron <expression>', 'Schedule a script run (example: "0 0 * * *")')
|
||||
.option('--gzip', 'Create a compressed version of the guide as well', false)
|
||||
.parse(process.argv)
|
||||
|
||||
export type GrabOptions = {
|
||||
site?: string
|
||||
channels?: string
|
||||
output: string
|
||||
gzip: boolean
|
||||
maxConnections: number
|
||||
timeout?: string
|
||||
lang?: string
|
||||
days?: number
|
||||
cron?: string
|
||||
}
|
||||
|
||||
const options: GrabOptions = program.opts()
|
||||
|
||||
async function main() {
|
||||
if (!options.site && !options.channels)
|
||||
throw new Error('One of the arguments must be presented: `--site` or `--channels`')
|
||||
|
||||
const logger = new Logger()
|
||||
|
||||
logger.start('staring...')
|
||||
|
||||
logger.info('config:')
|
||||
logger.tree(options)
|
||||
|
||||
logger.info('loading channels...')
|
||||
const storage = new Storage()
|
||||
const parser = new ChannelsParser({ storage })
|
||||
|
||||
let files: string[] = []
|
||||
if (options.site) {
|
||||
let pattern = path.join(SITES_DIR, options.site, '*.channels.xml')
|
||||
pattern = pattern.replace(/\\/g, '/')
|
||||
files = await storage.list(pattern)
|
||||
} else if (options.channels) {
|
||||
files = await storage.list(options.channels)
|
||||
}
|
||||
|
||||
let parsedChannels = new Collection()
|
||||
for (const filepath of files) {
|
||||
parsedChannels = parsedChannels.concat(await parser.parse(filepath))
|
||||
}
|
||||
if (options.lang) {
|
||||
parsedChannels = parsedChannels.filter((channel: Channel) => channel.lang === options.lang)
|
||||
}
|
||||
logger.info(` found ${parsedChannels.count()} channels`)
|
||||
|
||||
logger.info('creating queue...')
|
||||
const queueCreator = new QueueCreator({
|
||||
parsedChannels,
|
||||
logger,
|
||||
options
|
||||
})
|
||||
const queue = await queueCreator.create()
|
||||
logger.info(` added ${queue.size()} items`)
|
||||
|
||||
const job = new Job({
|
||||
queue,
|
||||
logger,
|
||||
options
|
||||
})
|
||||
|
||||
let runIndex = 1
|
||||
if (options.cron) {
|
||||
const cronJob = new CronJob(options.cron, async () => {
|
||||
logger.info(`run #${runIndex}:`)
|
||||
const timer = new Timer()
|
||||
timer.start()
|
||||
await job.run()
|
||||
runIndex++
|
||||
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
|
||||
})
|
||||
cronJob.start()
|
||||
} else {
|
||||
logger.info(`run #${runIndex}:`)
|
||||
const timer = new Timer()
|
||||
timer.start()
|
||||
await job.run()
|
||||
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
|
||||
}
|
||||
|
||||
logger.info('finished')
|
||||
}
|
||||
|
||||
main()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const SITES_DIR = process.env.SITES_DIR || './sites'
|
||||
export const GUIDES_DIR = process.env.GUIDES_DIR || './guides'
|
||||
export const DATA_DIR = process.env.DATA_DIR || './temp/data'
|
||||
export const CURR_DATE = process.env.CURR_DATE || new Date().toISOString()
|
||||
export const SITES_DIR = process.env.SITES_DIR || './sites'
|
||||
export const GUIDES_DIR = process.env.GUIDES_DIR || './guides'
|
||||
export const DATA_DIR = process.env.DATA_DIR || './temp/data'
|
||||
export const CURR_DATE = process.env.CURR_DATE || new Date().toISOString()
|
||||
|
|
|
@ -1,79 +1,79 @@
|
|||
import { Collection } from '@freearhey/core'
|
||||
|
||||
type ApiChannelProps = {
|
||||
id: string
|
||||
name: string
|
||||
alt_names: string[]
|
||||
network: string
|
||||
owners: string[]
|
||||
country: string
|
||||
subdivision: string
|
||||
city: string
|
||||
broadcast_area: string[]
|
||||
languages: string[]
|
||||
categories: string[]
|
||||
is_nsfw: boolean
|
||||
launched: string
|
||||
closed: string
|
||||
replaced_by: string
|
||||
website: string
|
||||
logo: string
|
||||
}
|
||||
|
||||
export class ApiChannel {
|
||||
id: string
|
||||
name: string
|
||||
altNames: Collection
|
||||
network: string
|
||||
owners: Collection
|
||||
country: string
|
||||
subdivision: string
|
||||
city: string
|
||||
broadcastArea: Collection
|
||||
languages: Collection
|
||||
categories: Collection
|
||||
isNSFW: boolean
|
||||
launched: string
|
||||
closed: string
|
||||
replacedBy: string
|
||||
website: string
|
||||
logo: string
|
||||
|
||||
constructor({
|
||||
id,
|
||||
name,
|
||||
alt_names,
|
||||
network,
|
||||
owners,
|
||||
country,
|
||||
subdivision,
|
||||
city,
|
||||
broadcast_area,
|
||||
languages,
|
||||
categories,
|
||||
is_nsfw,
|
||||
launched,
|
||||
closed,
|
||||
replaced_by,
|
||||
website,
|
||||
logo
|
||||
}: ApiChannelProps) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.altNames = new Collection(alt_names)
|
||||
this.network = network
|
||||
this.owners = new Collection(owners)
|
||||
this.country = country
|
||||
this.subdivision = subdivision
|
||||
this.city = city
|
||||
this.broadcastArea = new Collection(broadcast_area)
|
||||
this.languages = new Collection(languages)
|
||||
this.categories = new Collection(categories)
|
||||
this.isNSFW = is_nsfw
|
||||
this.launched = launched
|
||||
this.closed = closed
|
||||
this.replacedBy = replaced_by
|
||||
this.website = website
|
||||
this.logo = logo
|
||||
}
|
||||
}
|
||||
import { Collection } from '@freearhey/core'
|
||||
|
||||
type ApiChannelProps = {
|
||||
id: string
|
||||
name: string
|
||||
alt_names: string[]
|
||||
network: string
|
||||
owners: string[]
|
||||
country: string
|
||||
subdivision: string
|
||||
city: string
|
||||
broadcast_area: string[]
|
||||
languages: string[]
|
||||
categories: string[]
|
||||
is_nsfw: boolean
|
||||
launched: string
|
||||
closed: string
|
||||
replaced_by: string
|
||||
website: string
|
||||
logo: string
|
||||
}
|
||||
|
||||
export class ApiChannel {
|
||||
id: string
|
||||
name: string
|
||||
altNames: Collection
|
||||
network: string
|
||||
owners: Collection
|
||||
country: string
|
||||
subdivision: string
|
||||
city: string
|
||||
broadcastArea: Collection
|
||||
languages: Collection
|
||||
categories: Collection
|
||||
isNSFW: boolean
|
||||
launched: string
|
||||
closed: string
|
||||
replacedBy: string
|
||||
website: string
|
||||
logo: string
|
||||
|
||||
constructor({
|
||||
id,
|
||||
name,
|
||||
alt_names,
|
||||
network,
|
||||
owners,
|
||||
country,
|
||||
subdivision,
|
||||
city,
|
||||
broadcast_area,
|
||||
languages,
|
||||
categories,
|
||||
is_nsfw,
|
||||
launched,
|
||||
closed,
|
||||
replaced_by,
|
||||
website,
|
||||
logo
|
||||
}: ApiChannelProps) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.altNames = new Collection(alt_names)
|
||||
this.network = network
|
||||
this.owners = new Collection(owners)
|
||||
this.country = country
|
||||
this.subdivision = subdivision
|
||||
this.city = city
|
||||
this.broadcastArea = new Collection(broadcast_area)
|
||||
this.languages = new Collection(languages)
|
||||
this.categories = new Collection(categories)
|
||||
this.isNSFW = is_nsfw
|
||||
this.launched = launched
|
||||
this.closed = closed
|
||||
this.replacedBy = replaced_by
|
||||
this.website = website
|
||||
this.logo = logo
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
import { Logger, Storage } from '@freearhey/core'
|
||||
import axios, { AxiosInstance, AxiosResponse, AxiosProgressEvent } from 'axios'
|
||||
import cliProgress, { MultiBar } from 'cli-progress'
|
||||
import numeral from 'numeral'
|
||||
|
||||
export class ApiClient {
|
||||
progressBar: MultiBar
|
||||
client: AxiosInstance
|
||||
storage: Storage
|
||||
logger: Logger
|
||||
|
||||
constructor({ logger }: { logger: Logger }) {
|
||||
this.logger = logger
|
||||
this.client = axios.create({
|
||||
responseType: 'stream'
|
||||
})
|
||||
this.storage = new Storage()
|
||||
this.progressBar = new cliProgress.MultiBar({
|
||||
stopOnComplete: true,
|
||||
hideCursor: true,
|
||||
forceRedraw: true,
|
||||
barsize: 36,
|
||||
format(options, params, payload) {
|
||||
const filename = payload.filename.padEnd(18, ' ')
|
||||
const barsize = options.barsize || 40
|
||||
const percent = (params.progress * 100).toFixed(2)
|
||||
const speed = payload.speed ? numeral(payload.speed).format('0.0 b') + '/s' : 'N/A'
|
||||
const total = numeral(params.total).format('0.0 b')
|
||||
const completeSize = Math.round(params.progress * barsize)
|
||||
const incompleteSize = barsize - completeSize
|
||||
const bar =
|
||||
options.barCompleteString && options.barIncompleteString
|
||||
? options.barCompleteString.substr(0, completeSize) +
|
||||
options.barGlue +
|
||||
options.barIncompleteString.substr(0, incompleteSize)
|
||||
: '-'.repeat(barsize)
|
||||
|
||||
return `${filename} [${bar}] ${percent}% | ETA: ${params.eta}s | ${total} | ${speed}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async download(filename: string) {
|
||||
const stream = await this.storage.createStream(`/temp/data/${filename}`)
|
||||
|
||||
const bar = this.progressBar.create(0, 0, { filename })
|
||||
|
||||
this.client
|
||||
.get(`https://iptv-org.github.io/api/${filename}`, {
|
||||
onDownloadProgress({ total, loaded, rate }: AxiosProgressEvent) {
|
||||
if (total) bar.setTotal(total)
|
||||
bar.update(loaded, { speed: rate })
|
||||
}
|
||||
})
|
||||
.then((response: AxiosResponse) => {
|
||||
response.data.pipe(stream)
|
||||
})
|
||||
}
|
||||
}
|
||||
import { Logger, Storage } from '@freearhey/core'
|
||||
import axios, { AxiosInstance, AxiosResponse, AxiosProgressEvent } from 'axios'
|
||||
import cliProgress, { MultiBar } from 'cli-progress'
|
||||
import numeral from 'numeral'
|
||||
|
||||
export class ApiClient {
|
||||
progressBar: MultiBar
|
||||
client: AxiosInstance
|
||||
storage: Storage
|
||||
logger: Logger
|
||||
|
||||
constructor({ logger }: { logger: Logger }) {
|
||||
this.logger = logger
|
||||
this.client = axios.create({
|
||||
responseType: 'stream'
|
||||
})
|
||||
this.storage = new Storage()
|
||||
this.progressBar = new cliProgress.MultiBar({
|
||||
stopOnComplete: true,
|
||||
hideCursor: true,
|
||||
forceRedraw: true,
|
||||
barsize: 36,
|
||||
format(options, params, payload) {
|
||||
const filename = payload.filename.padEnd(18, ' ')
|
||||
const barsize = options.barsize || 40
|
||||
const percent = (params.progress * 100).toFixed(2)
|
||||
const speed = payload.speed ? numeral(payload.speed).format('0.0 b') + '/s' : 'N/A'
|
||||
const total = numeral(params.total).format('0.0 b')
|
||||
const completeSize = Math.round(params.progress * barsize)
|
||||
const incompleteSize = barsize - completeSize
|
||||
const bar =
|
||||
options.barCompleteString && options.barIncompleteString
|
||||
? options.barCompleteString.substr(0, completeSize) +
|
||||
options.barGlue +
|
||||
options.barIncompleteString.substr(0, incompleteSize)
|
||||
: '-'.repeat(barsize)
|
||||
|
||||
return `${filename} [${bar}] ${percent}% | ETA: ${params.eta}s | ${total} | ${speed}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async download(filename: string) {
|
||||
const stream = await this.storage.createStream(`/temp/data/${filename}`)
|
||||
|
||||
const bar = this.progressBar.create(0, 0, { filename })
|
||||
|
||||
this.client
|
||||
.get(`https://iptv-org.github.io/api/${filename}`, {
|
||||
onDownloadProgress({ total, loaded, rate }: AxiosProgressEvent) {
|
||||
if (total) bar.setTotal(total)
|
||||
bar.update(loaded, { speed: rate })
|
||||
}
|
||||
})
|
||||
.then((response: AxiosResponse) => {
|
||||
response.data.pipe(stream)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import { parseChannels } from 'epg-grabber'
|
||||
import { Storage, Collection } from '@freearhey/core'
|
||||
|
||||
type ChannelsParserProps = {
|
||||
storage: Storage
|
||||
}
|
||||
|
||||
export class ChannelsParser {
|
||||
storage: Storage
|
||||
|
||||
constructor({ storage }: ChannelsParserProps) {
|
||||
this.storage = storage
|
||||
}
|
||||
|
||||
async parse(filepath: string) {
|
||||
let parsedChannels = new Collection()
|
||||
|
||||
const content = await this.storage.load(filepath)
|
||||
const channels = parseChannels(content)
|
||||
parsedChannels = parsedChannels.concat(new Collection(channels))
|
||||
|
||||
return parsedChannels
|
||||
}
|
||||
}
|
||||
import { parseChannels } from 'epg-grabber'
|
||||
import { Storage, Collection } from '@freearhey/core'
|
||||
|
||||
type ChannelsParserProps = {
|
||||
storage: Storage
|
||||
}
|
||||
|
||||
export class ChannelsParser {
|
||||
storage: Storage
|
||||
|
||||
constructor({ storage }: ChannelsParserProps) {
|
||||
this.storage = storage
|
||||
}
|
||||
|
||||
async parse(filepath: string) {
|
||||
let parsedChannels = new Collection()
|
||||
|
||||
const content = await this.storage.load(filepath)
|
||||
const channels = parseChannels(content)
|
||||
parsedChannels = parsedChannels.concat(new Collection(channels))
|
||||
|
||||
return parsedChannels
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import { SiteConfig } from 'epg-grabber'
|
||||
import _ from 'lodash'
|
||||
import { pathToFileURL } from 'url'
|
||||
|
||||
export class ConfigLoader {
|
||||
async load(filepath: string): Promise<SiteConfig> {
|
||||
const fileUrl = pathToFileURL(filepath).toString()
|
||||
const config = (await import(fileUrl)).default
|
||||
|
||||
return _.merge(
|
||||
{
|
||||
delay: 0,
|
||||
maxConnections: 1,
|
||||
request: {
|
||||
timeout: 30000
|
||||
}
|
||||
},
|
||||
config
|
||||
)
|
||||
}
|
||||
}
|
||||
import { SiteConfig } from 'epg-grabber'
|
||||
import _ from 'lodash'
|
||||
import { pathToFileURL } from 'url'
|
||||
|
||||
export class ConfigLoader {
|
||||
async load(filepath: string): Promise<SiteConfig> {
|
||||
const fileUrl = pathToFileURL(filepath).toString()
|
||||
const config = (await import(fileUrl)).default
|
||||
|
||||
return _.merge(
|
||||
{
|
||||
delay: 0,
|
||||
maxConnections: 1,
|
||||
request: {
|
||||
timeout: 30000
|
||||
}
|
||||
},
|
||||
config
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = {}
|
||||
|
||||
date.getUTC = function (d = null) {
|
||||
if (typeof d === 'string') return dayjs.utc(d).startOf('d')
|
||||
|
||||
return dayjs.utc().startOf('d')
|
||||
}
|
||||
|
||||
module.exports = date
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = {}
|
||||
|
||||
date.getUTC = function (d = null) {
|
||||
if (typeof d === 'string') return dayjs.utc(d).startOf('d')
|
||||
|
||||
return dayjs.utc().startOf('d')
|
||||
}
|
||||
|
||||
module.exports = date
|
||||
|
|
|
@ -1,75 +1,75 @@
|
|||
import { EPGGrabber, GrabCallbackData, EPGGrabberMock, SiteConfig, Channel } from 'epg-grabber'
|
||||
import { Logger, Collection } from '@freearhey/core'
|
||||
import { Queue } from './'
|
||||
import { GrabOptions } from '../commands/epg/grab'
|
||||
import { TaskQueue, PromisyClass } from 'cwait'
|
||||
|
||||
type GrabberProps = {
|
||||
logger: Logger
|
||||
queue: Queue
|
||||
options: GrabOptions
|
||||
}
|
||||
|
||||
export class Grabber {
|
||||
logger: Logger
|
||||
queue: Queue
|
||||
options: GrabOptions
|
||||
|
||||
constructor({ logger, queue, options }: GrabberProps) {
|
||||
this.logger = logger
|
||||
this.queue = queue
|
||||
this.options = options
|
||||
}
|
||||
|
||||
async grab(): Promise<{ channels: Collection; programs: Collection }> {
|
||||
const taskQueue = new TaskQueue(Promise as PromisyClass, this.options.maxConnections)
|
||||
|
||||
const total = this.queue.size()
|
||||
|
||||
const channels = new Collection()
|
||||
let programs = new Collection()
|
||||
let i = 1
|
||||
|
||||
await Promise.all(
|
||||
this.queue.items().map(
|
||||
taskQueue.wrap(
|
||||
async (queueItem: { channel: Channel; config: SiteConfig; date: string }) => {
|
||||
const { channel, config, date } = queueItem
|
||||
|
||||
channels.add(channel)
|
||||
|
||||
if (this.options.timeout !== undefined) {
|
||||
const timeout = parseInt(this.options.timeout)
|
||||
config.request = { ...config.request, ...{ timeout } }
|
||||
}
|
||||
|
||||
const grabber =
|
||||
process.env.NODE_ENV === 'test' ? new EPGGrabberMock(config) : new EPGGrabber(config)
|
||||
const _programs = await grabber.grab(
|
||||
channel,
|
||||
date,
|
||||
(data: GrabCallbackData, error: Error | null) => {
|
||||
const { programs, date } = data
|
||||
|
||||
this.logger.info(
|
||||
` [${i}/${total}] ${channel.site} (${channel.lang}) - ${
|
||||
channel.xmltv_id
|
||||
} - ${date.format('MMM D, YYYY')} (${programs.length} programs)`
|
||||
)
|
||||
if (i < total) i++
|
||||
|
||||
if (error) {
|
||||
this.logger.info(` ERR: ${error.message}`)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
programs = programs.concat(new Collection(_programs))
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return { channels, programs }
|
||||
}
|
||||
}
|
||||
import { EPGGrabber, GrabCallbackData, EPGGrabberMock, SiteConfig, Channel } from 'epg-grabber'
|
||||
import { Logger, Collection } from '@freearhey/core'
|
||||
import { Queue } from './'
|
||||
import { GrabOptions } from '../commands/epg/grab'
|
||||
import { TaskQueue, PromisyClass } from 'cwait'
|
||||
|
||||
type GrabberProps = {
|
||||
logger: Logger
|
||||
queue: Queue
|
||||
options: GrabOptions
|
||||
}
|
||||
|
||||
export class Grabber {
|
||||
logger: Logger
|
||||
queue: Queue
|
||||
options: GrabOptions
|
||||
|
||||
constructor({ logger, queue, options }: GrabberProps) {
|
||||
this.logger = logger
|
||||
this.queue = queue
|
||||
this.options = options
|
||||
}
|
||||
|
||||
async grab(): Promise<{ channels: Collection; programs: Collection }> {
|
||||
const taskQueue = new TaskQueue(Promise as PromisyClass, this.options.maxConnections)
|
||||
|
||||
const total = this.queue.size()
|
||||
|
||||
const channels = new Collection()
|
||||
let programs = new Collection()
|
||||
let i = 1
|
||||
|
||||
await Promise.all(
|
||||
this.queue.items().map(
|
||||
taskQueue.wrap(
|
||||
async (queueItem: { channel: Channel; config: SiteConfig; date: string }) => {
|
||||
const { channel, config, date } = queueItem
|
||||
|
||||
channels.add(channel)
|
||||
|
||||
if (this.options.timeout !== undefined) {
|
||||
const timeout = parseInt(this.options.timeout)
|
||||
config.request = { ...config.request, ...{ timeout } }
|
||||
}
|
||||
|
||||
const grabber =
|
||||
process.env.NODE_ENV === 'test' ? new EPGGrabberMock(config) : new EPGGrabber(config)
|
||||
const _programs = await grabber.grab(
|
||||
channel,
|
||||
date,
|
||||
(data: GrabCallbackData, error: Error | null) => {
|
||||
const { programs, date } = data
|
||||
|
||||
this.logger.info(
|
||||
` [${i}/${total}] ${channel.site} (${channel.lang}) - ${
|
||||
channel.xmltv_id
|
||||
} - ${date.format('MMM D, YYYY')} (${programs.length} programs)`
|
||||
)
|
||||
if (i < total) i++
|
||||
|
||||
if (error) {
|
||||
this.logger.info(` ERR: ${error.message}`)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
programs = programs.concat(new Collection(_programs))
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return { channels, programs }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,55 +1,55 @@
|
|||
import { Collection, Logger, DateTime, Storage, Zip } from '@freearhey/core'
|
||||
import { Channel } from 'epg-grabber'
|
||||
import { XMLTV } from '../core'
|
||||
import { CURR_DATE } from '../constants'
|
||||
|
||||
type GuideProps = {
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
logger: Logger
|
||||
filepath: string
|
||||
gzip: boolean
|
||||
}
|
||||
|
||||
export class Guide {
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
logger: Logger
|
||||
storage: Storage
|
||||
filepath: string
|
||||
gzip: boolean
|
||||
|
||||
constructor({ channels, programs, logger, filepath, gzip }: GuideProps) {
|
||||
this.channels = channels
|
||||
this.programs = programs
|
||||
this.logger = logger
|
||||
this.storage = new Storage()
|
||||
this.filepath = filepath
|
||||
this.gzip = gzip || false
|
||||
}
|
||||
|
||||
async save() {
|
||||
const channels = this.channels.uniqBy(
|
||||
(channel: Channel) => `${channel.xmltv_id}:${channel.site}`
|
||||
)
|
||||
const programs = this.programs
|
||||
|
||||
const xmltv = new XMLTV({
|
||||
channels,
|
||||
programs,
|
||||
date: new DateTime(CURR_DATE, { zone: 'UTC' })
|
||||
})
|
||||
|
||||
const xmlFilepath = this.filepath
|
||||
this.logger.info(` saving to "${xmlFilepath}"...`)
|
||||
await this.storage.save(xmlFilepath, xmltv.toString())
|
||||
|
||||
if (this.gzip) {
|
||||
const zip = new Zip()
|
||||
const compressed = await zip.compress(xmltv.toString())
|
||||
const gzFilepath = `${this.filepath}.gz`
|
||||
this.logger.info(` saving to "${gzFilepath}"...`)
|
||||
await this.storage.save(gzFilepath, compressed)
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Collection, Logger, DateTime, Storage, Zip } from '@freearhey/core'
|
||||
import { Channel } from 'epg-grabber'
|
||||
import { XMLTV } from '../core'
|
||||
import { CURR_DATE } from '../constants'
|
||||
|
||||
type GuideProps = {
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
logger: Logger
|
||||
filepath: string
|
||||
gzip: boolean
|
||||
}
|
||||
|
||||
export class Guide {
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
logger: Logger
|
||||
storage: Storage
|
||||
filepath: string
|
||||
gzip: boolean
|
||||
|
||||
constructor({ channels, programs, logger, filepath, gzip }: GuideProps) {
|
||||
this.channels = channels
|
||||
this.programs = programs
|
||||
this.logger = logger
|
||||
this.storage = new Storage()
|
||||
this.filepath = filepath
|
||||
this.gzip = gzip || false
|
||||
}
|
||||
|
||||
async save() {
|
||||
const channels = this.channels.uniqBy(
|
||||
(channel: Channel) => `${channel.xmltv_id}:${channel.site}`
|
||||
)
|
||||
const programs = this.programs
|
||||
|
||||
const xmltv = new XMLTV({
|
||||
channels,
|
||||
programs,
|
||||
date: new DateTime(CURR_DATE, { zone: 'UTC' })
|
||||
})
|
||||
|
||||
const xmlFilepath = this.filepath
|
||||
this.logger.info(` saving to "${xmlFilepath}"...`)
|
||||
await this.storage.save(xmlFilepath, xmltv.toString())
|
||||
|
||||
if (this.gzip) {
|
||||
const zip = new Zip()
|
||||
const compressed = await zip.compress(xmltv.toString())
|
||||
const gzFilepath = `${this.filepath}.gz`
|
||||
this.logger.info(` saving to "${gzFilepath}"...`)
|
||||
await this.storage.save(gzFilepath, compressed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
import { Collection, Logger, Storage, StringTemplate } from '@freearhey/core'
|
||||
import { OptionValues } from 'commander'
|
||||
import { Channel, Program } from 'epg-grabber'
|
||||
import { Guide } from '.'
|
||||
|
||||
type GuideManagerProps = {
|
||||
options: OptionValues
|
||||
logger: Logger
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
}
|
||||
|
||||
export class GuideManager {
|
||||
options: OptionValues
|
||||
storage: Storage
|
||||
logger: Logger
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
|
||||
constructor({ channels, programs, logger, options }: GuideManagerProps) {
|
||||
this.options = options
|
||||
this.logger = logger
|
||||
this.channels = channels
|
||||
this.programs = programs
|
||||
this.storage = new Storage()
|
||||
}
|
||||
|
||||
async createGuides() {
|
||||
const pathTemplate = new StringTemplate(this.options.output)
|
||||
|
||||
const groupedChannels = this.channels
|
||||
.orderBy([(channel: Channel) => channel.xmltv_id])
|
||||
.uniqBy((channel: Channel) => `${channel.xmltv_id}:${channel.site}:${channel.lang}`)
|
||||
.groupBy((channel: Channel) => {
|
||||
return pathTemplate.format({ lang: channel.lang || 'en', site: channel.site || '' })
|
||||
})
|
||||
|
||||
const groupedPrograms = this.programs
|
||||
.orderBy([(program: Program) => program.channel, (program: Program) => program.start])
|
||||
.groupBy((program: Program) => {
|
||||
const lang =
|
||||
program.titles && program.titles.length && program.titles[0].lang
|
||||
? program.titles[0].lang
|
||||
: 'en'
|
||||
|
||||
return pathTemplate.format({ lang, site: program.site || '' })
|
||||
})
|
||||
|
||||
for (const groupKey of groupedPrograms.keys()) {
|
||||
const guide = new Guide({
|
||||
filepath: groupKey,
|
||||
gzip: this.options.gzip,
|
||||
channels: new Collection(groupedChannels.get(groupKey)),
|
||||
programs: new Collection(groupedPrograms.get(groupKey)),
|
||||
logger: this.logger
|
||||
})
|
||||
|
||||
await guide.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Collection, Logger, Storage, StringTemplate } from '@freearhey/core'
|
||||
import { OptionValues } from 'commander'
|
||||
import { Channel, Program } from 'epg-grabber'
|
||||
import { Guide } from '.'
|
||||
|
||||
type GuideManagerProps = {
|
||||
options: OptionValues
|
||||
logger: Logger
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
}
|
||||
|
||||
export class GuideManager {
|
||||
options: OptionValues
|
||||
storage: Storage
|
||||
logger: Logger
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
|
||||
constructor({ channels, programs, logger, options }: GuideManagerProps) {
|
||||
this.options = options
|
||||
this.logger = logger
|
||||
this.channels = channels
|
||||
this.programs = programs
|
||||
this.storage = new Storage()
|
||||
}
|
||||
|
||||
async createGuides() {
|
||||
const pathTemplate = new StringTemplate(this.options.output)
|
||||
|
||||
const groupedChannels = this.channels
|
||||
.orderBy([(channel: Channel) => channel.xmltv_id])
|
||||
.uniqBy((channel: Channel) => `${channel.xmltv_id}:${channel.site}:${channel.lang}`)
|
||||
.groupBy((channel: Channel) => {
|
||||
return pathTemplate.format({ lang: channel.lang || 'en', site: channel.site || '' })
|
||||
})
|
||||
|
||||
const groupedPrograms = this.programs
|
||||
.orderBy([(program: Program) => program.channel, (program: Program) => program.start])
|
||||
.groupBy((program: Program) => {
|
||||
const lang =
|
||||
program.titles && program.titles.length && program.titles[0].lang
|
||||
? program.titles[0].lang
|
||||
: 'en'
|
||||
|
||||
return pathTemplate.format({ lang, site: program.site || '' })
|
||||
})
|
||||
|
||||
for (const groupKey of groupedPrograms.keys()) {
|
||||
const guide = new Guide({
|
||||
filepath: groupKey,
|
||||
gzip: this.options.gzip,
|
||||
channels: new Collection(groupedChannels.get(groupKey)),
|
||||
programs: new Collection(groupedPrograms.get(groupKey)),
|
||||
logger: this.logger
|
||||
})
|
||||
|
||||
await guide.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
export * from './xml'
|
||||
export * from './channelsParser'
|
||||
export * from './xmltv'
|
||||
export * from './configLoader'
|
||||
export * from './grabber'
|
||||
export * from './job'
|
||||
export * from './queue'
|
||||
export * from './guideManager'
|
||||
export * from './guide'
|
||||
export * from './apiChannel'
|
||||
export * from './apiClient'
|
||||
export * from './queueCreator'
|
||||
export * from './xml'
|
||||
export * from './channelsParser'
|
||||
export * from './xmltv'
|
||||
export * from './configLoader'
|
||||
export * from './grabber'
|
||||
export * from './job'
|
||||
export * from './queue'
|
||||
export * from './guideManager'
|
||||
export * from './guide'
|
||||
export * from './apiChannel'
|
||||
export * from './apiClient'
|
||||
export * from './queueCreator'
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
import { Logger } from '@freearhey/core'
|
||||
import { Queue, Grabber, GuideManager } from '.'
|
||||
import { GrabOptions } from '../commands/epg/grab'
|
||||
|
||||
type JobProps = {
|
||||
options: GrabOptions
|
||||
logger: Logger
|
||||
queue: Queue
|
||||
}
|
||||
|
||||
export class Job {
|
||||
options: GrabOptions
|
||||
logger: Logger
|
||||
grabber: Grabber
|
||||
|
||||
constructor({ queue, logger, options }: JobProps) {
|
||||
this.options = options
|
||||
this.logger = logger
|
||||
this.grabber = new Grabber({ logger, queue, options })
|
||||
}
|
||||
|
||||
async run() {
|
||||
const { channels, programs } = await this.grabber.grab()
|
||||
|
||||
const manager = new GuideManager({
|
||||
channels,
|
||||
programs,
|
||||
options: this.options,
|
||||
logger: this.logger
|
||||
})
|
||||
|
||||
await manager.createGuides()
|
||||
}
|
||||
}
|
||||
import { Logger } from '@freearhey/core'
|
||||
import { Queue, Grabber, GuideManager } from '.'
|
||||
import { GrabOptions } from '../commands/epg/grab'
|
||||
|
||||
type JobProps = {
|
||||
options: GrabOptions
|
||||
logger: Logger
|
||||
queue: Queue
|
||||
}
|
||||
|
||||
export class Job {
|
||||
options: GrabOptions
|
||||
logger: Logger
|
||||
grabber: Grabber
|
||||
|
||||
constructor({ queue, logger, options }: JobProps) {
|
||||
this.options = options
|
||||
this.logger = logger
|
||||
this.grabber = new Grabber({ logger, queue, options })
|
||||
}
|
||||
|
||||
async run() {
|
||||
const { channels, programs } = await this.grabber.grab()
|
||||
|
||||
const manager = new GuideManager({
|
||||
channels,
|
||||
programs,
|
||||
options: this.options,
|
||||
logger: this.logger
|
||||
})
|
||||
|
||||
await manager.createGuides()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
import { Dictionary } from '@freearhey/core'
|
||||
import { SiteConfig, Channel } from 'epg-grabber'
|
||||
|
||||
export type QueueItem = {
|
||||
channel: Channel
|
||||
date: string
|
||||
config: SiteConfig
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export class Queue {
|
||||
_data: Dictionary
|
||||
|
||||
constructor() {
|
||||
this._data = new Dictionary()
|
||||
}
|
||||
|
||||
missing(key: string): boolean {
|
||||
return this._data.missing(key)
|
||||
}
|
||||
|
||||
add(
|
||||
key: string,
|
||||
{ channel, config, date }: { channel: Channel; date: string | null; config: SiteConfig }
|
||||
) {
|
||||
this._data.set(key, {
|
||||
channel,
|
||||
date,
|
||||
config,
|
||||
error: null
|
||||
})
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return Object.values(this._data.data()).length
|
||||
}
|
||||
|
||||
items(): QueueItem[] {
|
||||
return Object.values(this._data.data()) as QueueItem[]
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.size() === 0
|
||||
}
|
||||
}
|
||||
import { Dictionary } from '@freearhey/core'
|
||||
import { SiteConfig, Channel } from 'epg-grabber'
|
||||
|
||||
export type QueueItem = {
|
||||
channel: Channel
|
||||
date: string
|
||||
config: SiteConfig
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export class Queue {
|
||||
_data: Dictionary
|
||||
|
||||
constructor() {
|
||||
this._data = new Dictionary()
|
||||
}
|
||||
|
||||
missing(key: string): boolean {
|
||||
return this._data.missing(key)
|
||||
}
|
||||
|
||||
add(
|
||||
key: string,
|
||||
{ channel, config, date }: { channel: Channel; date: string | null; config: SiteConfig }
|
||||
) {
|
||||
this._data.set(key, {
|
||||
channel,
|
||||
date,
|
||||
config,
|
||||
error: null
|
||||
})
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return Object.values(this._data.data()).length
|
||||
}
|
||||
|
||||
items(): QueueItem[] {
|
||||
return Object.values(this._data.data()) as QueueItem[]
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.size() === 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +1,71 @@
|
|||
import { Storage, Collection, DateTime, Logger } from '@freearhey/core'
|
||||
import { ChannelsParser, ConfigLoader, ApiChannel, Queue } from './'
|
||||
import { SITES_DIR, DATA_DIR, CURR_DATE } from '../constants'
|
||||
import { SiteConfig } from 'epg-grabber'
|
||||
import path from 'path'
|
||||
import { GrabOptions } from '../commands/epg/grab'
|
||||
|
||||
type QueueCreatorProps = {
|
||||
logger: Logger
|
||||
options: GrabOptions
|
||||
parsedChannels: Collection
|
||||
}
|
||||
|
||||
export class QueueCreator {
|
||||
configLoader: ConfigLoader
|
||||
logger: Logger
|
||||
sitesStorage: Storage
|
||||
dataStorage: Storage
|
||||
parser: ChannelsParser
|
||||
parsedChannels: Collection
|
||||
options: GrabOptions
|
||||
date: DateTime
|
||||
|
||||
constructor({ parsedChannels, logger, options }: QueueCreatorProps) {
|
||||
this.parsedChannels = parsedChannels
|
||||
this.logger = logger
|
||||
this.sitesStorage = new Storage()
|
||||
this.dataStorage = new Storage(DATA_DIR)
|
||||
this.parser = new ChannelsParser({ storage: new Storage() })
|
||||
this.date = new DateTime(CURR_DATE)
|
||||
this.options = options
|
||||
this.configLoader = new ConfigLoader()
|
||||
}
|
||||
|
||||
async create(): Promise<Queue> {
|
||||
const channelsContent = await this.dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
||||
|
||||
const queue = new Queue()
|
||||
for (const channel of this.parsedChannels.all()) {
|
||||
if (!channel.site || !channel.xmltv_id) continue
|
||||
if (this.options.lang && channel.lang !== this.options.lang) continue
|
||||
|
||||
const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`)
|
||||
const config: SiteConfig = await this.configLoader.load(configPath)
|
||||
|
||||
const found: ApiChannel = channels.first(
|
||||
(_channel: ApiChannel) => _channel.id === channel.xmltv_id
|
||||
)
|
||||
if (found) {
|
||||
channel.logo = found.logo
|
||||
}
|
||||
|
||||
const days = this.options.days || config.days || 1
|
||||
const dates = Array.from({ length: days }, (_, day) => this.date.add(day, 'd'))
|
||||
dates.forEach((date: DateTime) => {
|
||||
const dateString = date.toJSON()
|
||||
const key = `${channel.site}:${channel.lang}:${channel.xmltv_id}:${dateString}`
|
||||
if (queue.missing(key)) {
|
||||
queue.add(key, {
|
||||
channel,
|
||||
date: dateString,
|
||||
config
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return queue
|
||||
}
|
||||
}
|
||||
import { Storage, Collection, DateTime, Logger } from '@freearhey/core'
|
||||
import { ChannelsParser, ConfigLoader, ApiChannel, Queue } from './'
|
||||
import { SITES_DIR, DATA_DIR, CURR_DATE } from '../constants'
|
||||
import { SiteConfig } from 'epg-grabber'
|
||||
import path from 'path'
|
||||
import { GrabOptions } from '../commands/epg/grab'
|
||||
|
||||
type QueueCreatorProps = {
|
||||
logger: Logger
|
||||
options: GrabOptions
|
||||
parsedChannels: Collection
|
||||
}
|
||||
|
||||
export class QueueCreator {
|
||||
configLoader: ConfigLoader
|
||||
logger: Logger
|
||||
sitesStorage: Storage
|
||||
dataStorage: Storage
|
||||
parser: ChannelsParser
|
||||
parsedChannels: Collection
|
||||
options: GrabOptions
|
||||
date: DateTime
|
||||
|
||||
constructor({ parsedChannels, logger, options }: QueueCreatorProps) {
|
||||
this.parsedChannels = parsedChannels
|
||||
this.logger = logger
|
||||
this.sitesStorage = new Storage()
|
||||
this.dataStorage = new Storage(DATA_DIR)
|
||||
this.parser = new ChannelsParser({ storage: new Storage() })
|
||||
this.date = new DateTime(CURR_DATE)
|
||||
this.options = options
|
||||
this.configLoader = new ConfigLoader()
|
||||
}
|
||||
|
||||
async create(): Promise<Queue> {
|
||||
const channelsContent = await this.dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
|
||||
|
||||
const queue = new Queue()
|
||||
for (const channel of this.parsedChannels.all()) {
|
||||
if (!channel.site || !channel.xmltv_id) continue
|
||||
if (this.options.lang && channel.lang !== this.options.lang) continue
|
||||
|
||||
const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`)
|
||||
const config: SiteConfig = await this.configLoader.load(configPath)
|
||||
|
||||
const found: ApiChannel = channels.first(
|
||||
(_channel: ApiChannel) => _channel.id === channel.xmltv_id
|
||||
)
|
||||
if (found) {
|
||||
channel.logo = found.logo
|
||||
}
|
||||
|
||||
const days = this.options.days || config.days || 1
|
||||
const dates = Array.from({ length: days }, (_, day) => this.date.add(day, 'd'))
|
||||
dates.forEach((date: DateTime) => {
|
||||
const dateString = date.toJSON()
|
||||
const key = `${channel.site}:${channel.lang}:${channel.xmltv_id}:${dateString}`
|
||||
if (queue.missing(key)) {
|
||||
queue.add(key, {
|
||||
channel,
|
||||
date: dateString,
|
||||
config
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return queue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
import { Collection } from '@freearhey/core'
|
||||
import { Channel } from 'epg-grabber'
|
||||
|
||||
export class XML {
|
||||
items: Collection
|
||||
|
||||
constructor(items: Collection) {
|
||||
this.items = items
|
||||
}
|
||||
|
||||
toString() {
|
||||
let output = '<?xml version="1.0" encoding="UTF-8"?>\r\n<channels>\r\n'
|
||||
|
||||
this.items.forEach((channel: Channel) => {
|
||||
const logo = channel.logo ? ` logo="${channel.logo}"` : ''
|
||||
const xmltv_id = channel.xmltv_id || ''
|
||||
const lang = channel.lang || ''
|
||||
const site_id = channel.site_id || ''
|
||||
output += ` <channel site="${channel.site}" lang="${lang}" xmltv_id="${escapeString(
|
||||
xmltv_id
|
||||
)}" site_id="${site_id}"${logo}>${escapeString(channel.name)}</channel>\r\n`
|
||||
})
|
||||
|
||||
output += '</channels>\r\n'
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
function escapeString(value: string, defaultValue: string = '') {
|
||||
if (!value) return defaultValue
|
||||
|
||||
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' +
|
||||
'FE\\uDFFF])|(?:\\uD8BF[\\uDFFE\\uDFFF])|(?:\\uD8FF[\\uDFFE\\uDFFF])|(?:\\uD93F[\\uDFFE\\uD' +
|
||||
'FFF])|(?:\\uD97F[\\uDFFE\\uDFFF])|(?:\\uD9BF[\\uDFFE\\uDFFF])|(?:\\uD9FF[\\uDFFE\\uDFFF])' +
|
||||
'|(?:\\uDA3F[\\uDFFE\\uDFFF])|(?:\\uDA7F[\\uDFFE\\uDFFF])|(?:\\uDABF[\\uDFFE\\uDFFF])|(?:\\' +
|
||||
'uDAFF[\\uDFFE\\uDFFF])|(?:\\uDB3F[\\uDFFE\\uDFFF])|(?:\\uDB7F[\\uDFFE\\uDFFF])|(?:\\uDBBF' +
|
||||
'[\\uDFFE\\uDFFF])|(?:\\uDBFF[\\uDFFE\\uDFFF])(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\' +
|
||||
'uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|' +
|
||||
'(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))',
|
||||
'g'
|
||||
)
|
||||
|
||||
value = String(value || '').replace(regex, '')
|
||||
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\n|\r/g, ' ')
|
||||
.replace(/ +/g, ' ')
|
||||
.trim()
|
||||
}
|
||||
import { Collection } from '@freearhey/core'
|
||||
import { Channel } from 'epg-grabber'
|
||||
|
||||
export class XML {
|
||||
items: Collection
|
||||
|
||||
constructor(items: Collection) {
|
||||
this.items = items
|
||||
}
|
||||
|
||||
toString() {
|
||||
let output = '<?xml version="1.0" encoding="UTF-8"?>\r\n<channels>\r\n'
|
||||
|
||||
this.items.forEach((channel: Channel) => {
|
||||
const logo = channel.logo ? ` logo="${channel.logo}"` : ''
|
||||
const xmltv_id = channel.xmltv_id || ''
|
||||
const lang = channel.lang || ''
|
||||
const site_id = channel.site_id || ''
|
||||
output += ` <channel site="${channel.site}" lang="${lang}" xmltv_id="${escapeString(
|
||||
xmltv_id
|
||||
)}" site_id="${site_id}"${logo}>${escapeString(channel.name)}</channel>\r\n`
|
||||
})
|
||||
|
||||
output += '</channels>\r\n'
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
function escapeString(value: string, defaultValue: string = '') {
|
||||
if (!value) return defaultValue
|
||||
|
||||
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' +
|
||||
'FE\\uDFFF])|(?:\\uD8BF[\\uDFFE\\uDFFF])|(?:\\uD8FF[\\uDFFE\\uDFFF])|(?:\\uD93F[\\uDFFE\\uD' +
|
||||
'FFF])|(?:\\uD97F[\\uDFFE\\uDFFF])|(?:\\uD9BF[\\uDFFE\\uDFFF])|(?:\\uD9FF[\\uDFFE\\uDFFF])' +
|
||||
'|(?:\\uDA3F[\\uDFFE\\uDFFF])|(?:\\uDA7F[\\uDFFE\\uDFFF])|(?:\\uDABF[\\uDFFE\\uDFFF])|(?:\\' +
|
||||
'uDAFF[\\uDFFE\\uDFFF])|(?:\\uDB3F[\\uDFFE\\uDFFF])|(?:\\uDB7F[\\uDFFE\\uDFFF])|(?:\\uDBBF' +
|
||||
'[\\uDFFE\\uDFFF])|(?:\\uDBFF[\\uDFFE\\uDFFF])(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\' +
|
||||
'uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|' +
|
||||
'(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))',
|
||||
'g'
|
||||
)
|
||||
|
||||
value = String(value || '').replace(regex, '')
|
||||
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\n|\r/g, ' ')
|
||||
.replace(/ +/g, ' ')
|
||||
.trim()
|
||||
}
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
import { DateTime, Collection } from '@freearhey/core'
|
||||
import { generateXMLTV } from 'epg-grabber'
|
||||
|
||||
type XMLTVProps = {
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
date: DateTime
|
||||
}
|
||||
|
||||
export class XMLTV {
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
date: DateTime
|
||||
|
||||
constructor({ channels, programs, date }: XMLTVProps) {
|
||||
this.channels = channels
|
||||
this.programs = programs
|
||||
this.date = date
|
||||
}
|
||||
|
||||
toString() {
|
||||
return generateXMLTV({
|
||||
channels: this.channels.all(),
|
||||
programs: this.programs.all(),
|
||||
date: this.date.toJSON()
|
||||
})
|
||||
}
|
||||
}
|
||||
import { DateTime, Collection } from '@freearhey/core'
|
||||
import { generateXMLTV } from 'epg-grabber'
|
||||
|
||||
type XMLTVProps = {
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
date: DateTime
|
||||
}
|
||||
|
||||
export class XMLTV {
|
||||
channels: Collection
|
||||
programs: Collection
|
||||
date: DateTime
|
||||
|
||||
constructor({ channels, programs, date }: XMLTVProps) {
|
||||
this.channels = channels
|
||||
this.programs = programs
|
||||
this.date = date
|
||||
}
|
||||
|
||||
toString() {
|
||||
return generateXMLTV({
|
||||
channels: this.channels.all(),
|
||||
programs: this.programs.all(),
|
||||
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 dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: '9tv.co.il',
|
||||
days: 2,
|
||||
url: function ({ date }) {
|
||||
return `https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=${date.format(
|
||||
'DD/MM/YYYY 00:00:00'
|
||||
)}`
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
const $item = cheerio.load(item)
|
||||
const start = parseStart($item, date)
|
||||
if (prev) prev.stop = start
|
||||
const stop = start.add(1, 'h')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
icon: parseIcon($item),
|
||||
description: parseDescription($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
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')
|
||||
}
|
||||
|
||||
function parseIcon($item) {
|
||||
const backgroundImage = $item('a > div.guide_info_group > div.guide_info_pict').css(
|
||||
'background-image'
|
||||
)
|
||||
if (!backgroundImage) return null
|
||||
const [, relativePath] = backgroundImage.match(/url\((.*)\)/) || [null, null]
|
||||
|
||||
return relativePath ? `https://www.9tv.co.il${relativePath}` : null
|
||||
}
|
||||
|
||||
function parseDescription($item) {
|
||||
return $item('a > div.guide_info_group > div.guide_txt_group > div').text().trim()
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('a > div.guide_info_group > div.guide_txt_group > h3').text().trim()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('li').toArray()
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: '9tv.co.il',
|
||||
days: 2,
|
||||
url: function ({ date }) {
|
||||
return `https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=${date.format(
|
||||
'DD/MM/YYYY 00:00:00'
|
||||
)}`
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
const $item = cheerio.load(item)
|
||||
const start = parseStart($item, date)
|
||||
if (prev) prev.stop = start
|
||||
const stop = start.add(1, 'h')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
icon: parseIcon($item),
|
||||
description: parseDescription($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
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')
|
||||
}
|
||||
|
||||
function parseIcon($item) {
|
||||
const backgroundImage = $item('a > div.guide_info_group > div.guide_info_pict').css(
|
||||
'background-image'
|
||||
)
|
||||
if (!backgroundImage) return null
|
||||
const [, relativePath] = backgroundImage.match(/url\((.*)\)/) || [null, null]
|
||||
|
||||
return relativePath ? `https://www.9tv.co.il${relativePath}` : null
|
||||
}
|
||||
|
||||
function parseDescription($item) {
|
||||
return $item('a > div.guide_info_group > div.guide_txt_group > div').text().trim()
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('a > div.guide_info_group > div.guide_txt_group > h3').text().trim()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('li').toArray()
|
||||
}
|
||||
|
|
|
@ -1,57 +1,57 @@
|
|||
// npm run grab -- --site=9tv.co.il
|
||||
|
||||
const { parser, url } = require('./9tv.co.il.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '#',
|
||||
xmltv_id: 'Channel9.il'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date })).toBe(
|
||||
'https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=06/03/2022 00:00:00'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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>'
|
||||
const result = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-03-06T04:30:00.000Z',
|
||||
stop: '2022-03-06T07:10:00.000Z',
|
||||
title: 'Слепая',
|
||||
icon: 'https://www.9tv.co.il/download/pictures/img_id=8484.jpg',
|
||||
description:
|
||||
'Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы.'
|
||||
},
|
||||
{
|
||||
start: '2022-03-06T07:10:00.000Z',
|
||||
stop: '2022-03-06T08:10:00.000Z',
|
||||
icon: 'https://www.9tv.co.il/download/pictures/img_id=23694.jpg',
|
||||
title: 'Орел и решка. Морской сезон',
|
||||
description: 'Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=9tv.co.il
|
||||
|
||||
const { parser, url } = require('./9tv.co.il.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '#',
|
||||
xmltv_id: 'Channel9.il'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date })).toBe(
|
||||
'https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=06/03/2022 00:00:00'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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>'
|
||||
const result = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-03-06T04:30:00.000Z',
|
||||
stop: '2022-03-06T07:10:00.000Z',
|
||||
title: 'Слепая',
|
||||
icon: 'https://www.9tv.co.il/download/pictures/img_id=8484.jpg',
|
||||
description:
|
||||
'Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы.'
|
||||
},
|
||||
{
|
||||
start: '2022-03-06T07:10:00.000Z',
|
||||
stop: '2022-03-06T08:10:00.000Z',
|
||||
icon: 'https://www.9tv.co.il/download/pictures/img_id=23694.jpg',
|
||||
title: 'Орел и решка. Морской сезон',
|
||||
description: 'Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,77 +1,77 @@
|
|||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'abc.net.au',
|
||||
days: 3,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url({ date }) {
|
||||
return `https://epg.abctv.net.au/processed/Sydney_${date.format('YYYY-MM-DD')}.json`
|
||||
},
|
||||
parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.title,
|
||||
sub_title: item.episode_title,
|
||||
category: item.genres,
|
||||
description: item.description,
|
||||
season: parseSeason(item),
|
||||
episode: parseEpisode(item),
|
||||
rating: parseRating(item),
|
||||
icon: parseIcon(item),
|
||||
start: parseTime(item.start_time),
|
||||
stop: parseTime(item.end_time)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
try {
|
||||
const data = JSON.parse(content)
|
||||
if (!data) return []
|
||||
if (!Array.isArray(data.schedule)) return []
|
||||
|
||||
const channelData = data.schedule.find(i => i.channel == channel.site_id)
|
||||
return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : []
|
||||
} catch (err) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function parseSeason(item) {
|
||||
return item.series_num || null
|
||||
}
|
||||
function parseEpisode(item) {
|
||||
return item.episode_num || null
|
||||
}
|
||||
function parseTime(time) {
|
||||
return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'Australia/Sydney')
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.image_file
|
||||
? `https://www.abc.net.au/tv/common/images/publicity/${item.image_file}`
|
||||
: null
|
||||
}
|
||||
function parseRating(item) {
|
||||
return item.rating
|
||||
? {
|
||||
system: 'ACB',
|
||||
value: item.rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'abc.net.au',
|
||||
days: 3,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url({ date }) {
|
||||
return `https://epg.abctv.net.au/processed/Sydney_${date.format('YYYY-MM-DD')}.json`
|
||||
},
|
||||
parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.title,
|
||||
sub_title: item.episode_title,
|
||||
category: item.genres,
|
||||
description: item.description,
|
||||
season: parseSeason(item),
|
||||
episode: parseEpisode(item),
|
||||
rating: parseRating(item),
|
||||
icon: parseIcon(item),
|
||||
start: parseTime(item.start_time),
|
||||
stop: parseTime(item.end_time)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
try {
|
||||
const data = JSON.parse(content)
|
||||
if (!data) return []
|
||||
if (!Array.isArray(data.schedule)) return []
|
||||
|
||||
const channelData = data.schedule.find(i => i.channel == channel.site_id)
|
||||
return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : []
|
||||
} catch (err) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function parseSeason(item) {
|
||||
return item.series_num || null
|
||||
}
|
||||
function parseEpisode(item) {
|
||||
return item.episode_num || null
|
||||
}
|
||||
function parseTime(time) {
|
||||
return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'Australia/Sydney')
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.image_file
|
||||
? `https://www.abc.net.au/tv/common/images/publicity/${item.image_file}`
|
||||
: null
|
||||
}
|
||||
function parseRating(item) {
|
||||
return item.rating
|
||||
? {
|
||||
system: 'ACB',
|
||||
value: item.rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
// npm run grab -- --site=abc.net.au
|
||||
|
||||
const { parser, url } = require('./abc.net.au.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-12-22', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'ABC1',
|
||||
xmltv_id: 'ABCTV.au'
|
||||
}
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date })).toBe('https://epg.abctv.net.au/processed/Sydney_2022-12-22.json')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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}]}]}'
|
||||
|
||||
const result = parser({ content, channel }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
title: 'Silent Witness',
|
||||
sub_title: 'Lift Up Your Hearts (part Two)',
|
||||
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?',
|
||||
category: ['Entertainment'],
|
||||
rating: {
|
||||
system: 'ACB',
|
||||
value: 'M'
|
||||
},
|
||||
season: 22,
|
||||
episode: 4,
|
||||
icon: 'https://www.abc.net.au/tv/common/images/publicity/ZW2178A004S00_460.jpg',
|
||||
start: '2022-12-21T13:46:00.000Z',
|
||||
stop: '2022-12-21T14:44:00.000Z'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser(
|
||||
{
|
||||
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>'
|
||||
},
|
||||
channel
|
||||
)
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=abc.net.au
|
||||
|
||||
const { parser, url } = require('./abc.net.au.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-12-22', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'ABC1',
|
||||
xmltv_id: 'ABCTV.au'
|
||||
}
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date })).toBe('https://epg.abctv.net.au/processed/Sydney_2022-12-22.json')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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}]}]}'
|
||||
|
||||
const result = parser({ content, channel }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
title: 'Silent Witness',
|
||||
sub_title: 'Lift Up Your Hearts (part Two)',
|
||||
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?',
|
||||
category: ['Entertainment'],
|
||||
rating: {
|
||||
system: 'ACB',
|
||||
value: 'M'
|
||||
},
|
||||
season: 22,
|
||||
episode: 4,
|
||||
icon: 'https://www.abc.net.au/tv/common/images/publicity/ZW2178A004S00_460.jpg',
|
||||
start: '2022-12-21T13:46:00.000Z',
|
||||
stop: '2022-12-21T14:44:00.000Z'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser(
|
||||
{
|
||||
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>'
|
||||
},
|
||||
channel
|
||||
)
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,63 +1,63 @@
|
|||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'allente.se',
|
||||
days: 2,
|
||||
url({ date, channel }) {
|
||||
const [country] = channel.site_id.split('#')
|
||||
|
||||
return `https://cs-vcb.allente.${country}/epg/events?date=${date.format('YYYY-MM-DD')}`
|
||||
},
|
||||
parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
if (!item.details) return
|
||||
const start = dayjs(item.time)
|
||||
const stop = start.add(item.details.duration, 'm')
|
||||
programs.push({
|
||||
title: item.title,
|
||||
category: item.details.categories,
|
||||
description: item.details.description,
|
||||
icon: item.details.image,
|
||||
season: parseSeason(item),
|
||||
episode: parseEpisode(item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ country, lang }) {
|
||||
const data = await axios
|
||||
.get(`https://cs-vcb.allente.${country}/epg/events?date=2021-11-17`)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return data.channels.map(item => {
|
||||
return {
|
||||
lang,
|
||||
site_id: `${country}#${item.id}`,
|
||||
name: item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.channels)) return []
|
||||
const channelData = data.channels.find(i => i.id === channelId)
|
||||
|
||||
return channelData && Array.isArray(channelData.events) ? channelData.events : []
|
||||
}
|
||||
|
||||
function parseSeason(item) {
|
||||
return item.details.season || null
|
||||
}
|
||||
function parseEpisode(item) {
|
||||
return item.details.episode || null
|
||||
}
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'allente.se',
|
||||
days: 2,
|
||||
url({ date, channel }) {
|
||||
const [country] = channel.site_id.split('#')
|
||||
|
||||
return `https://cs-vcb.allente.${country}/epg/events?date=${date.format('YYYY-MM-DD')}`
|
||||
},
|
||||
parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
if (!item.details) return
|
||||
const start = dayjs(item.time)
|
||||
const stop = start.add(item.details.duration, 'm')
|
||||
programs.push({
|
||||
title: item.title,
|
||||
category: item.details.categories,
|
||||
description: item.details.description,
|
||||
icon: item.details.image,
|
||||
season: parseSeason(item),
|
||||
episode: parseEpisode(item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ country, lang }) {
|
||||
const data = await axios
|
||||
.get(`https://cs-vcb.allente.${country}/epg/events?date=2021-11-17`)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return data.channels.map(item => {
|
||||
return {
|
||||
lang,
|
||||
site_id: `${country}#${item.id}`,
|
||||
name: item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.channels)) return []
|
||||
const channelData = data.channels.find(i => i.id === channelId)
|
||||
|
||||
return channelData && Array.isArray(channelData.events) ? channelData.events : []
|
||||
}
|
||||
|
||||
function parseSeason(item) {
|
||||
return item.details.season || null
|
||||
}
|
||||
function parseEpisode(item) {
|
||||
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_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_dk.channels.xml --set=country:dk --set=lang:da
|
||||
// npm run grab -- --site=allente.se
|
||||
|
||||
const { parser, url } = require('./allente.se.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'se#0148',
|
||||
xmltv_id: 'SVT1.se'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date, channel })).toBe('https://cs-vcb.allente.se/epg/events?date=2021-11-17')
|
||||
})
|
||||
|
||||
it('can generate valid url for different country', () => {
|
||||
const dkChannel = { site_id: 'dk#0148' }
|
||||
expect(url({ date, channel: dkChannel })).toBe(
|
||||
'https://cs-vcb.allente.dk/epg/events?date=2021-11-17'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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"}}]}]}'
|
||||
const result = parser({ content, channel }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-08-22T07:10:00.000Z',
|
||||
stop: '2022-08-22T07:30:00.000Z',
|
||||
title: 'Hemmagympa med Sofia',
|
||||
category: ['other'],
|
||||
description:
|
||||
'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',
|
||||
season: 4,
|
||||
episode: 1
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: '{"date":"2001-11-17","categories":[],"channels":[]}'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// 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_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 grab -- --site=allente.se
|
||||
|
||||
const { parser, url } = require('./allente.se.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'se#0148',
|
||||
xmltv_id: 'SVT1.se'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date, channel })).toBe('https://cs-vcb.allente.se/epg/events?date=2021-11-17')
|
||||
})
|
||||
|
||||
it('can generate valid url for different country', () => {
|
||||
const dkChannel = { site_id: 'dk#0148' }
|
||||
expect(url({ date, channel: dkChannel })).toBe(
|
||||
'https://cs-vcb.allente.dk/epg/events?date=2021-11-17'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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"}}]}]}'
|
||||
const result = parser({ content, channel }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-08-22T07:10:00.000Z',
|
||||
stop: '2022-08-22T07:30:00.000Z',
|
||||
title: 'Hemmagympa med Sofia',
|
||||
category: ['other'],
|
||||
description:
|
||||
'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',
|
||||
season: 4,
|
||||
episode: 1
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: '{"date":"2001-11-17","categories":[],"channels":[]}'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
const cheerio = require('cheerio')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'andorradifusio.ad',
|
||||
days: 2,
|
||||
url({ channel }) {
|
||||
return `https://www.andorradifusio.ad/programacio/${channel.site_id}`
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart(item, date)
|
||||
if (prev) {
|
||||
if (start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.plus({ hours: 1 })
|
||||
programs.push({
|
||||
title: item.title,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
const dateString = `${date.format('MM/DD/YYYY')} ${item.time}`
|
||||
|
||||
return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH:mm', { zone: 'Europe/Madrid' }).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
const day = DateTime.fromMillis(date.valueOf()).setLocale('ca').toFormat('dd LLLL').toLowerCase()
|
||||
const column = $('.programacio-dia > h3 > .dia')
|
||||
.filter((i, el) => $(el).text() === day.slice(0, 6) + '.')
|
||||
.first()
|
||||
.parent()
|
||||
.parent()
|
||||
const items = []
|
||||
const titles = column.find('p').toArray()
|
||||
column.find('h4').each((i, time) => {
|
||||
items.push({
|
||||
time: $(time).text(),
|
||||
title: $(titles[i]).text()
|
||||
})
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'andorradifusio.ad',
|
||||
days: 2,
|
||||
url({ channel }) {
|
||||
return `https://www.andorradifusio.ad/programacio/${channel.site_id}`
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart(item, date)
|
||||
if (prev) {
|
||||
if (start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.plus({ hours: 1 })
|
||||
programs.push({
|
||||
title: item.title,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
const dateString = `${date.format('MM/DD/YYYY')} ${item.time}`
|
||||
|
||||
return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH:mm', { zone: 'Europe/Madrid' }).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
const day = DateTime.fromMillis(date.valueOf()).setLocale('ca').toFormat('dd LLLL').toLowerCase()
|
||||
const column = $('.programacio-dia > h3 > .dia')
|
||||
.filter((i, el) => $(el).text() === day.slice(0, 6) + '.')
|
||||
.first()
|
||||
.parent()
|
||||
.parent()
|
||||
const items = []
|
||||
const titles = column.find('p').toArray()
|
||||
column.find('h4').each((i, time) => {
|
||||
items.push({
|
||||
time: $(time).text(),
|
||||
title: $(titles[i]).text()
|
||||
})
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
// npm run grab -- --site=andorradifusio.ad
|
||||
|
||||
const { parser, url } = require('./andorradifusio.ad.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-06-07', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'atv',
|
||||
xmltv_id: 'AndorraTV.ad'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('https://www.andorradifusio.ad/programacio/atv')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||
const results = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-07T05:00:00.000Z',
|
||||
stop: '2023-06-07T06:00:00.000Z',
|
||||
title: 'Club Piolet'
|
||||
})
|
||||
|
||||
expect(results[20]).toMatchObject({
|
||||
start: '2023-06-07T23:00:00.000Z',
|
||||
stop: '2023-06-08T00:00:00.000Z',
|
||||
title: 'Àrea Andorra Difusió'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=andorradifusio.ad
|
||||
|
||||
const { parser, url } = require('./andorradifusio.ad.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-06-07', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'atv',
|
||||
xmltv_id: 'AndorraTV.ad'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('https://www.andorradifusio.ad/programacio/atv')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||
const results = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-07T05:00:00.000Z',
|
||||
stop: '2023-06-07T06:00:00.000Z',
|
||||
title: 'Club Piolet'
|
||||
})
|
||||
|
||||
expect(results[20]).toMatchObject({
|
||||
start: '2023-06-07T23:00:00.000Z',
|
||||
stop: '2023-06-08T00:00:00.000Z',
|
||||
title: 'Àrea Andorra Difusió'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,82 +1,82 @@
|
|||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'arianaafgtv.com',
|
||||
days: 2,
|
||||
url: 'https://www.arianaafgtv.com/index.html',
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const title = item.title
|
||||
const start = parseStart(item, date)
|
||||
const stop = parseStop(item, date)
|
||||
programs.push({
|
||||
title,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStop(item, date) {
|
||||
const time = `${date.format('MM/DD/YYYY')} ${item.end.toUpperCase()}`
|
||||
|
||||
return dayjs.tz(time, 'MM/DD/YYYY hh:mm A', 'Asia/Kabul')
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
const time = `${date.format('MM/DD/YYYY')} ${item.start.toUpperCase()}`
|
||||
|
||||
return dayjs.tz(time, 'MM/DD/YYYY hh:mm A', 'Asia/Kabul')
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
const dayOfWeek = date.format('dddd')
|
||||
const column = $('.H4')
|
||||
.filter((i, el) => {
|
||||
return $(el).text() === dayOfWeek
|
||||
})
|
||||
.first()
|
||||
.parent()
|
||||
|
||||
const rows = column
|
||||
.find('.Paragraph')
|
||||
.map((i, el) => {
|
||||
return $(el).html()
|
||||
})
|
||||
.toArray()
|
||||
.map(r => (r === ' ' ? '|' : r))
|
||||
.join(' ')
|
||||
.split('|')
|
||||
|
||||
const items = []
|
||||
rows.forEach(row => {
|
||||
row = row.trim()
|
||||
if (row) {
|
||||
const found = row.match(/(\d+(|:\d+)(a|p)m-\d+(|:\d+)(a|p)m)/gi)
|
||||
if (!found) return
|
||||
const time = found[0]
|
||||
let start = time.match(/(\d+(|:\d+)(a|p)m)-/i)[1]
|
||||
start = dayjs(start.toUpperCase(), ['hh:mmA', 'h:mmA', 'hA']).format('hh:mm A')
|
||||
let end = time.match(/-(\d+(|:\d+)(a|p)m)/i)[1]
|
||||
end = dayjs(end.toUpperCase(), ['hh:mmA', 'h:mmA', 'hA']).format('hh:mm A')
|
||||
const title = row.replace(time, '').replace(' ', '').trim()
|
||||
items.push({ start, end, title })
|
||||
}
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'arianaafgtv.com',
|
||||
days: 2,
|
||||
url: 'https://www.arianaafgtv.com/index.html',
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const title = item.title
|
||||
const start = parseStart(item, date)
|
||||
const stop = parseStop(item, date)
|
||||
programs.push({
|
||||
title,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStop(item, date) {
|
||||
const time = `${date.format('MM/DD/YYYY')} ${item.end.toUpperCase()}`
|
||||
|
||||
return dayjs.tz(time, 'MM/DD/YYYY hh:mm A', 'Asia/Kabul')
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
const time = `${date.format('MM/DD/YYYY')} ${item.start.toUpperCase()}`
|
||||
|
||||
return dayjs.tz(time, 'MM/DD/YYYY hh:mm A', 'Asia/Kabul')
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
const dayOfWeek = date.format('dddd')
|
||||
const column = $('.H4')
|
||||
.filter((i, el) => {
|
||||
return $(el).text() === dayOfWeek
|
||||
})
|
||||
.first()
|
||||
.parent()
|
||||
|
||||
const rows = column
|
||||
.find('.Paragraph')
|
||||
.map((i, el) => {
|
||||
return $(el).html()
|
||||
})
|
||||
.toArray()
|
||||
.map(r => (r === ' ' ? '|' : r))
|
||||
.join(' ')
|
||||
.split('|')
|
||||
|
||||
const items = []
|
||||
rows.forEach(row => {
|
||||
row = row.trim()
|
||||
if (row) {
|
||||
const found = row.match(/(\d+(|:\d+)(a|p)m-\d+(|:\d+)(a|p)m)/gi)
|
||||
if (!found) return
|
||||
const time = found[0]
|
||||
let start = time.match(/(\d+(|:\d+)(a|p)m)-/i)[1]
|
||||
start = dayjs(start.toUpperCase(), ['hh:mmA', 'h:mmA', 'hA']).format('hh:mm A')
|
||||
let end = time.match(/-(\d+(|:\d+)(a|p)m)/i)[1]
|
||||
end = dayjs(end.toUpperCase(), ['hh:mmA', 'h:mmA', 'hA']).format('hh:mm A')
|
||||
const title = row.replace(time, '').replace(' ', '').trim()
|
||||
items.push({ start, end, title })
|
||||
}
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
|
|
|
@ -1,60 +1,60 @@
|
|||
const cheerio = require('cheerio')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'arianatelevision.com',
|
||||
days: 2,
|
||||
url: 'https://www.arianatelevision.com/program-schedule/',
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart(item, date)
|
||||
if (prev) {
|
||||
if (start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.plus({ minutes: 30 })
|
||||
programs.push({
|
||||
title: item.title,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
const time = `${date.format('YYYY-MM-DD')} ${item.start}`
|
||||
|
||||
return DateTime.fromFormat(time, 'yyyy-MM-dd H:mm', { zone: 'Asia/Kabul' }).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
const settings = $('#jtrt_table_settings_508').text()
|
||||
if (!settings) return []
|
||||
const data = JSON.parse(settings)
|
||||
if (!data || !Array.isArray(data)) return []
|
||||
|
||||
let rows = data[0]
|
||||
rows.shift()
|
||||
const output = []
|
||||
rows.forEach(row => {
|
||||
let day = date.day() + 2
|
||||
if (day > 7) day = 1
|
||||
if (!row[0] || !row[day]) return
|
||||
output.push({
|
||||
start: row[0].trim(),
|
||||
title: row[day].trim()
|
||||
})
|
||||
})
|
||||
|
||||
return output
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'arianatelevision.com',
|
||||
days: 2,
|
||||
url: 'https://www.arianatelevision.com/program-schedule/',
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart(item, date)
|
||||
if (prev) {
|
||||
if (start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.plus({ minutes: 30 })
|
||||
programs.push({
|
||||
title: item.title,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
const time = `${date.format('YYYY-MM-DD')} ${item.start}`
|
||||
|
||||
return DateTime.fromFormat(time, 'yyyy-MM-dd H:mm', { zone: 'Asia/Kabul' }).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
const settings = $('#jtrt_table_settings_508').text()
|
||||
if (!settings) return []
|
||||
const data = JSON.parse(settings)
|
||||
if (!data || !Array.isArray(data)) return []
|
||||
|
||||
let rows = data[0]
|
||||
rows.shift()
|
||||
const output = []
|
||||
rows.forEach(row => {
|
||||
let day = date.day() + 2
|
||||
if (day > 7) day = 1
|
||||
if (!row[0] || !row[day]) return
|
||||
output.push({
|
||||
start: row[0].trim(),
|
||||
title: row[day].trim()
|
||||
})
|
||||
})
|
||||
|
||||
return output
|
||||
}
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
// npm run grab -- --site=arianatelevision.com
|
||||
|
||||
const { parser, url } = require('./arianatelevision.com.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2021-11-27', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '#',
|
||||
xmltv_id: 'ArianaTVNational.af'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://www.arianatelevision.com/program-schedule/')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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>'
|
||||
const result = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2021-11-27T02:30:00.000Z',
|
||||
stop: '2021-11-27T03:00:00.000Z',
|
||||
title: 'City Report'
|
||||
},
|
||||
{
|
||||
start: '2021-11-27T03:00:00.000Z',
|
||||
stop: '2021-11-27T10:30:00.000Z',
|
||||
title: 'ICC T20 Highlights'
|
||||
},
|
||||
{
|
||||
start: '2021-11-27T10:30:00.000Z',
|
||||
stop: '2021-11-28T02:00:00.000Z',
|
||||
title: 'ICC T20 World Cup'
|
||||
},
|
||||
{
|
||||
start: '2021-11-28T02:00:00.000Z',
|
||||
stop: '2021-11-28T02:30:00.000Z',
|
||||
title: 'Quran and Hadis'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
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>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=arianatelevision.com
|
||||
|
||||
const { parser, url } = require('./arianatelevision.com.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2021-11-27', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '#',
|
||||
xmltv_id: 'ArianaTVNational.af'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://www.arianatelevision.com/program-schedule/')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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>'
|
||||
const result = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2021-11-27T02:30:00.000Z',
|
||||
stop: '2021-11-27T03:00:00.000Z',
|
||||
title: 'City Report'
|
||||
},
|
||||
{
|
||||
start: '2021-11-27T03:00:00.000Z',
|
||||
stop: '2021-11-27T10:30:00.000Z',
|
||||
title: 'ICC T20 Highlights'
|
||||
},
|
||||
{
|
||||
start: '2021-11-27T10:30:00.000Z',
|
||||
stop: '2021-11-28T02:00:00.000Z',
|
||||
title: 'ICC T20 World Cup'
|
||||
},
|
||||
{
|
||||
start: '2021-11-28T02:00:00.000Z',
|
||||
stop: '2021-11-28T02:30:00.000Z',
|
||||
title: 'Quran and Hadis'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
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>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,153 +1,153 @@
|
|||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'arirang.com',
|
||||
output: 'arirang.com.guide.xml',
|
||||
channels: 'arirang.com.channels.xml',
|
||||
lang: 'en',
|
||||
days: 7,
|
||||
delay: 5000,
|
||||
url: 'https://www.arirang.com/v1.0/open/external/proxy',
|
||||
|
||||
request: {
|
||||
method: 'POST',
|
||||
timeout: 5000,
|
||||
cache: { ttl: 60 * 60 * 1000 },
|
||||
headers: {
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Content-Type': 'application/json',
|
||||
Origin: 'https://www.arirang.com',
|
||||
Referer: 'https://www.arirang.com/schedule',
|
||||
'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'
|
||||
},
|
||||
data: function (context) {
|
||||
const { channel, date } = context
|
||||
return {
|
||||
address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
|
||||
method: 'POST',
|
||||
headers: {},
|
||||
body: {
|
||||
data: {
|
||||
dmParam: {
|
||||
chanId: channel.site_id,
|
||||
broadYmd: dayjs.tz(date, 'Asia/Seoul').format('YYYYMMDD'),
|
||||
planNo: '1'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
logo: function (context) {
|
||||
return context.channel.logo
|
||||
},
|
||||
|
||||
async parser(context) {
|
||||
const programs = []
|
||||
const items = parseItems(context.content)
|
||||
|
||||
for (let item of items) {
|
||||
const programDetail = await parseProgramDetail(item)
|
||||
|
||||
programs.push({
|
||||
title: item.displayNm,
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item),
|
||||
icon: parseIcon(programDetail),
|
||||
category: parseCategory(programDetail),
|
||||
description: parseDescription(programDetail)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
if (content != '') {
|
||||
const data = JSON.parse(content)
|
||||
return !data || !data.responseBody || !Array.isArray(data.responseBody.dsSchWeek)
|
||||
? []
|
||||
: data.responseBody.dsSchWeek
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs
|
||||
.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
|
||||
.add(item.broadRun, 'minute')
|
||||
}
|
||||
|
||||
async function parseProgramDetail(item) {
|
||||
return axios
|
||||
.post(
|
||||
'https://www.arirang.com/v1.0/open/program/detail',
|
||||
{
|
||||
bis_program_code: item.pgmCd
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Content-Type': 'application/json',
|
||||
Origin: 'https://www.arirang.com',
|
||||
Referer: 'https://www.arirang.com/schedule',
|
||||
'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'
|
||||
},
|
||||
timeout: 5000,
|
||||
cache: { ttl: 60 * 1000 }
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
return response.data
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
function parseIcon(programDetail) {
|
||||
if (programDetail && programDetail.image && programDetail.image[0].url) {
|
||||
return programDetail.image[0].url
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function parseCategory(programDetail) {
|
||||
if (programDetail && programDetail.category_Info && programDetail.category_Info[0].title) {
|
||||
return programDetail.category_Info[0].title
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function parseDescription(programDetail) {
|
||||
if (
|
||||
programDetail &&
|
||||
programDetail.content &&
|
||||
programDetail.content[0] &&
|
||||
programDetail.content[0].text
|
||||
) {
|
||||
let description = programDetail.content[0].text
|
||||
let regex = /(<([^>]+)>)/gi
|
||||
return description.replace(regex, '')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'arirang.com',
|
||||
output: 'arirang.com.guide.xml',
|
||||
channels: 'arirang.com.channels.xml',
|
||||
lang: 'en',
|
||||
days: 7,
|
||||
delay: 5000,
|
||||
url: 'https://www.arirang.com/v1.0/open/external/proxy',
|
||||
|
||||
request: {
|
||||
method: 'POST',
|
||||
timeout: 5000,
|
||||
cache: { ttl: 60 * 60 * 1000 },
|
||||
headers: {
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Content-Type': 'application/json',
|
||||
Origin: 'https://www.arirang.com',
|
||||
Referer: 'https://www.arirang.com/schedule',
|
||||
'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'
|
||||
},
|
||||
data: function (context) {
|
||||
const { channel, date } = context
|
||||
return {
|
||||
address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
|
||||
method: 'POST',
|
||||
headers: {},
|
||||
body: {
|
||||
data: {
|
||||
dmParam: {
|
||||
chanId: channel.site_id,
|
||||
broadYmd: dayjs.tz(date, 'Asia/Seoul').format('YYYYMMDD'),
|
||||
planNo: '1'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
logo: function (context) {
|
||||
return context.channel.logo
|
||||
},
|
||||
|
||||
async parser(context) {
|
||||
const programs = []
|
||||
const items = parseItems(context.content)
|
||||
|
||||
for (let item of items) {
|
||||
const programDetail = await parseProgramDetail(item)
|
||||
|
||||
programs.push({
|
||||
title: item.displayNm,
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item),
|
||||
icon: parseIcon(programDetail),
|
||||
category: parseCategory(programDetail),
|
||||
description: parseDescription(programDetail)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
if (content != '') {
|
||||
const data = JSON.parse(content)
|
||||
return !data || !data.responseBody || !Array.isArray(data.responseBody.dsSchWeek)
|
||||
? []
|
||||
: data.responseBody.dsSchWeek
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs
|
||||
.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
|
||||
.add(item.broadRun, 'minute')
|
||||
}
|
||||
|
||||
async function parseProgramDetail(item) {
|
||||
return axios
|
||||
.post(
|
||||
'https://www.arirang.com/v1.0/open/program/detail',
|
||||
{
|
||||
bis_program_code: item.pgmCd
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Content-Type': 'application/json',
|
||||
Origin: 'https://www.arirang.com',
|
||||
Referer: 'https://www.arirang.com/schedule',
|
||||
'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'
|
||||
},
|
||||
timeout: 5000,
|
||||
cache: { ttl: 60 * 1000 }
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
return response.data
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
function parseIcon(programDetail) {
|
||||
if (programDetail && programDetail.image && programDetail.image[0].url) {
|
||||
return programDetail.image[0].url
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function parseCategory(programDetail) {
|
||||
if (programDetail && programDetail.category_Info && programDetail.category_Info[0].title) {
|
||||
return programDetail.category_Info[0].title
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function parseDescription(programDetail) {
|
||||
if (
|
||||
programDetail &&
|
||||
programDetail.content &&
|
||||
programDetail.content[0] &&
|
||||
programDetail.content[0].text
|
||||
) {
|
||||
let description = programDetail.content[0].text
|
||||
let regex = /(<([^>]+)>)/gi
|
||||
return description.replace(regex, '')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,74 +1,74 @@
|
|||
// npm run grab -- --site=arirang.com
|
||||
// npx jest arirang.com.test.js
|
||||
|
||||
const { url, parser } = require('./arirang.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const date = dayjs.tz('2023-08-25', 'Asia/Seoul').startOf('d')
|
||||
const channel = {
|
||||
xmltv_id: 'ArirangWorld.kr',
|
||||
site_id: 'CH_W',
|
||||
name: 'Arirang World',
|
||||
lang: 'en',
|
||||
logo: 'https://i.imgur.com/5Aoithj.png'
|
||||
}
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json'), 'utf8')
|
||||
const programDetail = fs.readFileSync(path.resolve(__dirname, '__data__/detail.json'), 'utf8')
|
||||
const context = { channel: channel, content: content, date: date }
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://www.arirang.com/v1.0/open/external/proxy')
|
||||
})
|
||||
|
||||
it('can handle empty guide', async () => {
|
||||
const results = await parser({ channel: channel, content: '', date: date })
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
|
||||
it('can parse response', async () => {
|
||||
axios.post.mockImplementation((url, data) => {
|
||||
if (
|
||||
url === 'https://www.arirang.com/v1.0/open/external/proxy' &&
|
||||
JSON.stringify(data) ===
|
||||
JSON.stringify({
|
||||
address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
|
||||
method: 'POST',
|
||||
headers: {},
|
||||
body: { data: { dmParam: { chanId: 'CH_W', broadYmd: '20230825', planNo: '1' } } }
|
||||
})
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(content)
|
||||
})
|
||||
} else if (
|
||||
url === 'https://www.arirang.com/v1.0/open/program/detail' &&
|
||||
JSON.stringify(data) === JSON.stringify({ bis_program_code: '2023004T' })
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(programDetail)
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({
|
||||
data: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const results = await parser(context)
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
title: 'WITHIN THE FRAME [R]',
|
||||
start: dayjs.tz(date, 'Asia/Seoul'),
|
||||
stop: dayjs.tz(date, 'Asia/Seoul').add(30, 'minute'),
|
||||
icon: 'https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202308/2080840096998752900.png',
|
||||
description: 'NEWS',
|
||||
category: 'Current Affairs'
|
||||
})
|
||||
})
|
||||
// npm run grab -- --site=arirang.com
|
||||
// npx jest arirang.com.test.js
|
||||
|
||||
const { url, parser } = require('./arirang.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const date = dayjs.tz('2023-08-25', 'Asia/Seoul').startOf('d')
|
||||
const channel = {
|
||||
xmltv_id: 'ArirangWorld.kr',
|
||||
site_id: 'CH_W',
|
||||
name: 'Arirang World',
|
||||
lang: 'en',
|
||||
logo: 'https://i.imgur.com/5Aoithj.png'
|
||||
}
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json'), 'utf8')
|
||||
const programDetail = fs.readFileSync(path.resolve(__dirname, '__data__/detail.json'), 'utf8')
|
||||
const context = { channel: channel, content: content, date: date }
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://www.arirang.com/v1.0/open/external/proxy')
|
||||
})
|
||||
|
||||
it('can handle empty guide', async () => {
|
||||
const results = await parser({ channel: channel, content: '', date: date })
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
|
||||
it('can parse response', async () => {
|
||||
axios.post.mockImplementation((url, data) => {
|
||||
if (
|
||||
url === 'https://www.arirang.com/v1.0/open/external/proxy' &&
|
||||
JSON.stringify(data) ===
|
||||
JSON.stringify({
|
||||
address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
|
||||
method: 'POST',
|
||||
headers: {},
|
||||
body: { data: { dmParam: { chanId: 'CH_W', broadYmd: '20230825', planNo: '1' } } }
|
||||
})
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(content)
|
||||
})
|
||||
} else if (
|
||||
url === 'https://www.arirang.com/v1.0/open/program/detail' &&
|
||||
JSON.stringify(data) === JSON.stringify({ bis_program_code: '2023004T' })
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(programDetail)
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({
|
||||
data: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const results = await parser(context)
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
title: 'WITHIN THE FRAME [R]',
|
||||
start: dayjs.tz(date, 'Asia/Seoul'),
|
||||
stop: dayjs.tz(date, 'Asia/Seoul').add(30, 'minute'),
|
||||
icon: 'https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202308/2080840096998752900.png',
|
||||
description: 'NEWS',
|
||||
category: 'Current Affairs'
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,68 +1,68 @@
|
|||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
|
||||
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'artonline.tv',
|
||||
days: 2,
|
||||
url: function ({ channel }) {
|
||||
return `https://www.artonline.tv/Home/Tvlist${channel.site_id}`
|
||||
},
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: function ({ date }) {
|
||||
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
||||
const params = new URLSearchParams()
|
||||
params.append('objId', diff)
|
||||
|
||||
return params
|
||||
}
|
||||
},
|
||||
parser: function ({ content }) {
|
||||
const programs = []
|
||||
if (!content) return programs
|
||||
const items = JSON.parse(content)
|
||||
items.forEach(item => {
|
||||
const icon = parseIcon(item)
|
||||
const start = parseStart(item)
|
||||
const duration = parseDuration(item)
|
||||
const stop = start.add(duration, 's')
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
icon,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
const [, M, D, YYYY] = item.adddate.match(/(\d+)\/(\d+)\/(\d+) /)
|
||||
const [HH, mm] = item.start_Time.split(':')
|
||||
|
||||
return dayjs.tz(`${YYYY}-${M}-${D}T${HH}:${mm}:00`, 'YYYY-M-DTHH:mm:ss', 'Asia/Riyadh')
|
||||
}
|
||||
|
||||
function parseDuration(item) {
|
||||
const [, HH, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)/)
|
||||
|
||||
return parseInt(HH) * 3600 + parseInt(mm) * 60 + parseInt(ss)
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
return item.thumbnail ? `https://www.artonline.tv${item.thumbnail}` : null
|
||||
}
|
||||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
|
||||
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'artonline.tv',
|
||||
days: 2,
|
||||
url: function ({ channel }) {
|
||||
return `https://www.artonline.tv/Home/Tvlist${channel.site_id}`
|
||||
},
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: function ({ date }) {
|
||||
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
||||
const params = new URLSearchParams()
|
||||
params.append('objId', diff)
|
||||
|
||||
return params
|
||||
}
|
||||
},
|
||||
parser: function ({ content }) {
|
||||
const programs = []
|
||||
if (!content) return programs
|
||||
const items = JSON.parse(content)
|
||||
items.forEach(item => {
|
||||
const icon = parseIcon(item)
|
||||
const start = parseStart(item)
|
||||
const duration = parseDuration(item)
|
||||
const stop = start.add(duration, 's')
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
icon,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
const [, M, D, YYYY] = item.adddate.match(/(\d+)\/(\d+)\/(\d+) /)
|
||||
const [HH, mm] = item.start_Time.split(':')
|
||||
|
||||
return dayjs.tz(`${YYYY}-${M}-${D}T${HH}:${mm}:00`, 'YYYY-M-DTHH:mm:ss', 'Asia/Riyadh')
|
||||
}
|
||||
|
||||
function parseDuration(item) {
|
||||
const [, HH, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)/)
|
||||
|
||||
return parseInt(HH) * 3600 + parseInt(mm) * 60 + parseInt(ss)
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
return item.thumbnail ? `https://www.artonline.tv${item.thumbnail}` : null
|
||||
}
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
// npm run grab -- --site=artonline.tv
|
||||
|
||||
const { parser, url, request } = require('./artonline.tv.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const channel = {
|
||||
site_id: 'Aflam2',
|
||||
xmltv_id: 'ARTAflam2.sa'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('https://www.artonline.tv/Home/TvlistAflam2')
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data for today', () => {
|
||||
const date = dayjs.utc().startOf('d')
|
||||
const data = request.data({ date })
|
||||
expect(data.get('objId')).toBe('0')
|
||||
})
|
||||
|
||||
it('can generate valid request data for tomorrow', () => {
|
||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||
const data = request.data({ date })
|
||||
expect(data.get('objId')).toBe('1')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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}]'
|
||||
const result = parser({ content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-03-03T21:30:00.000Z',
|
||||
stop: '2022-03-03T23:04:00.000Z',
|
||||
title: 'الراقصه و السياسي',
|
||||
description:
|
||||
'تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .',
|
||||
icon: 'https://www.artonline.tv/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: ''
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=artonline.tv
|
||||
|
||||
const { parser, url, request } = require('./artonline.tv.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const channel = {
|
||||
site_id: 'Aflam2',
|
||||
xmltv_id: 'ARTAflam2.sa'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('https://www.artonline.tv/Home/TvlistAflam2')
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data for today', () => {
|
||||
const date = dayjs.utc().startOf('d')
|
||||
const data = request.data({ date })
|
||||
expect(data.get('objId')).toBe('0')
|
||||
})
|
||||
|
||||
it('can generate valid request data for tomorrow', () => {
|
||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||
const data = request.data({ date })
|
||||
expect(data.get('objId')).toBe('1')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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}]'
|
||||
const result = parser({ content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-03-03T21:30:00.000Z',
|
||||
stop: '2022-03-03T23:04:00.000Z',
|
||||
title: 'الراقصه و السياسي',
|
||||
description:
|
||||
'تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .',
|
||||
icon: 'https://www.artonline.tv/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: ''
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,123 +1,123 @@
|
|||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
const API_ENDPOINT = 'https://contenthub-api.eco.astro.com.my'
|
||||
|
||||
module.exports = {
|
||||
site: 'astro.com.my',
|
||||
days: 2,
|
||||
url: function ({ channel }) {
|
||||
return `${API_ENDPOINT}/channel/${channel.site_id}.json`
|
||||
},
|
||||
async parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
for (let item of items) {
|
||||
const start = dayjs.utc(item.datetimeInUtc)
|
||||
const duration = parseDuration(item.duration)
|
||||
const stop = start.add(duration, 's')
|
||||
const details = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: details.title,
|
||||
sub_title: item.subtitles,
|
||||
description: details.longSynopsis || details.shortSynopsis,
|
||||
actors: parseList(details.cast),
|
||||
directors: parseList(details.director),
|
||||
icon: details.imageUrl,
|
||||
rating: parseRating(details),
|
||||
categories: parseCategories(details),
|
||||
episode: parseEpisode(item),
|
||||
season: parseSeason(details),
|
||||
start: start,
|
||||
stop: stop
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseEpisode(item) {
|
||||
const [, number] = item.title.match(/Ep(\d+)$/) || [null, null]
|
||||
|
||||
return number ? parseInt(number) : null
|
||||
}
|
||||
|
||||
function parseSeason(details) {
|
||||
const [, season] = details.title ? details.title.match(/ S(\d+)/) || [null, null] : [null, null]
|
||||
|
||||
return season ? parseInt(season) : null
|
||||
}
|
||||
|
||||
function parseList(list) {
|
||||
return typeof list === 'string' ? list.split(',') : []
|
||||
}
|
||||
|
||||
function parseRating(details) {
|
||||
return details.certification
|
||||
? {
|
||||
system: 'LPF',
|
||||
value: details.certification
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
try {
|
||||
const data = JSON.parse(content)
|
||||
const schedules = data.response.schedule
|
||||
|
||||
return schedules[date.format('YYYY-MM-DD')] || []
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function parseDuration(duration) {
|
||||
const match = duration.match(/(\d{2}):(\d{2}):(\d{2})/)
|
||||
const hours = parseInt(match[1])
|
||||
const minutes = parseInt(match[2])
|
||||
const seconds = parseInt(match[3])
|
||||
|
||||
return hours * 3600 + minutes * 60 + seconds
|
||||
}
|
||||
|
||||
function parseCategories(details) {
|
||||
const genres = {
|
||||
'filter/2': 'Action',
|
||||
'filter/4': 'Anime',
|
||||
'filter/12': 'Cartoons',
|
||||
'filter/16': 'Comedy',
|
||||
'filter/19': 'Crime',
|
||||
'filter/24': 'Drama',
|
||||
'filter/25': 'Educational',
|
||||
'filter/36': 'Horror',
|
||||
'filter/39': 'Live Action',
|
||||
'filter/55': 'Pre-school',
|
||||
'filter/56': 'Reality',
|
||||
'filter/60': 'Romance',
|
||||
'filter/68': 'Talk Show',
|
||||
'filter/69': 'Thriller',
|
||||
'filter/72': 'Variety',
|
||||
'filter/75': 'Series',
|
||||
'filter/100': 'Others (Children)'
|
||||
}
|
||||
|
||||
return Array.isArray(details.subFilter)
|
||||
? details.subFilter.map(g => genres[g.toLowerCase()]).filter(Boolean)
|
||||
: []
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
const url = `${API_ENDPOINT}/api/v1/linear-detail?siTrafficKey=${item.siTrafficKey}`
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(error => console.log(error.message))
|
||||
if (!data) return {}
|
||||
|
||||
return data.response || {}
|
||||
}
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
const API_ENDPOINT = 'https://contenthub-api.eco.astro.com.my'
|
||||
|
||||
module.exports = {
|
||||
site: 'astro.com.my',
|
||||
days: 2,
|
||||
url: function ({ channel }) {
|
||||
return `${API_ENDPOINT}/channel/${channel.site_id}.json`
|
||||
},
|
||||
async parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
for (let item of items) {
|
||||
const start = dayjs.utc(item.datetimeInUtc)
|
||||
const duration = parseDuration(item.duration)
|
||||
const stop = start.add(duration, 's')
|
||||
const details = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: details.title,
|
||||
sub_title: item.subtitles,
|
||||
description: details.longSynopsis || details.shortSynopsis,
|
||||
actors: parseList(details.cast),
|
||||
directors: parseList(details.director),
|
||||
icon: details.imageUrl,
|
||||
rating: parseRating(details),
|
||||
categories: parseCategories(details),
|
||||
episode: parseEpisode(item),
|
||||
season: parseSeason(details),
|
||||
start: start,
|
||||
stop: stop
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseEpisode(item) {
|
||||
const [, number] = item.title.match(/Ep(\d+)$/) || [null, null]
|
||||
|
||||
return number ? parseInt(number) : null
|
||||
}
|
||||
|
||||
function parseSeason(details) {
|
||||
const [, season] = details.title ? details.title.match(/ S(\d+)/) || [null, null] : [null, null]
|
||||
|
||||
return season ? parseInt(season) : null
|
||||
}
|
||||
|
||||
function parseList(list) {
|
||||
return typeof list === 'string' ? list.split(',') : []
|
||||
}
|
||||
|
||||
function parseRating(details) {
|
||||
return details.certification
|
||||
? {
|
||||
system: 'LPF',
|
||||
value: details.certification
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
try {
|
||||
const data = JSON.parse(content)
|
||||
const schedules = data.response.schedule
|
||||
|
||||
return schedules[date.format('YYYY-MM-DD')] || []
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function parseDuration(duration) {
|
||||
const match = duration.match(/(\d{2}):(\d{2}):(\d{2})/)
|
||||
const hours = parseInt(match[1])
|
||||
const minutes = parseInt(match[2])
|
||||
const seconds = parseInt(match[3])
|
||||
|
||||
return hours * 3600 + minutes * 60 + seconds
|
||||
}
|
||||
|
||||
function parseCategories(details) {
|
||||
const genres = {
|
||||
'filter/2': 'Action',
|
||||
'filter/4': 'Anime',
|
||||
'filter/12': 'Cartoons',
|
||||
'filter/16': 'Comedy',
|
||||
'filter/19': 'Crime',
|
||||
'filter/24': 'Drama',
|
||||
'filter/25': 'Educational',
|
||||
'filter/36': 'Horror',
|
||||
'filter/39': 'Live Action',
|
||||
'filter/55': 'Pre-school',
|
||||
'filter/56': 'Reality',
|
||||
'filter/60': 'Romance',
|
||||
'filter/68': 'Talk Show',
|
||||
'filter/69': 'Thriller',
|
||||
'filter/72': 'Variety',
|
||||
'filter/75': 'Series',
|
||||
'filter/100': 'Others (Children)'
|
||||
}
|
||||
|
||||
return Array.isArray(details.subFilter)
|
||||
? details.subFilter.map(g => genres[g.toLowerCase()]).filter(Boolean)
|
||||
: []
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
const url = `${API_ENDPOINT}/api/v1/linear-detail?siTrafficKey=${item.siTrafficKey}`
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(error => console.log(error.message))
|
||||
if (!data) return {}
|
||||
|
||||
return data.response || {}
|
||||
}
|
||||
|
|
|
@ -1,73 +1,73 @@
|
|||
// npm run grab -- --site=astro.com.my
|
||||
|
||||
const { parser, url } = require('./astro.com.my.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const date = dayjs.utc('2022-10-31', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '425',
|
||||
xmltv_id: 'TVBClassic.hk'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('https://contenthub-api.eco.astro.com.my/channel/425.json')
|
||||
})
|
||||
|
||||
it('can parse response', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (
|
||||
url ===
|
||||
'https://contenthub-api.eco.astro.com.my/api/v1/linear-detail?siTrafficKey=1:10000526:47979653'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json')))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
let results = await parser({ content, channel, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results.length).toBe(31)
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-10-30T16:10:00.000Z',
|
||||
stop: '2022-10-30T17:02:00.000Z',
|
||||
title: 'Triumph in the Skies S1 Ep06',
|
||||
description:
|
||||
'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'],
|
||||
directors: ['Joe Ma Tak Chung'],
|
||||
icon: 'https://s3-ap-southeast-1.amazonaws.com/ams-astro/production/images/1035X328883.jpg',
|
||||
rating: {
|
||||
system: 'LPF',
|
||||
value: 'U'
|
||||
},
|
||||
episode: 6,
|
||||
season: 1,
|
||||
categories: ['Drama']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'))
|
||||
const results = await parser({ date, content })
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=astro.com.my
|
||||
|
||||
const { parser, url } = require('./astro.com.my.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const date = dayjs.utc('2022-10-31', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '425',
|
||||
xmltv_id: 'TVBClassic.hk'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('https://contenthub-api.eco.astro.com.my/channel/425.json')
|
||||
})
|
||||
|
||||
it('can parse response', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (
|
||||
url ===
|
||||
'https://contenthub-api.eco.astro.com.my/api/v1/linear-detail?siTrafficKey=1:10000526:47979653'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json')))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
let results = await parser({ content, channel, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results.length).toBe(31)
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-10-30T16:10:00.000Z',
|
||||
stop: '2022-10-30T17:02:00.000Z',
|
||||
title: 'Triumph in the Skies S1 Ep06',
|
||||
description:
|
||||
'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'],
|
||||
directors: ['Joe Ma Tak Chung'],
|
||||
icon: 'https://s3-ap-southeast-1.amazonaws.com/ams-astro/production/images/1035X328883.jpg',
|
||||
rating: {
|
||||
system: 'LPF',
|
||||
value: 'U'
|
||||
},
|
||||
episode: 6,
|
||||
season: 1,
|
||||
categories: ['Drama']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'))
|
||||
const results = await parser({ date, content })
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,79 +1,79 @@
|
|||
const cheerio = require('cheerio')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'bein.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url: function ({ date, channel }) {
|
||||
const [category] = channel.site_id.split('#')
|
||||
const postid = channel.lang === 'ar' ? '25344' : '25356'
|
||||
|
||||
return `https://www.bein.com/${
|
||||
channel.lang
|
||||
}/epg-ajax-template/?action=epg_fetch&category=${category}&cdate=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}&language=${channel.lang.toUpperCase()}&loadindex=0&mins=00&offset=0&postid=${postid}&serviceidentity=bein.net`
|
||||
},
|
||||
parser: function ({ content, channel, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
date = DateTime.fromMillis(date.valueOf()).minus({ days: 1 })
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const title = parseTitle($item)
|
||||
if (!title) return
|
||||
const category = parseCategory($item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseTime($item, date)
|
||||
if (prev) {
|
||||
if (start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
date = date.plus({ days: 1 })
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
let stop = parseTime($item, start)
|
||||
if (stop < start) {
|
||||
stop = stop.plus({ days: 1 })
|
||||
}
|
||||
programs.push({
|
||||
title,
|
||||
category,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.title').text()
|
||||
}
|
||||
|
||||
function parseCategory($item) {
|
||||
return $item('.format').text()
|
||||
}
|
||||
|
||||
function parseTime($item, date) {
|
||||
let [, time] = $item('.time')
|
||||
.text()
|
||||
.match(/^(\d{2}:\d{2})/) || [null, null]
|
||||
if (!time) return null
|
||||
time = `${date.toFormat('yyyy-MM-dd')} ${time}`
|
||||
|
||||
return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Asia/Qatar' }).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray()
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'bein.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url: function ({ date, channel }) {
|
||||
const [category] = channel.site_id.split('#')
|
||||
const postid = channel.lang === 'ar' ? '25344' : '25356'
|
||||
|
||||
return `https://www.bein.com/${
|
||||
channel.lang
|
||||
}/epg-ajax-template/?action=epg_fetch&category=${category}&cdate=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}&language=${channel.lang.toUpperCase()}&loadindex=0&mins=00&offset=0&postid=${postid}&serviceidentity=bein.net`
|
||||
},
|
||||
parser: function ({ content, channel, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
date = DateTime.fromMillis(date.valueOf()).minus({ days: 1 })
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const title = parseTitle($item)
|
||||
if (!title) return
|
||||
const category = parseCategory($item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseTime($item, date)
|
||||
if (prev) {
|
||||
if (start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
date = date.plus({ days: 1 })
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
let stop = parseTime($item, start)
|
||||
if (stop < start) {
|
||||
stop = stop.plus({ days: 1 })
|
||||
}
|
||||
programs.push({
|
||||
title,
|
||||
category,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.title').text()
|
||||
}
|
||||
|
||||
function parseCategory($item) {
|
||||
return $item('.format').text()
|
||||
}
|
||||
|
||||
function parseTime($item, date) {
|
||||
let [, time] = $item('.time')
|
||||
.text()
|
||||
.match(/^(\d{2}:\d{2})/) || [null, null]
|
||||
if (!time) return null
|
||||
time = `${date.toFormat('yyyy-MM-dd')} ${time}`
|
||||
|
||||
return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Asia/Qatar' }).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray()
|
||||
}
|
||||
|
|
|
@ -1,60 +1,60 @@
|
|||
// npm run grab -- --site=bein.com
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { parser, url } = require('./bein.com.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = { site_id: 'entertainment#1', xmltv_id: 'beINMovies1Premiere.qa', lang: 'en' }
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ date, channel })
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve('sites/bein.com/__data__/content.html'))
|
||||
const results = parser({ date, channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-18T20:15:00.000Z',
|
||||
stop: '2023-01-18T22:15:00.000Z',
|
||||
title: 'The Walk',
|
||||
category: 'Movies'
|
||||
})
|
||||
|
||||
expect(results[1]).toMatchObject({
|
||||
start: '2023-01-18T22:15:00.000Z',
|
||||
stop: '2023-01-19T00:00:00.000Z',
|
||||
title: 'Resident Evil: Welcome To Raccoon City',
|
||||
category: 'Movies'
|
||||
})
|
||||
|
||||
expect(results[10]).toMatchObject({
|
||||
start: '2023-01-19T15:30:00.000Z',
|
||||
stop: '2023-01-19T18:00:00.000Z',
|
||||
title: 'Spider-Man: No Way Home',
|
||||
category: 'Movies'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const noContent = fs.readFileSync(path.resolve('sites/bein.com/__data__/no-content.html'))
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: noContent
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=bein.com
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { parser, url } = require('./bein.com.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = { site_id: 'entertainment#1', xmltv_id: 'beINMovies1Premiere.qa', lang: 'en' }
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ date, channel })
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve('sites/bein.com/__data__/content.html'))
|
||||
const results = parser({ date, channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-18T20:15:00.000Z',
|
||||
stop: '2023-01-18T22:15:00.000Z',
|
||||
title: 'The Walk',
|
||||
category: 'Movies'
|
||||
})
|
||||
|
||||
expect(results[1]).toMatchObject({
|
||||
start: '2023-01-18T22:15:00.000Z',
|
||||
stop: '2023-01-19T00:00:00.000Z',
|
||||
title: 'Resident Evil: Welcome To Raccoon City',
|
||||
category: 'Movies'
|
||||
})
|
||||
|
||||
expect(results[10]).toMatchObject({
|
||||
start: '2023-01-19T15:30:00.000Z',
|
||||
stop: '2023-01-19T18:00:00.000Z',
|
||||
title: 'Spider-Man: No Way Home',
|
||||
category: 'Movies'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const noContent = fs.readFileSync(path.resolve('sites/bein.com/__data__/no-content.html'))
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: noContent
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,130 +1,130 @@
|
|||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'beinsports.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000, // 1h
|
||||
interpretHeader: false
|
||||
}
|
||||
},
|
||||
url: function ({ date, channel }) {
|
||||
let [region] = channel.site_id.split('#')
|
||||
region = region ? `_${region}` : ''
|
||||
|
||||
return `https://epg.beinsports.com/utctime${region}.php?mins=00&serviceidentity=beinsports.com&cdate=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}`
|
||||
},
|
||||
parser: function ({ content, channel, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
let i = 0
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const title = parseTitle($item)
|
||||
if (!title) return
|
||||
const category = parseCategory($item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart($item, date)
|
||||
if (i === 0 && start.hour() > 18) {
|
||||
date = date.subtract(1, 'd')
|
||||
start = start.subtract(1, 'd')
|
||||
}
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
let stop = parseStop($item, start)
|
||||
if (stop.isBefore(start)) {
|
||||
stop = stop.add(1, 'd')
|
||||
}
|
||||
|
||||
programs.push({ title, category, start, stop })
|
||||
i++
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ region, lang }) {
|
||||
const suffix = region ? `_${region}` : ''
|
||||
const content = await axios
|
||||
.get(
|
||||
`https://epg.beinsports.com/utctime${suffix}.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08`
|
||||
)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
const $ = cheerio.load(content)
|
||||
const items = $('.container > div, #epg_div > div').toArray()
|
||||
return items
|
||||
.map(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const id = $item('*').attr('id')
|
||||
if (!/^channels_[0-9]+$/.test(id)) return null
|
||||
const channelId = id.replace('channels_', '')
|
||||
const imgSrc = $item('img').attr('src')
|
||||
const [, , name] = imgSrc.match(/(\/|)([a-z0-9-_.]+)(.png|.svg)$/i) || [null, null, '']
|
||||
|
||||
return {
|
||||
lang,
|
||||
site_id: `${region}#${channelId}`,
|
||||
name
|
||||
}
|
||||
})
|
||||
.filter(i => i)
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.title').text()
|
||||
}
|
||||
|
||||
function parseCategory($item) {
|
||||
return $item('.format')
|
||||
.map(function () {
|
||||
return $item(this).text()
|
||||
})
|
||||
.get()
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
let time = $item('.time').text()
|
||||
if (!time) return null
|
||||
let [, start, period] = time.match(/^(\d{2}:\d{2})( AM| PM|)/) || [null, null, null]
|
||||
if (!start) return null
|
||||
start = `${date.format('YYYY-MM-DD')} ${start}${period}`
|
||||
const format = period ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'
|
||||
|
||||
return dayjs.tz(start, format, 'Asia/Qatar')
|
||||
}
|
||||
|
||||
function parseStop($item, date) {
|
||||
let time = $item('.time').text()
|
||||
if (!time) return null
|
||||
let [, stop, period] = time.match(/(\d{2}:\d{2})( AM| PM|)$/) || [null, null, null]
|
||||
if (!stop) return null
|
||||
stop = `${date.format('YYYY-MM-DD')} ${stop}${period}`
|
||||
const format = period ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'
|
||||
|
||||
return dayjs.tz(stop, format, 'Asia/Qatar')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray()
|
||||
}
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'beinsports.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000, // 1h
|
||||
interpretHeader: false
|
||||
}
|
||||
},
|
||||
url: function ({ date, channel }) {
|
||||
let [region] = channel.site_id.split('#')
|
||||
region = region ? `_${region}` : ''
|
||||
|
||||
return `https://epg.beinsports.com/utctime${region}.php?mins=00&serviceidentity=beinsports.com&cdate=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}`
|
||||
},
|
||||
parser: function ({ content, channel, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
let i = 0
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const title = parseTitle($item)
|
||||
if (!title) return
|
||||
const category = parseCategory($item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart($item, date)
|
||||
if (i === 0 && start.hour() > 18) {
|
||||
date = date.subtract(1, 'd')
|
||||
start = start.subtract(1, 'd')
|
||||
}
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
let stop = parseStop($item, start)
|
||||
if (stop.isBefore(start)) {
|
||||
stop = stop.add(1, 'd')
|
||||
}
|
||||
|
||||
programs.push({ title, category, start, stop })
|
||||
i++
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ region, lang }) {
|
||||
const suffix = region ? `_${region}` : ''
|
||||
const content = await axios
|
||||
.get(
|
||||
`https://epg.beinsports.com/utctime${suffix}.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08`
|
||||
)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
const $ = cheerio.load(content)
|
||||
const items = $('.container > div, #epg_div > div').toArray()
|
||||
return items
|
||||
.map(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const id = $item('*').attr('id')
|
||||
if (!/^channels_[0-9]+$/.test(id)) return null
|
||||
const channelId = id.replace('channels_', '')
|
||||
const imgSrc = $item('img').attr('src')
|
||||
const [, , name] = imgSrc.match(/(\/|)([a-z0-9-_.]+)(.png|.svg)$/i) || [null, null, '']
|
||||
|
||||
return {
|
||||
lang,
|
||||
site_id: `${region}#${channelId}`,
|
||||
name
|
||||
}
|
||||
})
|
||||
.filter(i => i)
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.title').text()
|
||||
}
|
||||
|
||||
function parseCategory($item) {
|
||||
return $item('.format')
|
||||
.map(function () {
|
||||
return $item(this).text()
|
||||
})
|
||||
.get()
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
let time = $item('.time').text()
|
||||
if (!time) return null
|
||||
let [, start, period] = time.match(/^(\d{2}:\d{2})( AM| PM|)/) || [null, null, null]
|
||||
if (!start) return null
|
||||
start = `${date.format('YYYY-MM-DD')} ${start}${period}`
|
||||
const format = period ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'
|
||||
|
||||
return dayjs.tz(start, format, 'Asia/Qatar')
|
||||
}
|
||||
|
||||
function parseStop($item, date) {
|
||||
let time = $item('.time').text()
|
||||
if (!time) return null
|
||||
let [, stop, period] = time.match(/(\d{2}:\d{2})( AM| PM|)$/) || [null, null, null]
|
||||
if (!stop) return null
|
||||
stop = `${date.format('YYYY-MM-DD')} ${stop}${period}`
|
||||
const format = period ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'
|
||||
|
||||
return dayjs.tz(stop, format, 'Asia/Qatar')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
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 grab -- --site=beinsports.com
|
||||
// npm run grab -- --site=beinsports.com
|
||||
|
||||
const { parser, url } = require('./beinsports.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-05-08', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = { site_id: '#2', xmltv_id: 'BeINSports.qa' }
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ date, channel })
|
||||
expect(result).toBe(
|
||||
'https://epg.beinsports.com/utctime.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid url for arabic guide', () => {
|
||||
const channel = { site_id: 'ar#1', xmltv_id: 'BeINSports.qa' }
|
||||
const result = url({ date, channel })
|
||||
expect(result).toBe(
|
||||
'https://epg.beinsports.com/utctime_ar.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/content.html'))
|
||||
const results = parser({ date, channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-05-07T19:30:00.000Z',
|
||||
stop: '2022-05-07T21:20:00.000Z',
|
||||
title: 'Lorient vs Marseille',
|
||||
category: ['Ligue 1 2021/22']
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response for tomorrow', () => {
|
||||
const date = dayjs.utc('2022-05-09', 'YYYY-MM-DD').startOf('d')
|
||||
const content = fs.readFileSync(
|
||||
path.resolve('sites/beinsports.com/__data__/content_tomorrow.html')
|
||||
)
|
||||
const results = parser({ date, channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-05-08T21:20:00.000Z',
|
||||
stop: '2022-05-08T23:10:00.000Z',
|
||||
title: 'Celtic vs Hearts',
|
||||
category: ['SPFL Premiership 2021/22']
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse US response', () => {
|
||||
const content = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/content_us.html'))
|
||||
const results = parser({ date, channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-05-07T20:00:00.000Z',
|
||||
stop: '2022-05-07T22:00:00.000Z',
|
||||
title: 'Basaksehir vs. Galatasaray',
|
||||
category: ['Fútbol Turco Superliga', 'Soccer']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const noContent = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/no-content.html'))
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: noContent
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url } = require('./beinsports.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-05-08', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = { site_id: '#2', xmltv_id: 'BeINSports.qa' }
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ date, channel })
|
||||
expect(result).toBe(
|
||||
'https://epg.beinsports.com/utctime.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid url for arabic guide', () => {
|
||||
const channel = { site_id: 'ar#1', xmltv_id: 'BeINSports.qa' }
|
||||
const result = url({ date, channel })
|
||||
expect(result).toBe(
|
||||
'https://epg.beinsports.com/utctime_ar.php?mins=00&serviceidentity=beinsports.com&cdate=2022-05-08'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/content.html'))
|
||||
const results = parser({ date, channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-05-07T19:30:00.000Z',
|
||||
stop: '2022-05-07T21:20:00.000Z',
|
||||
title: 'Lorient vs Marseille',
|
||||
category: ['Ligue 1 2021/22']
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response for tomorrow', () => {
|
||||
const date = dayjs.utc('2022-05-09', 'YYYY-MM-DD').startOf('d')
|
||||
const content = fs.readFileSync(
|
||||
path.resolve('sites/beinsports.com/__data__/content_tomorrow.html')
|
||||
)
|
||||
const results = parser({ date, channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-05-08T21:20:00.000Z',
|
||||
stop: '2022-05-08T23:10:00.000Z',
|
||||
title: 'Celtic vs Hearts',
|
||||
category: ['SPFL Premiership 2021/22']
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse US response', () => {
|
||||
const content = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/content_us.html'))
|
||||
const results = parser({ date, channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-05-07T20:00:00.000Z',
|
||||
stop: '2022-05-07T22:00:00.000Z',
|
||||
title: 'Basaksehir vs. Galatasaray',
|
||||
category: ['Fútbol Turco Superliga', 'Soccer']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const noContent = fs.readFileSync(path.resolve('sites/beinsports.com/__data__/no-content.html'))
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: noContent
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,93 +1,93 @@
|
|||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
dayjs.Ls.en.weekStart = 1
|
||||
|
||||
module.exports = {
|
||||
site: 'berrymedia.co.kr',
|
||||
days: 2,
|
||||
url({ channel }) {
|
||||
return `http://www.berrymedia.co.kr/schedule_proc${channel.site_id}.php`
|
||||
},
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
data({ date }) {
|
||||
let params = new URLSearchParams()
|
||||
let startOfWeek = date.startOf('week').format('YYYY-MM-DD')
|
||||
let endOfWeek = date.endOf('week').format('YYYY-MM-DD')
|
||||
|
||||
params.append('week', `${startOfWeek}~${endOfWeek}`)
|
||||
params.append('day', date.format('YYYY-MM-DD'))
|
||||
|
||||
return params
|
||||
}
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart($item, date)
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.add(30, 'm')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
category: parseCategory($item),
|
||||
rating: parseRating($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
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')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('span.sdfsdf').clone().children().remove().end().text().trim()
|
||||
}
|
||||
|
||||
function parseCategory($item) {
|
||||
return $item('span:nth-child(2) > p').text().trim()
|
||||
}
|
||||
|
||||
function parseRating($item) {
|
||||
const rating = $item('span:nth-child(5) > p:nth-child(1)').text().trim()
|
||||
|
||||
return rating
|
||||
? {
|
||||
system: 'KMRB',
|
||||
value: rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('.sc_time dd').toArray()
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
dayjs.Ls.en.weekStart = 1
|
||||
|
||||
module.exports = {
|
||||
site: 'berrymedia.co.kr',
|
||||
days: 2,
|
||||
url({ channel }) {
|
||||
return `http://www.berrymedia.co.kr/schedule_proc${channel.site_id}.php`
|
||||
},
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
data({ date }) {
|
||||
let params = new URLSearchParams()
|
||||
let startOfWeek = date.startOf('week').format('YYYY-MM-DD')
|
||||
let endOfWeek = date.endOf('week').format('YYYY-MM-DD')
|
||||
|
||||
params.append('week', `${startOfWeek}~${endOfWeek}`)
|
||||
params.append('day', date.format('YYYY-MM-DD'))
|
||||
|
||||
return params
|
||||
}
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart($item, date)
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.add(30, 'm')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
category: parseCategory($item),
|
||||
rating: parseRating($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
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')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('span.sdfsdf').clone().children().remove().end().text().trim()
|
||||
}
|
||||
|
||||
function parseCategory($item) {
|
||||
return $item('span:nth-child(2) > p').text().trim()
|
||||
}
|
||||
|
||||
function parseRating($item) {
|
||||
const rating = $item('span:nth-child(5) > p:nth-child(1)').text().trim()
|
||||
|
||||
return rating
|
||||
? {
|
||||
system: 'KMRB',
|
||||
value: rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('.sc_time dd').toArray()
|
||||
}
|
||||
|
|
|
@ -1,79 +1,79 @@
|
|||
// npm run grab -- --site=berrymedia.co.kr
|
||||
|
||||
const { parser, url, request } = require('./berrymedia.co.kr.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-26', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '',
|
||||
xmltv_id: 'GTV.kr'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('http://www.berrymedia.co.kr/schedule_proc.php')
|
||||
})
|
||||
|
||||
it('can generate request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
let params = request.data({ date })
|
||||
|
||||
expect(params.get('week')).toBe('2023-01-23~2023-01-29')
|
||||
expect(params.get('day')).toBe('2023-01-26')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||
let results = parser({ content, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-25T15:00:00.000Z',
|
||||
stop: '2023-01-25T16:00:00.000Z',
|
||||
title: '더트롯쇼',
|
||||
category: '연예/오락',
|
||||
rating: {
|
||||
system: 'KMRB',
|
||||
value: '15'
|
||||
}
|
||||
})
|
||||
|
||||
expect(results[17]).toMatchObject({
|
||||
start: '2023-01-26T13:50:00.000Z',
|
||||
stop: '2023-01-26T14:20:00.000Z',
|
||||
title: '나는 자연인이다',
|
||||
category: '교양',
|
||||
rating: {
|
||||
system: 'KMRB',
|
||||
value: 'ALL'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({
|
||||
date,
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||
})
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=berrymedia.co.kr
|
||||
|
||||
const { parser, url, request } = require('./berrymedia.co.kr.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-26', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '',
|
||||
xmltv_id: 'GTV.kr'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('http://www.berrymedia.co.kr/schedule_proc.php')
|
||||
})
|
||||
|
||||
it('can generate request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
let params = request.data({ date })
|
||||
|
||||
expect(params.get('week')).toBe('2023-01-23~2023-01-29')
|
||||
expect(params.get('day')).toBe('2023-01-26')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||
let results = parser({ content, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-25T15:00:00.000Z',
|
||||
stop: '2023-01-25T16:00:00.000Z',
|
||||
title: '더트롯쇼',
|
||||
category: '연예/오락',
|
||||
rating: {
|
||||
system: 'KMRB',
|
||||
value: '15'
|
||||
}
|
||||
})
|
||||
|
||||
expect(results[17]).toMatchObject({
|
||||
start: '2023-01-26T13:50:00.000Z',
|
||||
stop: '2023-01-26T14:20:00.000Z',
|
||||
title: '나는 자연인이다',
|
||||
category: '교양',
|
||||
rating: {
|
||||
system: 'KMRB',
|
||||
value: 'ALL'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({
|
||||
date,
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||
})
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'bt.com',
|
||||
days: 2,
|
||||
url: function ({ date, channel }) {
|
||||
return `https://voila.metabroadcast.com/4/schedules/${
|
||||
channel.site_id
|
||||
}.json?key=b4d2edb68da14dfb9e47b5465e99b1b1&from=${date.utc().format()}&to=${date
|
||||
.add(1, 'd')
|
||||
.utc()
|
||||
.format()}&source=api.youview.tv&annotations=content.description`
|
||||
},
|
||||
parser: function ({ content }) {
|
||||
const programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.item.title,
|
||||
description: item.item.description,
|
||||
icon: parseIcon(item),
|
||||
season: parseSeason(item),
|
||||
episode: parseEpisode(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
return data && data.schedule.entries ? data.schedule.entries : []
|
||||
}
|
||||
function parseSeason(item) {
|
||||
if (item.item.type !== 'episode') return null
|
||||
return item.item.series_number || null
|
||||
}
|
||||
function parseEpisode(item) {
|
||||
if (item.item.type !== 'episode') return null
|
||||
return item.item.episode_number || null
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.item.image || null
|
||||
}
|
||||
function parseStart(item) {
|
||||
return dayjs(item.broadcast.transmission_time)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs(item.broadcast.transmission_end_time)
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'bt.com',
|
||||
days: 2,
|
||||
url: function ({ date, channel }) {
|
||||
return `https://voila.metabroadcast.com/4/schedules/${
|
||||
channel.site_id
|
||||
}.json?key=b4d2edb68da14dfb9e47b5465e99b1b1&from=${date.utc().format()}&to=${date
|
||||
.add(1, 'd')
|
||||
.utc()
|
||||
.format()}&source=api.youview.tv&annotations=content.description`
|
||||
},
|
||||
parser: function ({ content }) {
|
||||
const programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.item.title,
|
||||
description: item.item.description,
|
||||
icon: parseIcon(item),
|
||||
season: parseSeason(item),
|
||||
episode: parseEpisode(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
return data && data.schedule.entries ? data.schedule.entries : []
|
||||
}
|
||||
function parseSeason(item) {
|
||||
if (item.item.type !== 'episode') return null
|
||||
return item.item.series_number || null
|
||||
}
|
||||
function parseEpisode(item) {
|
||||
if (item.item.type !== 'episode') return null
|
||||
return item.item.episode_number || null
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.item.image || null
|
||||
}
|
||||
function parseStart(item) {
|
||||
return dayjs(item.broadcast.transmission_time)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs(item.broadcast.transmission_end_time)
|
||||
}
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
// npm run grab -- --site=bt.com
|
||||
|
||||
const { parser, url } = require('./bt.com.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-03-20', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'hsxv',
|
||||
xmltv_id: 'BBCOneHD.uk'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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"}}}'
|
||||
|
||||
const result = parser({ content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
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.",
|
||||
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,
|
||||
episode: null,
|
||||
start: '2022-03-19T23:30:00.000Z',
|
||||
stop: '2022-03-20T01:20:00.000Z'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
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"}}}'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=bt.com
|
||||
|
||||
const { parser, url } = require('./bt.com.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-03-20', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'hsxv',
|
||||
xmltv_id: 'BBCOneHD.uk'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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"}}}'
|
||||
|
||||
const result = parser({ content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
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.",
|
||||
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,
|
||||
episode: null,
|
||||
start: '2022-03-19T23:30:00.000Z',
|
||||
stop: '2022-03-20T01:20:00.000Z'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
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"}}}'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,109 +1,109 @@
|
|||
const dayjs = require('dayjs')
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'cablego.com.pe',
|
||||
days: 2,
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-requested-with': 'XMLHttpRequest'
|
||||
},
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url({ channel, date }) {
|
||||
const [page] = channel.site_id.split('#')
|
||||
|
||||
return `https://cablego.com.pe/epg/default/${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}?page=${page}&do=loadPage`
|
||||
},
|
||||
parser: function ({ content, channel, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart($item, date)
|
||||
if (!start) return
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.add(30, 'm')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const promises = [0, 1, 2, 3, 4].map(page => {
|
||||
return axios.post(
|
||||
`https://cablego.com.pe/epg/default/2022-11-28?page=${page}&do=loadPage`,
|
||||
null,
|
||||
{
|
||||
headers: {
|
||||
'x-requested-with': 'XMLHttpRequest'
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const channels = []
|
||||
await Promise.allSettled(promises).then(results => {
|
||||
results.forEach((r, page) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
const html = r.value.data.snippets['snippet--channelGrid']
|
||||
const $ = cheerio.load(html)
|
||||
$('.epg-channel-strip').each((i, el) => {
|
||||
const channelId = $(el).find('.epg-channel-logo').attr('id')
|
||||
channels.push({
|
||||
lang: 'es',
|
||||
site_id: `${page}#${channelId}`,
|
||||
name: $(el).find('img').attr('alt')
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return channels
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('span:nth-child(2) > a').text().trim()
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const time = $item('.epg-show-start').text().trim()
|
||||
|
||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'America/Lima')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !data.snippets || !data.snippets['snippet--channelGrid']) return []
|
||||
const html = data.snippets['snippet--channelGrid']
|
||||
const $ = cheerio.load(html)
|
||||
|
||||
return $(`#${channelId}`).parent().find('.epg-show').toArray()
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'cablego.com.pe',
|
||||
days: 2,
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-requested-with': 'XMLHttpRequest'
|
||||
},
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url({ channel, date }) {
|
||||
const [page] = channel.site_id.split('#')
|
||||
|
||||
return `https://cablego.com.pe/epg/default/${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}?page=${page}&do=loadPage`
|
||||
},
|
||||
parser: function ({ content, channel, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart($item, date)
|
||||
if (!start) return
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.add(30, 'm')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const promises = [0, 1, 2, 3, 4].map(page => {
|
||||
return axios.post(
|
||||
`https://cablego.com.pe/epg/default/2022-11-28?page=${page}&do=loadPage`,
|
||||
null,
|
||||
{
|
||||
headers: {
|
||||
'x-requested-with': 'XMLHttpRequest'
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const channels = []
|
||||
await Promise.allSettled(promises).then(results => {
|
||||
results.forEach((r, page) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
const html = r.value.data.snippets['snippet--channelGrid']
|
||||
const $ = cheerio.load(html)
|
||||
$('.epg-channel-strip').each((i, el) => {
|
||||
const channelId = $(el).find('.epg-channel-logo').attr('id')
|
||||
channels.push({
|
||||
lang: 'es',
|
||||
site_id: `${page}#${channelId}`,
|
||||
name: $(el).find('img').attr('alt')
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return channels
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('span:nth-child(2) > a').text().trim()
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const time = $item('.epg-show-start').text().trim()
|
||||
|
||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'America/Lima')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !data.snippets || !data.snippets['snippet--channelGrid']) return []
|
||||
const html = data.snippets['snippet--channelGrid']
|
||||
const $ = cheerio.load(html)
|
||||
|
||||
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 grab -- --site=cablego.com.pe
|
||||
|
||||
const { parser, url, request } = require('./cablego.com.pe.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-11-28', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '0#LATINA',
|
||||
xmltv_id: 'Latina.pe'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://cablego.com.pe/epg/default/2022-11-28?page=0&do=loadPage'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'x-requested-with': 'XMLHttpRequest'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
let results = parser({ content, channel, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-28T05:00:00.000Z',
|
||||
stop: '2022-11-28T06:30:00.000Z',
|
||||
title: 'Especiales Qatar'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||
const result = parser({ content, channel, date })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url, request } = require('./cablego.com.pe.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-11-28', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '0#LATINA',
|
||||
xmltv_id: 'Latina.pe'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://cablego.com.pe/epg/default/2022-11-28?page=0&do=loadPage'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'x-requested-with': 'XMLHttpRequest'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
let results = parser({ content, channel, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-28T05:00:00.000Z',
|
||||
stop: '2022-11-28T06:30:00.000Z',
|
||||
title: 'Especiales Qatar'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||
const result = parser({ content, channel, date })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,133 +1,133 @@
|
|||
const cheerio = require('cheerio')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const API_ENDPOINT = 'https://www.reportv.com.ar/finder'
|
||||
|
||||
module.exports = {
|
||||
site: 'cableplus.com.uy',
|
||||
days: 2,
|
||||
url: `${API_ENDPOINT}/channel`,
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
},
|
||||
data({ date, channel }) {
|
||||
const params = new URLSearchParams()
|
||||
params.append('idAlineacion', '3017')
|
||||
params.append('idSenial', channel.site_id)
|
||||
params.append('fecha', date.format('YYYY-MM-DD'))
|
||||
params.append('hora', '00:00')
|
||||
|
||||
return params
|
||||
}
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart($item, date)
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.add(30, 'm')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
categories: parseCategories($item),
|
||||
icon: parseIcon($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const params = new URLSearchParams({ idAlineacion: '3017' })
|
||||
const data = await axios
|
||||
.post(`${API_ENDPOINT}/channelGrid`, params, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }
|
||||
})
|
||||
.then(r => r.data)
|
||||
.catch(console.error)
|
||||
const $ = cheerio.load(data)
|
||||
|
||||
return $('.senial')
|
||||
.map(function () {
|
||||
return {
|
||||
lang: 'es',
|
||||
site_id: $(this).attr('id'),
|
||||
name: $(this).find('img').attr('alt')
|
||||
}
|
||||
})
|
||||
.get()
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('p.evento_titulo.texto_a_continuacion.dotdotdot,.programa-titulo > span:first-child')
|
||||
.text()
|
||||
.trim()
|
||||
}
|
||||
|
||||
function parseIcon($item) {
|
||||
return $item('img').data('src') || $item('img').attr('src') || null
|
||||
}
|
||||
|
||||
function parseCategories($item) {
|
||||
return $item('p.evento_genero')
|
||||
.map(function () {
|
||||
return $item(this).text().trim()
|
||||
})
|
||||
.toArray()
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
let time = $item('.grid_fecha_hora').text().trim()
|
||||
|
||||
if (time) {
|
||||
return dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD-MM HH:mm[hs.]', 'America/Montevideo')
|
||||
}
|
||||
|
||||
time = $item('.fechaHora').text().trim()
|
||||
|
||||
return time
|
||||
? dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD/MM HH:mm[hs.]', 'America/Montevideo')
|
||||
: null
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
let featuredItems = $('.vista-pc > .programacion-fila > .channel-programa')
|
||||
.filter(function () {
|
||||
return $(this).find('.grid_fecha_hora').text().indexOf(date.format('DD-MM')) > -1
|
||||
})
|
||||
.toArray()
|
||||
let otherItems = $('#owl-pc > .item-program')
|
||||
.filter(function () {
|
||||
return (
|
||||
$(this)
|
||||
.find('.evento_titulo > .horario > p.fechaHora')
|
||||
.text()
|
||||
.indexOf(date.format('DD/MM')) > -1
|
||||
)
|
||||
})
|
||||
.toArray()
|
||||
|
||||
return featuredItems.concat(otherItems)
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const API_ENDPOINT = 'https://www.reportv.com.ar/finder'
|
||||
|
||||
module.exports = {
|
||||
site: 'cableplus.com.uy',
|
||||
days: 2,
|
||||
url: `${API_ENDPOINT}/channel`,
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
},
|
||||
data({ date, channel }) {
|
||||
const params = new URLSearchParams()
|
||||
params.append('idAlineacion', '3017')
|
||||
params.append('idSenial', channel.site_id)
|
||||
params.append('fecha', date.format('YYYY-MM-DD'))
|
||||
params.append('hora', '00:00')
|
||||
|
||||
return params
|
||||
}
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart($item, date)
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.add(30, 'm')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
categories: parseCategories($item),
|
||||
icon: parseIcon($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const params = new URLSearchParams({ idAlineacion: '3017' })
|
||||
const data = await axios
|
||||
.post(`${API_ENDPOINT}/channelGrid`, params, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }
|
||||
})
|
||||
.then(r => r.data)
|
||||
.catch(console.error)
|
||||
const $ = cheerio.load(data)
|
||||
|
||||
return $('.senial')
|
||||
.map(function () {
|
||||
return {
|
||||
lang: 'es',
|
||||
site_id: $(this).attr('id'),
|
||||
name: $(this).find('img').attr('alt')
|
||||
}
|
||||
})
|
||||
.get()
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('p.evento_titulo.texto_a_continuacion.dotdotdot,.programa-titulo > span:first-child')
|
||||
.text()
|
||||
.trim()
|
||||
}
|
||||
|
||||
function parseIcon($item) {
|
||||
return $item('img').data('src') || $item('img').attr('src') || null
|
||||
}
|
||||
|
||||
function parseCategories($item) {
|
||||
return $item('p.evento_genero')
|
||||
.map(function () {
|
||||
return $item(this).text().trim()
|
||||
})
|
||||
.toArray()
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
let time = $item('.grid_fecha_hora').text().trim()
|
||||
|
||||
if (time) {
|
||||
return dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD-MM HH:mm[hs.]', 'America/Montevideo')
|
||||
}
|
||||
|
||||
time = $item('.fechaHora').text().trim()
|
||||
|
||||
return time
|
||||
? dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD/MM HH:mm[hs.]', 'America/Montevideo')
|
||||
: null
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
let featuredItems = $('.vista-pc > .programacion-fila > .channel-programa')
|
||||
.filter(function () {
|
||||
return $(this).find('.grid_fecha_hora').text().indexOf(date.format('DD-MM')) > -1
|
||||
})
|
||||
.toArray()
|
||||
let otherItems = $('#owl-pc > .item-program')
|
||||
.filter(function () {
|
||||
return (
|
||||
$(this)
|
||||
.find('.evento_titulo > .horario > p.fechaHora')
|
||||
.text()
|
||||
.indexOf(date.format('DD/MM')) > -1
|
||||
)
|
||||
})
|
||||
.toArray()
|
||||
|
||||
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 grab -- --site=cableplus.com.uy
|
||||
|
||||
const { parser, url, request } = require('./cableplus.com.uy.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-02-12', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '2035',
|
||||
xmltv_id: 'APlusV.uy'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://www.reportv.com.ar/finder/channel')
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
const params = request.data({ date, channel })
|
||||
|
||||
expect(params.get('idAlineacion')).toBe('3017')
|
||||
expect(params.get('idSenial')).toBe('2035')
|
||||
expect(params.get('fecha')).toBe('2023-02-12')
|
||||
expect(params.get('hora')).toBe('00:00')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||
let results = parser({ content, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results.length).toBe(21)
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-02-12T09:30:00.000Z',
|
||||
stop: '2023-02-12T10:30:00.000Z',
|
||||
title: 'Revista agropecuaria',
|
||||
icon: 'https://www.reportv.com.ar/buscador/img/Programas/2797844.jpg',
|
||||
categories: []
|
||||
})
|
||||
|
||||
expect(results[4]).toMatchObject({
|
||||
start: '2023-02-12T12:30:00.000Z',
|
||||
stop: '2023-02-12T13:30:00.000Z',
|
||||
title: 'De pago en pago',
|
||||
icon: 'https://www.reportv.com.ar/buscador/img/Programas/3772835.jpg',
|
||||
categories: ['Cultural']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url, request } = require('./cableplus.com.uy.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-02-12', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '2035',
|
||||
xmltv_id: 'APlusV.uy'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://www.reportv.com.ar/finder/channel')
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
const params = request.data({ date, channel })
|
||||
|
||||
expect(params.get('idAlineacion')).toBe('3017')
|
||||
expect(params.get('idSenial')).toBe('2035')
|
||||
expect(params.get('fecha')).toBe('2023-02-12')
|
||||
expect(params.get('hora')).toBe('00:00')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||
let results = parser({ content, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results.length).toBe(21)
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-02-12T09:30:00.000Z',
|
||||
stop: '2023-02-12T10:30:00.000Z',
|
||||
title: 'Revista agropecuaria',
|
||||
icon: 'https://www.reportv.com.ar/buscador/img/Programas/2797844.jpg',
|
||||
categories: []
|
||||
})
|
||||
|
||||
expect(results[4]).toMatchObject({
|
||||
start: '2023-02-12T12:30:00.000Z',
|
||||
stop: '2023-02-12T13:30:00.000Z',
|
||||
title: 'De pago en pago',
|
||||
icon: 'https://www.reportv.com.ar/buscador/img/Programas/3772835.jpg',
|
||||
categories: ['Cultural']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,93 +1,93 @@
|
|||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'canalplus-caraibes.com',
|
||||
days: 2,
|
||||
url: function ({ channel, date }) {
|
||||
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}`
|
||||
},
|
||||
async parser({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
for (let item of items) {
|
||||
if (item.title === 'Fin des programmes') return
|
||||
const detail = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: parseDescription(detail),
|
||||
category: parseCategory(detail),
|
||||
icon: parseIcon(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const html = await axios
|
||||
.get('https://www.canalplus-caraibes.com/bl/guide-tv-ce-soir')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
const $ = cheerio.load(html)
|
||||
const script = $('body > script:nth-child(2)').html()
|
||||
const [, json] = script.match(/window.APP_STATE=(.*);/) || [null, null]
|
||||
const data = JSON.parse(json)
|
||||
const items = data.tvGuide.channels.byZapNumber
|
||||
|
||||
return Object.values(items).map(item => {
|
||||
return {
|
||||
lang: 'fr',
|
||||
site_id: item.epgID,
|
||||
name: item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.onClick.URLPage) return {}
|
||||
const url = item.onClick.URLPage
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseDescription(detail) {
|
||||
return detail.detail.informations.summary || null
|
||||
}
|
||||
|
||||
function parseCategory(detail) {
|
||||
return detail.detail.informations.subGenre || null
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.URLImage || item.URLImageDefault
|
||||
}
|
||||
function parseStart(item) {
|
||||
return dayjs.unix(item.startTime)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.unix(item.endTime)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !data.timeSlices) return []
|
||||
const items = data.timeSlices.reduce((acc, curr) => {
|
||||
acc = acc.concat(curr.contents)
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
return items
|
||||
}
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'canalplus-caraibes.com',
|
||||
days: 2,
|
||||
url: function ({ channel, date }) {
|
||||
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}`
|
||||
},
|
||||
async parser({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
for (let item of items) {
|
||||
if (item.title === 'Fin des programmes') return
|
||||
const detail = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: parseDescription(detail),
|
||||
category: parseCategory(detail),
|
||||
icon: parseIcon(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const html = await axios
|
||||
.get('https://www.canalplus-caraibes.com/bl/guide-tv-ce-soir')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
const $ = cheerio.load(html)
|
||||
const script = $('body > script:nth-child(2)').html()
|
||||
const [, json] = script.match(/window.APP_STATE=(.*);/) || [null, null]
|
||||
const data = JSON.parse(json)
|
||||
const items = data.tvGuide.channels.byZapNumber
|
||||
|
||||
return Object.values(items).map(item => {
|
||||
return {
|
||||
lang: 'fr',
|
||||
site_id: item.epgID,
|
||||
name: item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.onClick.URLPage) return {}
|
||||
const url = item.onClick.URLPage
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseDescription(detail) {
|
||||
return detail.detail.informations.summary || null
|
||||
}
|
||||
|
||||
function parseCategory(detail) {
|
||||
return detail.detail.informations.subGenre || null
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.URLImage || item.URLImageDefault
|
||||
}
|
||||
function parseStart(item) {
|
||||
return dayjs.unix(item.startTime)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.unix(item.endTime)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !data.timeSlices) return []
|
||||
const items = data.timeSlices.reduce((acc, curr) => {
|
||||
acc = acc.concat(curr.contents)
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
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
|
||||
// npm run grab -- --site=canalplus-caraibes.com
|
||||
|
||||
const { parser, url } = require('./canalplus-caraibes.com.config.js')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const channel = {
|
||||
site_id: '50115',
|
||||
xmltv_id: 'beINSports1France.fr'
|
||||
}
|
||||
|
||||
it('can generate valid url for today', () => {
|
||||
const date = dayjs.utc().startOf('d')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/53001/channel/50115/events?filter.day=0'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid url for tomorrow', () => {
|
||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/53001/channel/50115/events?filter.day=1'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
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"}]}'
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/53001/event/140377765') {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(`{
|
||||
"currentPage": {
|
||||
"displayName": "Rugby - Leinster / La Rochelle",
|
||||
"displayTemplate": "detailPage",
|
||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53001/program/224515801/recommendations"
|
||||
},
|
||||
"detail": {
|
||||
"informations": {
|
||||
"programmeType": "EPG",
|
||||
"isInOffer": false,
|
||||
"isInOfferOnDevice": false,
|
||||
"isInOfferForD2G": false,
|
||||
"availableInVoDOnDevice": false,
|
||||
"availableInVoDOnG5": false,
|
||||
"availableInD2GOnDevice": false,
|
||||
"availableInLiveOnDevice": false,
|
||||
"rediffusions": true,
|
||||
"canBeRecorded": false,
|
||||
"channelName": "BEIN SPORTS 1 HD",
|
||||
"startTime": 1660815000,
|
||||
"endTime": 1660816800,
|
||||
"title": "Rugby - Leinster / La Rochelle",
|
||||
"subtitle": "Rugby",
|
||||
"thirdTitle": "BEIN SPORTS 1 HD",
|
||||
"genre": "Sport",
|
||||
"subGenre": "Rugby",
|
||||
"editorialTitle": "Sport, France, 0h30",
|
||||
"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.",
|
||||
"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,
|
||||
"sharingURL": "https://www.canalplus-caraibes.com/grille-tv/event/140377765-rugby-leinster-la-rochelle.html",
|
||||
"EpgId": 50115,
|
||||
"CSA": 1,
|
||||
"HD": false,
|
||||
"3D": false,
|
||||
"diffusionID": "140377765",
|
||||
"duration": "1800",
|
||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/75fca4586fdc3458930dd1ab6fc2e643",
|
||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/7854e20fb6efecd398598653c57cc771",
|
||||
"URLLogo": "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"
|
||||
},
|
||||
"diffusions": [
|
||||
{
|
||||
"diffusionDateUTC": 1660815000,
|
||||
"sharingUrl": "https://www.canalplus-caraibes.com/grille-tv/event/140377765-rugby-leinster-la-rochelle.html",
|
||||
"broadcastId": "140377765",
|
||||
"name": "BEIN SPORTS 1 HD",
|
||||
"epgID": "50115",
|
||||
"ZapNumber": "191",
|
||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e",
|
||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content })
|
||||
.then(result => {
|
||||
result = result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-08-18T09:30:00.000Z',
|
||||
stop: '2022-08-18T10:00:00.000Z',
|
||||
title: 'Rugby - Leinster / La Rochelle',
|
||||
icon: 'https://service.canal-overseas.com/image-api/v1/image/7854e20fb6efecd398598653c57cc771',
|
||||
category: 'Rugby',
|
||||
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."
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
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}'
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
// [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
|
||||
|
||||
const { parser, url } = require('./canalplus-caraibes.com.config.js')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const channel = {
|
||||
site_id: '50115',
|
||||
xmltv_id: 'beINSports1France.fr'
|
||||
}
|
||||
|
||||
it('can generate valid url for today', () => {
|
||||
const date = dayjs.utc().startOf('d')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/53001/channel/50115/events?filter.day=0'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid url for tomorrow', () => {
|
||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/53001/channel/50115/events?filter.day=1'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
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"}]}'
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/53001/event/140377765') {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(`{
|
||||
"currentPage": {
|
||||
"displayName": "Rugby - Leinster / La Rochelle",
|
||||
"displayTemplate": "detailPage",
|
||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53001/program/224515801/recommendations"
|
||||
},
|
||||
"detail": {
|
||||
"informations": {
|
||||
"programmeType": "EPG",
|
||||
"isInOffer": false,
|
||||
"isInOfferOnDevice": false,
|
||||
"isInOfferForD2G": false,
|
||||
"availableInVoDOnDevice": false,
|
||||
"availableInVoDOnG5": false,
|
||||
"availableInD2GOnDevice": false,
|
||||
"availableInLiveOnDevice": false,
|
||||
"rediffusions": true,
|
||||
"canBeRecorded": false,
|
||||
"channelName": "BEIN SPORTS 1 HD",
|
||||
"startTime": 1660815000,
|
||||
"endTime": 1660816800,
|
||||
"title": "Rugby - Leinster / La Rochelle",
|
||||
"subtitle": "Rugby",
|
||||
"thirdTitle": "BEIN SPORTS 1 HD",
|
||||
"genre": "Sport",
|
||||
"subGenre": "Rugby",
|
||||
"editorialTitle": "Sport, France, 0h30",
|
||||
"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.",
|
||||
"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,
|
||||
"sharingURL": "https://www.canalplus-caraibes.com/grille-tv/event/140377765-rugby-leinster-la-rochelle.html",
|
||||
"EpgId": 50115,
|
||||
"CSA": 1,
|
||||
"HD": false,
|
||||
"3D": false,
|
||||
"diffusionID": "140377765",
|
||||
"duration": "1800",
|
||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/75fca4586fdc3458930dd1ab6fc2e643",
|
||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/7854e20fb6efecd398598653c57cc771",
|
||||
"URLLogo": "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"
|
||||
},
|
||||
"diffusions": [
|
||||
{
|
||||
"diffusionDateUTC": 1660815000,
|
||||
"sharingUrl": "https://www.canalplus-caraibes.com/grille-tv/event/140377765-rugby-leinster-la-rochelle.html",
|
||||
"broadcastId": "140377765",
|
||||
"name": "BEIN SPORTS 1 HD",
|
||||
"epgID": "50115",
|
||||
"ZapNumber": "191",
|
||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e",
|
||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/4e121baf92f46b2df622c6d4f9cebf8e"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content })
|
||||
.then(result => {
|
||||
result = result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-08-18T09:30:00.000Z',
|
||||
stop: '2022-08-18T10:00:00.000Z',
|
||||
title: 'Rugby - Leinster / La Rochelle',
|
||||
icon: 'https://service.canal-overseas.com/image-api/v1/image/7854e20fb6efecd398598653c57cc771',
|
||||
category: 'Rugby',
|
||||
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."
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
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}'
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
|
|
@ -1,94 +1,94 @@
|
|||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'canalplus-haiti.com',
|
||||
days: 2,
|
||||
url: function ({ channel, date }) {
|
||||
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}`
|
||||
},
|
||||
async parser({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
for (let item of items) {
|
||||
if (item.title === 'Fin des programmes') return
|
||||
const detail = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: parseDescription(detail),
|
||||
category: parseCategory(detail),
|
||||
icon: parseIcon(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const html = await axios
|
||||
.get('https://www.canalplus-haiti.com/guide-tv-ce-soir')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
const $ = cheerio.load(html)
|
||||
const script = $('body > script:nth-child(2)').html()
|
||||
const [, json] = script.match(/window.APP_STATE=(.*);/) || [null, null]
|
||||
const data = JSON.parse(json)
|
||||
const items = data.tvGuide.channels.byZapNumber
|
||||
|
||||
return Object.values(items).map(item => {
|
||||
return {
|
||||
lang: 'fr',
|
||||
site_id: item.epgID,
|
||||
name: item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.onClick.URLPage) return {}
|
||||
const url = item.onClick.URLPage
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseDescription(detail) {
|
||||
return detail.detail.informations.summary || null
|
||||
}
|
||||
|
||||
function parseCategory(detail) {
|
||||
return detail.detail.informations.subGenre || null
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.URLImage || item.URLImageDefault
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.unix(item.startTime)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.unix(item.endTime)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !data.timeSlices) return []
|
||||
const items = data.timeSlices.reduce((acc, curr) => {
|
||||
acc = acc.concat(curr.contents)
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
return items
|
||||
}
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'canalplus-haiti.com',
|
||||
days: 2,
|
||||
url: function ({ channel, date }) {
|
||||
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}`
|
||||
},
|
||||
async parser({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
for (let item of items) {
|
||||
if (item.title === 'Fin des programmes') return
|
||||
const detail = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: parseDescription(detail),
|
||||
category: parseCategory(detail),
|
||||
icon: parseIcon(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const html = await axios
|
||||
.get('https://www.canalplus-haiti.com/guide-tv-ce-soir')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
const $ = cheerio.load(html)
|
||||
const script = $('body > script:nth-child(2)').html()
|
||||
const [, json] = script.match(/window.APP_STATE=(.*);/) || [null, null]
|
||||
const data = JSON.parse(json)
|
||||
const items = data.tvGuide.channels.byZapNumber
|
||||
|
||||
return Object.values(items).map(item => {
|
||||
return {
|
||||
lang: 'fr',
|
||||
site_id: item.epgID,
|
||||
name: item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.onClick.URLPage) return {}
|
||||
const url = item.onClick.URLPage
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseDescription(detail) {
|
||||
return detail.detail.informations.summary || null
|
||||
}
|
||||
|
||||
function parseCategory(detail) {
|
||||
return detail.detail.informations.subGenre || null
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.URLImage || item.URLImageDefault
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.unix(item.startTime)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.unix(item.endTime)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !data.timeSlices) return []
|
||||
const items = data.timeSlices.reduce((acc, curr) => {
|
||||
acc = acc.concat(curr.contents)
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
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
|
||||
// npm run grab -- --site=canalplus-haiti.com
|
||||
|
||||
const { parser, url } = require('./canalplus-haiti.com.config.js')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const channel = {
|
||||
site_id: '51006',
|
||||
xmltv_id: 'ViaATV.mq'
|
||||
}
|
||||
|
||||
it('can generate valid url for today', () => {
|
||||
const date = dayjs.utc().startOf('d')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/53101/channel/51006/events?filter.day=0'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid url for tomorrow', () => {
|
||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/53101/channel/51006/events?filter.day=1'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
const content = `{
|
||||
"timeSlices": [
|
||||
{
|
||||
"contents": [
|
||||
{
|
||||
"title": "New Amsterdam - S3 - Ep7",
|
||||
"subtitle": "Episode 7 - Le mur de la honte",
|
||||
"thirdTitle": "viaATV",
|
||||
"startTime": 1660780500,
|
||||
"endTime": 1660783200,
|
||||
"onClick": {
|
||||
"displayTemplate": "miniDetail",
|
||||
"displayName": "New Amsterdam - S3 - Ep7",
|
||||
"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"
|
||||
},
|
||||
"programID": 187882282,
|
||||
"diffusionID": "140952809",
|
||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/generic",
|
||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e"
|
||||
}
|
||||
],
|
||||
"timeSlice": "2"
|
||||
}
|
||||
]
|
||||
}`
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/53101/event/140952809') {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(`{
|
||||
"currentPage": {
|
||||
"displayName": "New Amsterdam - S3 - Ep7",
|
||||
"displayTemplate": "detailPage",
|
||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53101/program/187882282/recommendations"
|
||||
},
|
||||
"detail": {
|
||||
"informations": {
|
||||
"programmeType": "EPG",
|
||||
"isInOffer": false,
|
||||
"isInOfferOnDevice": false,
|
||||
"isInOfferForD2G": false,
|
||||
"availableInVoDOnDevice": false,
|
||||
"availableInVoDOnG5": false,
|
||||
"availableInD2GOnDevice": false,
|
||||
"availableInLiveOnDevice": false,
|
||||
"rediffusions": true,
|
||||
"canBeRecorded": false,
|
||||
"channelName": "viaATV",
|
||||
"startTime": 1660780500,
|
||||
"endTime": 1660783200,
|
||||
"title": "New Amsterdam - S3 - Ep7",
|
||||
"subtitle": "Episode 7 - Le mur de la honte",
|
||||
"thirdTitle": "viaATV",
|
||||
"genre": "Séries",
|
||||
"subGenre": "Série Hôpital",
|
||||
"editorialTitle": "Séries, Etats-Unis, 2020, 0h45",
|
||||
"audioLanguage": "VF",
|
||||
"personnalities": [
|
||||
{
|
||||
"prefix": "De :",
|
||||
"content": "Darnell Martin"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"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.",
|
||||
"programID": 187882282,
|
||||
"sharingURL": "https://www.canalplus-haiti.com/grille-tv/event/140952809-new-amsterdam-s3-ep7.html",
|
||||
"labels": {
|
||||
"allocine": false,
|
||||
"telerama": false,
|
||||
"sensCritique": false
|
||||
},
|
||||
"EpgId": 51006,
|
||||
"CSA": 1,
|
||||
"HD": false,
|
||||
"3D": false,
|
||||
"diffusionID": "140952809",
|
||||
"duration": "2700",
|
||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/generic",
|
||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e",
|
||||
"URLLogo": "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"
|
||||
},
|
||||
"diffusions": [
|
||||
{
|
||||
"diffusionDateUTC": 1660780500,
|
||||
"sharingUrl": "https://www.canalplus-haiti.com/grille-tv/event/140952809-new-amsterdam.html",
|
||||
"broadcastId": "140952809",
|
||||
"name": "viaATV",
|
||||
"epgID": "51006",
|
||||
"ZapNumber": "28",
|
||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce",
|
||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content })
|
||||
.then(result => {
|
||||
result = result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-08-17T23:55:00.000Z',
|
||||
stop: '2022-08-18T00:40:00.000Z',
|
||||
title: 'New Amsterdam - S3 - Ep7',
|
||||
icon: 'https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e',
|
||||
category: 'Série Hôpital',
|
||||
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."
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
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}'
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
// [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
|
||||
|
||||
const { parser, url } = require('./canalplus-haiti.com.config.js')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const channel = {
|
||||
site_id: '51006',
|
||||
xmltv_id: 'ViaATV.mq'
|
||||
}
|
||||
|
||||
it('can generate valid url for today', () => {
|
||||
const date = dayjs.utc().startOf('d')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/53101/channel/51006/events?filter.day=0'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid url for tomorrow', () => {
|
||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/53101/channel/51006/events?filter.day=1'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
const content = `{
|
||||
"timeSlices": [
|
||||
{
|
||||
"contents": [
|
||||
{
|
||||
"title": "New Amsterdam - S3 - Ep7",
|
||||
"subtitle": "Episode 7 - Le mur de la honte",
|
||||
"thirdTitle": "viaATV",
|
||||
"startTime": 1660780500,
|
||||
"endTime": 1660783200,
|
||||
"onClick": {
|
||||
"displayTemplate": "miniDetail",
|
||||
"displayName": "New Amsterdam - S3 - Ep7",
|
||||
"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"
|
||||
},
|
||||
"programID": 187882282,
|
||||
"diffusionID": "140952809",
|
||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/generic",
|
||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e"
|
||||
}
|
||||
],
|
||||
"timeSlice": "2"
|
||||
}
|
||||
]
|
||||
}`
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/53101/event/140952809') {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(`{
|
||||
"currentPage": {
|
||||
"displayName": "New Amsterdam - S3 - Ep7",
|
||||
"displayTemplate": "detailPage",
|
||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/53101/program/187882282/recommendations"
|
||||
},
|
||||
"detail": {
|
||||
"informations": {
|
||||
"programmeType": "EPG",
|
||||
"isInOffer": false,
|
||||
"isInOfferOnDevice": false,
|
||||
"isInOfferForD2G": false,
|
||||
"availableInVoDOnDevice": false,
|
||||
"availableInVoDOnG5": false,
|
||||
"availableInD2GOnDevice": false,
|
||||
"availableInLiveOnDevice": false,
|
||||
"rediffusions": true,
|
||||
"canBeRecorded": false,
|
||||
"channelName": "viaATV",
|
||||
"startTime": 1660780500,
|
||||
"endTime": 1660783200,
|
||||
"title": "New Amsterdam - S3 - Ep7",
|
||||
"subtitle": "Episode 7 - Le mur de la honte",
|
||||
"thirdTitle": "viaATV",
|
||||
"genre": "Séries",
|
||||
"subGenre": "Série Hôpital",
|
||||
"editorialTitle": "Séries, Etats-Unis, 2020, 0h45",
|
||||
"audioLanguage": "VF",
|
||||
"personnalities": [
|
||||
{
|
||||
"prefix": "De :",
|
||||
"content": "Darnell Martin"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"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.",
|
||||
"programID": 187882282,
|
||||
"sharingURL": "https://www.canalplus-haiti.com/grille-tv/event/140952809-new-amsterdam-s3-ep7.html",
|
||||
"labels": {
|
||||
"allocine": false,
|
||||
"telerama": false,
|
||||
"sensCritique": false
|
||||
},
|
||||
"EpgId": 51006,
|
||||
"CSA": 1,
|
||||
"HD": false,
|
||||
"3D": false,
|
||||
"diffusionID": "140952809",
|
||||
"duration": "2700",
|
||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/generic",
|
||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e",
|
||||
"URLLogo": "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"
|
||||
},
|
||||
"diffusions": [
|
||||
{
|
||||
"diffusionDateUTC": 1660780500,
|
||||
"sharingUrl": "https://www.canalplus-haiti.com/grille-tv/event/140952809-new-amsterdam.html",
|
||||
"broadcastId": "140952809",
|
||||
"name": "viaATV",
|
||||
"epgID": "51006",
|
||||
"ZapNumber": "28",
|
||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce",
|
||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/0f67b2e85f74101c4c776cf423240fce"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content })
|
||||
.then(result => {
|
||||
result = result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-08-17T23:55:00.000Z',
|
||||
stop: '2022-08-18T00:40:00.000Z',
|
||||
title: 'New Amsterdam - S3 - Ep7',
|
||||
icon: 'https://service.canal-overseas.com/image-api/v1/image/52a18a209e28380b199201961c27097e',
|
||||
category: 'Série Hôpital',
|
||||
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."
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
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}'
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
|
|
@ -1,72 +1,72 @@
|
|||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'canalplus-reunion.com',
|
||||
days: 2,
|
||||
url: function ({ channel, date }) {
|
||||
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}`
|
||||
},
|
||||
async parser({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
for (let item of items) {
|
||||
if (item.title === 'Fin des programmes') return
|
||||
const detail = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: parseDescription(detail),
|
||||
category: parseCategory(detail),
|
||||
icon: parseIcon(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.onClick.URLPage) return {}
|
||||
const url = item.onClick.URLPage
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseDescription(detail) {
|
||||
return detail.detail.informations.summary || null
|
||||
}
|
||||
|
||||
function parseCategory(detail) {
|
||||
return detail.detail.informations.subGenre || null
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.URLImage || item.URLImageDefault
|
||||
}
|
||||
function parseStart(item) {
|
||||
return dayjs.unix(item.startTime)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.unix(item.endTime)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !data.timeSlices) return []
|
||||
const items = data.timeSlices.reduce((acc, curr) => {
|
||||
acc = acc.concat(curr.contents)
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
return items
|
||||
}
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'canalplus-reunion.com',
|
||||
days: 2,
|
||||
url: function ({ channel, date }) {
|
||||
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}`
|
||||
},
|
||||
async parser({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
for (let item of items) {
|
||||
if (item.title === 'Fin des programmes') return
|
||||
const detail = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: parseDescription(detail),
|
||||
category: parseCategory(detail),
|
||||
icon: parseIcon(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.onClick.URLPage) return {}
|
||||
const url = item.onClick.URLPage
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseDescription(detail) {
|
||||
return detail.detail.informations.summary || null
|
||||
}
|
||||
|
||||
function parseCategory(detail) {
|
||||
return detail.detail.informations.subGenre || null
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.URLImage || item.URLImageDefault
|
||||
}
|
||||
function parseStart(item) {
|
||||
return dayjs.unix(item.startTime)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.unix(item.endTime)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !data.timeSlices) return []
|
||||
const items = data.timeSlices.reduce((acc, curr) => {
|
||||
acc = acc.concat(curr.contents)
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
return items
|
||||
}
|
||||
|
|
|
@ -1,160 +1,160 @@
|
|||
// npm run grab -- --site=canalplus-reunion.com
|
||||
|
||||
const { parser, url } = require('./canalplus-reunion.com.config.js')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const channel = {
|
||||
site_id: '60243',
|
||||
xmltv_id: 'beINSports2France.fr'
|
||||
}
|
||||
|
||||
it('can generate valid url for today', () => {
|
||||
const date = dayjs.utc().startOf('d')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/63001/channel/60243/events?filter.day=0'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid url for tomorrow', () => {
|
||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/63001/channel/60243/events?filter.day=1'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
const content = `{
|
||||
"timeSlices": [
|
||||
{
|
||||
"contents": [
|
||||
{
|
||||
"title": "Almeria / Real Madrid",
|
||||
"subtitle": "Football",
|
||||
"thirdTitle": "BEIN SPORTS 2 HD",
|
||||
"startTime": 1660780800,
|
||||
"endTime": 1660788000,
|
||||
"onClick": {
|
||||
"displayTemplate": "miniDetail",
|
||||
"displayName": "Almeria / Real Madrid",
|
||||
"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"
|
||||
},
|
||||
"programID": 224523053,
|
||||
"diffusionID": "140382363",
|
||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/a6b640e16608ffa3d862e2bd8a4b3e4c",
|
||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20"
|
||||
}
|
||||
],
|
||||
"timeSlice": "4"
|
||||
}
|
||||
]
|
||||
}`
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/63001/event/140382363') {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(`{
|
||||
"currentPage": {
|
||||
"displayName": "Almeria / Real Madrid",
|
||||
"displayTemplate": "detailPage",
|
||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/63001/program/224523053/recommendations"
|
||||
},
|
||||
"detail": {
|
||||
"informations": {
|
||||
"programmeType": "EPG",
|
||||
"isInOffer": false,
|
||||
"isInOfferOnDevice": false,
|
||||
"isInOfferForD2G": false,
|
||||
"availableInVoDOnDevice": false,
|
||||
"availableInVoDOnG5": false,
|
||||
"availableInD2GOnDevice": false,
|
||||
"availableInLiveOnDevice": false,
|
||||
"rediffusions": true,
|
||||
"canBeRecorded": false,
|
||||
"channelName": "BEIN SPORTS 2 HD",
|
||||
"startTime": 1660780800,
|
||||
"endTime": 1660788000,
|
||||
"title": "Almeria / Real Madrid",
|
||||
"subtitle": "Football",
|
||||
"thirdTitle": "BEIN SPORTS 2 HD",
|
||||
"genre": "Sport",
|
||||
"subGenre": "Football",
|
||||
"editorialTitle": "Sport, Espagne, 2h00",
|
||||
"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.",
|
||||
"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,
|
||||
"sharingURL": "https://www.canalplus-reunion.com/grille-tv/event/140382363-almeria-real-madrid.html",
|
||||
"EpgId": 60243,
|
||||
"CSA": 1,
|
||||
"HD": false,
|
||||
"3D": false,
|
||||
"diffusionID": "140382363",
|
||||
"duration": "7200",
|
||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/a6b640e16608ffa3d862e2bd8a4b3e4c",
|
||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20",
|
||||
"URLLogo": "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"
|
||||
},
|
||||
"diffusions": [
|
||||
{
|
||||
"diffusionDateUTC": 1660780800,
|
||||
"sharingUrl": "https://www.canalplus-reunion.com/grille-tv/event/140382363-almeria-real-madrid.html",
|
||||
"broadcastId": "140382363",
|
||||
"name": "BEIN SPORTS 2 HD",
|
||||
"epgID": "60243",
|
||||
"ZapNumber": "96",
|
||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9",
|
||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content })
|
||||
.then(result => {
|
||||
result = result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-08-18T00:00:00.000Z',
|
||||
stop: '2022-08-18T02:00:00.000Z',
|
||||
title: 'Almeria / Real Madrid',
|
||||
icon: 'https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20',
|
||||
category: 'Football',
|
||||
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."
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
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}'
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
// npm run grab -- --site=canalplus-reunion.com
|
||||
|
||||
const { parser, url } = require('./canalplus-reunion.com.config.js')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const channel = {
|
||||
site_id: '60243',
|
||||
xmltv_id: 'beINSports2France.fr'
|
||||
}
|
||||
|
||||
it('can generate valid url for today', () => {
|
||||
const date = dayjs.utc().startOf('d')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/63001/channel/60243/events?filter.day=0'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid url for tomorrow', () => {
|
||||
const date = dayjs.utc().startOf('d').add(1, 'd')
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://service.canal-overseas.com/ott-frontend/vector/63001/channel/60243/events?filter.day=1'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
const content = `{
|
||||
"timeSlices": [
|
||||
{
|
||||
"contents": [
|
||||
{
|
||||
"title": "Almeria / Real Madrid",
|
||||
"subtitle": "Football",
|
||||
"thirdTitle": "BEIN SPORTS 2 HD",
|
||||
"startTime": 1660780800,
|
||||
"endTime": 1660788000,
|
||||
"onClick": {
|
||||
"displayTemplate": "miniDetail",
|
||||
"displayName": "Almeria / Real Madrid",
|
||||
"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"
|
||||
},
|
||||
"programID": 224523053,
|
||||
"diffusionID": "140382363",
|
||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/a6b640e16608ffa3d862e2bd8a4b3e4c",
|
||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20"
|
||||
}
|
||||
],
|
||||
"timeSlice": "4"
|
||||
}
|
||||
]
|
||||
}`
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://service.canal-overseas.com/ott-frontend/vector/63001/event/140382363') {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(`{
|
||||
"currentPage": {
|
||||
"displayName": "Almeria / Real Madrid",
|
||||
"displayTemplate": "detailPage",
|
||||
"URLVitrine": "https://service.canal-overseas.com/ott-frontend/vector/63001/program/224523053/recommendations"
|
||||
},
|
||||
"detail": {
|
||||
"informations": {
|
||||
"programmeType": "EPG",
|
||||
"isInOffer": false,
|
||||
"isInOfferOnDevice": false,
|
||||
"isInOfferForD2G": false,
|
||||
"availableInVoDOnDevice": false,
|
||||
"availableInVoDOnG5": false,
|
||||
"availableInD2GOnDevice": false,
|
||||
"availableInLiveOnDevice": false,
|
||||
"rediffusions": true,
|
||||
"canBeRecorded": false,
|
||||
"channelName": "BEIN SPORTS 2 HD",
|
||||
"startTime": 1660780800,
|
||||
"endTime": 1660788000,
|
||||
"title": "Almeria / Real Madrid",
|
||||
"subtitle": "Football",
|
||||
"thirdTitle": "BEIN SPORTS 2 HD",
|
||||
"genre": "Sport",
|
||||
"subGenre": "Football",
|
||||
"editorialTitle": "Sport, Espagne, 2h00",
|
||||
"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.",
|
||||
"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,
|
||||
"sharingURL": "https://www.canalplus-reunion.com/grille-tv/event/140382363-almeria-real-madrid.html",
|
||||
"EpgId": 60243,
|
||||
"CSA": 1,
|
||||
"HD": false,
|
||||
"3D": false,
|
||||
"diffusionID": "140382363",
|
||||
"duration": "7200",
|
||||
"URLImageDefault": "https://service.canal-overseas.com/image-api/v1/image/a6b640e16608ffa3d862e2bd8a4b3e4c",
|
||||
"URLImage": "https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20",
|
||||
"URLLogo": "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"
|
||||
},
|
||||
"diffusions": [
|
||||
{
|
||||
"diffusionDateUTC": 1660780800,
|
||||
"sharingUrl": "https://www.canalplus-reunion.com/grille-tv/event/140382363-almeria-real-madrid.html",
|
||||
"broadcastId": "140382363",
|
||||
"name": "BEIN SPORTS 2 HD",
|
||||
"epgID": "60243",
|
||||
"ZapNumber": "96",
|
||||
"URLLogo": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9",
|
||||
"URLLogoBlack": "https://service.canal-overseas.com/image-api/v1/image/6e2124827406ed41236a8430352d4ed9"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content })
|
||||
.then(result => {
|
||||
result = result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-08-18T00:00:00.000Z',
|
||||
stop: '2022-08-18T02:00:00.000Z',
|
||||
title: 'Almeria / Real Madrid',
|
||||
icon: 'https://service.canal-overseas.com/image-api/v1/image/47000149dabce60d1769589c766aad20',
|
||||
category: 'Football',
|
||||
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."
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
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}'
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
|
|
@ -1,185 +1,185 @@
|
|||
const dayjs = require('dayjs')
|
||||
const axios = require('axios')
|
||||
|
||||
module.exports = {
|
||||
site: 'canalplus.com',
|
||||
days: 2,
|
||||
url: async function ({ channel, date }) {
|
||||
const [region, site_id] = channel.site_id.split('#')
|
||||
const data = await axios
|
||||
.get(`https://www.canalplus.com/${region}/programme-tv/`)
|
||||
.then(r => r.data.toString())
|
||||
.catch(err => console.log(err))
|
||||
const token = parseToken(data)
|
||||
|
||||
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
||||
|
||||
return `https://hodor.canalplus.pro/api/v2/mycanal/channels/${token}/${site_id}/broadcasts/day/${diff}`
|
||||
},
|
||||
async parser({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
for (let item of items) {
|
||||
const prev = programs[programs.length - 1]
|
||||
const details = await loadProgramDetails(item)
|
||||
const info = parseInfo(details)
|
||||
const start = parseStart(item)
|
||||
if (prev) prev.stop = start
|
||||
const stop = start.add(1, 'h')
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: parseDescription(info),
|
||||
icon: parseIcon(info),
|
||||
actors: parseCast(info, 'Avec :'),
|
||||
director: parseCast(info, 'De :'),
|
||||
writer: parseCast(info, 'Scénario :'),
|
||||
composer: parseCast(info, 'Musique :'),
|
||||
presenter: parseCast(info, 'Présenté par :'),
|
||||
date: parseDate(info),
|
||||
rating: parseRating(info),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const endpoints = {
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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'
|
||||
}
|
||||
|
||||
let channels = []
|
||||
for (let [region, url] of Object.entries(endpoints)) {
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
data.channels.forEach(channel => {
|
||||
const site_id = region === 'fr' ? `#${channel.id}` : `${region}#${channel.id}`
|
||||
|
||||
if (channel.name === '.') return
|
||||
|
||||
channels.push({
|
||||
lang: 'fr',
|
||||
site_id,
|
||||
name: channel.name
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
}
|
||||
|
||||
function parseToken(data) {
|
||||
const [, token] = data.match(/"token":"([^"]+)/) || [null, null]
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return item && item.startTime ? dayjs(item.startTime) : null
|
||||
}
|
||||
|
||||
function parseIcon(info) {
|
||||
return info ? info.URLImage : null
|
||||
}
|
||||
|
||||
function parseDescription(info) {
|
||||
return info ? info.summary : null
|
||||
}
|
||||
|
||||
function parseInfo(data) {
|
||||
if (!data || !data.detail || !data.detail.informations) return null
|
||||
|
||||
return data.detail.informations
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.onClick || !item.onClick.URLPage) return {}
|
||||
|
||||
return await axios
|
||||
.get(item.onClick.URLPage)
|
||||
.then(r => r.data)
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.timeSlices)) return []
|
||||
|
||||
return data.timeSlices.reduce((acc, curr) => {
|
||||
acc = acc.concat(curr.contents)
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
|
||||
function parseCast(info, type) {
|
||||
let people = []
|
||||
if (info && info.personnalities) {
|
||||
const personnalities = info.personnalities.find(i => i.prefix == type)
|
||||
if (!personnalities) return people
|
||||
for (let person of personnalities.personnalitiesList) {
|
||||
people.push(person.title)
|
||||
}
|
||||
}
|
||||
return people
|
||||
}
|
||||
|
||||
function parseDate(info) {
|
||||
return info && info.productionYear ? info.productionYear : null
|
||||
}
|
||||
|
||||
function parseRating(info) {
|
||||
if (!info || !info.parentalRatings) return null
|
||||
let rating = info.parentalRatings.find(i => i.authority === 'CSA')
|
||||
if (!rating || Array.isArray(rating)) return null
|
||||
if (rating.value === '1') return null
|
||||
if (rating.value === '2') rating.value = '-10'
|
||||
if (rating.value === '3') rating.value = '-12'
|
||||
if (rating.value === '4') rating.value = '-16'
|
||||
if (rating.value === '5') rating.value = '-18'
|
||||
return {
|
||||
system: rating.authority,
|
||||
value: rating.value
|
||||
}
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
const axios = require('axios')
|
||||
|
||||
module.exports = {
|
||||
site: 'canalplus.com',
|
||||
days: 2,
|
||||
url: async function ({ channel, date }) {
|
||||
const [region, site_id] = channel.site_id.split('#')
|
||||
const data = await axios
|
||||
.get(`https://www.canalplus.com/${region}/programme-tv/`)
|
||||
.then(r => r.data.toString())
|
||||
.catch(err => console.log(err))
|
||||
const token = parseToken(data)
|
||||
|
||||
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
|
||||
|
||||
return `https://hodor.canalplus.pro/api/v2/mycanal/channels/${token}/${site_id}/broadcasts/day/${diff}`
|
||||
},
|
||||
async parser({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
for (let item of items) {
|
||||
const prev = programs[programs.length - 1]
|
||||
const details = await loadProgramDetails(item)
|
||||
const info = parseInfo(details)
|
||||
const start = parseStart(item)
|
||||
if (prev) prev.stop = start
|
||||
const stop = start.add(1, 'h')
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: parseDescription(info),
|
||||
icon: parseIcon(info),
|
||||
actors: parseCast(info, 'Avec :'),
|
||||
director: parseCast(info, 'De :'),
|
||||
writer: parseCast(info, 'Scénario :'),
|
||||
composer: parseCast(info, 'Musique :'),
|
||||
presenter: parseCast(info, 'Présenté par :'),
|
||||
date: parseDate(info),
|
||||
rating: parseRating(info),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const endpoints = {
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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'
|
||||
}
|
||||
|
||||
let channels = []
|
||||
for (let [region, url] of Object.entries(endpoints)) {
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
data.channels.forEach(channel => {
|
||||
const site_id = region === 'fr' ? `#${channel.id}` : `${region}#${channel.id}`
|
||||
|
||||
if (channel.name === '.') return
|
||||
|
||||
channels.push({
|
||||
lang: 'fr',
|
||||
site_id,
|
||||
name: channel.name
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
}
|
||||
|
||||
function parseToken(data) {
|
||||
const [, token] = data.match(/"token":"([^"]+)/) || [null, null]
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return item && item.startTime ? dayjs(item.startTime) : null
|
||||
}
|
||||
|
||||
function parseIcon(info) {
|
||||
return info ? info.URLImage : null
|
||||
}
|
||||
|
||||
function parseDescription(info) {
|
||||
return info ? info.summary : null
|
||||
}
|
||||
|
||||
function parseInfo(data) {
|
||||
if (!data || !data.detail || !data.detail.informations) return null
|
||||
|
||||
return data.detail.informations
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.onClick || !item.onClick.URLPage) return {}
|
||||
|
||||
return await axios
|
||||
.get(item.onClick.URLPage)
|
||||
.then(r => r.data)
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.timeSlices)) return []
|
||||
|
||||
return data.timeSlices.reduce((acc, curr) => {
|
||||
acc = acc.concat(curr.contents)
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
|
||||
function parseCast(info, type) {
|
||||
let people = []
|
||||
if (info && info.personnalities) {
|
||||
const personnalities = info.personnalities.find(i => i.prefix == type)
|
||||
if (!personnalities) return people
|
||||
for (let person of personnalities.personnalitiesList) {
|
||||
people.push(person.title)
|
||||
}
|
||||
}
|
||||
return people
|
||||
}
|
||||
|
||||
function parseDate(info) {
|
||||
return info && info.productionYear ? info.productionYear : null
|
||||
}
|
||||
|
||||
function parseRating(info) {
|
||||
if (!info || !info.parentalRatings) return null
|
||||
let rating = info.parentalRatings.find(i => i.authority === 'CSA')
|
||||
if (!rating || Array.isArray(rating)) return null
|
||||
if (rating.value === '1') return null
|
||||
if (rating.value === '2') rating.value = '-10'
|
||||
if (rating.value === '3') rating.value = '-12'
|
||||
if (rating.value === '4') rating.value = '-16'
|
||||
if (rating.value === '5') rating.value = '-18'
|
||||
return {
|
||||
system: rating.authority,
|
||||
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 grab -- --site=canalplus.com
|
||||
|
||||
const { parser, url } = require('./canalplus.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
jest.mock('axios')
|
||||
|
||||
const channel = {
|
||||
site_id: 'bi#198',
|
||||
xmltv_id: 'CanalPlusCinemaFrance.fr'
|
||||
}
|
||||
|
||||
it('can generate valid url for today', done => {
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://www.canalplus.com/bi/programme-tv/') {
|
||||
return Promise.resolve({
|
||||
data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html'))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
const today = dayjs.utc().startOf('d')
|
||||
url({ channel, date: today })
|
||||
.then(result => {
|
||||
expect(result).toBe(
|
||||
'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/0'
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can generate valid url for tomorrow', done => {
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://www.canalplus.com/bi/programme-tv/') {
|
||||
return Promise.resolve({
|
||||
data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html'))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
const tomorrow = dayjs.utc().startOf('d').add(1, 'd')
|
||||
url({ channel, date: tomorrow })
|
||||
.then(result => {
|
||||
expect(result).toBe(
|
||||
'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/1'
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (
|
||||
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'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
|
||||
})
|
||||
} else if (
|
||||
url ===
|
||||
'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({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content })
|
||||
.then(result => {
|
||||
result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2023-01-12T06:28:00.000Z',
|
||||
stop: '2023-01-12T12:06:00.000Z',
|
||||
title: 'Le cercle',
|
||||
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.",
|
||||
icon: 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107297573',
|
||||
presenter: ['Lily Bloom'],
|
||||
rating: {
|
||||
system: 'CSA',
|
||||
value: '-10'
|
||||
}
|
||||
},
|
||||
{
|
||||
start: '2023-01-12T12:06:00.000Z',
|
||||
stop: '2023-01-12T13:06:00.000Z',
|
||||
title: 'Illusions perdues',
|
||||
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...",
|
||||
icon: 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107356485',
|
||||
director: ['Xavier Giannoli'],
|
||||
actors: [
|
||||
'Benjamin Voisin',
|
||||
'Cécile de France',
|
||||
'Vincent Lacoste',
|
||||
'Xavier Dolan',
|
||||
'Gérard Depardieu',
|
||||
'Salomé Dewaels',
|
||||
'Jeanne Balibar',
|
||||
'Louis-Do de Lencquesaing',
|
||||
'Alexis Barbosa',
|
||||
'Jean-François Stévenin',
|
||||
'André Marcon',
|
||||
'Marie Cornillon'
|
||||
],
|
||||
writer: ['Xavier Giannoli'],
|
||||
rating: {
|
||||
system: 'CSA',
|
||||
value: '-10'
|
||||
}
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||
const result = await parser({ content })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url } = require('./canalplus.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
jest.mock('axios')
|
||||
|
||||
const channel = {
|
||||
site_id: 'bi#198',
|
||||
xmltv_id: 'CanalPlusCinemaFrance.fr'
|
||||
}
|
||||
|
||||
it('can generate valid url for today', done => {
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://www.canalplus.com/bi/programme-tv/') {
|
||||
return Promise.resolve({
|
||||
data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html'))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
const today = dayjs.utc().startOf('d')
|
||||
url({ channel, date: today })
|
||||
.then(result => {
|
||||
expect(result).toBe(
|
||||
'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/0'
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can generate valid url for tomorrow', done => {
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://www.canalplus.com/bi/programme-tv/') {
|
||||
return Promise.resolve({
|
||||
data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html'))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
const tomorrow = dayjs.utc().startOf('d').add(1, 'd')
|
||||
url({ channel, date: tomorrow })
|
||||
.then(result => {
|
||||
expect(result).toBe(
|
||||
'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/1'
|
||||
)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (
|
||||
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'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
|
||||
})
|
||||
} else if (
|
||||
url ===
|
||||
'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({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content })
|
||||
.then(result => {
|
||||
result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2023-01-12T06:28:00.000Z',
|
||||
stop: '2023-01-12T12:06:00.000Z',
|
||||
title: 'Le cercle',
|
||||
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.",
|
||||
icon: 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107297573',
|
||||
presenter: ['Lily Bloom'],
|
||||
rating: {
|
||||
system: 'CSA',
|
||||
value: '-10'
|
||||
}
|
||||
},
|
||||
{
|
||||
start: '2023-01-12T12:06:00.000Z',
|
||||
stop: '2023-01-12T13:06:00.000Z',
|
||||
title: 'Illusions perdues',
|
||||
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...",
|
||||
icon: 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107356485',
|
||||
director: ['Xavier Giannoli'],
|
||||
actors: [
|
||||
'Benjamin Voisin',
|
||||
'Cécile de France',
|
||||
'Vincent Lacoste',
|
||||
'Xavier Dolan',
|
||||
'Gérard Depardieu',
|
||||
'Salomé Dewaels',
|
||||
'Jeanne Balibar',
|
||||
'Louis-Do de Lencquesaing',
|
||||
'Alexis Barbosa',
|
||||
'Jean-François Stévenin',
|
||||
'André Marcon',
|
||||
'Marie Cornillon'
|
||||
],
|
||||
writer: ['Xavier Giannoli'],
|
||||
rating: {
|
||||
system: 'CSA',
|
||||
value: '-10'
|
||||
}
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||
const result = await parser({ content })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,92 +1,92 @@
|
|||
const dayjs = require('dayjs')
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'cgates.lt',
|
||||
days: 2,
|
||||
url: function ({ channel }) {
|
||||
return `https://www.cgates.lt/tv-kanalai/${channel.site_id}/`
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.add(30, 'm')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
description: parseDescription($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
let html = await axios
|
||||
.get('https://www.cgates.lt/televizija/tv-programa-savaitei/')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
let $ = cheerio.load(html)
|
||||
const items = $('.kanalas_wrap').toArray()
|
||||
|
||||
return items.map(item => {
|
||||
const name = $(item).find('h6').text().trim()
|
||||
const link = $(item).find('a').attr('href')
|
||||
const [, site_id] = link.match(/\/tv-kanalai\/(.*)\//) || [null, null]
|
||||
|
||||
return {
|
||||
lang: 'lt',
|
||||
site_id,
|
||||
name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
const title = $item('td:nth-child(2) > .vc_toggle > .vc_toggle_title').text().trim()
|
||||
|
||||
return title || $item('td:nth-child(2)').text().trim()
|
||||
}
|
||||
|
||||
function parseDescription($item) {
|
||||
return $item('.vc_toggle_content > p').text().trim()
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const time = $item('.laikas')
|
||||
|
||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Vilnius')
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
const section = $(
|
||||
'article > div:nth-child(2) > div.vc_row.wpb_row.vc_row-fluid > div > div > div > div > div'
|
||||
)
|
||||
.filter(function () {
|
||||
return $(`.dt-fancy-title:contains("${date.format('YYYY-MM-DD')}")`, this).length === 1
|
||||
})
|
||||
.first()
|
||||
|
||||
return $('.tv_programa tr', section).toArray()
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'cgates.lt',
|
||||
days: 2,
|
||||
url: function ({ channel }) {
|
||||
return `https://www.cgates.lt/tv-kanalai/${channel.site_id}/`
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.add(30, 'm')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
description: parseDescription($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
let html = await axios
|
||||
.get('https://www.cgates.lt/televizija/tv-programa-savaitei/')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
let $ = cheerio.load(html)
|
||||
const items = $('.kanalas_wrap').toArray()
|
||||
|
||||
return items.map(item => {
|
||||
const name = $(item).find('h6').text().trim()
|
||||
const link = $(item).find('a').attr('href')
|
||||
const [, site_id] = link.match(/\/tv-kanalai\/(.*)\//) || [null, null]
|
||||
|
||||
return {
|
||||
lang: 'lt',
|
||||
site_id,
|
||||
name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
const title = $item('td:nth-child(2) > .vc_toggle > .vc_toggle_title').text().trim()
|
||||
|
||||
return title || $item('td:nth-child(2)').text().trim()
|
||||
}
|
||||
|
||||
function parseDescription($item) {
|
||||
return $item('.vc_toggle_content > p').text().trim()
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const time = $item('.laikas')
|
||||
|
||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Vilnius')
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
const section = $(
|
||||
'article > div:nth-child(2) > div.vc_row.wpb_row.vc_row-fluid > div > div > div > div > div'
|
||||
)
|
||||
.filter(function () {
|
||||
return $(`.dt-fancy-title:contains("${date.format('YYYY-MM-DD')}")`, this).length === 1
|
||||
})
|
||||
.first()
|
||||
|
||||
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 grab -- --site=cgates.lt
|
||||
|
||||
const { parser, url } = require('./cgates.lt.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-08-30', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'lrt-televizija-hd',
|
||||
xmltv_id: 'LRTTV.lt'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe('https://www.cgates.lt/tv-kanalai/lrt-televizija-hd/')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||
const results = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results.length).toBe(35)
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-08-29T21:05:00.000Z',
|
||||
stop: '2022-08-29T21:30:00.000Z',
|
||||
title: '31-oji nuovada (District 31), Drama, 2016',
|
||||
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ą.'
|
||||
})
|
||||
|
||||
expect(results[34]).toMatchObject({
|
||||
start: '2022-08-30T20:45:00.000Z',
|
||||
stop: '2022-08-30T21:15:00.000Z',
|
||||
title: '31-oji nuovada (District 31), Drama, 2016!'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: ''
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url } = require('./cgates.lt.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-08-30', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'lrt-televizija-hd',
|
||||
xmltv_id: 'LRTTV.lt'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe('https://www.cgates.lt/tv-kanalai/lrt-televizija-hd/')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||
const results = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results.length).toBe(35)
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-08-29T21:05:00.000Z',
|
||||
stop: '2022-08-29T21:30:00.000Z',
|
||||
title: '31-oji nuovada (District 31), Drama, 2016',
|
||||
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ą.'
|
||||
})
|
||||
|
||||
expect(results[34]).toMatchObject({
|
||||
start: '2022-08-30T20:45:00.000Z',
|
||||
stop: '2022-08-30T21:15:00.000Z',
|
||||
title: '31-oji nuovada (District 31), Drama, 2016!'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: ''
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,47 +1,47 @@
|
|||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'chaines-tv.orange.fr',
|
||||
days: 2,
|
||||
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
|
||||
.add(1, 'd')
|
||||
.valueOf()}&after=${channel.site_id}&limit=1`
|
||||
},
|
||||
parser: function ({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
const start = parseStart(item)
|
||||
const stop = parseStop(item, start)
|
||||
programs.push({
|
||||
title: item.title,
|
||||
category: item.genreDetailed,
|
||||
description: item.synopsis,
|
||||
icon: parseIcon(item),
|
||||
start: start.toJSON(),
|
||||
stop: stop.toJSON()
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
return item.covers && item.covers.length ? item.covers[0].url : null
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.unix(item.diffusionDate)
|
||||
}
|
||||
|
||||
function parseStop(item, start) {
|
||||
return start.add(item.duration, 's')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const data = JSON.parse(content)
|
||||
|
||||
return data && data[channel.site_id] ? data[channel.site_id] : []
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'chaines-tv.orange.fr',
|
||||
days: 2,
|
||||
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
|
||||
.add(1, 'd')
|
||||
.valueOf()}&after=${channel.site_id}&limit=1`
|
||||
},
|
||||
parser: function ({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
const start = parseStart(item)
|
||||
const stop = parseStop(item, start)
|
||||
programs.push({
|
||||
title: item.title,
|
||||
category: item.genreDetailed,
|
||||
description: item.synopsis,
|
||||
icon: parseIcon(item),
|
||||
start: start.toJSON(),
|
||||
stop: stop.toJSON()
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
return item.covers && item.covers.length ? item.covers[0].url : null
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.unix(item.diffusionDate)
|
||||
}
|
||||
|
||||
function parseStop(item, start) {
|
||||
return start.add(item.duration, 's')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const data = JSON.parse(content)
|
||||
|
||||
return data && data[channel.site_id] ? data[channel.site_id] : []
|
||||
}
|
||||
|
|
|
@ -1,48 +1,48 @@
|
|||
// npm run grab -- --site=chaines-tv.orange.fr
|
||||
|
||||
const { parser, url } = require('./chaines-tv.orange.fr.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2021-11-08', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '192',
|
||||
xmltv_id: 'TF1.fr'
|
||||
}
|
||||
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"}]}'
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ channel, date })
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const result = parser({ date, channel, content })
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2021-11-07T23:35:00.000Z',
|
||||
stop: '2021-11-08T00:20:00.000Z',
|
||||
title: 'Tête de liste',
|
||||
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.",
|
||||
category: 'Série Suspense',
|
||||
icon: 'https://proxymedia.woopic.com/340/p/169_EMI_9697669.jpg'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content:
|
||||
'{"code":60,"message":"Resource not found","param":{},"description":"L\'URI demandé ou la ressource demandée n\'existe pas.","stackTrace":null}'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=chaines-tv.orange.fr
|
||||
|
||||
const { parser, url } = require('./chaines-tv.orange.fr.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2021-11-08', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '192',
|
||||
xmltv_id: 'TF1.fr'
|
||||
}
|
||||
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"}]}'
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ channel, date })
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const result = parser({ date, channel, content })
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2021-11-07T23:35:00.000Z',
|
||||
stop: '2021-11-08T00:20:00.000Z',
|
||||
title: 'Tête de liste',
|
||||
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.",
|
||||
category: 'Série Suspense',
|
||||
icon: 'https://proxymedia.woopic.com/340/p/169_EMI_9697669.jpg'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content:
|
||||
'{"code":60,"message":"Resource not found","param":{},"description":"L\'URI demandé ou la ressource demandée n\'existe pas.","stackTrace":null}'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,99 +1,99 @@
|
|||
const cheerio = require('cheerio')
|
||||
const axios = require('axios')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'clickthecity.com',
|
||||
days: 2,
|
||||
url({ channel }) {
|
||||
return `https://www.clickthecity.com/tv/channels/?netid=${channel.site_id}`
|
||||
},
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data({ date }) {
|
||||
const params = new URLSearchParams()
|
||||
params.append(
|
||||
'optDate',
|
||||
DateTime.fromMillis(date.valueOf()).setZone('Asia/Manila').toFormat('yyyy-MM-dd')
|
||||
)
|
||||
params.append('optTime', '00:00:00')
|
||||
|
||||
return params
|
||||
}
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
let stop = parseStop($item, date)
|
||||
if (!start || !stop) return
|
||||
if (start > stop) {
|
||||
stop = stop.plus({ days: 1 })
|
||||
}
|
||||
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const html = await axios
|
||||
.get('https://www.clickthecity.com/tv/channels/')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
const $ = cheerio.load(html)
|
||||
const items = $('#channels .col').toArray()
|
||||
|
||||
return items.map(item => {
|
||||
const name = $(item).find('.card-body').text().trim()
|
||||
const url = $(item).find('a').attr('href')
|
||||
const [, site_id] = url.match(/netid=(\d+)/) || [null, null]
|
||||
|
||||
return {
|
||||
site_id,
|
||||
name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('td > a').text().trim()
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const url = $item('td.cPrg > a').attr('href') || ''
|
||||
let [, time] = url.match(/starttime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null]
|
||||
if (!time) return null
|
||||
time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}`
|
||||
|
||||
return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC()
|
||||
}
|
||||
|
||||
function parseStop($item, date) {
|
||||
const url = $item('td.cPrg > a').attr('href') || ''
|
||||
let [, time] = url.match(/endtime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null]
|
||||
if (!time) return null
|
||||
time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}`
|
||||
|
||||
return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('#tvlistings > tbody > tr')
|
||||
.filter(function () {
|
||||
return $(this).find('td.cPrg').length
|
||||
})
|
||||
.toArray()
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const axios = require('axios')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'clickthecity.com',
|
||||
days: 2,
|
||||
url({ channel }) {
|
||||
return `https://www.clickthecity.com/tv/channels/?netid=${channel.site_id}`
|
||||
},
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data({ date }) {
|
||||
const params = new URLSearchParams()
|
||||
params.append(
|
||||
'optDate',
|
||||
DateTime.fromMillis(date.valueOf()).setZone('Asia/Manila').toFormat('yyyy-MM-dd')
|
||||
)
|
||||
params.append('optTime', '00:00:00')
|
||||
|
||||
return params
|
||||
}
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
let stop = parseStop($item, date)
|
||||
if (!start || !stop) return
|
||||
if (start > stop) {
|
||||
stop = stop.plus({ days: 1 })
|
||||
}
|
||||
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const html = await axios
|
||||
.get('https://www.clickthecity.com/tv/channels/')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
const $ = cheerio.load(html)
|
||||
const items = $('#channels .col').toArray()
|
||||
|
||||
return items.map(item => {
|
||||
const name = $(item).find('.card-body').text().trim()
|
||||
const url = $(item).find('a').attr('href')
|
||||
const [, site_id] = url.match(/netid=(\d+)/) || [null, null]
|
||||
|
||||
return {
|
||||
site_id,
|
||||
name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('td > a').text().trim()
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const url = $item('td.cPrg > a').attr('href') || ''
|
||||
let [, time] = url.match(/starttime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null]
|
||||
if (!time) return null
|
||||
time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}`
|
||||
|
||||
return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC()
|
||||
}
|
||||
|
||||
function parseStop($item, date) {
|
||||
const url = $item('td.cPrg > a').attr('href') || ''
|
||||
let [, time] = url.match(/endtime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null]
|
||||
if (!time) return null
|
||||
time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}`
|
||||
|
||||
return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('#tvlistings > tbody > tr')
|
||||
.filter(function () {
|
||||
return $(this).find('td.cPrg').length
|
||||
})
|
||||
.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 grab -- --site=clickthecity.com
|
||||
|
||||
const { parser, url, request } = require('./clickthecity.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-06-12', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '5',
|
||||
xmltv_id: 'TV5.ph'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('https://www.clickthecity.com/tv/channels/?netid=5')
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
const result = request.data({ date })
|
||||
expect(result.get('optDate')).toBe('2023-06-12')
|
||||
expect(result.get('optTime')).toBe('00:00:00')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||
const results = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results.length).toBe(20)
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-11T21:00:00.000Z',
|
||||
stop: '2023-06-11T22:00:00.000Z',
|
||||
title: 'Word Of God'
|
||||
})
|
||||
|
||||
expect(results[19]).toMatchObject({
|
||||
start: '2023-06-12T15:30:00.000Z',
|
||||
stop: '2023-06-12T16:00:00.000Z',
|
||||
title: 'La Suerte De Loli'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content:
|
||||
'<!DOCTYPE html><html class="html" lang="en-US" prefix="og: https://ogp.me/ns#"><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url, request } = require('./clickthecity.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-06-12', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '5',
|
||||
xmltv_id: 'TV5.ph'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('https://www.clickthecity.com/tv/channels/?netid=5')
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
const result = request.data({ date })
|
||||
expect(result.get('optDate')).toBe('2023-06-12')
|
||||
expect(result.get('optTime')).toBe('00:00:00')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||
const results = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results.length).toBe(20)
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-11T21:00:00.000Z',
|
||||
stop: '2023-06-11T22:00:00.000Z',
|
||||
title: 'Word Of God'
|
||||
})
|
||||
|
||||
expect(results[19]).toMatchObject({
|
||||
start: '2023-06-12T15:30:00.000Z',
|
||||
stop: '2023-06-12T16:00:00.000Z',
|
||||
title: 'La Suerte De Loli'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content:
|
||||
'<!DOCTYPE html><html class="html" lang="en-US" prefix="og: https://ogp.me/ns#"><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
const parser = require('epg-parser')
|
||||
|
||||
module.exports = {
|
||||
site: 'compulms.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url: 'https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml',
|
||||
parser: function ({ content, channel, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel, date)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.title?.[0].value,
|
||||
description: item.desc?.[0].value,
|
||||
icon: item.icon?.[0],
|
||||
start: item.start,
|
||||
stop: item.stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content, channel, date) {
|
||||
const { programs } = parser.parse(content)
|
||||
|
||||
return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day'))
|
||||
}
|
||||
const parser = require('epg-parser')
|
||||
|
||||
module.exports = {
|
||||
site: 'compulms.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url: 'https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml',
|
||||
parser: function ({ content, channel, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel, date)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.title?.[0].value,
|
||||
description: item.desc?.[0].value,
|
||||
icon: item.icon?.[0],
|
||||
start: item.start,
|
||||
stop: item.stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content, channel, date) {
|
||||
const { programs } = parser.parse(content)
|
||||
|
||||
return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day'))
|
||||
}
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
// npm run grab -- --site=compulms.com
|
||||
|
||||
const { parser, url } = require('./compulms.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-11-29', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'EnerGeek Retro',
|
||||
xmltv_id: 'EnerGeekRetro.cl'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'))
|
||||
let results = parser({ content, channel, date })
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-29T03:00:00.000Z',
|
||||
stop: '2022-11-29T03:30:00.000Z',
|
||||
title: 'Noir',
|
||||
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',
|
||||
icon: 'https://pics.filmaffinity.com/nowaru_noir_tv_series-225888552-mmed.jpg'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({ content: '', channel, date })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=compulms.com
|
||||
|
||||
const { parser, url } = require('./compulms.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-11-29', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'EnerGeek Retro',
|
||||
xmltv_id: 'EnerGeekRetro.cl'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'))
|
||||
let results = parser({ content, channel, date })
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-29T03:00:00.000Z',
|
||||
stop: '2022-11-29T03:30:00.000Z',
|
||||
title: 'Noir',
|
||||
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',
|
||||
icon: 'https://pics.filmaffinity.com/nowaru_noir_tv_series-225888552-mmed.jpg'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({ content: '', channel, date })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,68 +1,68 @@
|
|||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'comteco.com.bo',
|
||||
days: 2,
|
||||
url: function ({ channel }) {
|
||||
return `https://comteco.com.bo/pages/canales-y-programacion-tv/paquete-oro/${channel.site_id}`
|
||||
},
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: function ({ date }) {
|
||||
const params = new URLSearchParams()
|
||||
params.append('_method', 'POST')
|
||||
params.append('fechaini', date.format('D/M/YYYY'))
|
||||
params.append('fechafin', date.format('D/M/YYYY'))
|
||||
|
||||
return params
|
||||
}
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.add(30, 'm')
|
||||
programs.push({ title: parseTitle($item), start, stop })
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const timeString = $item('div > div.col-xs-11 > p > span').text().trim()
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||
|
||||
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm:ss', 'America/La_Paz')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('div > div.col-xs-11 > p > strong').text().trim()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('#datosasociados > div > .list-group-item').toArray()
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'comteco.com.bo',
|
||||
days: 2,
|
||||
url: function ({ channel }) {
|
||||
return `https://comteco.com.bo/pages/canales-y-programacion-tv/paquete-oro/${channel.site_id}`
|
||||
},
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: function ({ date }) {
|
||||
const params = new URLSearchParams()
|
||||
params.append('_method', 'POST')
|
||||
params.append('fechaini', date.format('D/M/YYYY'))
|
||||
params.append('fechafin', date.format('D/M/YYYY'))
|
||||
|
||||
return params
|
||||
}
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
if (prev) {
|
||||
if (start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
const stop = start.add(30, 'm')
|
||||
programs.push({ title: parseTitle($item), start, stop })
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const timeString = $item('div > div.col-xs-11 > p > span').text().trim()
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||
|
||||
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm:ss', 'America/La_Paz')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('div > div.col-xs-11 > p > strong').text().trim()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('#datosasociados > div > .list-group-item').toArray()
|
||||
}
|
||||
|
|
|
@ -1,74 +1,74 @@
|
|||
// npm run grab -- --site=comteco.com.bo
|
||||
|
||||
const { parser, url, request } = require('./comteco.com.bo.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2021-11-25', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'ABYA YALA',
|
||||
xmltv_id: 'AbyaYalaTV.bo'
|
||||
}
|
||||
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>'
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe(
|
||||
'https://comteco.com.bo/pages/canales-y-programacion-tv/paquete-oro/ABYA YALA'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
const result = request.data({ date })
|
||||
expect(result.get('_method')).toBe('POST')
|
||||
expect(result.get('fechaini')).toBe('25/11/2021')
|
||||
expect(result.get('fechafin')).toBe('25/11/2021')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const result = parser({ content, channel, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2021-11-25T04:00:00.000Z',
|
||||
stop: '2021-11-25T05:00:00.000Z',
|
||||
title: 'Abya Yala noticias - 3ra edición'
|
||||
},
|
||||
{
|
||||
start: '2021-11-25T05:00:00.000Z',
|
||||
stop: '2021-11-26T03:00:00.000Z',
|
||||
title: 'Cierre de emisión'
|
||||
},
|
||||
{
|
||||
start: '2021-11-26T03:00:00.000Z',
|
||||
stop: '2021-11-26T03:30:00.000Z',
|
||||
title: 'Referentes'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=comteco.com.bo
|
||||
|
||||
const { parser, url, request } = require('./comteco.com.bo.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2021-11-25', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'ABYA YALA',
|
||||
xmltv_id: 'AbyaYalaTV.bo'
|
||||
}
|
||||
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>'
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe(
|
||||
'https://comteco.com.bo/pages/canales-y-programacion-tv/paquete-oro/ABYA YALA'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
const result = request.data({ date })
|
||||
expect(result.get('_method')).toBe('POST')
|
||||
expect(result.get('fechaini')).toBe('25/11/2021')
|
||||
expect(result.get('fechafin')).toBe('25/11/2021')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const result = parser({ content, channel, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2021-11-25T04:00:00.000Z',
|
||||
stop: '2021-11-25T05:00:00.000Z',
|
||||
title: 'Abya Yala noticias - 3ra edición'
|
||||
},
|
||||
{
|
||||
start: '2021-11-25T05:00:00.000Z',
|
||||
stop: '2021-11-26T03:00:00.000Z',
|
||||
title: 'Cierre de emisión'
|
||||
},
|
||||
{
|
||||
start: '2021-11-26T03:00:00.000Z',
|
||||
stop: '2021-11-26T03:30:00.000Z',
|
||||
title: 'Referentes'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
const cheerio = require('cheerio')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'cosmote.gr',
|
||||
days: 2,
|
||||
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(
|
||||
'DD-MM-YYYY'
|
||||
)}&_channelprogram_WAR_OTETVportlet_articleTitleUrl=${channel.site_id}`
|
||||
},
|
||||
parser: function ({ date, content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach((item, i) => {
|
||||
const prev = programs[programs.length - 1]
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
if (i === 0 && start.hour > 12 && start.hour < 21) {
|
||||
date = date.subtract(1, 'd')
|
||||
start = start.minus({ days: 1 })
|
||||
}
|
||||
if (prev && start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
let stop = parseStop($item, date)
|
||||
if (stop < start) {
|
||||
stop = stop.plus({ days: 1 })
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
category: parseCategory($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.channel_program-table--program > a').text()
|
||||
}
|
||||
|
||||
function parseCategory($item) {
|
||||
const typeString = $item('.channel_program-table--program_type')
|
||||
.children()
|
||||
.remove()
|
||||
.end()
|
||||
.text()
|
||||
.trim()
|
||||
const [, category] = typeString.match(/\| (.*)/) || [null, null]
|
||||
|
||||
return category
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const timeString = $item('span.start-time').text()
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||
|
||||
return DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Athens' }).toUTC()
|
||||
}
|
||||
|
||||
function parseStop($item, date) {
|
||||
const timeString = $item('span.end-time').text()
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||
|
||||
return DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Athens' }).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('#_channelprogram_WAR_OTETVportlet_programs > tr.d-sm-table-row').toArray()
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'cosmote.gr',
|
||||
days: 2,
|
||||
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(
|
||||
'DD-MM-YYYY'
|
||||
)}&_channelprogram_WAR_OTETVportlet_articleTitleUrl=${channel.site_id}`
|
||||
},
|
||||
parser: function ({ date, content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach((item, i) => {
|
||||
const prev = programs[programs.length - 1]
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
if (i === 0 && start.hour > 12 && start.hour < 21) {
|
||||
date = date.subtract(1, 'd')
|
||||
start = start.minus({ days: 1 })
|
||||
}
|
||||
if (prev && start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
let stop = parseStop($item, date)
|
||||
if (stop < start) {
|
||||
stop = stop.plus({ days: 1 })
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
category: parseCategory($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.channel_program-table--program > a').text()
|
||||
}
|
||||
|
||||
function parseCategory($item) {
|
||||
const typeString = $item('.channel_program-table--program_type')
|
||||
.children()
|
||||
.remove()
|
||||
.end()
|
||||
.text()
|
||||
.trim()
|
||||
const [, category] = typeString.match(/\| (.*)/) || [null, null]
|
||||
|
||||
return category
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const timeString = $item('span.start-time').text()
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||
|
||||
return DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Athens' }).toUTC()
|
||||
}
|
||||
|
||||
function parseStop($item, date) {
|
||||
const timeString = $item('span.end-time').text()
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||
|
||||
return DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Athens' }).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('#_channelprogram_WAR_OTETVportlet_programs > tr.d-sm-table-row').toArray()
|
||||
}
|
||||
|
|
|
@ -1,79 +1,79 @@
|
|||
// npm run grab -- --site=cosmote.gr
|
||||
|
||||
const { parser, url } = require('./cosmote.gr.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const date = dayjs.utc('2023-06-08', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '4e',
|
||||
xmltv_id: '4E.gr'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content1.html'))
|
||||
const results = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-07T20:30:00.000Z',
|
||||
stop: '2023-06-07T21:45:00.000Z',
|
||||
title: 'Τηλεφημερίδα',
|
||||
category: 'Εκπομπή - Μαγκαζίνο'
|
||||
})
|
||||
|
||||
expect(results[30]).toMatchObject({
|
||||
start: '2023-06-08T19:45:00.000Z',
|
||||
stop: '2023-06-08T20:30:00.000Z',
|
||||
title: 'Μικρό Απόδειπνο',
|
||||
category: 'Special'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response when the guide starting before midnight', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content2.html'))
|
||||
const results = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-07T21:30:00.000Z',
|
||||
stop: '2023-06-07T22:30:00.000Z',
|
||||
title: 'Καλύτερα Αργά',
|
||||
category: 'Ψυχαγωγική Εκπομπή'
|
||||
})
|
||||
|
||||
expect(results[22]).toMatchObject({
|
||||
start: '2023-06-08T19:00:00.000Z',
|
||||
stop: '2023-06-08T21:30:00.000Z',
|
||||
title: 'Πίσω Από Τις Γραμμές',
|
||||
category: 'Εκπομπή - Μαγκαζίνο'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=cosmote.gr
|
||||
|
||||
const { parser, url } = require('./cosmote.gr.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const date = dayjs.utc('2023-06-08', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '4e',
|
||||
xmltv_id: '4E.gr'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content1.html'))
|
||||
const results = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-07T20:30:00.000Z',
|
||||
stop: '2023-06-07T21:45:00.000Z',
|
||||
title: 'Τηλεφημερίδα',
|
||||
category: 'Εκπομπή - Μαγκαζίνο'
|
||||
})
|
||||
|
||||
expect(results[30]).toMatchObject({
|
||||
start: '2023-06-08T19:45:00.000Z',
|
||||
stop: '2023-06-08T20:30:00.000Z',
|
||||
title: 'Μικρό Απόδειπνο',
|
||||
category: 'Special'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response when the guide starting before midnight', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content2.html'))
|
||||
const results = parser({ content, date }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-07T21:30:00.000Z',
|
||||
stop: '2023-06-07T22:30:00.000Z',
|
||||
title: 'Καλύτερα Αργά',
|
||||
category: 'Ψυχαγωγική Εκπομπή'
|
||||
})
|
||||
|
||||
expect(results[22]).toMatchObject({
|
||||
start: '2023-06-08T19:00:00.000Z',
|
||||
stop: '2023-06-08T21:30:00.000Z',
|
||||
title: 'Πίσω Από Τις Γραμμές',
|
||||
category: 'Εκπομπή - Μαγκαζίνο'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,70 +1,70 @@
|
|||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'delta.nl',
|
||||
days: 2,
|
||||
url: function ({ channel, date }) {
|
||||
return `https://clientapi.tv.delta.nl/guide/channels/list?start=${date.unix()}&end=${date
|
||||
.add(1, 'd')
|
||||
.unix()}&includeDetails=true&channels=${channel.site_id}`
|
||||
},
|
||||
async parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
for (let item of items) {
|
||||
const details = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.title,
|
||||
icon: item.images.thumbnail.url,
|
||||
description: details.description,
|
||||
start: parseStart(item).toJSON(),
|
||||
stop: parseStop(item).toJSON()
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const items = await axios
|
||||
.get('https://clientapi.tv.delta.nl/channels/list')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return items
|
||||
.filter(i => i.type === 'TV')
|
||||
.map(item => {
|
||||
return {
|
||||
lang: 'nl',
|
||||
site_id: item['ID'],
|
||||
name: item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.ID) return {}
|
||||
const url = `https://clientapi.tv.delta.nl/guide/4/details/${item.ID}?X-Response-Version=4.5`
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.unix(item.start)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.unix(item.end)
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data) return []
|
||||
|
||||
return data[channel.site_id] || []
|
||||
}
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'delta.nl',
|
||||
days: 2,
|
||||
url: function ({ channel, date }) {
|
||||
return `https://clientapi.tv.delta.nl/guide/channels/list?start=${date.unix()}&end=${date
|
||||
.add(1, 'd')
|
||||
.unix()}&includeDetails=true&channels=${channel.site_id}`
|
||||
},
|
||||
async parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
for (let item of items) {
|
||||
const details = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.title,
|
||||
icon: item.images.thumbnail.url,
|
||||
description: details.description,
|
||||
start: parseStart(item).toJSON(),
|
||||
stop: parseStop(item).toJSON()
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const items = await axios
|
||||
.get('https://clientapi.tv.delta.nl/channels/list')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return items
|
||||
.filter(i => i.type === 'TV')
|
||||
.map(item => {
|
||||
return {
|
||||
lang: 'nl',
|
||||
site_id: item['ID'],
|
||||
name: item.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.ID) return {}
|
||||
const url = `https://clientapi.tv.delta.nl/guide/4/details/${item.ID}?X-Response-Version=4.5`
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.unix(item.start)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.unix(item.end)
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data) return []
|
||||
|
||||
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 grab -- --site=delta.nl
|
||||
|
||||
const { parser, url } = require('./delta.nl.config.js')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const date = dayjs.utc('2021-11-12', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '1',
|
||||
xmltv_id: 'NPO1.nl'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://clientapi.tv.delta.nl/guide/channels/list?start=1636675200&end=1636761600&includeDetails=true&channels=1'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
axios.get.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
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}'
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
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}]}'
|
||||
|
||||
parser({ date, channel, content })
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2021-11-11T23:56:00.000Z',
|
||||
stop: '2021-11-12T00:22:00.000Z',
|
||||
title: 'NOS Journaal',
|
||||
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.',
|
||||
icon: 'https://cdn.gvidi.tv/img/booxmedia/e19c/static/NOS%20Journaal5.jpg'
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(error => {
|
||||
done(error)
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
date,
|
||||
channel,
|
||||
content: '{"code":500,"message":"Error retrieving guide"}'
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(error => {
|
||||
done(error)
|
||||
})
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url } = require('./delta.nl.config.js')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const date = dayjs.utc('2021-11-12', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '1',
|
||||
xmltv_id: 'NPO1.nl'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://clientapi.tv.delta.nl/guide/channels/list?start=1636675200&end=1636761600&includeDetails=true&channels=1'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
axios.get.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
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}'
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
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}]}'
|
||||
|
||||
parser({ date, channel, content })
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2021-11-11T23:56:00.000Z',
|
||||
stop: '2021-11-12T00:22:00.000Z',
|
||||
title: 'NOS Journaal',
|
||||
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.',
|
||||
icon: 'https://cdn.gvidi.tv/img/booxmedia/e19c/static/NOS%20Journaal5.jpg'
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(error => {
|
||||
done(error)
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
date,
|
||||
channel,
|
||||
content: '{"code":500,"message":"Error retrieving guide"}'
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(error => {
|
||||
done(error)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,77 +1,77 @@
|
|||
const _ = require('lodash')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
// category list is not complete
|
||||
// const categories = {
|
||||
// '00': 'Diğer',
|
||||
// E0: 'Romantik Komedi',
|
||||
// E1: 'Aksiyon',
|
||||
// E4: 'Macera',
|
||||
// E5: 'Dram',
|
||||
// E6: 'Fantastik',
|
||||
// E7: 'Komedi',
|
||||
// E8: 'Korku',
|
||||
// EB: 'Polisiye',
|
||||
// EF: 'Western',
|
||||
// FA: 'Macera',
|
||||
// FB: 'Yarışma',
|
||||
// FC: 'Eğlence',
|
||||
// F0: 'Reality-Show',
|
||||
// F2: 'Haberler',
|
||||
// F4: 'Belgesel',
|
||||
// F6: 'Eğitim',
|
||||
// F7: 'Sanat ve Kültür',
|
||||
// F9: 'Life Style'
|
||||
// }
|
||||
|
||||
module.exports = {
|
||||
site: 'digiturk.com.tr',
|
||||
days: 2,
|
||||
url: function ({ date, channel }) {
|
||||
return `https://www.digiturk.com.tr/_Ajax/getBroadcast.aspx?channelNo=${
|
||||
channel.site_id
|
||||
}&date=${date.format('DD.MM.YYYY')}&tomorrow=false&primetime=false`
|
||||
},
|
||||
request: {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Referer: 'https://www.digiturk.com.tr/'
|
||||
}
|
||||
},
|
||||
parser: function ({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.PName,
|
||||
// description: item.LongDescription,
|
||||
// category: parseCategory(item),
|
||||
start: parseTime(item.PStartTime),
|
||||
stop: parseTime(item.PEndTime)
|
||||
})
|
||||
})
|
||||
|
||||
programs = _.sortBy(programs, 'start')
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseTime(time) {
|
||||
let timestamp = parseInt(time.replace('/Date(', '').replace('+0300)/', ''))
|
||||
return dayjs(timestamp)
|
||||
}
|
||||
|
||||
// function parseCategory(item) {
|
||||
// return (item.PGenre) ? categories[item.PGenre] : null
|
||||
// }
|
||||
|
||||
function parseItems(content) {
|
||||
if (!content) return []
|
||||
const data = JSON.parse(content)
|
||||
return data && data.BChannels && data.BChannels[0].CPrograms ? data.BChannels[0].CPrograms : []
|
||||
}
|
||||
const _ = require('lodash')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
// category list is not complete
|
||||
// const categories = {
|
||||
// '00': 'Diğer',
|
||||
// E0: 'Romantik Komedi',
|
||||
// E1: 'Aksiyon',
|
||||
// E4: 'Macera',
|
||||
// E5: 'Dram',
|
||||
// E6: 'Fantastik',
|
||||
// E7: 'Komedi',
|
||||
// E8: 'Korku',
|
||||
// EB: 'Polisiye',
|
||||
// EF: 'Western',
|
||||
// FA: 'Macera',
|
||||
// FB: 'Yarışma',
|
||||
// FC: 'Eğlence',
|
||||
// F0: 'Reality-Show',
|
||||
// F2: 'Haberler',
|
||||
// F4: 'Belgesel',
|
||||
// F6: 'Eğitim',
|
||||
// F7: 'Sanat ve Kültür',
|
||||
// F9: 'Life Style'
|
||||
// }
|
||||
|
||||
module.exports = {
|
||||
site: 'digiturk.com.tr',
|
||||
days: 2,
|
||||
url: function ({ date, channel }) {
|
||||
return `https://www.digiturk.com.tr/_Ajax/getBroadcast.aspx?channelNo=${
|
||||
channel.site_id
|
||||
}&date=${date.format('DD.MM.YYYY')}&tomorrow=false&primetime=false`
|
||||
},
|
||||
request: {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Referer: 'https://www.digiturk.com.tr/'
|
||||
}
|
||||
},
|
||||
parser: function ({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.PName,
|
||||
// description: item.LongDescription,
|
||||
// category: parseCategory(item),
|
||||
start: parseTime(item.PStartTime),
|
||||
stop: parseTime(item.PEndTime)
|
||||
})
|
||||
})
|
||||
|
||||
programs = _.sortBy(programs, 'start')
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseTime(time) {
|
||||
let timestamp = parseInt(time.replace('/Date(', '').replace('+0300)/', ''))
|
||||
return dayjs(timestamp)
|
||||
}
|
||||
|
||||
// function parseCategory(item) {
|
||||
// return (item.PGenre) ? categories[item.PGenre] : null
|
||||
// }
|
||||
|
||||
function parseItems(content) {
|
||||
if (!content) return []
|
||||
const data = JSON.parse(content)
|
||||
return data && data.BChannels && data.BChannels[0].CPrograms ? data.BChannels[0].CPrograms : []
|
||||
}
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
// npm run grab -- --site=digiturk.com.tr
|
||||
|
||||
const { parser, url } = require('./digiturk.com.tr.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '14',
|
||||
xmltv_id: 'beINMovies2Action.qa'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ date, channel })
|
||||
expect(result).toBe(
|
||||
'https://www.digiturk.com.tr/_Ajax/getBroadcast.aspx?channelNo=14&date=19.01.2023&tomorrow=false&primetime=false'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
const results = parser({ content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-18T20:40:00.000Z',
|
||||
stop: '2023-01-18T22:32:00.000Z',
|
||||
title: 'PARÇALANMIŞ'
|
||||
})
|
||||
|
||||
expect(results[10]).toMatchObject({
|
||||
start: '2023-01-19T05:04:00.000Z',
|
||||
stop: '2023-01-19T06:42:00.000Z',
|
||||
title: 'HIZLI VE ÖFKELİ: TOKYO YARIŞI'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({ content: '' })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=digiturk.com.tr
|
||||
|
||||
const { parser, url } = require('./digiturk.com.tr.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '14',
|
||||
xmltv_id: 'beINMovies2Action.qa'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ date, channel })
|
||||
expect(result).toBe(
|
||||
'https://www.digiturk.com.tr/_Ajax/getBroadcast.aspx?channelNo=14&date=19.01.2023&tomorrow=false&primetime=false'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
const results = parser({ content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-18T20:40:00.000Z',
|
||||
stop: '2023-01-18T22:32:00.000Z',
|
||||
title: 'PARÇALANMIŞ'
|
||||
})
|
||||
|
||||
expect(results[10]).toMatchObject({
|
||||
start: '2023-01-19T05:04:00.000Z',
|
||||
stop: '2023-01-19T06:42:00.000Z',
|
||||
title: 'HIZLI VE ÖFKELİ: TOKYO YARIŞI'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({ content: '' })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,100 +1,100 @@
|
|||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'directv.com.ar',
|
||||
days: 2,
|
||||
url: 'https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming',
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;',
|
||||
Accept: '*/*',
|
||||
'Accept-Language': 'es-419,es;q=0.9',
|
||||
Connection: 'keep-alive',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
Origin: 'https://www.directv.com.ar',
|
||||
Referer: 'https://www.directv.com.ar/guia/ChannelDetail.aspx?id=1740&name=TLCHD',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'same-origin',
|
||||
'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',
|
||||
'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"'
|
||||
},
|
||||
data({ channel, date }) {
|
||||
const [channelNum, channelName] = channel.site_id.split('#')
|
||||
|
||||
return {
|
||||
filterParameters: {
|
||||
day: date.date(),
|
||||
time: 0,
|
||||
minute: 0,
|
||||
month: date.month() + 1,
|
||||
year: date.year(),
|
||||
offSetValue: 0,
|
||||
homeScreenFilter: '',
|
||||
filtersScreenFilters: [''],
|
||||
isHd: '',
|
||||
isChannelDetails: 'Y',
|
||||
channelNum,
|
||||
channelName: channelName.replace('&', '&')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
rating: parseRating(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseRating(item) {
|
||||
return item.rating
|
||||
? {
|
||||
system: 'MPA',
|
||||
value: item.rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires')
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
if (!content) return []
|
||||
let [ChannelNumber, ChannelName] = channel.site_id.split('#')
|
||||
ChannelName = ChannelName.replace('&', '&')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.d)) return []
|
||||
const channelData = data.d.find(
|
||||
c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName
|
||||
)
|
||||
|
||||
return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : []
|
||||
}
|
||||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'directv.com.ar',
|
||||
days: 2,
|
||||
url: 'https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming',
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;',
|
||||
Accept: '*/*',
|
||||
'Accept-Language': 'es-419,es;q=0.9',
|
||||
Connection: 'keep-alive',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
Origin: 'https://www.directv.com.ar',
|
||||
Referer: 'https://www.directv.com.ar/guia/ChannelDetail.aspx?id=1740&name=TLCHD',
|
||||
'Sec-Fetch-Dest': 'empty',
|
||||
'Sec-Fetch-Mode': 'cors',
|
||||
'Sec-Fetch-Site': 'same-origin',
|
||||
'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',
|
||||
'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"'
|
||||
},
|
||||
data({ channel, date }) {
|
||||
const [channelNum, channelName] = channel.site_id.split('#')
|
||||
|
||||
return {
|
||||
filterParameters: {
|
||||
day: date.date(),
|
||||
time: 0,
|
||||
minute: 0,
|
||||
month: date.month() + 1,
|
||||
year: date.year(),
|
||||
offSetValue: 0,
|
||||
homeScreenFilter: '',
|
||||
filtersScreenFilters: [''],
|
||||
isHd: '',
|
||||
isChannelDetails: 'Y',
|
||||
channelNum,
|
||||
channelName: channelName.replace('&', '&')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
rating: parseRating(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseRating(item) {
|
||||
return item.rating
|
||||
? {
|
||||
system: 'MPA',
|
||||
value: item.rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires')
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
if (!content) return []
|
||||
let [ChannelNumber, ChannelName] = channel.site_id.split('#')
|
||||
ChannelName = ChannelName.replace('&', '&')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.d)) return []
|
||||
const channelData = data.d.find(
|
||||
c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName
|
||||
)
|
||||
|
||||
return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : []
|
||||
}
|
||||
|
|
|
@ -1,79 +1,79 @@
|
|||
// npm run grab -- --site=directv.com.ar
|
||||
|
||||
const { parser, url, request } = require('./directv.com.ar.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-06-19', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '207#A&EHD',
|
||||
xmltv_id: 'AEHDSouth.us'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming')
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
expect(request.data({ channel, date })).toMatchObject({
|
||||
filterParameters: {
|
||||
day: 19,
|
||||
time: 0,
|
||||
minute: 0,
|
||||
month: 6,
|
||||
year: 2022,
|
||||
offSetValue: 0,
|
||||
filtersScreenFilters: [''],
|
||||
isHd: '',
|
||||
isChannelDetails: 'Y',
|
||||
channelNum: '207',
|
||||
channelName: 'A&EHD'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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}]}]}'
|
||||
const result = parser({ content, channel }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-06-19T03:00:00.000Z',
|
||||
stop: '2022-06-19T03:15:00.000Z',
|
||||
title: 'Chicas guapas',
|
||||
description:
|
||||
'Un espacio destinado a la belleza y los distintos estilos de vida, que muestra el trabajo inspiracional de la moda latinoamericana.',
|
||||
rating: {
|
||||
system: 'MPA',
|
||||
value: 'NR'
|
||||
}
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: '',
|
||||
channel
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=directv.com.ar
|
||||
|
||||
const { parser, url, request } = require('./directv.com.ar.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-06-19', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '207#A&EHD',
|
||||
xmltv_id: 'AEHDSouth.us'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming')
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
expect(request.data({ channel, date })).toMatchObject({
|
||||
filterParameters: {
|
||||
day: 19,
|
||||
time: 0,
|
||||
minute: 0,
|
||||
month: 6,
|
||||
year: 2022,
|
||||
offSetValue: 0,
|
||||
filtersScreenFilters: [''],
|
||||
isHd: '',
|
||||
isChannelDetails: 'Y',
|
||||
channelNum: '207',
|
||||
channelName: 'A&EHD'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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}]}]}'
|
||||
const result = parser({ content, channel }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-06-19T03:00:00.000Z',
|
||||
stop: '2022-06-19T03:15:00.000Z',
|
||||
title: 'Chicas guapas',
|
||||
description:
|
||||
'Un espacio destinado a la belleza y los distintos estilos de vida, que muestra el trabajo inspiracional de la moda latinoamericana.',
|
||||
rating: {
|
||||
system: 'MPA',
|
||||
value: 'NR'
|
||||
}
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: '',
|
||||
channel
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,85 +1,85 @@
|
|||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'directv.com.uy',
|
||||
days: 2,
|
||||
url: 'https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming',
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;'
|
||||
},
|
||||
data({ channel, date }) {
|
||||
const [channelNum, channelName] = channel.site_id.split('#')
|
||||
|
||||
return {
|
||||
filterParameters: {
|
||||
day: date.date(),
|
||||
time: 0,
|
||||
minute: 0,
|
||||
month: date.month() + 1,
|
||||
year: date.year(),
|
||||
offSetValue: 0,
|
||||
filtersScreenFilters: [''],
|
||||
isHd: '',
|
||||
isChannelDetails: 'Y',
|
||||
channelNum,
|
||||
channelName: channelName.replace('&', '&')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
rating: parseRating(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseRating(item) {
|
||||
return item.rating
|
||||
? {
|
||||
system: 'MPA',
|
||||
value: item.rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo')
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
if (!content) return []
|
||||
let [ChannelNumber, ChannelName] = channel.site_id.split('#')
|
||||
ChannelName = ChannelName.replace('&', '&')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.d)) return []
|
||||
const channelData = data.d.find(
|
||||
c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName
|
||||
)
|
||||
|
||||
return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : []
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'directv.com.uy',
|
||||
days: 2,
|
||||
url: 'https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming',
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;'
|
||||
},
|
||||
data({ channel, date }) {
|
||||
const [channelNum, channelName] = channel.site_id.split('#')
|
||||
|
||||
return {
|
||||
filterParameters: {
|
||||
day: date.date(),
|
||||
time: 0,
|
||||
minute: 0,
|
||||
month: date.month() + 1,
|
||||
year: date.year(),
|
||||
offSetValue: 0,
|
||||
filtersScreenFilters: [''],
|
||||
isHd: '',
|
||||
isChannelDetails: 'Y',
|
||||
channelNum,
|
||||
channelName: channelName.replace('&', '&')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
rating: parseRating(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseRating(item) {
|
||||
return item.rating
|
||||
? {
|
||||
system: 'MPA',
|
||||
value: item.rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo')
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
if (!content) return []
|
||||
let [ChannelNumber, ChannelName] = channel.site_id.split('#')
|
||||
ChannelName = ChannelName.replace('&', '&')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.d)) return []
|
||||
const channelData = data.d.find(
|
||||
c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName
|
||||
)
|
||||
|
||||
return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : []
|
||||
}
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
// npm run grab -- --site=directv.com.uy
|
||||
|
||||
const { parser, url, request } = require('./directv.com.uy.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-08-29', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '184#VTV',
|
||||
xmltv_id: 'VTV.uy'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming')
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
expect(request.data({ channel, date })).toMatchObject({
|
||||
filterParameters: {
|
||||
day: 29,
|
||||
time: 0,
|
||||
minute: 0,
|
||||
month: 8,
|
||||
year: 2022,
|
||||
offSetValue: 0,
|
||||
filtersScreenFilters: [''],
|
||||
isHd: '',
|
||||
isChannelDetails: 'Y',
|
||||
channelNum: '184',
|
||||
channelName: 'VTV'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
const results = parser({ content, channel }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-08-29T03: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',
|
||||
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).',
|
||||
rating: {
|
||||
system: 'MPA',
|
||||
value: 'NR'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: '',
|
||||
channel
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=directv.com.uy
|
||||
|
||||
const { parser, url, request } = require('./directv.com.uy.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-08-29', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '184#VTV',
|
||||
xmltv_id: 'VTV.uy'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe('https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming')
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
expect(request.data({ channel, date })).toMatchObject({
|
||||
filterParameters: {
|
||||
day: 29,
|
||||
time: 0,
|
||||
minute: 0,
|
||||
month: 8,
|
||||
year: 2022,
|
||||
offSetValue: 0,
|
||||
filtersScreenFilters: [''],
|
||||
isHd: '',
|
||||
isChannelDetails: 'Y',
|
||||
channelNum: '184',
|
||||
channelName: 'VTV'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
const results = parser({ content, channel }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-08-29T03: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',
|
||||
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).',
|
||||
rating: {
|
||||
system: 'MPA',
|
||||
value: 'NR'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: '',
|
||||
channel
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,113 +1,113 @@
|
|||
const cheerio = require('cheerio')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'directv.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
},
|
||||
headers: {
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
Connection: 'keep-alive'
|
||||
}
|
||||
},
|
||||
url({ date, channel }) {
|
||||
const [channelId, childId] = channel.site_id.split('#')
|
||||
return `https://www.directv.com/json/channelschedule?channels=${channelId}&startTime=${date.format()}&hours=24&chId=${childId}`
|
||||
},
|
||||
async parser({ content, channel }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, channel)
|
||||
for (let item of items) {
|
||||
if (item.programID === '-1') continue
|
||||
const detail = await loadProgramDetail(item.programID)
|
||||
const start = parseStart(item)
|
||||
const stop = start.add(item.duration, 'm')
|
||||
programs.push({
|
||||
title: item.title,
|
||||
sub_title: item.episodeTitle,
|
||||
description: parseDescription(detail),
|
||||
rating: parseRating(item),
|
||||
date: parseYear(detail),
|
||||
category: item.subcategoryList,
|
||||
season: item.seasonNumber,
|
||||
episode: item.episodeNumber,
|
||||
icon: parseIcon(item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ zip }) {
|
||||
const html = await axios
|
||||
.get('https://www.directv.com/guide', {
|
||||
headers: {
|
||||
cookie: `dtve-prospect-zip=${zip}`
|
||||
}
|
||||
})
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
const $ = cheerio.load(html)
|
||||
const script = $('#dtvClientData').html()
|
||||
const [, json] = script.match(/var dtvClientData = (.*);/) || [null, null]
|
||||
const data = JSON.parse(json)
|
||||
|
||||
let items = data.guideData.channels
|
||||
|
||||
return items.map(item => {
|
||||
return {
|
||||
lang: 'en',
|
||||
site_id: item.chNum,
|
||||
name: item.chName
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseDescription(detail) {
|
||||
return detail ? detail.description : null
|
||||
}
|
||||
function parseYear(detail) {
|
||||
return detail ? detail.releaseYear : null
|
||||
}
|
||||
function parseRating(item) {
|
||||
return item.rating
|
||||
? {
|
||||
system: 'MPA',
|
||||
value: item.rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.primaryImageUrl ? `https://www.directv.com${item.primaryImageUrl}` : null
|
||||
}
|
||||
function loadProgramDetail(programID) {
|
||||
return axios
|
||||
.get(`https://www.directv.com/json/program/flip/${programID}`)
|
||||
.then(r => r.data)
|
||||
.then(d => d.programDetail)
|
||||
.catch(console.err)
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.utc(item.airTime)
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data) return []
|
||||
if (!Array.isArray(data.schedule)) return []
|
||||
|
||||
const [, childId] = channel.site_id.split('#')
|
||||
const channelData = data.schedule.find(i => i.chId == childId)
|
||||
return channelData.schedules && Array.isArray(channelData.schedules) ? channelData.schedules : []
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'directv.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
},
|
||||
headers: {
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
Connection: 'keep-alive'
|
||||
}
|
||||
},
|
||||
url({ date, channel }) {
|
||||
const [channelId, childId] = channel.site_id.split('#')
|
||||
return `https://www.directv.com/json/channelschedule?channels=${channelId}&startTime=${date.format()}&hours=24&chId=${childId}`
|
||||
},
|
||||
async parser({ content, channel }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, channel)
|
||||
for (let item of items) {
|
||||
if (item.programID === '-1') continue
|
||||
const detail = await loadProgramDetail(item.programID)
|
||||
const start = parseStart(item)
|
||||
const stop = start.add(item.duration, 'm')
|
||||
programs.push({
|
||||
title: item.title,
|
||||
sub_title: item.episodeTitle,
|
||||
description: parseDescription(detail),
|
||||
rating: parseRating(item),
|
||||
date: parseYear(detail),
|
||||
category: item.subcategoryList,
|
||||
season: item.seasonNumber,
|
||||
episode: item.episodeNumber,
|
||||
icon: parseIcon(item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ zip }) {
|
||||
const html = await axios
|
||||
.get('https://www.directv.com/guide', {
|
||||
headers: {
|
||||
cookie: `dtve-prospect-zip=${zip}`
|
||||
}
|
||||
})
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
const $ = cheerio.load(html)
|
||||
const script = $('#dtvClientData').html()
|
||||
const [, json] = script.match(/var dtvClientData = (.*);/) || [null, null]
|
||||
const data = JSON.parse(json)
|
||||
|
||||
let items = data.guideData.channels
|
||||
|
||||
return items.map(item => {
|
||||
return {
|
||||
lang: 'en',
|
||||
site_id: item.chNum,
|
||||
name: item.chName
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseDescription(detail) {
|
||||
return detail ? detail.description : null
|
||||
}
|
||||
function parseYear(detail) {
|
||||
return detail ? detail.releaseYear : null
|
||||
}
|
||||
function parseRating(item) {
|
||||
return item.rating
|
||||
? {
|
||||
system: 'MPA',
|
||||
value: item.rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
function parseIcon(item) {
|
||||
return item.primaryImageUrl ? `https://www.directv.com${item.primaryImageUrl}` : null
|
||||
}
|
||||
function loadProgramDetail(programID) {
|
||||
return axios
|
||||
.get(`https://www.directv.com/json/program/flip/${programID}`)
|
||||
.then(r => r.data)
|
||||
.then(d => d.programDetail)
|
||||
.catch(console.err)
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.utc(item.airTime)
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data) return []
|
||||
if (!Array.isArray(data.schedule)) return []
|
||||
|
||||
const [, childId] = channel.site_id.split('#')
|
||||
const channelData = data.schedule.find(i => i.chId == childId)
|
||||
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
|
||||
// npm run grab -- --site=directv.com
|
||||
|
||||
const { parser, url } = require('./directv.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const date = dayjs.utc('2023-01-15', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '249#249',
|
||||
xmltv_id: 'ComedyCentralEast.us'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ date, channel })
|
||||
expect(result).toBe(
|
||||
'https://www.directv.com/json/channelschedule?channels=249&startTime=2023-01-15T00:00:00Z&hours=24&chId=249'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://www.directv.com/json/program/flip/MV001173520000') {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
|
||||
})
|
||||
} else if (url === 'https://www.directv.com/json/program/flip/EP002298270445') {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content, channel })
|
||||
.then(result => {
|
||||
result = result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2023-01-14T23:00:00.000Z',
|
||||
stop: '2023-01-15T01:00:00.000Z',
|
||||
title: 'Men in Black II',
|
||||
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.',
|
||||
date: '2002',
|
||||
icon: 'https://www.directv.com/db_photos/movies/AllPhotosAPGI/29160/29160_aa.jpg',
|
||||
category: ['Comedy', 'Movies Anywhere', 'Action/Adventure', 'Science Fiction'],
|
||||
rating: {
|
||||
system: 'MPA',
|
||||
value: 'TV14'
|
||||
}
|
||||
},
|
||||
{
|
||||
start: '2023-01-15T06:00:00.000Z',
|
||||
stop: '2023-01-15T06:30:00.000Z',
|
||||
title: 'South Park',
|
||||
sub_title: 'Goth Kids 3: Dawn of the Posers',
|
||||
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',
|
||||
category: ['Series', 'Animation', 'Comedy'],
|
||||
season: 17,
|
||||
episode: 4,
|
||||
rating: {
|
||||
system: 'MPA',
|
||||
value: 'TVMA'
|
||||
}
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.json'))
|
||||
parser({ content, channel })
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url } = require('./directv.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const date = dayjs.utc('2023-01-15', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '249#249',
|
||||
xmltv_id: 'ComedyCentralEast.us'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ date, channel })
|
||||
expect(result).toBe(
|
||||
'https://www.directv.com/json/channelschedule?channels=249&startTime=2023-01-15T00:00:00Z&hours=24&chId=249'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === 'https://www.directv.com/json/program/flip/MV001173520000') {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
|
||||
})
|
||||
} else if (url === 'https://www.directv.com/json/program/flip/EP002298270445') {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content, channel })
|
||||
.then(result => {
|
||||
result = result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2023-01-14T23:00:00.000Z',
|
||||
stop: '2023-01-15T01:00:00.000Z',
|
||||
title: 'Men in Black II',
|
||||
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.',
|
||||
date: '2002',
|
||||
icon: 'https://www.directv.com/db_photos/movies/AllPhotosAPGI/29160/29160_aa.jpg',
|
||||
category: ['Comedy', 'Movies Anywhere', 'Action/Adventure', 'Science Fiction'],
|
||||
rating: {
|
||||
system: 'MPA',
|
||||
value: 'TV14'
|
||||
}
|
||||
},
|
||||
{
|
||||
start: '2023-01-15T06:00:00.000Z',
|
||||
stop: '2023-01-15T06:30:00.000Z',
|
||||
title: 'South Park',
|
||||
sub_title: 'Goth Kids 3: Dawn of the Posers',
|
||||
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',
|
||||
category: ['Series', 'Animation', 'Comedy'],
|
||||
season: 17,
|
||||
episode: 4,
|
||||
rating: {
|
||||
system: 'MPA',
|
||||
value: 'TVMA'
|
||||
}
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.json'))
|
||||
parser({ content, channel })
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
|
|
@ -1,145 +1,145 @@
|
|||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'dishtv.in',
|
||||
days: 2,
|
||||
url: 'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram',
|
||||
request: {
|
||||
method: 'POST',
|
||||
data({ channel, date }) {
|
||||
return {
|
||||
Channelarr: channel.site_id,
|
||||
fromdate: date.format('YYYYMMDDHHmm'),
|
||||
todate: date.add(1, 'd').format('YYYYMMDDHHmm')
|
||||
}
|
||||
}
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
let programs = []
|
||||
const data = parseContent(content)
|
||||
const items = parseItems(data)
|
||||
items.forEach(item => {
|
||||
const title = parseTitle(item)
|
||||
const start = parseStart(item, date)
|
||||
const stop = parseStop(item, start)
|
||||
if (title === 'No Information Available') return
|
||||
|
||||
programs.push({
|
||||
title,
|
||||
start: start.toString(),
|
||||
stop: stop.toString()
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const channelguide = await axios
|
||||
.get('https://www.dishtv.in/channelguide/')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
const $channelguide = cheerio.load(channelguide)
|
||||
|
||||
let ids = []
|
||||
$channelguide('#MainContent_recordPagging li').each((i, item) => {
|
||||
const onclick = $channelguide(item).find('a').attr('onclick')
|
||||
const [, list] = onclick.match(/ShowNextPageResult\('([^']+)/) || [null, null]
|
||||
|
||||
ids = ids.concat(list.split(','))
|
||||
})
|
||||
ids = ids.filter(Boolean)
|
||||
|
||||
const channels = {}
|
||||
const channelList = await axios
|
||||
.post('https://www.dishtv.in/WebServiceMethod.aspx/GetChannelListFromMobileAPI', {
|
||||
strChannel: ''
|
||||
})
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
const $channelList = cheerio.load(channelList.d)
|
||||
$channelList('#tblpackChnl > div').each((i, item) => {
|
||||
let num = $channelList(item).find('p:nth-child(2)').text().trim()
|
||||
const name = $channelList(item).find('p').first().text().trim()
|
||||
|
||||
if (num === '') return
|
||||
|
||||
channels[parseInt(num)] = {
|
||||
name
|
||||
}
|
||||
})
|
||||
|
||||
const date = dayjs().add(1, 'd')
|
||||
const promises = []
|
||||
for (let id of ids) {
|
||||
const promise = axios
|
||||
.post(
|
||||
'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram',
|
||||
{
|
||||
Channelarr: id,
|
||||
fromdate: date.format('YYYYMMDD[0000]'),
|
||||
todate: date.format('YYYYMMDD[2300]')
|
||||
},
|
||||
{ timeout: 5000 }
|
||||
)
|
||||
.then(r => r.data)
|
||||
.then(data => {
|
||||
const $channelGuide = cheerio.load(data.d)
|
||||
|
||||
const num = $channelGuide('.cnl-fav > a > span').text().trim()
|
||||
|
||||
if (channels[num]) {
|
||||
channels[num].site_id = id
|
||||
}
|
||||
})
|
||||
.catch(console.log)
|
||||
|
||||
promises.push(promise)
|
||||
}
|
||||
|
||||
await Promise.allSettled(promises)
|
||||
|
||||
return Object.values(channels)
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle(item) {
|
||||
const $ = cheerio.load(item)
|
||||
|
||||
return $('a').text()
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
const $ = cheerio.load(item)
|
||||
const onclick = $('i.fa-circle').attr('onclick')
|
||||
const [, time] = onclick.match(/RecordingEnteryOpen\('.*','.*','(.*)','.*',.*\)/)
|
||||
|
||||
return dayjs.tz(time, 'YYYYMMDDHHmm', 'Asia/Kolkata')
|
||||
}
|
||||
|
||||
function parseStop(item, start) {
|
||||
const $ = cheerio.load(item)
|
||||
const duration = $('*').data('time')
|
||||
|
||||
return start.add(duration, 'm')
|
||||
}
|
||||
|
||||
function parseContent(content) {
|
||||
const data = JSON.parse(content)
|
||||
|
||||
return data.d
|
||||
}
|
||||
|
||||
function parseItems(data) {
|
||||
const $ = cheerio.load(data)
|
||||
|
||||
return $('.datatime').toArray()
|
||||
}
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'dishtv.in',
|
||||
days: 2,
|
||||
url: 'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram',
|
||||
request: {
|
||||
method: 'POST',
|
||||
data({ channel, date }) {
|
||||
return {
|
||||
Channelarr: channel.site_id,
|
||||
fromdate: date.format('YYYYMMDDHHmm'),
|
||||
todate: date.add(1, 'd').format('YYYYMMDDHHmm')
|
||||
}
|
||||
}
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
let programs = []
|
||||
const data = parseContent(content)
|
||||
const items = parseItems(data)
|
||||
items.forEach(item => {
|
||||
const title = parseTitle(item)
|
||||
const start = parseStart(item, date)
|
||||
const stop = parseStop(item, start)
|
||||
if (title === 'No Information Available') return
|
||||
|
||||
programs.push({
|
||||
title,
|
||||
start: start.toString(),
|
||||
stop: stop.toString()
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const channelguide = await axios
|
||||
.get('https://www.dishtv.in/channelguide/')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
const $channelguide = cheerio.load(channelguide)
|
||||
|
||||
let ids = []
|
||||
$channelguide('#MainContent_recordPagging li').each((i, item) => {
|
||||
const onclick = $channelguide(item).find('a').attr('onclick')
|
||||
const [, list] = onclick.match(/ShowNextPageResult\('([^']+)/) || [null, null]
|
||||
|
||||
ids = ids.concat(list.split(','))
|
||||
})
|
||||
ids = ids.filter(Boolean)
|
||||
|
||||
const channels = {}
|
||||
const channelList = await axios
|
||||
.post('https://www.dishtv.in/WebServiceMethod.aspx/GetChannelListFromMobileAPI', {
|
||||
strChannel: ''
|
||||
})
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
const $channelList = cheerio.load(channelList.d)
|
||||
$channelList('#tblpackChnl > div').each((i, item) => {
|
||||
let num = $channelList(item).find('p:nth-child(2)').text().trim()
|
||||
const name = $channelList(item).find('p').first().text().trim()
|
||||
|
||||
if (num === '') return
|
||||
|
||||
channels[parseInt(num)] = {
|
||||
name
|
||||
}
|
||||
})
|
||||
|
||||
const date = dayjs().add(1, 'd')
|
||||
const promises = []
|
||||
for (let id of ids) {
|
||||
const promise = axios
|
||||
.post(
|
||||
'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram',
|
||||
{
|
||||
Channelarr: id,
|
||||
fromdate: date.format('YYYYMMDD[0000]'),
|
||||
todate: date.format('YYYYMMDD[2300]')
|
||||
},
|
||||
{ timeout: 5000 }
|
||||
)
|
||||
.then(r => r.data)
|
||||
.then(data => {
|
||||
const $channelGuide = cheerio.load(data.d)
|
||||
|
||||
const num = $channelGuide('.cnl-fav > a > span').text().trim()
|
||||
|
||||
if (channels[num]) {
|
||||
channels[num].site_id = id
|
||||
}
|
||||
})
|
||||
.catch(console.log)
|
||||
|
||||
promises.push(promise)
|
||||
}
|
||||
|
||||
await Promise.allSettled(promises)
|
||||
|
||||
return Object.values(channels)
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle(item) {
|
||||
const $ = cheerio.load(item)
|
||||
|
||||
return $('a').text()
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
const $ = cheerio.load(item)
|
||||
const onclick = $('i.fa-circle').attr('onclick')
|
||||
const [, time] = onclick.match(/RecordingEnteryOpen\('.*','.*','(.*)','.*',.*\)/)
|
||||
|
||||
return dayjs.tz(time, 'YYYYMMDDHHmm', 'Asia/Kolkata')
|
||||
}
|
||||
|
||||
function parseStop(item, start) {
|
||||
const $ = cheerio.load(item)
|
||||
const duration = $('*').data('time')
|
||||
|
||||
return start.add(duration, 'm')
|
||||
}
|
||||
|
||||
function parseContent(content) {
|
||||
const data = JSON.parse(content)
|
||||
|
||||
return data.d
|
||||
}
|
||||
|
||||
function parseItems(data) {
|
||||
const $ = cheerio.load(data)
|
||||
|
||||
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 grab -- --site=dishtv.in
|
||||
|
||||
const { parser, url, request } = require('./dishtv.in.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2021-11-05', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = { site_id: '10000000075992337', xmltv_id: 'WomensActive.in' }
|
||||
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"}'
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe(
|
||||
'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
const result = request.data({ channel, date })
|
||||
expect(result).toMatchObject({
|
||||
Channelarr: '10000000075992337',
|
||||
fromdate: '202111050000',
|
||||
todate: '202111060000'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const result = parser({ date, channel, content })
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: 'Thu, 04 Nov 2021 18:30:00 GMT',
|
||||
stop: 'Thu, 04 Nov 2021 18:54:00 GMT',
|
||||
title: 'Cynthia Williams - Diwali Look Part 01'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({ date, channel, content: '{"d":""}' })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url, request } = require('./dishtv.in.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2021-11-05', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = { site_id: '10000000075992337', xmltv_id: 'WomensActive.in' }
|
||||
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"}'
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe(
|
||||
'https://www.dishtv.in/WhatsonIndiaWebService.asmx/LoadPagginResultDataForProgram'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid request data', () => {
|
||||
const result = request.data({ channel, date })
|
||||
expect(result).toMatchObject({
|
||||
Channelarr: '10000000075992337',
|
||||
fromdate: '202111050000',
|
||||
todate: '202111060000'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const result = parser({ date, channel, content })
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: 'Thu, 04 Nov 2021 18:30:00 GMT',
|
||||
stop: 'Thu, 04 Nov 2021 18:54:00 GMT',
|
||||
title: 'Cynthia Williams - Diwali Look Part 01'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({ date, channel, content: '{"d":""}' })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,104 +1,104 @@
|
|||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const API_ENDPOINT = 'https://www.dsmart.com.tr/api/v1/public/epg/schedules'
|
||||
|
||||
module.exports = {
|
||||
site: 'dsmart.com.tr',
|
||||
days: 2,
|
||||
url({ date, channel }) {
|
||||
const [page] = channel.site_id.split('#')
|
||||
|
||||
return `${API_ENDPOINT}?page=${page}&limit=1&day=${date.format('YYYY-MM-DD')}`
|
||||
},
|
||||
parser: function ({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
let start
|
||||
if (prev) {
|
||||
start = parseStart(item, prev.stop)
|
||||
} else {
|
||||
start = parseStart(item, dayjs.utc(item.day))
|
||||
}
|
||||
let duration = parseDuration(item)
|
||||
let stop = start.add(duration, 's')
|
||||
|
||||
programs.push({
|
||||
title: item.program_name,
|
||||
category: parseCategory(item),
|
||||
description: item.description.trim(),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const perPage = 1
|
||||
const totalChannels = 210
|
||||
const pages = Math.ceil(totalChannels / perPage)
|
||||
|
||||
const channels = []
|
||||
for (let i in Array(pages).fill(0)) {
|
||||
const page = parseInt(i) + 1
|
||||
const url = `${API_ENDPOINT}?page=${page}&limit=${perPage}&day=${dayjs().format(
|
||||
'YYYY-MM-DD'
|
||||
)}`
|
||||
let offset = i * perPage
|
||||
await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.then(data => {
|
||||
offset++
|
||||
if (data && data.data && Array.isArray(data.data.channels)) {
|
||||
data.data.channels.forEach((item, j) => {
|
||||
const index = offset + j
|
||||
channels.push({
|
||||
lang: 'tr',
|
||||
name: item.channel_name,
|
||||
site_id: index + '#' + item._id
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err.message)
|
||||
})
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
}
|
||||
|
||||
function parseCategory(item) {
|
||||
return item.genre !== '0' ? item.genre : null
|
||||
}
|
||||
|
||||
function parseStart(item, 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')
|
||||
}
|
||||
|
||||
function parseDuration(item) {
|
||||
const [, H, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)$/)
|
||||
|
||||
return parseInt(H) * 3600 + parseInt(mm) * 60 + parseInt(ss)
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !data.data || !Array.isArray(data.data.channels)) return null
|
||||
const channelData = data.data.channels.find(i => i._id == channelId)
|
||||
|
||||
return channelData && Array.isArray(channelData.schedule) ? channelData.schedule : []
|
||||
}
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const API_ENDPOINT = 'https://www.dsmart.com.tr/api/v1/public/epg/schedules'
|
||||
|
||||
module.exports = {
|
||||
site: 'dsmart.com.tr',
|
||||
days: 2,
|
||||
url({ date, channel }) {
|
||||
const [page] = channel.site_id.split('#')
|
||||
|
||||
return `${API_ENDPOINT}?page=${page}&limit=1&day=${date.format('YYYY-MM-DD')}`
|
||||
},
|
||||
parser: function ({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
let start
|
||||
if (prev) {
|
||||
start = parseStart(item, prev.stop)
|
||||
} else {
|
||||
start = parseStart(item, dayjs.utc(item.day))
|
||||
}
|
||||
let duration = parseDuration(item)
|
||||
let stop = start.add(duration, 's')
|
||||
|
||||
programs.push({
|
||||
title: item.program_name,
|
||||
category: parseCategory(item),
|
||||
description: item.description.trim(),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const perPage = 1
|
||||
const totalChannels = 210
|
||||
const pages = Math.ceil(totalChannels / perPage)
|
||||
|
||||
const channels = []
|
||||
for (let i in Array(pages).fill(0)) {
|
||||
const page = parseInt(i) + 1
|
||||
const url = `${API_ENDPOINT}?page=${page}&limit=${perPage}&day=${dayjs().format(
|
||||
'YYYY-MM-DD'
|
||||
)}`
|
||||
let offset = i * perPage
|
||||
await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.then(data => {
|
||||
offset++
|
||||
if (data && data.data && Array.isArray(data.data.channels)) {
|
||||
data.data.channels.forEach((item, j) => {
|
||||
const index = offset + j
|
||||
channels.push({
|
||||
lang: 'tr',
|
||||
name: item.channel_name,
|
||||
site_id: index + '#' + item._id
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err.message)
|
||||
})
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
}
|
||||
|
||||
function parseCategory(item) {
|
||||
return item.genre !== '0' ? item.genre : null
|
||||
}
|
||||
|
||||
function parseStart(item, 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')
|
||||
}
|
||||
|
||||
function parseDuration(item) {
|
||||
const [, H, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)$/)
|
||||
|
||||
return parseInt(H) * 3600 + parseInt(mm) * 60 + parseInt(ss)
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !data.data || !Array.isArray(data.data.channels)) return null
|
||||
const channelData = data.data.channels.find(i => i._id == channelId)
|
||||
|
||||
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 grab -- --site=dsmart.com.tr
|
||||
|
||||
const { parser, url } = require('./dsmart.com.tr.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-16', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '3#5fe07d7acfef0b1593275751',
|
||||
xmltv_id: 'SinemaTV.tr'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date, channel })).toBe(
|
||||
'https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=3&limit=1&day=2023-01-16'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
const results = parser({ channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-15T22:00:00.000Z',
|
||||
stop: '2023-01-15T23:45:00.000Z',
|
||||
title: 'Bizi Ayıran Her Şey',
|
||||
category: 'sinema/genel',
|
||||
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.'
|
||||
})
|
||||
|
||||
expect(results[1]).toMatchObject({
|
||||
start: '2023-01-15T23:45:00.000Z',
|
||||
stop: '2023-01-16T01:30:00.000Z',
|
||||
title: 'Pixie',
|
||||
category: 'sinema/genel',
|
||||
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.'
|
||||
})
|
||||
|
||||
expect(results[12]).toMatchObject({
|
||||
start: '2023-01-16T20:30:00.000Z',
|
||||
stop: '2023-01-16T22:30:00.000Z',
|
||||
title: 'Seberg',
|
||||
category: 'sinema/genel',
|
||||
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.'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({
|
||||
channel,
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||
})
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url } = require('./dsmart.com.tr.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-16', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '3#5fe07d7acfef0b1593275751',
|
||||
xmltv_id: 'SinemaTV.tr'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date, channel })).toBe(
|
||||
'https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=3&limit=1&day=2023-01-16'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
const results = parser({ channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-15T22:00:00.000Z',
|
||||
stop: '2023-01-15T23:45:00.000Z',
|
||||
title: 'Bizi Ayıran Her Şey',
|
||||
category: 'sinema/genel',
|
||||
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.'
|
||||
})
|
||||
|
||||
expect(results[1]).toMatchObject({
|
||||
start: '2023-01-15T23:45:00.000Z',
|
||||
stop: '2023-01-16T01:30:00.000Z',
|
||||
title: 'Pixie',
|
||||
category: 'sinema/genel',
|
||||
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.'
|
||||
})
|
||||
|
||||
expect(results[12]).toMatchObject({
|
||||
start: '2023-01-16T20:30:00.000Z',
|
||||
stop: '2023-01-16T22:30:00.000Z',
|
||||
title: 'Seberg',
|
||||
category: 'sinema/genel',
|
||||
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.'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({
|
||||
channel,
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||
})
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,101 +1,101 @@
|
|||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide'
|
||||
|
||||
module.exports = {
|
||||
site: 'dstv.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 3 * 60 * 60 * 1000, // 3h
|
||||
interpretHeader: false
|
||||
}
|
||||
},
|
||||
url: function ({ channel, date }) {
|
||||
const [region] = channel.site_id.split('#')
|
||||
const packageName = region === 'nga' ? '&package=DStv%20Premium' : ''
|
||||
|
||||
return `${API_ENDPOINT}/GetProgrammes?d=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}${packageName}&country=${region}`
|
||||
},
|
||||
async parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
for (const item of items) {
|
||||
const details = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.Title,
|
||||
description: parseDescription(details),
|
||||
icon: parseIcon(details),
|
||||
category: parseCategory(details),
|
||||
start: parseTime(item.StartTime, channel),
|
||||
stop: parseTime(item.EndTime, channel)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ country }) {
|
||||
const data = await axios
|
||||
.get(`${API_ENDPOINT}/GetProgrammes?d=2022-03-10&package=DStv%20Premium&country=${country}`)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return data.Channels.map(item => {
|
||||
return {
|
||||
site_id: `${country}#${item.Number}`,
|
||||
name: item.Name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseTime(time, channel) {
|
||||
const [region] = channel.site_id.split('#')
|
||||
const tz = {
|
||||
zaf: 'Africa/Johannesburg',
|
||||
nga: 'Africa/Lagos'
|
||||
}
|
||||
|
||||
return dayjs.tz(time, 'YYYY-MM-DDTHH:mm:ss', tz[region])
|
||||
}
|
||||
|
||||
function parseDescription(details) {
|
||||
return details ? details.Synopsis : null
|
||||
}
|
||||
|
||||
function parseIcon(details) {
|
||||
return details ? details.ThumbnailUri : null
|
||||
}
|
||||
|
||||
function parseCategory(details) {
|
||||
return details ? details.SubGenres : null
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
const url = `${API_ENDPOINT}/GetProgramme?id=${item.Id}`
|
||||
|
||||
return axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.Channels)) return []
|
||||
const channelData = data.Channels.find(c => c.Number === channelId)
|
||||
if (!channelData || !Array.isArray(channelData.Programmes)) return []
|
||||
|
||||
return channelData.Programmes
|
||||
}
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide'
|
||||
|
||||
module.exports = {
|
||||
site: 'dstv.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 3 * 60 * 60 * 1000, // 3h
|
||||
interpretHeader: false
|
||||
}
|
||||
},
|
||||
url: function ({ channel, date }) {
|
||||
const [region] = channel.site_id.split('#')
|
||||
const packageName = region === 'nga' ? '&package=DStv%20Premium' : ''
|
||||
|
||||
return `${API_ENDPOINT}/GetProgrammes?d=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}${packageName}&country=${region}`
|
||||
},
|
||||
async parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
for (const item of items) {
|
||||
const details = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.Title,
|
||||
description: parseDescription(details),
|
||||
icon: parseIcon(details),
|
||||
category: parseCategory(details),
|
||||
start: parseTime(item.StartTime, channel),
|
||||
stop: parseTime(item.EndTime, channel)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ country }) {
|
||||
const data = await axios
|
||||
.get(`${API_ENDPOINT}/GetProgrammes?d=2022-03-10&package=DStv%20Premium&country=${country}`)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return data.Channels.map(item => {
|
||||
return {
|
||||
site_id: `${country}#${item.Number}`,
|
||||
name: item.Name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseTime(time, channel) {
|
||||
const [region] = channel.site_id.split('#')
|
||||
const tz = {
|
||||
zaf: 'Africa/Johannesburg',
|
||||
nga: 'Africa/Lagos'
|
||||
}
|
||||
|
||||
return dayjs.tz(time, 'YYYY-MM-DDTHH:mm:ss', tz[region])
|
||||
}
|
||||
|
||||
function parseDescription(details) {
|
||||
return details ? details.Synopsis : null
|
||||
}
|
||||
|
||||
function parseIcon(details) {
|
||||
return details ? details.ThumbnailUri : null
|
||||
}
|
||||
|
||||
function parseCategory(details) {
|
||||
return details ? details.SubGenres : null
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
const url = `${API_ENDPOINT}/GetProgramme?id=${item.Id}`
|
||||
|
||||
return axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelId] = channel.site_id.split('#')
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.Channels)) return []
|
||||
const channelData = data.Channels.find(c => c.Number === channelId)
|
||||
if (!channelData || !Array.isArray(channelData.Programmes)) return []
|
||||
|
||||
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 grab -- --site=dstv.com
|
||||
|
||||
const { parser, url } = require('./dstv.com.config.js')
|
||||
const axios = require('axios')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide'
|
||||
|
||||
const date = dayjs.utc('2022-11-22', 'YYYY-MM-DD').startOf('d')
|
||||
const channelZA = {
|
||||
site_id: 'zaf#201',
|
||||
xmltv_id: 'SuperSportGrandstand.za'
|
||||
}
|
||||
const channelNG = {
|
||||
site_id: 'nga#201',
|
||||
xmltv_id: 'SuperSportGrandstand.za'
|
||||
}
|
||||
|
||||
it('can generate valid url for zaf', () => {
|
||||
expect(url({ channel: channelZA, date })).toBe(
|
||||
`${API_ENDPOINT}/GetProgrammes?d=2022-11-22&country=zaf`
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid url for nga', () => {
|
||||
expect(url({ channel: channelNG, date })).toBe(
|
||||
`${API_ENDPOINT}/GetProgrammes?d=2022-11-22&package=DStv%20Premium&country=nga`
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response for ZA', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_zaf.json'))
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === `${API_ENDPOINT}/GetProgramme?id=8b237235-aa17-4bb8-9ea6-097e7a813336`) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_zaf.json')))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
let results = await parser({ content, channel: channelZA })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[1]).toMatchObject({
|
||||
start: '2022-11-21T23:00:00.000Z',
|
||||
stop: '2022-11-22T00:00:00.000Z',
|
||||
title: 'UFC FN HL: Nzechukwu v Cutelaba',
|
||||
description:
|
||||
"'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',
|
||||
category: ['All Sport', 'Mixed Martial Arts']
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response for NG', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_nga.json'))
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === `${API_ENDPOINT}/GetProgramme?id=6d58931e-2192-486a-a202-14720136d204`) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_nga.json')))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
let results = await parser({ content, channel: channelNG })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-21T23:00:00.000Z',
|
||||
stop: '2022-11-22T00:00:00.000Z',
|
||||
title: 'UFC FN HL: Nzechukwu v Cutelaba',
|
||||
description:
|
||||
"'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',
|
||||
category: ['All Sport', 'Mixed Martial Arts']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
content: '{"Total":0,"Channels":[]}',
|
||||
channel: channelZA
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url } = require('./dstv.com.config.js')
|
||||
const axios = require('axios')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide'
|
||||
|
||||
const date = dayjs.utc('2022-11-22', 'YYYY-MM-DD').startOf('d')
|
||||
const channelZA = {
|
||||
site_id: 'zaf#201',
|
||||
xmltv_id: 'SuperSportGrandstand.za'
|
||||
}
|
||||
const channelNG = {
|
||||
site_id: 'nga#201',
|
||||
xmltv_id: 'SuperSportGrandstand.za'
|
||||
}
|
||||
|
||||
it('can generate valid url for zaf', () => {
|
||||
expect(url({ channel: channelZA, date })).toBe(
|
||||
`${API_ENDPOINT}/GetProgrammes?d=2022-11-22&country=zaf`
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid url for nga', () => {
|
||||
expect(url({ channel: channelNG, date })).toBe(
|
||||
`${API_ENDPOINT}/GetProgrammes?d=2022-11-22&package=DStv%20Premium&country=nga`
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response for ZA', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_zaf.json'))
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === `${API_ENDPOINT}/GetProgramme?id=8b237235-aa17-4bb8-9ea6-097e7a813336`) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_zaf.json')))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
let results = await parser({ content, channel: channelZA })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[1]).toMatchObject({
|
||||
start: '2022-11-21T23:00:00.000Z',
|
||||
stop: '2022-11-22T00:00:00.000Z',
|
||||
title: 'UFC FN HL: Nzechukwu v Cutelaba',
|
||||
description:
|
||||
"'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',
|
||||
category: ['All Sport', 'Mixed Martial Arts']
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response for NG', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_nga.json'))
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (url === `${API_ENDPOINT}/GetProgramme?id=6d58931e-2192-486a-a202-14720136d204`) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_nga.json')))
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
let results = await parser({ content, channel: channelNG })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-21T23:00:00.000Z',
|
||||
stop: '2022-11-22T00:00:00.000Z',
|
||||
title: 'UFC FN HL: Nzechukwu v Cutelaba',
|
||||
description:
|
||||
"'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',
|
||||
category: ['All Sport', 'Mixed Martial Arts']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
content: '{"Total":0,"Channels":[]}',
|
||||
channel: channelZA
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
|
|
@ -1,118 +1,118 @@
|
|||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
require('dayjs/locale/ar')
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'elcinema.com',
|
||||
days: 2,
|
||||
url({ channel }) {
|
||||
const lang = channel.lang === 'en' ? 'en/' : '/'
|
||||
|
||||
return `https://elcinema.com/${lang}tvguide/${channel.site_id}/`
|
||||
},
|
||||
parser({ content, channel, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, channel, date)
|
||||
items.forEach(item => {
|
||||
const start = parseStart(item, date)
|
||||
const duration = parseDuration(item)
|
||||
const stop = start.add(duration, 'm')
|
||||
programs.push({
|
||||
title: parseTitle(item),
|
||||
description: parseDescription(item),
|
||||
category: parseCategory(item),
|
||||
icon: parseIcon(item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
const $ = cheerio.load(item)
|
||||
const imgSrc =
|
||||
$('.row > div.columns.small-3.large-1 > a > img').data('src') ||
|
||||
$('.row > div.columns.small-5.large-1 > img').data('src')
|
||||
|
||||
return imgSrc || null
|
||||
}
|
||||
|
||||
function parseCategory(item) {
|
||||
const $ = cheerio.load(item)
|
||||
const category = $('.row > div.columns.small-6.large-3 > ul > li:nth-child(2)').text()
|
||||
|
||||
return category.replace(/\(\d+\)/, '').trim() || null
|
||||
}
|
||||
|
||||
function parseDuration(item) {
|
||||
const $ = cheerio.load(item)
|
||||
const duration =
|
||||
$('.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()
|
||||
|
||||
return duration.replace(/\D/g, '') || ''
|
||||
}
|
||||
|
||||
function parseStart(item, initDate) {
|
||||
const $ = cheerio.load(item)
|
||||
let time =
|
||||
$('.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() ||
|
||||
''
|
||||
|
||||
time = time
|
||||
.replace(/\[.*\]/, '')
|
||||
.replace('مساءً', 'PM')
|
||||
.replace('صباحًا', 'AM')
|
||||
.trim()
|
||||
|
||||
time = `${initDate.format('YYYY-MM-DD')} ${time}`
|
||||
|
||||
return dayjs.tz(time, 'YYYY-MM-DD hh:mm A', dayjs.tz.guess())
|
||||
}
|
||||
|
||||
function parseTitle(item) {
|
||||
const $ = cheerio.load(item)
|
||||
|
||||
return (
|
||||
$('.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() ||
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
function parseDescription(item) {
|
||||
const $ = cheerio.load(item)
|
||||
const excerpt = $('.row > div.columns.small-12.large-6 > ul > li:nth-child(3)').text() || ''
|
||||
|
||||
return excerpt.replace('...اقرأ المزيد', '').replace('...Read more', '')
|
||||
}
|
||||
|
||||
function parseItems(content, channel, date) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
const dateString = date.locale(channel.lang).format('dddd D')
|
||||
|
||||
const list = $('.dates')
|
||||
.filter((i, el) => {
|
||||
let parsedDateString = $(el).text().trim()
|
||||
parsedDateString = parsedDateString.replace(/\s\s+/g, ' ')
|
||||
|
||||
return parsedDateString.includes(dateString)
|
||||
})
|
||||
.first()
|
||||
.parent()
|
||||
.next()
|
||||
|
||||
return $('.padded-half', list).toArray()
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
require('dayjs/locale/ar')
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'elcinema.com',
|
||||
days: 2,
|
||||
url({ channel }) {
|
||||
const lang = channel.lang === 'en' ? 'en/' : '/'
|
||||
|
||||
return `https://elcinema.com/${lang}tvguide/${channel.site_id}/`
|
||||
},
|
||||
parser({ content, channel, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, channel, date)
|
||||
items.forEach(item => {
|
||||
const start = parseStart(item, date)
|
||||
const duration = parseDuration(item)
|
||||
const stop = start.add(duration, 'm')
|
||||
programs.push({
|
||||
title: parseTitle(item),
|
||||
description: parseDescription(item),
|
||||
category: parseCategory(item),
|
||||
icon: parseIcon(item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
const $ = cheerio.load(item)
|
||||
const imgSrc =
|
||||
$('.row > div.columns.small-3.large-1 > a > img').data('src') ||
|
||||
$('.row > div.columns.small-5.large-1 > img').data('src')
|
||||
|
||||
return imgSrc || null
|
||||
}
|
||||
|
||||
function parseCategory(item) {
|
||||
const $ = cheerio.load(item)
|
||||
const category = $('.row > div.columns.small-6.large-3 > ul > li:nth-child(2)').text()
|
||||
|
||||
return category.replace(/\(\d+\)/, '').trim() || null
|
||||
}
|
||||
|
||||
function parseDuration(item) {
|
||||
const $ = cheerio.load(item)
|
||||
const duration =
|
||||
$('.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()
|
||||
|
||||
return duration.replace(/\D/g, '') || ''
|
||||
}
|
||||
|
||||
function parseStart(item, initDate) {
|
||||
const $ = cheerio.load(item)
|
||||
let time =
|
||||
$('.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() ||
|
||||
''
|
||||
|
||||
time = time
|
||||
.replace(/\[.*\]/, '')
|
||||
.replace('مساءً', 'PM')
|
||||
.replace('صباحًا', 'AM')
|
||||
.trim()
|
||||
|
||||
time = `${initDate.format('YYYY-MM-DD')} ${time}`
|
||||
|
||||
return dayjs.tz(time, 'YYYY-MM-DD hh:mm A', dayjs.tz.guess())
|
||||
}
|
||||
|
||||
function parseTitle(item) {
|
||||
const $ = cheerio.load(item)
|
||||
|
||||
return (
|
||||
$('.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() ||
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
function parseDescription(item) {
|
||||
const $ = cheerio.load(item)
|
||||
const excerpt = $('.row > div.columns.small-12.large-6 > ul > li:nth-child(3)').text() || ''
|
||||
|
||||
return excerpt.replace('...اقرأ المزيد', '').replace('...Read more', '')
|
||||
}
|
||||
|
||||
function parseItems(content, channel, date) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
const dateString = date.locale(channel.lang).format('dddd D')
|
||||
|
||||
const list = $('.dates')
|
||||
.filter((i, el) => {
|
||||
let parsedDateString = $(el).text().trim()
|
||||
parsedDateString = parsedDateString.replace(/\s\s+/g, ' ')
|
||||
|
||||
return parsedDateString.includes(dateString)
|
||||
})
|
||||
.first()
|
||||
.parent()
|
||||
.next()
|
||||
|
||||
return $('.padded-half', list).toArray()
|
||||
}
|
||||
|
|
|
@ -1,69 +1,69 @@
|
|||
// npm run grab -- --site=elcinema.com
|
||||
|
||||
const { parser, url } = require('./elcinema.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-08-28', 'YYYY-MM-DD').startOf('d')
|
||||
const channelAR = {
|
||||
lang: 'ar',
|
||||
site_id: '1254',
|
||||
xmltv_id: 'OSNSeries.ae'
|
||||
}
|
||||
const channelEN = {
|
||||
lang: 'en',
|
||||
site_id: '1254',
|
||||
xmltv_id: 'OSNSeries.ae'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel: channelEN })).toBe('https://elcinema.com/en/tvguide/1254/')
|
||||
})
|
||||
|
||||
it('can parse response (en)', () => {
|
||||
const contentEN = fs.readFileSync(path.resolve(__dirname, '__data__/content.en.html'))
|
||||
const results = parser({ date, channel: channelEN, content: contentEN }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-08-27T14:25:00.000Z',
|
||||
stop: '2022-08-27T15:15:00.000Z',
|
||||
title: 'Station 19 S5',
|
||||
icon: 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg',
|
||||
category: 'Series'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response (ar)', () => {
|
||||
const contentAR = fs.readFileSync(path.resolve(__dirname, '__data__/content.ar.html'))
|
||||
const results = parser({ date, channel: channelAR, content: contentAR }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-08-27T14:25:00.000Z',
|
||||
stop: '2022-08-27T15:15:00.000Z',
|
||||
title: 'Station 19 S5',
|
||||
icon: 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg',
|
||||
category: 'مسلسل'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel: channelEN,
|
||||
content: '<!DOCTYPE html><html lang="ar" dir="rtl"><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=elcinema.com
|
||||
|
||||
const { parser, url } = require('./elcinema.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-08-28', 'YYYY-MM-DD').startOf('d')
|
||||
const channelAR = {
|
||||
lang: 'ar',
|
||||
site_id: '1254',
|
||||
xmltv_id: 'OSNSeries.ae'
|
||||
}
|
||||
const channelEN = {
|
||||
lang: 'en',
|
||||
site_id: '1254',
|
||||
xmltv_id: 'OSNSeries.ae'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel: channelEN })).toBe('https://elcinema.com/en/tvguide/1254/')
|
||||
})
|
||||
|
||||
it('can parse response (en)', () => {
|
||||
const contentEN = fs.readFileSync(path.resolve(__dirname, '__data__/content.en.html'))
|
||||
const results = parser({ date, channel: channelEN, content: contentEN }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-08-27T14:25:00.000Z',
|
||||
stop: '2022-08-27T15:15:00.000Z',
|
||||
title: 'Station 19 S5',
|
||||
icon: 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg',
|
||||
category: 'Series'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response (ar)', () => {
|
||||
const contentAR = fs.readFileSync(path.resolve(__dirname, '__data__/content.ar.html'))
|
||||
const results = parser({ date, channel: channelAR, content: contentAR }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-08-27T14:25:00.000Z',
|
||||
stop: '2022-08-27T15:15:00.000Z',
|
||||
title: 'Station 19 S5',
|
||||
icon: 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg',
|
||||
category: 'مسلسل'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel: channelEN,
|
||||
content: '<!DOCTYPE html><html lang="ar" dir="rtl"><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,68 +1,68 @@
|
|||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'ena.skylifetv.co.kr',
|
||||
days: 2,
|
||||
url({ channel, date }) {
|
||||
return `http://ena.skylifetv.co.kr/${channel.site_id}/?day=${date.format('YYYYMMDD')}&sc_dvsn=U`
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const start = parseStart($item, date)
|
||||
const duration = parseDuration($item)
|
||||
const stop = start.add(duration, 'm')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
rating: parseRating($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.col2 > .tit').text().trim()
|
||||
}
|
||||
|
||||
function parseRating($item) {
|
||||
const rating = $item('.col4').text().trim()
|
||||
|
||||
return rating
|
||||
? {
|
||||
system: 'KMRB',
|
||||
value: rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseDuration($item) {
|
||||
const duration = $item('.col5').text().trim()
|
||||
|
||||
return duration ? parseInt(duration) : 30
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const time = $item('.col1').text().trim()
|
||||
|
||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul')
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('.tbl_schedule > tbody > tr').toArray()
|
||||
}
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
module.exports = {
|
||||
site: 'ena.skylifetv.co.kr',
|
||||
days: 2,
|
||||
url({ channel, date }) {
|
||||
return `http://ena.skylifetv.co.kr/${channel.site_id}/?day=${date.format('YYYYMMDD')}&sc_dvsn=U`
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const start = parseStart($item, date)
|
||||
const duration = parseDuration($item)
|
||||
const stop = start.add(duration, 'm')
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
rating: parseRating($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.col2 > .tit').text().trim()
|
||||
}
|
||||
|
||||
function parseRating($item) {
|
||||
const rating = $item('.col4').text().trim()
|
||||
|
||||
return rating
|
||||
? {
|
||||
system: 'KMRB',
|
||||
value: rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseDuration($item) {
|
||||
const duration = $item('.col5').text().trim()
|
||||
|
||||
return duration ? parseInt(duration) : 30
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const time = $item('.col1').text().trim()
|
||||
|
||||
return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul')
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('.tbl_schedule > tbody > tr').toArray()
|
||||
}
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
// npm run grab -- --site=ena.skylifetv.co.kr
|
||||
|
||||
const { parser, url } = require('./ena.skylifetv.co.kr.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-27', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'ENA',
|
||||
xmltv_id: 'ENA.kr'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe('http://ena.skylifetv.co.kr/ENA/?day=20230127&sc_dvsn=U')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||
let results = parser({ content, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-26T16:05:00.000Z',
|
||||
stop: '2023-01-26T17:20:00.000Z',
|
||||
title: '법쩐 6화',
|
||||
rating: {
|
||||
system: 'KMRB',
|
||||
value: '15'
|
||||
}
|
||||
})
|
||||
|
||||
expect(results[17]).toMatchObject({
|
||||
start: '2023-01-27T14:10:00.000Z',
|
||||
stop: '2023-01-27T15:25:00.000Z',
|
||||
title: '남이 될 수 있을까 4화',
|
||||
rating: {
|
||||
system: 'KMRB',
|
||||
value: '15'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({
|
||||
date,
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||
})
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=ena.skylifetv.co.kr
|
||||
|
||||
const { parser, url } = require('./ena.skylifetv.co.kr.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-27', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'ENA',
|
||||
xmltv_id: 'ENA.kr'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe('http://ena.skylifetv.co.kr/ENA/?day=20230127&sc_dvsn=U')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||
let results = parser({ content, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-26T16:05:00.000Z',
|
||||
stop: '2023-01-26T17:20:00.000Z',
|
||||
title: '법쩐 6화',
|
||||
rating: {
|
||||
system: 'KMRB',
|
||||
value: '15'
|
||||
}
|
||||
})
|
||||
|
||||
expect(results[17]).toMatchObject({
|
||||
start: '2023-01-27T14:10:00.000Z',
|
||||
stop: '2023-01-27T15:25:00.000Z',
|
||||
title: '남이 될 수 있을까 4화',
|
||||
rating: {
|
||||
system: 'KMRB',
|
||||
value: '15'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({
|
||||
date,
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||
})
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,95 +1,95 @@
|
|||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'entertainment.ie',
|
||||
days: 2,
|
||||
url: function ({ date, channel }) {
|
||||
return `https://entertainment.ie/tv/${channel.site_id}/?date=${date.format(
|
||||
'DD-MM-YYYY'
|
||||
)}&time=all-day`
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
if (!start) return
|
||||
if (prev && start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
}
|
||||
const duration = parseDuration($item)
|
||||
const stop = start.plus({ minutes: duration })
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
description: parseDescription($item),
|
||||
categories: parseCategories($item),
|
||||
icon: parseIcon($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const data = await axios
|
||||
.get('https://entertainment.ie/tv/all-channels/')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
const $ = cheerio.load(data)
|
||||
let channels = $('.tv-filter-container > tv-filter').attr(':channels')
|
||||
channels = JSON.parse(channels)
|
||||
|
||||
return channels.map(c => {
|
||||
return {
|
||||
site_id: c.slug,
|
||||
name: c.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseIcon($item) {
|
||||
return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('img')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.text-holder h3').text().trim()
|
||||
}
|
||||
|
||||
function parseDescription($item) {
|
||||
return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('description')
|
||||
}
|
||||
|
||||
function parseCategories($item) {
|
||||
const genres = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('genres')
|
||||
|
||||
return genres ? genres.split(', ') : []
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
let d = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('time')
|
||||
let [, time] = d ? d.split(', ') : [null, null]
|
||||
|
||||
return time
|
||||
? DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', {
|
||||
zone: 'UTC'
|
||||
}).toUTC()
|
||||
: null
|
||||
}
|
||||
|
||||
function parseDuration($item) {
|
||||
const duration = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('duration')
|
||||
|
||||
return parseInt(duration)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('.info-list > li').toArray()
|
||||
}
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'entertainment.ie',
|
||||
days: 2,
|
||||
url: function ({ date, channel }) {
|
||||
return `https://entertainment.ie/tv/${channel.site_id}/?date=${date.format(
|
||||
'DD-MM-YYYY'
|
||||
)}&time=all-day`
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
const prev = programs[programs.length - 1]
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
if (!start) return
|
||||
if (prev && start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
}
|
||||
const duration = parseDuration($item)
|
||||
const stop = start.plus({ minutes: duration })
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
description: parseDescription($item),
|
||||
categories: parseCategories($item),
|
||||
icon: parseIcon($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const data = await axios
|
||||
.get('https://entertainment.ie/tv/all-channels/')
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
const $ = cheerio.load(data)
|
||||
let channels = $('.tv-filter-container > tv-filter').attr(':channels')
|
||||
channels = JSON.parse(channels)
|
||||
|
||||
return channels.map(c => {
|
||||
return {
|
||||
site_id: c.slug,
|
||||
name: c.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseIcon($item) {
|
||||
return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('img')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.text-holder h3').text().trim()
|
||||
}
|
||||
|
||||
function parseDescription($item) {
|
||||
return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('description')
|
||||
}
|
||||
|
||||
function parseCategories($item) {
|
||||
const genres = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('genres')
|
||||
|
||||
return genres ? genres.split(', ') : []
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
let d = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('time')
|
||||
let [, time] = d ? d.split(', ') : [null, null]
|
||||
|
||||
return time
|
||||
? DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', {
|
||||
zone: 'UTC'
|
||||
}).toUTC()
|
||||
: null
|
||||
}
|
||||
|
||||
function parseDuration($item) {
|
||||
const duration = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('duration')
|
||||
|
||||
return parseInt(duration)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
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 grab -- --site=entertainment.ie
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { parser, url } = require('./entertainment.ie.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-06-29', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = { site_id: 'rte2', xmltv_id: 'RTE2.ie' }
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date, channel })).toBe(
|
||||
'https://entertainment.ie/tv/rte2/?date=29-06-2023&time=all-day'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||
const results = parser({ date, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results.length).toBe(51)
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-29T06:00:00.000Z',
|
||||
stop: '2023-06-29T08:00:00.000Z',
|
||||
title: 'EuroNews',
|
||||
description: 'European and international headlines live via satellite',
|
||||
icon: 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg',
|
||||
categories: ['Factual']
|
||||
})
|
||||
|
||||
expect(results[50]).toMatchObject({
|
||||
start: '2023-06-30T02:25:00.000Z',
|
||||
stop: '2023-06-30T06:00:00.000Z',
|
||||
title: 'EuroNews',
|
||||
description: 'European and international headlines live via satellite',
|
||||
icon: 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg',
|
||||
categories: ['Factual']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html'))
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// 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
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { parser, url } = require('./entertainment.ie.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-06-29', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = { site_id: 'rte2', xmltv_id: 'RTE2.ie' }
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date, channel })).toBe(
|
||||
'https://entertainment.ie/tv/rte2/?date=29-06-2023&time=all-day'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||
const results = parser({ date, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results.length).toBe(51)
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-29T06:00:00.000Z',
|
||||
stop: '2023-06-29T08:00:00.000Z',
|
||||
title: 'EuroNews',
|
||||
description: 'European and international headlines live via satellite',
|
||||
icon: 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg',
|
||||
categories: ['Factual']
|
||||
})
|
||||
|
||||
expect(results[50]).toMatchObject({
|
||||
start: '2023-06-30T02:25:00.000Z',
|
||||
stop: '2023-06-30T06:00:00.000Z',
|
||||
title: 'EuroNews',
|
||||
description: 'European and international headlines live via satellite',
|
||||
icon: 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg',
|
||||
categories: ['Factual']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html'))
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,89 +1,89 @@
|
|||
const axios = require('axios')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
const API_ENDPOINT = 'http://epg.i-cable.com/ci/channel'
|
||||
|
||||
module.exports = {
|
||||
site: 'epg.i-cable.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1h
|
||||
}
|
||||
},
|
||||
url: function ({ channel, date }) {
|
||||
return `${API_ENDPOINT}/epg/${channel.site_id}/${date.format('YYYY-MM-DD')}?api=api`
|
||||
},
|
||||
parser({ content, channel, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
for (let item of items) {
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart(item, date)
|
||||
const stop = start.plus({ minutes: 30 })
|
||||
if (prev) {
|
||||
if (start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
programs.push({
|
||||
title: parseTitle(item, channel),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ lang }) {
|
||||
const data = await axios
|
||||
.get(`${API_ENDPOINT}/category/0?api=api`)
|
||||
.then(r => r.data)
|
||||
.catch(console.error)
|
||||
|
||||
let channels = []
|
||||
const promises = data.cates.map(c => axios.get(`${API_ENDPOINT}/category/${c.cate_id}?api=api`))
|
||||
await Promise.allSettled(promises).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.status === 'fulfilled') {
|
||||
channels = channels.concat(r.value.data.chs)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return channels.map(c => {
|
||||
let name = lang === 'zh' ? c.channel_name : c.channel_name_en
|
||||
name = c.remark_id == 3 ? `${name} [HD]` : name
|
||||
|
||||
return {
|
||||
site_id: c.channel_no,
|
||||
name,
|
||||
lang
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle(item, channel) {
|
||||
return channel.lang === 'en' ? item.programme_name_eng : item.programme_name_chi
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
let meridiem = item.session_mark === 'PM' ? 'PM' : 'AM'
|
||||
return DateTime.fromFormat(
|
||||
`${date.format('YYYY-MM-DD')} ${item.time} ${meridiem}`,
|
||||
'yyyy-MM-dd hh:mm a',
|
||||
{
|
||||
zone: 'Asia/Hong_Kong'
|
||||
}
|
||||
).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.epgs)) return []
|
||||
|
||||
return data.epgs
|
||||
}
|
||||
const axios = require('axios')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
const API_ENDPOINT = 'http://epg.i-cable.com/ci/channel'
|
||||
|
||||
module.exports = {
|
||||
site: 'epg.i-cable.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1h
|
||||
}
|
||||
},
|
||||
url: function ({ channel, date }) {
|
||||
return `${API_ENDPOINT}/epg/${channel.site_id}/${date.format('YYYY-MM-DD')}?api=api`
|
||||
},
|
||||
parser({ content, channel, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, date)
|
||||
for (let item of items) {
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart(item, date)
|
||||
const stop = start.plus({ minutes: 30 })
|
||||
if (prev) {
|
||||
if (start < prev.start) {
|
||||
start = start.plus({ days: 1 })
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
prev.stop = start
|
||||
}
|
||||
programs.push({
|
||||
title: parseTitle(item, channel),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ lang }) {
|
||||
const data = await axios
|
||||
.get(`${API_ENDPOINT}/category/0?api=api`)
|
||||
.then(r => r.data)
|
||||
.catch(console.error)
|
||||
|
||||
let channels = []
|
||||
const promises = data.cates.map(c => axios.get(`${API_ENDPOINT}/category/${c.cate_id}?api=api`))
|
||||
await Promise.allSettled(promises).then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.status === 'fulfilled') {
|
||||
channels = channels.concat(r.value.data.chs)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return channels.map(c => {
|
||||
let name = lang === 'zh' ? c.channel_name : c.channel_name_en
|
||||
name = c.remark_id == 3 ? `${name} [HD]` : name
|
||||
|
||||
return {
|
||||
site_id: c.channel_no,
|
||||
name,
|
||||
lang
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle(item, channel) {
|
||||
return channel.lang === 'en' ? item.programme_name_eng : item.programme_name_chi
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
let meridiem = item.session_mark === 'PM' ? 'PM' : 'AM'
|
||||
return DateTime.fromFormat(
|
||||
`${date.format('YYYY-MM-DD')} ${item.time} ${meridiem}`,
|
||||
'yyyy-MM-dd hh:mm a',
|
||||
{
|
||||
zone: 'Asia/Hong_Kong'
|
||||
}
|
||||
).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data || !Array.isArray(data.epgs)) return []
|
||||
|
||||
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 grab -- --site=epg.i-cable.com
|
||||
|
||||
const { parser, url } = require('./epg.i-cable.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const date = dayjs.utc('2022-11-15', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '003',
|
||||
xmltv_id: 'HOYTV.hk',
|
||||
lang: 'zh'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe(
|
||||
'http://epg.i-cable.com/ci/channel/epg/003/2022-11-15?api=api'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
let results = parser({ content, channel, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-14T22:00:00.000Z',
|
||||
stop: '2022-11-14T23:00:00.000Z',
|
||||
title: 'Bloomberg 時段'
|
||||
})
|
||||
|
||||
expect(results[31]).toMatchObject({
|
||||
start: '2022-11-15T21:00:00.000Z',
|
||||
stop: '2022-11-15T21:30:00.000Z',
|
||||
title: 'Bloomberg 時段'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response in English', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
const channelEN = { ...channel, lang: 'en' }
|
||||
let results = parser({ content, channel: channelEN, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-14T22:00:00.000Z',
|
||||
stop: '2022-11-14T23:00:00.000Z',
|
||||
title: 'Bloomberg Hour'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||
const results = parser({ date, channel, content })
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
// 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
|
||||
|
||||
const { parser, url } = require('./epg.i-cable.com.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
jest.mock('axios')
|
||||
|
||||
const date = dayjs.utc('2022-11-15', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '003',
|
||||
xmltv_id: 'HOYTV.hk',
|
||||
lang: 'zh'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe(
|
||||
'http://epg.i-cable.com/ci/channel/epg/003/2022-11-15?api=api'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
let results = parser({ content, channel, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-14T22:00:00.000Z',
|
||||
stop: '2022-11-14T23:00:00.000Z',
|
||||
title: 'Bloomberg 時段'
|
||||
})
|
||||
|
||||
expect(results[31]).toMatchObject({
|
||||
start: '2022-11-15T21:00:00.000Z',
|
||||
stop: '2022-11-15T21:30:00.000Z',
|
||||
title: 'Bloomberg 時段'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response in English', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
const channelEN = { ...channel, lang: 'en' }
|
||||
let results = parser({ content, channel: channelEN, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-14T22:00:00.000Z',
|
||||
stop: '2022-11-14T23:00:00.000Z',
|
||||
title: 'Bloomberg Hour'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||
const results = parser({ date, channel, content })
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
const dayjs = require('dayjs')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'firstmedia.com',
|
||||
days: 1,
|
||||
url: function ({ channel, date }) {
|
||||
return `https://www.firstmedia.com/ajax/schedule?date=${date.format('DD/MM/YYYY')}&channel=${
|
||||
channel.site_id
|
||||
}&start_time=1&end_time=24&need_channels=0`
|
||||
},
|
||||
parser: function ({ content, channel }) {
|
||||
if (!content || !channel) return []
|
||||
|
||||
let programs = []
|
||||
const items = parseItems(content, channel.site_id)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: parseTitle(item),
|
||||
description: parseDescription(item),
|
||||
start: parseStart(item).toISOString(),
|
||||
stop: parseStop(item).toISOString()
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
return JSON.parse(content.trim()).entries[channel]
|
||||
}
|
||||
|
||||
function parseTitle(item) {
|
||||
return item.title
|
||||
}
|
||||
|
||||
function parseDescription(item) {
|
||||
return item.long_description
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.tz(item.start_time, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs.tz(item.end_time, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
const timezone = require('dayjs/plugin/timezone')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(utc)
|
||||
|
||||
module.exports = {
|
||||
site: 'firstmedia.com',
|
||||
days: 1,
|
||||
url: function ({ channel, date }) {
|
||||
return `https://www.firstmedia.com/ajax/schedule?date=${date.format('DD/MM/YYYY')}&channel=${
|
||||
channel.site_id
|
||||
}&start_time=1&end_time=24&need_channels=0`
|
||||
},
|
||||
parser: function ({ content, channel }) {
|
||||
if (!content || !channel) return []
|
||||
|
||||
let programs = []
|
||||
const items = parseItems(content, channel.site_id)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: parseTitle(item),
|
||||
description: parseDescription(item),
|
||||
start: parseStart(item).toISOString(),
|
||||
stop: parseStop(item).toISOString()
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
return JSON.parse(content.trim()).entries[channel]
|
||||
}
|
||||
|
||||
function parseTitle(item) {
|
||||
return item.title
|
||||
}
|
||||
|
||||
function parseDescription(item) {
|
||||
return item.long_description
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs.tz(item.start_time, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
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 dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-06-18', 'DD/MM/YYYY').startOf('d')
|
||||
const channel = { site_id: '251', xmltv_id: 'ABCAustralia.au', lang: 'id' }
|
||||
|
||||
it('can generate valid url', () => {
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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"}]}}'
|
||||
const results = parser({ content, channel })
|
||||
|
||||
expect(results).toMatchObject([
|
||||
{
|
||||
start: '2023-06-13T03:55:00.000Z',
|
||||
stop: '2023-06-13T04:30:00.000Z',
|
||||
title: 'China Tonight',
|
||||
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."
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({ content: '' })
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
const { url, parser } = require('./firstmedia.com.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-06-18', 'DD/MM/YYYY').startOf('d')
|
||||
const channel = { site_id: '251', xmltv_id: 'ABCAustralia.au', lang: 'id' }
|
||||
|
||||
it('can generate valid url', () => {
|
||||
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'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
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"}]}}'
|
||||
const results = parser({ content, channel })
|
||||
|
||||
expect(results).toMatchObject([
|
||||
{
|
||||
start: '2023-06-13T03:55:00.000Z',
|
||||
stop: '2023-06-13T04:30:00.000Z',
|
||||
title: 'China Tonight',
|
||||
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."
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({ content: '' })
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'flixed.io',
|
||||
days: 1, // NOTE: changing the date in a request does not change the response
|
||||
url: function ({ date, channel }) {
|
||||
return `https://tv-guide.vercel.app/api/stationAirings?stationId=${
|
||||
channel.site_id
|
||||
}&startDateTime=${date.toJSON()}`
|
||||
},
|
||||
parser({ content }) {
|
||||
let programs = []
|
||||
let items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.program.title,
|
||||
description: item.program.longDescription,
|
||||
category: item.program.subType,
|
||||
icon: parseIcon(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
const uri = item.program.preferredImage.uri
|
||||
|
||||
return uri ? `https://adma.tmsimg.com/assets/${uri}` : null
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs(item.startTime)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs(item.endTime)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
return JSON.parse(content)
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'flixed.io',
|
||||
days: 1, // NOTE: changing the date in a request does not change the response
|
||||
url: function ({ date, channel }) {
|
||||
return `https://tv-guide.vercel.app/api/stationAirings?stationId=${
|
||||
channel.site_id
|
||||
}&startDateTime=${date.toJSON()}`
|
||||
},
|
||||
parser({ content }) {
|
||||
let programs = []
|
||||
let items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.program.title,
|
||||
description: item.program.longDescription,
|
||||
category: item.program.subType,
|
||||
icon: parseIcon(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
const uri = item.program.preferredImage.uri
|
||||
|
||||
return uri ? `https://adma.tmsimg.com/assets/${uri}` : null
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs(item.startTime)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs(item.endTime)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
return JSON.parse(content)
|
||||
}
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
// npm run grab -- --site=flixed.io
|
||||
|
||||
const { parser, url } = require('./flixed.io.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '108970',
|
||||
xmltv_id: 'VSiN.us'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date, channel })).toBe(
|
||||
'https://tv-guide.vercel.app/api/stationAirings?stationId=108970&startDateTime=2023-01-19T00:00:00.000Z'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
let results = parser({ content, channel, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-19T05:00:00.000Z',
|
||||
stop: '2023-01-19T06:00:00.000Z',
|
||||
title: 'The Greg Peterson Experience',
|
||||
category: 'Sports non-event',
|
||||
icon: 'https://adma.tmsimg.com/assets/assets/p20628892_b_v13_aa.jpg?w=270&h=360',
|
||||
description: 'A different kind of sports betting.'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({
|
||||
content: '[]'
|
||||
})
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=flixed.io
|
||||
|
||||
const { parser, url } = require('./flixed.io.config.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '108970',
|
||||
xmltv_id: 'VSiN.us'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date, channel })).toBe(
|
||||
'https://tv-guide.vercel.app/api/stationAirings?stationId=108970&startDateTime=2023-01-19T00:00:00.000Z'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
let results = parser({ content, channel, date })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-01-19T05:00:00.000Z',
|
||||
stop: '2023-01-19T06:00:00.000Z',
|
||||
title: 'The Greg Peterson Experience',
|
||||
category: 'Sports non-event',
|
||||
icon: 'https://adma.tmsimg.com/assets/assets/p20628892_b_v13_aa.jpg?w=270&h=360',
|
||||
description: 'A different kind of sports betting.'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({
|
||||
content: '[]'
|
||||
})
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'foxsports.com.au',
|
||||
days: 3,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url({ date }) {
|
||||
return `https://tvguide.foxsports.com.au/granite-api/programmes.json?from=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}&to=${date.add(1, 'd').format('YYYY-MM-DD')}`
|
||||
},
|
||||
parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.programmeTitle,
|
||||
sub_title: item.title,
|
||||
category: item.genreTitle,
|
||||
description: item.synopsis,
|
||||
start: dayjs.utc(item.startTime),
|
||||
stop: dayjs.utc(item.endTime)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data) return []
|
||||
const programmes = data['channel-programme']
|
||||
if (!Array.isArray(programmes)) return []
|
||||
|
||||
const channelData = programmes.filter(i => i.channelId == channel.site_id)
|
||||
return channelData && Array.isArray(channelData) ? channelData : []
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'foxsports.com.au',
|
||||
days: 3,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url({ date }) {
|
||||
return `https://tvguide.foxsports.com.au/granite-api/programmes.json?from=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}&to=${date.add(1, 'd').format('YYYY-MM-DD')}`
|
||||
},
|
||||
parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.programmeTitle,
|
||||
sub_title: item.title,
|
||||
category: item.genreTitle,
|
||||
description: item.synopsis,
|
||||
start: dayjs.utc(item.startTime),
|
||||
stop: dayjs.utc(item.endTime)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const data = JSON.parse(content)
|
||||
if (!data) return []
|
||||
const programmes = data['channel-programme']
|
||||
if (!Array.isArray(programmes)) return []
|
||||
|
||||
const channelData = programmes.filter(i => i.channelId == channel.site_id)
|
||||
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