mirror of
https://github.com/iptv-org/epg.git
synced 2025-05-11 09:30:06 -04:00
Merge branch 'master' into issue_2184
This commit is contained in:
commit
adc44b5bf7
399 changed files with 29212 additions and 28414 deletions
|
@ -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 : []
|
||||
}
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
// npm run grab -- --site=foxsports.com.au
|
||||
|
||||
const { parser, url } = require('./foxsports.com.au.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-12-14', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '2',
|
||||
xmltv_id: 'FoxLeague.au'
|
||||
}
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date })).toBe(
|
||||
'https://tvguide.foxsports.com.au/granite-api/programmes.json?from=2022-12-14&to=2022-12-15'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content =
|
||||
'{"channel-programme":[{"id":"31cc8b4c-3711-49f0-bf22-2ec3993b0a07","programmeTitle":"NRL","title":"Eels v Titans","startTime":"2022-12-14T00:00:00+11:00","endTime":"2022-12-14T01:00:00+11:00","duration":60,"live":false,"genreId":"5c389cf4-8db7-4b52-9773-52355bd28559","channelId":2,"channelName":"FOX League","channelAbbreviation":"LEAGUE","programmeUID":235220,"round":"R1","statsMatchId":null,"closedCaptioned":true,"statsFixtureId":10207,"genreTitle":"Rugby League","parentGenreId":"a953f929-2d12-41a4-b0e9-97f401afff11","parentGenreTitle":"Sport","pmgId":"PMG01306944","statsSport":"league","type":"GAME","hiDef":true,"widescreen":true,"classification":"","synopsis":"The Eels and Titans have plenty of motivation this season after heartbreaking Finals losses in 2021. Parramatta has won their past five against Gold Coast.","preGameStartTime":null,"closeCaptioned":true}]}'
|
||||
|
||||
const result = parser({ content, channel }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
title: 'NRL',
|
||||
sub_title: 'Eels v Titans',
|
||||
description:
|
||||
'The Eels and Titans have plenty of motivation this season after heartbreaking Finals losses in 2021. Parramatta has won their past five against Gold Coast.',
|
||||
category: 'Rugby League',
|
||||
start: '2022-12-13T13:00:00.000Z',
|
||||
stop: '2022-12-13T14:00:00.000Z'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser(
|
||||
{
|
||||
content: '{"channel-programme":[]}'
|
||||
},
|
||||
channel
|
||||
)
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=foxsports.com.au
|
||||
|
||||
const { parser, url } = require('./foxsports.com.au.config.js')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
const date = dayjs.utc('2022-12-14', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '2',
|
||||
xmltv_id: 'FoxLeague.au'
|
||||
}
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date })).toBe(
|
||||
'https://tvguide.foxsports.com.au/granite-api/programmes.json?from=2022-12-14&to=2022-12-15'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content =
|
||||
'{"channel-programme":[{"id":"31cc8b4c-3711-49f0-bf22-2ec3993b0a07","programmeTitle":"NRL","title":"Eels v Titans","startTime":"2022-12-14T00:00:00+11:00","endTime":"2022-12-14T01:00:00+11:00","duration":60,"live":false,"genreId":"5c389cf4-8db7-4b52-9773-52355bd28559","channelId":2,"channelName":"FOX League","channelAbbreviation":"LEAGUE","programmeUID":235220,"round":"R1","statsMatchId":null,"closedCaptioned":true,"statsFixtureId":10207,"genreTitle":"Rugby League","parentGenreId":"a953f929-2d12-41a4-b0e9-97f401afff11","parentGenreTitle":"Sport","pmgId":"PMG01306944","statsSport":"league","type":"GAME","hiDef":true,"widescreen":true,"classification":"","synopsis":"The Eels and Titans have plenty of motivation this season after heartbreaking Finals losses in 2021. Parramatta has won their past five against Gold Coast.","preGameStartTime":null,"closeCaptioned":true}]}'
|
||||
|
||||
const result = parser({ content, channel }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
title: 'NRL',
|
||||
sub_title: 'Eels v Titans',
|
||||
description:
|
||||
'The Eels and Titans have plenty of motivation this season after heartbreaking Finals losses in 2021. Parramatta has won their past five against Gold Coast.',
|
||||
category: 'Rugby League',
|
||||
start: '2022-12-13T13:00:00.000Z',
|
||||
stop: '2022-12-13T14:00:00.000Z'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser(
|
||||
{
|
||||
content: '{"channel-programme":[]}'
|
||||
},
|
||||
channel
|
||||
)
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,134 +1,134 @@
|
|||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const cheerio = require('cheerio')
|
||||
|
||||
module.exports = {
|
||||
site: 'foxtel.com.au',
|
||||
days: 2,
|
||||
url({ channel, date }) {
|
||||
return `https://www.foxtel.com.au/tv-guide/channel/${channel.site_id}/${date.format(
|
||||
'YYYY/MM/DD'
|
||||
)}`
|
||||
},
|
||||
request: {
|
||||
headers: {
|
||||
'Accept-Language': 'en-US,en;',
|
||||
Cookie: 'AAMC_foxtel_0=REGION|6'
|
||||
}
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
for (let item of items) {
|
||||
const $item = cheerio.load(item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart($item)
|
||||
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),
|
||||
sub_title: parseSubTitle($item),
|
||||
icon: parseIcon($item),
|
||||
rating: parseRating($item),
|
||||
season: parseSeason($item),
|
||||
episode: parseEpisode($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const data = await axios
|
||||
.get('https://www.foxtel.com.au/webepg/ws/foxtel/channels?regionId=8336', {
|
||||
headers: {
|
||||
'Accept-Language': 'en-US,en;',
|
||||
Cookie: 'AAMC_foxtel_0=REGION|6'
|
||||
}
|
||||
})
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return data.channels.forEach(item => {
|
||||
let name = item.name.replace(/\+/g, '-').replace(/&/g, '')
|
||||
const slug = name.replace(/[^a-z0-9\s]/gi, '').replace(/[^a-z0-9]/i, '-')
|
||||
|
||||
return {
|
||||
name: item.name.replace(/&/g, '&'),
|
||||
site_id: `${slug}/${item.channelTag}`
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseSeason($item) {
|
||||
let seasonString = $item('.epg-event-description > div > abbr:nth-child(1)').attr('title')
|
||||
if (!seasonString) return null
|
||||
let [, season] = seasonString.match(/^Season: (\d+)/) || [null, null]
|
||||
|
||||
return season ? parseInt(season) : null
|
||||
}
|
||||
|
||||
function parseEpisode($item) {
|
||||
let episodeString = $item('.epg-event-description > div > abbr:nth-child(2)').attr('title')
|
||||
if (!episodeString) return null
|
||||
let [, episode] = episodeString.match(/^Episode: (\d+)/) || [null, null]
|
||||
|
||||
return episode ? parseInt(episode) : null
|
||||
}
|
||||
|
||||
function parseIcon($item) {
|
||||
return $item('.epg-event-thumbnail > img').attr('src')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.epg-event-description').clone().children().remove().end().text().trim()
|
||||
}
|
||||
|
||||
function parseSubTitle($item) {
|
||||
let subtitle = $item('.epg-event-description > div')
|
||||
.clone()
|
||||
.children()
|
||||
.remove()
|
||||
.end()
|
||||
.text()
|
||||
.trim()
|
||||
.split(',')
|
||||
|
||||
subtitle = subtitle.pop()
|
||||
const [, rating] = subtitle.match(/\(([^)]+)\)$/) || [null, null]
|
||||
|
||||
return subtitle.replace(`(${rating})`, '').trim()
|
||||
}
|
||||
|
||||
function parseRating($item) {
|
||||
const subtitle = $item('.epg-event-description > div').text().trim()
|
||||
const [, rating] = subtitle.match(/\(([^)]+)\)$/) || [null, null]
|
||||
|
||||
return rating
|
||||
? {
|
||||
system: 'ACB',
|
||||
value: rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseStart($item) {
|
||||
const unix = $item('*').data('scheduled-date')
|
||||
|
||||
return dayjs(parseInt(unix))
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
if (!content) return []
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('#epg-channel-events > a').toArray()
|
||||
}
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const cheerio = require('cheerio')
|
||||
|
||||
module.exports = {
|
||||
site: 'foxtel.com.au',
|
||||
days: 2,
|
||||
url({ channel, date }) {
|
||||
return `https://www.foxtel.com.au/tv-guide/channel/${channel.site_id}/${date.format(
|
||||
'YYYY/MM/DD'
|
||||
)}`
|
||||
},
|
||||
request: {
|
||||
headers: {
|
||||
'Accept-Language': 'en-US,en;',
|
||||
Cookie: 'AAMC_foxtel_0=REGION|6'
|
||||
}
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
for (let item of items) {
|
||||
const $item = cheerio.load(item)
|
||||
const prev = programs[programs.length - 1]
|
||||
let start = parseStart($item)
|
||||
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),
|
||||
sub_title: parseSubTitle($item),
|
||||
icon: parseIcon($item),
|
||||
rating: parseRating($item),
|
||||
season: parseSeason($item),
|
||||
episode: parseEpisode($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const data = await axios
|
||||
.get('https://www.foxtel.com.au/webepg/ws/foxtel/channels?regionId=8336', {
|
||||
headers: {
|
||||
'Accept-Language': 'en-US,en;',
|
||||
Cookie: 'AAMC_foxtel_0=REGION|6'
|
||||
}
|
||||
})
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return data.channels.forEach(item => {
|
||||
let name = item.name.replace(/\+/g, '-').replace(/&/g, '')
|
||||
const slug = name.replace(/[^a-z0-9\s]/gi, '').replace(/[^a-z0-9]/i, '-')
|
||||
|
||||
return {
|
||||
name: item.name.replace(/&/g, '&'),
|
||||
site_id: `${slug}/${item.channelTag}`
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseSeason($item) {
|
||||
let seasonString = $item('.epg-event-description > div > abbr:nth-child(1)').attr('title')
|
||||
if (!seasonString) return null
|
||||
let [, season] = seasonString.match(/^Season: (\d+)/) || [null, null]
|
||||
|
||||
return season ? parseInt(season) : null
|
||||
}
|
||||
|
||||
function parseEpisode($item) {
|
||||
let episodeString = $item('.epg-event-description > div > abbr:nth-child(2)').attr('title')
|
||||
if (!episodeString) return null
|
||||
let [, episode] = episodeString.match(/^Episode: (\d+)/) || [null, null]
|
||||
|
||||
return episode ? parseInt(episode) : null
|
||||
}
|
||||
|
||||
function parseIcon($item) {
|
||||
return $item('.epg-event-thumbnail > img').attr('src')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('.epg-event-description').clone().children().remove().end().text().trim()
|
||||
}
|
||||
|
||||
function parseSubTitle($item) {
|
||||
let subtitle = $item('.epg-event-description > div')
|
||||
.clone()
|
||||
.children()
|
||||
.remove()
|
||||
.end()
|
||||
.text()
|
||||
.trim()
|
||||
.split(',')
|
||||
|
||||
subtitle = subtitle.pop()
|
||||
const [, rating] = subtitle.match(/\(([^)]+)\)$/) || [null, null]
|
||||
|
||||
return subtitle.replace(`(${rating})`, '').trim()
|
||||
}
|
||||
|
||||
function parseRating($item) {
|
||||
const subtitle = $item('.epg-event-description > div').text().trim()
|
||||
const [, rating] = subtitle.match(/\(([^)]+)\)$/) || [null, null]
|
||||
|
||||
return rating
|
||||
? {
|
||||
system: 'ACB',
|
||||
value: rating
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
function parseStart($item) {
|
||||
const unix = $item('*').data('scheduled-date')
|
||||
|
||||
return dayjs(parseInt(unix))
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
if (!content) return []
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('#epg-channel-events > a').toArray()
|
||||
}
|
||||
|
|
|
@ -1,62 +1,62 @@
|
|||
// npm run channels:parse -- --config=./sites/foxtel.com.au/foxtel.com.au.config.js --output=./sites/foxtel.com.au/foxtel.com.au.channels.xml
|
||||
// npm run grab -- --site=foxtel.com.au
|
||||
|
||||
const { parser, url, request } = require('./foxtel.com.au.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-08', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'Channel-9-Sydney/NIN',
|
||||
xmltv_id: 'Channel9Sydney.au'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://www.foxtel.com.au/tv-guide/channel/Channel-9-Sydney/NIN/2022/11/08'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Accept-Language': 'en-US,en;',
|
||||
Cookie: 'AAMC_foxtel_0=REGION|6'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||
|
||||
let results = parser({ content })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-07T12:40:00.000Z',
|
||||
stop: '2022-11-07T13:30:00.000Z',
|
||||
title: 'The Equalizer',
|
||||
sub_title: 'Glory',
|
||||
icon: 'https://images1.resources.foxtel.com.au/store2/mount1/16/3/69e0v.jpg?maxheight=90&limit=91aa1c7a2c485aeeba0706941f79f111adb35830',
|
||||
rating: {
|
||||
system: 'ACB',
|
||||
value: 'M'
|
||||
},
|
||||
season: 1,
|
||||
episode: 2
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html'))
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run channels:parse -- --config=./sites/foxtel.com.au/foxtel.com.au.config.js --output=./sites/foxtel.com.au/foxtel.com.au.channels.xml
|
||||
// npm run grab -- --site=foxtel.com.au
|
||||
|
||||
const { parser, url, request } = require('./foxtel.com.au.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-08', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'Channel-9-Sydney/NIN',
|
||||
xmltv_id: 'Channel9Sydney.au'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://www.foxtel.com.au/tv-guide/channel/Channel-9-Sydney/NIN/2022/11/08'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
'Accept-Language': 'en-US,en;',
|
||||
Cookie: 'AAMC_foxtel_0=REGION|6'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||
|
||||
let results = parser({ content })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-11-07T12:40:00.000Z',
|
||||
stop: '2022-11-07T13:30:00.000Z',
|
||||
title: 'The Equalizer',
|
||||
sub_title: 'Glory',
|
||||
icon: 'https://images1.resources.foxtel.com.au/store2/mount1/16/3/69e0v.jpg?maxheight=90&limit=91aa1c7a2c485aeeba0706941f79f111adb35830',
|
||||
rating: {
|
||||
system: 'ACB',
|
||||
value: 'M'
|
||||
},
|
||||
season: 1,
|
||||
episode: 2
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html'))
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'frikanalen.no',
|
||||
days: 2,
|
||||
url({ date }) {
|
||||
return `https://frikanalen.no/api/scheduleitems/?date=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}&format=json&limit=100`
|
||||
},
|
||||
parser({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: parseTitle(item),
|
||||
category: parseCategory(item),
|
||||
description: parseDescription(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle(item) {
|
||||
return item.video.name
|
||||
}
|
||||
|
||||
function parseCategory(item) {
|
||||
return item.video.categories
|
||||
}
|
||||
|
||||
function parseDescription(item) {
|
||||
return item.video.header
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs(item.starttime)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs(item.endtime)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
|
||||
return data && Array.isArray(data.results) ? data.results : []
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'frikanalen.no',
|
||||
days: 2,
|
||||
url({ date }) {
|
||||
return `https://frikanalen.no/api/scheduleitems/?date=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}&format=json&limit=100`
|
||||
},
|
||||
parser({ content }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: parseTitle(item),
|
||||
category: parseCategory(item),
|
||||
description: parseDescription(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle(item) {
|
||||
return item.video.name
|
||||
}
|
||||
|
||||
function parseCategory(item) {
|
||||
return item.video.categories
|
||||
}
|
||||
|
||||
function parseDescription(item) {
|
||||
return item.video.header
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs(item.starttime)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs(item.endtime)
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const data = JSON.parse(content)
|
||||
|
||||
return data && Array.isArray(data.results) ? data.results : []
|
||||
}
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
// npm run grab -- --site=frikanalen.no
|
||||
|
||||
const { parser, url } = require('./frikanalen.no.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-01-19', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '#',
|
||||
xmltv_id: 'Frikanalen.no'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date })).toBe(
|
||||
'https://frikanalen.no/api/scheduleitems/?date=2022-01-19&format=json&limit=100'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content =
|
||||
'{"count":83,"next":null,"previous":null,"results":[{"id":135605,"video":{"id":626094,"name":"FSCONS 2017 - Keynote: TBA - Linda Sandvik","header":"Linda Sandvik\'s keynote at FSCONS 2017\\r\\n\\r\\nRecorded by NUUG for FSCONS.","description":null,"creator":"davidwnoble@gmail.com","organization":{"id":82,"name":"NUUG","homepage":"https://www.nuug.no/","description":"Forening NUUG er for alle som er interessert i fri programvare, åpne standarder og Unix-lignende operativsystemer.","postalAddress":"","streetAddress":"","editorId":2148,"editorName":"David Noble","editorEmail":"davidwnoble@gmail.com","editorMsisdn":"","fkmember":true},"duration":"00:57:55.640000","categories":["Samfunn"]},"schedulereason":5,"starttime":"2022-01-19T00:47:00+01:00","endtime":"2022-01-19T01:44:55.640000+01:00","duration":"00:57:55.640000"}]}'
|
||||
const result = parser({ content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-01-18T23:47:00.000Z',
|
||||
stop: '2022-01-19T00:44:55.640Z',
|
||||
title: 'FSCONS 2017 - Keynote: TBA - Linda Sandvik',
|
||||
category: ['Samfunn'],
|
||||
description: "Linda Sandvik's keynote at FSCONS 2017\r\n\r\nRecorded by NUUG for FSCONS."
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: '{"count":0,"next":null,"previous":null,"results":[]}'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=frikanalen.no
|
||||
|
||||
const { parser, url } = require('./frikanalen.no.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-01-19', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '#',
|
||||
xmltv_id: 'Frikanalen.no'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date })).toBe(
|
||||
'https://frikanalen.no/api/scheduleitems/?date=2022-01-19&format=json&limit=100'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content =
|
||||
'{"count":83,"next":null,"previous":null,"results":[{"id":135605,"video":{"id":626094,"name":"FSCONS 2017 - Keynote: TBA - Linda Sandvik","header":"Linda Sandvik\'s keynote at FSCONS 2017\\r\\n\\r\\nRecorded by NUUG for FSCONS.","description":null,"creator":"davidwnoble@gmail.com","organization":{"id":82,"name":"NUUG","homepage":"https://www.nuug.no/","description":"Forening NUUG er for alle som er interessert i fri programvare, åpne standarder og Unix-lignende operativsystemer.","postalAddress":"","streetAddress":"","editorId":2148,"editorName":"David Noble","editorEmail":"davidwnoble@gmail.com","editorMsisdn":"","fkmember":true},"duration":"00:57:55.640000","categories":["Samfunn"]},"schedulereason":5,"starttime":"2022-01-19T00:47:00+01:00","endtime":"2022-01-19T01:44:55.640000+01:00","duration":"00:57:55.640000"}]}'
|
||||
const result = parser({ content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-01-18T23:47:00.000Z',
|
||||
stop: '2022-01-19T00:44:55.640Z',
|
||||
title: 'FSCONS 2017 - Keynote: TBA - Linda Sandvik',
|
||||
category: ['Samfunn'],
|
||||
description: "Linda Sandvik's keynote at FSCONS 2017\r\n\r\nRecorded by NUUG for FSCONS."
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: '{"count":0,"next":null,"previous":null,"results":[]}'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,100 +1,100 @@
|
|||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const url = require('url')
|
||||
const path = require('path')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'gatotv.com',
|
||||
days: 2,
|
||||
url({ channel, date }) {
|
||||
return `https://www.gatotv.com/canal/${channel.site_id}/${date.format('YYYY-MM-DD')}`
|
||||
},
|
||||
parser({ content, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
date = date.subtract(1, 'd')
|
||||
items.forEach((item, i) => {
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
if (i === 0 && start.hour >= 5) {
|
||||
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),
|
||||
description: parseDescription($item),
|
||||
icon: parseIcon($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const data = await axios
|
||||
.get('https://www.gatotv.com/guia_tv/completa')
|
||||
.then(response => response.data)
|
||||
.catch(console.log)
|
||||
|
||||
const $ = cheerio.load(data)
|
||||
const items = $('.tbl_EPG_row,.tbl_EPG_rowAlternate').toArray()
|
||||
|
||||
return items.map(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const link = $item('td:nth-child(1) > div:nth-child(2) > a:nth-child(3)').attr('href')
|
||||
const parsed = url.parse(link)
|
||||
|
||||
return {
|
||||
lang: 'es',
|
||||
site_id: path.basename(parsed.pathname),
|
||||
name: $item('td:nth-child(1) > div:nth-child(2) > a:nth-child(3)').text()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('td:nth-child(4) > div > div > a > span,td:nth-child(3) > div > div > span,td:nth-child(3) > div > div > a > span').text()
|
||||
}
|
||||
|
||||
function parseDescription($item) {
|
||||
return $item('td:nth-child(4) > div').clone().children().remove().end().text().trim()
|
||||
}
|
||||
|
||||
function parseIcon($item) {
|
||||
return $item('td:nth-child(3) > a > img').attr('src')
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const time = $item('td:nth-child(1) > div > time').attr('datetime')
|
||||
|
||||
return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', {
|
||||
zone: 'EST'
|
||||
}).toUTC()
|
||||
}
|
||||
|
||||
function parseStop($item, date) {
|
||||
const time = $item('td:nth-child(2) > div > time').attr('datetime')
|
||||
|
||||
return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', {
|
||||
zone: 'EST'
|
||||
}).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $(
|
||||
'body > div.div_content > table:nth-child(8) > tbody > tr:nth-child(2) > td:nth-child(1) > table.tbl_EPG'
|
||||
)
|
||||
.find('.tbl_EPG_row,.tbl_EPG_rowAlternate,.tbl_EPG_row_selected')
|
||||
.toArray()
|
||||
}
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const url = require('url')
|
||||
const path = require('path')
|
||||
const { DateTime } = require('luxon')
|
||||
|
||||
module.exports = {
|
||||
site: 'gatotv.com',
|
||||
days: 2,
|
||||
url({ channel, date }) {
|
||||
return `https://www.gatotv.com/canal/${channel.site_id}/${date.format('YYYY-MM-DD')}`
|
||||
},
|
||||
parser({ content, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
date = date.subtract(1, 'd')
|
||||
items.forEach((item, i) => {
|
||||
const $item = cheerio.load(item)
|
||||
let start = parseStart($item, date)
|
||||
if (i === 0 && start.hour >= 5) {
|
||||
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),
|
||||
description: parseDescription($item),
|
||||
icon: parseIcon($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const data = await axios
|
||||
.get('https://www.gatotv.com/guia_tv/completa')
|
||||
.then(response => response.data)
|
||||
.catch(console.log)
|
||||
|
||||
const $ = cheerio.load(data)
|
||||
const items = $('.tbl_EPG_row,.tbl_EPG_rowAlternate').toArray()
|
||||
|
||||
return items.map(item => {
|
||||
const $item = cheerio.load(item)
|
||||
const link = $item('td:nth-child(1) > div:nth-child(2) > a:nth-child(3)').attr('href')
|
||||
const parsed = url.parse(link)
|
||||
|
||||
return {
|
||||
lang: 'es',
|
||||
site_id: path.basename(parsed.pathname),
|
||||
name: $item('td:nth-child(1) > div:nth-child(2) > a:nth-child(3)').text()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('td:nth-child(4) > div > div > a > span,td:nth-child(3) > div > div > span,td:nth-child(3) > div > div > a > span').text()
|
||||
}
|
||||
|
||||
function parseDescription($item) {
|
||||
return $item('td:nth-child(4) > div').clone().children().remove().end().text().trim()
|
||||
}
|
||||
|
||||
function parseIcon($item) {
|
||||
return $item('td:nth-child(3) > a > img').attr('src')
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const time = $item('td:nth-child(1) > div > time').attr('datetime')
|
||||
|
||||
return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', {
|
||||
zone: 'EST'
|
||||
}).toUTC()
|
||||
}
|
||||
|
||||
function parseStop($item, date) {
|
||||
const time = $item('td:nth-child(2) > div > time').attr('datetime')
|
||||
|
||||
return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', {
|
||||
zone: 'EST'
|
||||
}).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $(
|
||||
'body > div.div_content > table:nth-child(8) > tbody > tr:nth-child(2) > td:nth-child(1) > table.tbl_EPG'
|
||||
)
|
||||
.find('.tbl_EPG_row,.tbl_EPG_rowAlternate,.tbl_EPG_row_selected')
|
||||
.toArray()
|
||||
}
|
||||
|
|
|
@ -1,82 +1,82 @@
|
|||
// npm run channels:parse -- --config=./sites/gatotv.com/gatotv.com.config.js --output=./sites/gatotv.com/gatotv.com.channels.xml
|
||||
// npm run grab -- --site=gatotv.com
|
||||
|
||||
const { parser, url } = require('./gatotv.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)
|
||||
|
||||
let date = dayjs.utc('2023-06-13', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'm_0',
|
||||
xmltv_id: '0porMovistarPlus.es'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe('https://www.gatotv.com/canal/m_0/2023-06-13')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0.html'), 'utf8')
|
||||
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-06-13T04:30:00.000Z',
|
||||
stop: '2023-06-13T05:32:00.000Z',
|
||||
title: 'Supergarcía'
|
||||
})
|
||||
|
||||
expect(results[1]).toMatchObject({
|
||||
start: '2023-06-13T05:32:00.000Z',
|
||||
stop: '2023-06-13T06:59:00.000Z',
|
||||
title: 'La resistencia'
|
||||
})
|
||||
|
||||
expect(results[25]).toMatchObject({
|
||||
start: '2023-06-14T04:46:00.000Z',
|
||||
stop: '2023-06-14T05:00:00.000Z',
|
||||
title: 'Una familia absolutamente normal'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response when the guide starts from midnight', () => {
|
||||
date = date.add(1, 'd')
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_1.html'), 'utf8')
|
||||
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-06-14T05:00:00.000Z',
|
||||
stop: '2023-06-14T05:32:00.000Z',
|
||||
title: 'Ilustres Ignorantes'
|
||||
})
|
||||
|
||||
expect(results[26]).toMatchObject({
|
||||
start: '2023-06-15T04:30:00.000Z',
|
||||
stop: '2023-06-15T05:30:00.000Z',
|
||||
title: 'Showriano'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({
|
||||
date,
|
||||
channel,
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||
})
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
// npm run channels:parse -- --config=./sites/gatotv.com/gatotv.com.config.js --output=./sites/gatotv.com/gatotv.com.channels.xml
|
||||
// npm run grab -- --site=gatotv.com
|
||||
|
||||
const { parser, url } = require('./gatotv.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)
|
||||
|
||||
let date = dayjs.utc('2023-06-13', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'm_0',
|
||||
xmltv_id: '0porMovistarPlus.es'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe('https://www.gatotv.com/canal/m_0/2023-06-13')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0.html'), 'utf8')
|
||||
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-06-13T04:30:00.000Z',
|
||||
stop: '2023-06-13T05:32:00.000Z',
|
||||
title: 'Supergarcía'
|
||||
})
|
||||
|
||||
expect(results[1]).toMatchObject({
|
||||
start: '2023-06-13T05:32:00.000Z',
|
||||
stop: '2023-06-13T06:59:00.000Z',
|
||||
title: 'La resistencia'
|
||||
})
|
||||
|
||||
expect(results[25]).toMatchObject({
|
||||
start: '2023-06-14T04:46:00.000Z',
|
||||
stop: '2023-06-14T05:00:00.000Z',
|
||||
title: 'Una familia absolutamente normal'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response when the guide starts from midnight', () => {
|
||||
date = date.add(1, 'd')
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_1.html'), 'utf8')
|
||||
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-06-14T05:00:00.000Z',
|
||||
stop: '2023-06-14T05:32:00.000Z',
|
||||
title: 'Ilustres Ignorantes'
|
||||
})
|
||||
|
||||
expect(results[26]).toMatchObject({
|
||||
start: '2023-06-15T04:30:00.000Z',
|
||||
stop: '2023-06-15T05:30:00.000Z',
|
||||
title: 'Showriano'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const results = parser({
|
||||
date,
|
||||
channel,
|
||||
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8')
|
||||
})
|
||||
|
||||
expect(results).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
const table2array = require('table2array')
|
||||
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')
|
||||
const isoWeek = require('dayjs/plugin/isoWeek')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(isoWeek)
|
||||
|
||||
module.exports = {
|
||||
site: 'getafteritmedia.com',
|
||||
days: 2,
|
||||
url: 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQcDmb9OnO0HpbjINfGaepqgGTp3VSmPs7hs654n3sRKrq4Q9y6uPSEvVvq9MwTLYG_n_V7vh0rFYP9/pubhtml',
|
||||
parser({ content, channel, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, channel, date)
|
||||
items.forEach(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: item.title,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
return dayjs.tz(
|
||||
`${date.format('YYYY-MM-DD')} ${item.time}`,
|
||||
'YYYY-MM-DD HH:mm A',
|
||||
'America/New_York'
|
||||
)
|
||||
}
|
||||
|
||||
function parseItems(content, channel, date) {
|
||||
const day = date.isoWeekday()
|
||||
const $ = cheerio.load(content)
|
||||
const table = $.html($(`#${channel.site_id} table`))
|
||||
let data = table2array(table)
|
||||
data.splice(0, 5)
|
||||
|
||||
return data.map(row => {
|
||||
return {
|
||||
time: row[1],
|
||||
title: row[day + 1]
|
||||
}
|
||||
})
|
||||
}
|
||||
const table2array = require('table2array')
|
||||
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')
|
||||
const isoWeek = require('dayjs/plugin/isoWeek')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(isoWeek)
|
||||
|
||||
module.exports = {
|
||||
site: 'getafteritmedia.com',
|
||||
days: 2,
|
||||
url: 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQcDmb9OnO0HpbjINfGaepqgGTp3VSmPs7hs654n3sRKrq4Q9y6uPSEvVvq9MwTLYG_n_V7vh0rFYP9/pubhtml',
|
||||
parser({ content, channel, date }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, channel, date)
|
||||
items.forEach(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: item.title,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
return dayjs.tz(
|
||||
`${date.format('YYYY-MM-DD')} ${item.time}`,
|
||||
'YYYY-MM-DD HH:mm A',
|
||||
'America/New_York'
|
||||
)
|
||||
}
|
||||
|
||||
function parseItems(content, channel, date) {
|
||||
const day = date.isoWeekday()
|
||||
const $ = cheerio.load(content)
|
||||
const table = $.html($(`#${channel.site_id} table`))
|
||||
let data = table2array(table)
|
||||
data.splice(0, 5)
|
||||
|
||||
return data.map(row => {
|
||||
return {
|
||||
time: row[1],
|
||||
title: row[day + 1]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,47 +1,47 @@
|
|||
// npm run grab -- --site=getafteritmedia.com
|
||||
|
||||
const { parser, url } = require('./getafteritmedia.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-26', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '494637005',
|
||||
xmltv_id: 'REVNWebFeed.us'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe(
|
||||
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQcDmb9OnO0HpbjINfGaepqgGTp3VSmPs7hs654n3sRKrq4Q9y6uPSEvVvq9MwTLYG_n_V7vh0rFYP9/pubhtml'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||
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-26T05:00:00.000Z',
|
||||
stop: '2022-11-26T05:30:00.000Z',
|
||||
title: 'The Appraisers'
|
||||
})
|
||||
})
|
||||
|
||||
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=getafteritmedia.com
|
||||
|
||||
const { parser, url } = require('./getafteritmedia.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-26', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '494637005',
|
||||
xmltv_id: 'REVNWebFeed.us'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe(
|
||||
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQcDmb9OnO0HpbjINfGaepqgGTp3VSmPs7hs654n3sRKrq4Q9y6uPSEvVvq9MwTLYG_n_V7vh0rFYP9/pubhtml'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8')
|
||||
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-26T05:00:00.000Z',
|
||||
stop: '2022-11-26T05:30:00.000Z',
|
||||
title: 'The Appraisers'
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,72 +1,72 @@
|
|||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'guidatv.sky.it',
|
||||
days: 2,
|
||||
url: function ({ date, channel }) {
|
||||
const [env, id] = channel.site_id.split('#')
|
||||
return `https://apid.sky.it/gtv/v1/events?from=${date.format('YYYY-MM-DD')}T00:00:00Z&to=${date
|
||||
.add(1, 'd')
|
||||
.format('YYYY-MM-DD')}T00:00:00Z&pageSize=999&pageNum=0&env=${env}&channels=${id}`
|
||||
},
|
||||
parser: function ({ content }) {
|
||||
const programs = []
|
||||
const data = JSON.parse(content)
|
||||
const items = data.events
|
||||
if (!items.length) return programs
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.eventTitle,
|
||||
description: item.eventSynopsis,
|
||||
category: parseCategory(item),
|
||||
season: parseSeason(item),
|
||||
episode: parseEpisode(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item),
|
||||
url: parseURL(item),
|
||||
icon: parseIcon(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseCategory(item) {
|
||||
let category = item.content.genre.name || null
|
||||
const subcategory = item.content.subgenre.name || null
|
||||
if (category && subcategory) {
|
||||
category += `/${subcategory}`
|
||||
}
|
||||
return category
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return item.starttime ? dayjs(item.starttime) : null
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return item.endtime ? dayjs(item.endtime) : null
|
||||
}
|
||||
|
||||
function parseURL(item) {
|
||||
return item.content.url ? `https://guidatv.sky.it${item.content.url}` : null
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
const cover = item.content.imagesMap ? item.content.imagesMap.find(i => i.key === 'cover') : null
|
||||
|
||||
return cover && cover.img && cover.img.url ? `https://guidatv.sky.it${cover.img.url}` : null
|
||||
}
|
||||
|
||||
function parseSeason(item) {
|
||||
if (!item.content.seasonNumber) return null
|
||||
if (String(item.content.seasonNumber).length > 2) return null
|
||||
return item.content.seasonNumber
|
||||
}
|
||||
|
||||
function parseEpisode(item) {
|
||||
if (!item.content.episodeNumber) return null
|
||||
if (String(item.content.episodeNumber).length > 3) return null
|
||||
return item.content.episodeNumber
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
module.exports = {
|
||||
site: 'guidatv.sky.it',
|
||||
days: 2,
|
||||
url: function ({ date, channel }) {
|
||||
const [env, id] = channel.site_id.split('#')
|
||||
return `https://apid.sky.it/gtv/v1/events?from=${date.format('YYYY-MM-DD')}T00:00:00Z&to=${date
|
||||
.add(1, 'd')
|
||||
.format('YYYY-MM-DD')}T00:00:00Z&pageSize=999&pageNum=0&env=${env}&channels=${id}`
|
||||
},
|
||||
parser: function ({ content }) {
|
||||
const programs = []
|
||||
const data = JSON.parse(content)
|
||||
const items = data.events
|
||||
if (!items.length) return programs
|
||||
items.forEach(item => {
|
||||
programs.push({
|
||||
title: item.eventTitle,
|
||||
description: item.eventSynopsis,
|
||||
category: parseCategory(item),
|
||||
season: parseSeason(item),
|
||||
episode: parseEpisode(item),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item),
|
||||
url: parseURL(item),
|
||||
icon: parseIcon(item)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseCategory(item) {
|
||||
let category = item.content.genre.name || null
|
||||
const subcategory = item.content.subgenre.name || null
|
||||
if (category && subcategory) {
|
||||
category += `/${subcategory}`
|
||||
}
|
||||
return category
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return item.starttime ? dayjs(item.starttime) : null
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return item.endtime ? dayjs(item.endtime) : null
|
||||
}
|
||||
|
||||
function parseURL(item) {
|
||||
return item.content.url ? `https://guidatv.sky.it${item.content.url}` : null
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
const cover = item.content.imagesMap ? item.content.imagesMap.find(i => i.key === 'cover') : null
|
||||
|
||||
return cover && cover.img && cover.img.url ? `https://guidatv.sky.it${cover.img.url}` : null
|
||||
}
|
||||
|
||||
function parseSeason(item) {
|
||||
if (!item.content.seasonNumber) return null
|
||||
if (String(item.content.seasonNumber).length > 2) return null
|
||||
return item.content.seasonNumber
|
||||
}
|
||||
|
||||
function parseEpisode(item) {
|
||||
if (!item.content.episodeNumber) return null
|
||||
if (String(item.content.episodeNumber).length > 3) return null
|
||||
return item.content.episodeNumber
|
||||
}
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
// npm run grab -- --site=guidatv.sky.it
|
||||
|
||||
const { parser, url } = require('./guidatv.sky.it.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-05-06', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'DTH#10458',
|
||||
xmltv_id: '20Mediaset.it'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://apid.sky.it/gtv/v1/events?from=2022-05-06T00:00:00Z&to=2022-05-07T00:00:00Z&pageSize=999&pageNum=0&env=DTH&channels=10458'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content =
|
||||
'{"events": [ { "channel": { "id": 10458, "logo": "/logo/545820mediasethd_Light_Fit.png", "logoPadding": "/logo/545820mediasethd_Light_Padding.png", "logoDark": "/logo/545820mediasethd_Dark_Fit.png", "logoDarkPadding": "/logo/545820mediasethd_Dark_Padding.png", "logoLight": "/logo/545820mediasethd_Light_Padding.png", "name": "20Mediaset HD", "number": 151, "category": { "id": 3, "name": "Intrattenimento" } }, "content": { "uuid": "77c630aa-4744-44cb-a88e-3e871c6b73d9", "contentTitle": "Distretto di Polizia", "episodeNumber": 26, "seasonNumber": 6, "url": "/serie-tv/distretto-di-polizia/stagione-6/episodio-26/77c630aa-4744-44cb-a88e-3e871c6b73d9", "genre": { "id": 1, "name": "Intrattenimento" }, "subgenre": { "id": 9, "name": "Fiction" }, "imagesMap": [ { "key": "background", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/background?md5ChecksumParam=88d3f48ce855316f4be25ab9bb846d32" } }, { "key": "cover", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/cover?md5ChecksumParam=61135b999a63e3d3f4a933b9edeb0c1b" } }, { "key": "scene", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/16-9?md5ChecksumParam=f41bfe414bec32505abdab19d00b8b43" } } ] }, "eventId": "139585132", "starttime": "2022-05-06T00:35:40Z", "endtime": "2022-05-06T01:15:40Z", "eventTitle": "Distretto di Polizia", "eventSynopsis": "S6 Ep26 La resa dei conti - Fino all\'ultimo la sfida tra Ardenzi e Carrano, nemici di vecchia data, riserva clamorosi colpi di scena. E si scopre che non e\' tutto come sembrava.", "epgEventTitle": "S6 Ep26 - Distretto di Polizia", "primeVision": false, "resolutions": [ { "resolutionType": "resolution4k", "value": false } ] }]}'
|
||||
const result = parser({ content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-05-06T00:35:40.000Z',
|
||||
stop: '2022-05-06T01:15:40.000Z',
|
||||
title: 'Distretto di Polizia',
|
||||
description:
|
||||
"S6 Ep26 La resa dei conti - Fino all'ultimo la sfida tra Ardenzi e Carrano, nemici di vecchia data, riserva clamorosi colpi di scena. E si scopre che non e' tutto come sembrava.",
|
||||
season: 6,
|
||||
episode: 26,
|
||||
icon: 'https://guidatv.sky.it/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/cover?md5ChecksumParam=61135b999a63e3d3f4a933b9edeb0c1b',
|
||||
category: 'Intrattenimento/Fiction',
|
||||
url: 'https://guidatv.sky.it/serie-tv/distretto-di-polizia/stagione-6/episodio-26/77c630aa-4744-44cb-a88e-3e871c6b73d9'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: '{"events":[],"total":0}'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=guidatv.sky.it
|
||||
|
||||
const { parser, url } = require('./guidatv.sky.it.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-05-06', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'DTH#10458',
|
||||
xmltv_id: '20Mediaset.it'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://apid.sky.it/gtv/v1/events?from=2022-05-06T00:00:00Z&to=2022-05-07T00:00:00Z&pageSize=999&pageNum=0&env=DTH&channels=10458'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content =
|
||||
'{"events": [ { "channel": { "id": 10458, "logo": "/logo/545820mediasethd_Light_Fit.png", "logoPadding": "/logo/545820mediasethd_Light_Padding.png", "logoDark": "/logo/545820mediasethd_Dark_Fit.png", "logoDarkPadding": "/logo/545820mediasethd_Dark_Padding.png", "logoLight": "/logo/545820mediasethd_Light_Padding.png", "name": "20Mediaset HD", "number": 151, "category": { "id": 3, "name": "Intrattenimento" } }, "content": { "uuid": "77c630aa-4744-44cb-a88e-3e871c6b73d9", "contentTitle": "Distretto di Polizia", "episodeNumber": 26, "seasonNumber": 6, "url": "/serie-tv/distretto-di-polizia/stagione-6/episodio-26/77c630aa-4744-44cb-a88e-3e871c6b73d9", "genre": { "id": 1, "name": "Intrattenimento" }, "subgenre": { "id": 9, "name": "Fiction" }, "imagesMap": [ { "key": "background", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/background?md5ChecksumParam=88d3f48ce855316f4be25ab9bb846d32" } }, { "key": "cover", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/cover?md5ChecksumParam=61135b999a63e3d3f4a933b9edeb0c1b" } }, { "key": "scene", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/16-9?md5ChecksumParam=f41bfe414bec32505abdab19d00b8b43" } } ] }, "eventId": "139585132", "starttime": "2022-05-06T00:35:40Z", "endtime": "2022-05-06T01:15:40Z", "eventTitle": "Distretto di Polizia", "eventSynopsis": "S6 Ep26 La resa dei conti - Fino all\'ultimo la sfida tra Ardenzi e Carrano, nemici di vecchia data, riserva clamorosi colpi di scena. E si scopre che non e\' tutto come sembrava.", "epgEventTitle": "S6 Ep26 - Distretto di Polizia", "primeVision": false, "resolutions": [ { "resolutionType": "resolution4k", "value": false } ] }]}'
|
||||
const result = parser({ content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2022-05-06T00:35:40.000Z',
|
||||
stop: '2022-05-06T01:15:40.000Z',
|
||||
title: 'Distretto di Polizia',
|
||||
description:
|
||||
"S6 Ep26 La resa dei conti - Fino all'ultimo la sfida tra Ardenzi e Carrano, nemici di vecchia data, riserva clamorosi colpi di scena. E si scopre che non e' tutto come sembrava.",
|
||||
season: 6,
|
||||
episode: 26,
|
||||
icon: 'https://guidatv.sky.it/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/cover?md5ChecksumParam=61135b999a63e3d3f4a933b9edeb0c1b',
|
||||
category: 'Intrattenimento/Fiction',
|
||||
url: 'https://guidatv.sky.it/serie-tv/distretto-di-polizia/stagione-6/episodio-26/77c630aa-4744-44cb-a88e-3e871c6b73d9'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: '{"events":[],"total":0}'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,94 +1,94 @@
|
|||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
// ERR: certificate has expired
|
||||
module.exports = {
|
||||
site: 'guide.dstv.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000,
|
||||
interpretHeader: false
|
||||
}
|
||||
},
|
||||
url({ channel, date }) {
|
||||
const [bouquetId] = channel.site_id.split('#')
|
||||
|
||||
return `https://guide.dstv.com/api/gridview/page?bouquetId=${bouquetId}&genre=all&date=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}`
|
||||
},
|
||||
parser({ content, date, channel }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(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
|
||||
} else if (start.hour() > 12) {
|
||||
start = start.subtract(1, 'd')
|
||||
date = date.subtract(1, 'd')
|
||||
}
|
||||
const stop = start.add(1, 'h')
|
||||
programs.push({
|
||||
title: item.title,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ bouquet }) {
|
||||
const data = await axios
|
||||
.get(
|
||||
`https://guide.dstv.com/api/channel/fetchChannelsByGenresInBouquet?bouquetId=${bouquet}&genre=all`
|
||||
)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
const items = data.items
|
||||
return items.map(item => {
|
||||
return {
|
||||
lang: 'en',
|
||||
site_id: `${bouquet}#${item.channelTag}`,
|
||||
name: item.channelName
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
const time = `${date.format('MM/DD/YYYY')} ${item.time}`
|
||||
|
||||
return dayjs.utc(time, 'MM/DD/YYYY HH:mm')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelTag] = channel.site_id.split('#')
|
||||
const data = JSON.parse(content)
|
||||
const html = data[channelTag]
|
||||
if (!html) return []
|
||||
const $ = cheerio.load(html)
|
||||
|
||||
return $('li')
|
||||
.map((i, el) => {
|
||||
return {
|
||||
time: $(el).find('.event-time').text().trim(),
|
||||
title: $(el).find('.event-title').text().trim()
|
||||
}
|
||||
})
|
||||
.toArray()
|
||||
.filter(i => i.time && i.title)
|
||||
}
|
||||
const axios = require('axios')
|
||||
const cheerio = require('cheerio')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
// ERR: certificate has expired
|
||||
module.exports = {
|
||||
site: 'guide.dstv.com',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000,
|
||||
interpretHeader: false
|
||||
}
|
||||
},
|
||||
url({ channel, date }) {
|
||||
const [bouquetId] = channel.site_id.split('#')
|
||||
|
||||
return `https://guide.dstv.com/api/gridview/page?bouquetId=${bouquetId}&genre=all&date=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}`
|
||||
},
|
||||
parser({ content, date, channel }) {
|
||||
const programs = []
|
||||
const items = parseItems(content, channel)
|
||||
items.forEach(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
|
||||
} else if (start.hour() > 12) {
|
||||
start = start.subtract(1, 'd')
|
||||
date = date.subtract(1, 'd')
|
||||
}
|
||||
const stop = start.add(1, 'h')
|
||||
programs.push({
|
||||
title: item.title,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ bouquet }) {
|
||||
const data = await axios
|
||||
.get(
|
||||
`https://guide.dstv.com/api/channel/fetchChannelsByGenresInBouquet?bouquetId=${bouquet}&genre=all`
|
||||
)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
const items = data.items
|
||||
return items.map(item => {
|
||||
return {
|
||||
lang: 'en',
|
||||
site_id: `${bouquet}#${item.channelTag}`,
|
||||
name: item.channelName
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
const time = `${date.format('MM/DD/YYYY')} ${item.time}`
|
||||
|
||||
return dayjs.utc(time, 'MM/DD/YYYY HH:mm')
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
const [, channelTag] = channel.site_id.split('#')
|
||||
const data = JSON.parse(content)
|
||||
const html = data[channelTag]
|
||||
if (!html) return []
|
||||
const $ = cheerio.load(html)
|
||||
|
||||
return $('li')
|
||||
.map((i, el) => {
|
||||
return {
|
||||
time: $(el).find('.event-time').text().trim(),
|
||||
title: $(el).find('.event-title').text().trim()
|
||||
}
|
||||
})
|
||||
.toArray()
|
||||
.filter(i => i.time && i.title)
|
||||
}
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
// npm run grab -- --site=guide.dstv.com
|
||||
// npm run channels:parse -- --config=./sites/guide.dstv.com/guide.dstv.com.config.js --output=./sites/guide.dstv.com/guide.dstv.com.channels.xml --set=bouquet:c35aaecd-5dd1-480b-ae24-357e600a0e4d
|
||||
|
||||
const { parser, url } = require('./guide.dstv.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-24', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'b0dc42b8-c651-4c3c-8713-a7fcd04744ee#M4H',
|
||||
xmltv_id: 'MNetMovies4.za'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ date, channel })
|
||||
expect(result).toBe(
|
||||
'https://guide.dstv.com/api/gridview/page?bouquetId=b0dc42b8-c651-4c3c-8713-a7fcd04744ee&genre=all&date=2021-11-24'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content =
|
||||
"{\"M4H\": \" <li schedule-id=7be16b76-51fa-4c73-9e85-3b94993cd6ff style='width:540px; left:-35460px'> <a class='event' analytics-id=\\\"eventOpenEvent\\\" analytics-text=\\\"Deadly Flight\\\"> <p class='event-time'> 21:30 </p> <p class='event-title'>Deadly Flight</p> </a> </li> <li schedule-id=7e156af9-005a-4308-b91f-5d45b6fd04e6 style='width:720px; left:-31530px'> <a class='event' analytics-id=\\\"eventOpenEvent\\\" analytics-text=\\\"I Still Believe\\\"> <p class='event-time'> 08:25 </p> <p class='event-title'>I Still Believe</p> </a> </li> <li schedule-id=0464d009-ed1e-4b1d-8282-c2464123ac5a style='width:570px; left:-28860px'> <a class='event' analytics-id=\\\"eventOpenEvent\\\" analytics-text=\\\"Despicable Me\\\"> <p class='event-time'> 15:50 </p> <p class='event-title'>Despicable Me</p> </a> </li> <li schedule-id=7d346d4d-40e1-4177-83a1-56c173c008e2 style='width:690px; left:-27150px'> <a class='event' analytics-id=\\\"eventOpenEvent\\\" analytics-text=\\\"The Foreigner\\\"> <p class='event-time'> 20:35 </p> <p class='event-title'>The Foreigner</p> </a> </li> \"}"
|
||||
const result = parser({ date, channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2021-11-23T21:30:00.000Z',
|
||||
stop: '2021-11-24T08:25:00.000Z',
|
||||
title: 'Deadly Flight'
|
||||
},
|
||||
{
|
||||
start: '2021-11-24T08:25:00.000Z',
|
||||
stop: '2021-11-24T15:50:00.000Z',
|
||||
title: 'I Still Believe'
|
||||
},
|
||||
{
|
||||
start: '2021-11-24T15:50:00.000Z',
|
||||
stop: '2021-11-24T20:35:00.000Z',
|
||||
title: 'Despicable Me'
|
||||
},
|
||||
{
|
||||
start: '2021-11-24T20:35:00.000Z',
|
||||
stop: '2021-11-24T21:35:00.000Z',
|
||||
title: 'The Foreigner'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({ date, channel, content: '{}' })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=guide.dstv.com
|
||||
// npm run channels:parse -- --config=./sites/guide.dstv.com/guide.dstv.com.config.js --output=./sites/guide.dstv.com/guide.dstv.com.channels.xml --set=bouquet:c35aaecd-5dd1-480b-ae24-357e600a0e4d
|
||||
|
||||
const { parser, url } = require('./guide.dstv.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-24', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'b0dc42b8-c651-4c3c-8713-a7fcd04744ee#M4H',
|
||||
xmltv_id: 'MNetMovies4.za'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const result = url({ date, channel })
|
||||
expect(result).toBe(
|
||||
'https://guide.dstv.com/api/gridview/page?bouquetId=b0dc42b8-c651-4c3c-8713-a7fcd04744ee&genre=all&date=2021-11-24'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content =
|
||||
"{\"M4H\": \" <li schedule-id=7be16b76-51fa-4c73-9e85-3b94993cd6ff style='width:540px; left:-35460px'> <a class='event' analytics-id=\\\"eventOpenEvent\\\" analytics-text=\\\"Deadly Flight\\\"> <p class='event-time'> 21:30 </p> <p class='event-title'>Deadly Flight</p> </a> </li> <li schedule-id=7e156af9-005a-4308-b91f-5d45b6fd04e6 style='width:720px; left:-31530px'> <a class='event' analytics-id=\\\"eventOpenEvent\\\" analytics-text=\\\"I Still Believe\\\"> <p class='event-time'> 08:25 </p> <p class='event-title'>I Still Believe</p> </a> </li> <li schedule-id=0464d009-ed1e-4b1d-8282-c2464123ac5a style='width:570px; left:-28860px'> <a class='event' analytics-id=\\\"eventOpenEvent\\\" analytics-text=\\\"Despicable Me\\\"> <p class='event-time'> 15:50 </p> <p class='event-title'>Despicable Me</p> </a> </li> <li schedule-id=7d346d4d-40e1-4177-83a1-56c173c008e2 style='width:690px; left:-27150px'> <a class='event' analytics-id=\\\"eventOpenEvent\\\" analytics-text=\\\"The Foreigner\\\"> <p class='event-time'> 20:35 </p> <p class='event-title'>The Foreigner</p> </a> </li> \"}"
|
||||
const result = parser({ date, channel, content }).map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2021-11-23T21:30:00.000Z',
|
||||
stop: '2021-11-24T08:25:00.000Z',
|
||||
title: 'Deadly Flight'
|
||||
},
|
||||
{
|
||||
start: '2021-11-24T08:25:00.000Z',
|
||||
stop: '2021-11-24T15:50:00.000Z',
|
||||
title: 'I Still Believe'
|
||||
},
|
||||
{
|
||||
start: '2021-11-24T15:50:00.000Z',
|
||||
stop: '2021-11-24T20:35:00.000Z',
|
||||
title: 'Despicable Me'
|
||||
},
|
||||
{
|
||||
start: '2021-11-24T20:35:00.000Z',
|
||||
stop: '2021-11-24T21:35:00.000Z',
|
||||
title: 'The Foreigner'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({ date, channel, content: '{}' })
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,57 +1,57 @@
|
|||
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: 'hd-plus.de',
|
||||
days: 2,
|
||||
url({ date, channel }) {
|
||||
const today = dayjs().utc().startOf('d')
|
||||
const day = date.diff(today, 'd')
|
||||
|
||||
return `https://www.hd-plus.de/epg/channel/${channel.site_id}?d=${day}`
|
||||
},
|
||||
parser({ 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(1, 'h')
|
||||
programs.push({ title: parseTitle($item), start, stop })
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const timeString = $item('td:nth-child(2)').text().split(' ').pop()
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||
|
||||
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Europe/Berlin')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('td:nth-child(1) > a').text()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('table > 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: 'hd-plus.de',
|
||||
days: 2,
|
||||
url({ date, channel }) {
|
||||
const today = dayjs().utc().startOf('d')
|
||||
const day = date.diff(today, 'd')
|
||||
|
||||
return `https://www.hd-plus.de/epg/channel/${channel.site_id}?d=${day}`
|
||||
},
|
||||
parser({ 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(1, 'h')
|
||||
programs.push({ title: parseTitle($item), start, stop })
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const timeString = $item('td:nth-child(2)').text().split(' ').pop()
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||
|
||||
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Europe/Berlin')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('td:nth-child(1) > a').text()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $('table > tbody > tr').toArray()
|
||||
}
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
// npm run grab -- --site=hd-plus.de
|
||||
|
||||
const { parser, url } = require('./hd-plus.de.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: '1-2-3-tv-hd',
|
||||
xmltv_id: '123tv.de'
|
||||
}
|
||||
const content =
|
||||
'<!DOCTYPE html><html> <head lang="de"></head> <body data-sensory-parallax-role="main" data-sensory-controller=\'{"controllerName": "OffscreenController"}\' class="webshop-epg red" > <main data-sensory-controller=\'{"controllerName": "TeaserController"}\'> <div class="grid-container-epg channel"> <div class="site_overlay"> <div class="loading_icon"></div></div><div class="site_wrapper"> <div id="UIChannelContent-619fb9d2e185d" class="channel-content"> <header> <img src="//cdn.hd-plus.de/senderlogos/bright-cropped/24444-2.png" alt="1-2-3.tv HD" class="channel-image"/> <h2 class="title">1-2-3.tv HD</h2> </header> <table> <thead> <tr> <th>Titel</th> <th>Ausstrahlungszeit</th> </tr></thead> <tbody> <tr> <td> <a href="/epg/show/1-2-3-tv-hd-ihre-lieblingsuhren/1442396582" >Ihre Lieblingsuhren</a > </td><td>Do 25.11 00:00</td></tr><tr> <td> <a href="/epg/show/1-2-3-tv-hd-ihre-lieblingsuhren/1442396584" >Ihre Lieblingsuhren</a > </td><td>Do 25.11 01:00</td></tr><tr> <td><a href="/epg/show/1-2-3-tv-hd-flash-deals/1452944370">Flash Deals</a></td><td>Do 25.11 06:00</td></tr></tbody> </table> </div></div></div></main> </body></html>'
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const today = dayjs.utc().startOf('d')
|
||||
expect(url({ channel, date: today })).toBe('https://www.hd-plus.de/epg/channel/1-2-3-tv-hd?d=0')
|
||||
})
|
||||
|
||||
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-24T23:00:00.000Z',
|
||||
stop: '2021-11-25T00:00:00.000Z',
|
||||
title: 'Ihre Lieblingsuhren'
|
||||
},
|
||||
{
|
||||
start: '2021-11-25T00:00:00.000Z',
|
||||
stop: '2021-11-25T05:00:00.000Z',
|
||||
title: 'Ihre Lieblingsuhren'
|
||||
},
|
||||
{
|
||||
start: '2021-11-25T05:00:00.000Z',
|
||||
stop: '2021-11-25T06:00:00.000Z',
|
||||
title: 'Flash Deals'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
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=hd-plus.de
|
||||
|
||||
const { parser, url } = require('./hd-plus.de.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: '1-2-3-tv-hd',
|
||||
xmltv_id: '123tv.de'
|
||||
}
|
||||
const content =
|
||||
'<!DOCTYPE html><html> <head lang="de"></head> <body data-sensory-parallax-role="main" data-sensory-controller=\'{"controllerName": "OffscreenController"}\' class="webshop-epg red" > <main data-sensory-controller=\'{"controllerName": "TeaserController"}\'> <div class="grid-container-epg channel"> <div class="site_overlay"> <div class="loading_icon"></div></div><div class="site_wrapper"> <div id="UIChannelContent-619fb9d2e185d" class="channel-content"> <header> <img src="//cdn.hd-plus.de/senderlogos/bright-cropped/24444-2.png" alt="1-2-3.tv HD" class="channel-image"/> <h2 class="title">1-2-3.tv HD</h2> </header> <table> <thead> <tr> <th>Titel</th> <th>Ausstrahlungszeit</th> </tr></thead> <tbody> <tr> <td> <a href="/epg/show/1-2-3-tv-hd-ihre-lieblingsuhren/1442396582" >Ihre Lieblingsuhren</a > </td><td>Do 25.11 00:00</td></tr><tr> <td> <a href="/epg/show/1-2-3-tv-hd-ihre-lieblingsuhren/1442396584" >Ihre Lieblingsuhren</a > </td><td>Do 25.11 01:00</td></tr><tr> <td><a href="/epg/show/1-2-3-tv-hd-flash-deals/1452944370">Flash Deals</a></td><td>Do 25.11 06:00</td></tr></tbody> </table> </div></div></div></main> </body></html>'
|
||||
|
||||
it('can generate valid url', () => {
|
||||
const today = dayjs.utc().startOf('d')
|
||||
expect(url({ channel, date: today })).toBe('https://www.hd-plus.de/epg/channel/1-2-3-tv-hd?d=0')
|
||||
})
|
||||
|
||||
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-24T23:00:00.000Z',
|
||||
stop: '2021-11-25T00:00:00.000Z',
|
||||
title: 'Ihre Lieblingsuhren'
|
||||
},
|
||||
{
|
||||
start: '2021-11-25T00:00:00.000Z',
|
||||
stop: '2021-11-25T05:00:00.000Z',
|
||||
title: 'Ihre Lieblingsuhren'
|
||||
},
|
||||
{
|
||||
start: '2021-11-25T05:00:00.000Z',
|
||||
stop: '2021-11-25T06:00:00.000Z',
|
||||
title: 'Flash Deals'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
date,
|
||||
channel,
|
||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,145 +1,145 @@
|
|||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
const API_ENDPOINT = 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web'
|
||||
|
||||
module.exports = {
|
||||
site: 'horizon.tv',
|
||||
days: 3,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url: function ({ date }) {
|
||||
return `${API_ENDPOINT}/programschedules/${date.format('YYYYMMDD')}/1`
|
||||
},
|
||||
async parser({ content, channel, date }) {
|
||||
let programs = []
|
||||
let items = parseItems(content, channel)
|
||||
if (!items.length) return programs
|
||||
const d = date.format('YYYYMMDD')
|
||||
const promises = [
|
||||
axios.get(`${API_ENDPOINT}/programschedules/${d}/2`),
|
||||
axios.get(`${API_ENDPOINT}/programschedules/${d}/3`),
|
||||
axios.get(`${API_ENDPOINT}/programschedules/${d}/4`)
|
||||
]
|
||||
await Promise.allSettled(promises)
|
||||
.then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.status === 'fulfilled') {
|
||||
items = items.concat(parseItems(r.value.data, channel))
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(console.error)
|
||||
for (let item of items) {
|
||||
const detail = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.t,
|
||||
description: parseDescription(detail),
|
||||
category: parseCategory(detail),
|
||||
season: parseSeason(detail),
|
||||
episode: parseEpisode(detail),
|
||||
actors: parseActors(detail),
|
||||
directors: parseDirectors(detail),
|
||||
date: parseYear(detail),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const data = await axios
|
||||
.get(`${API_ENDPOINT}/channels`)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return data.channels.map(item => {
|
||||
return {
|
||||
lang: 'sk',
|
||||
site_id: item.id.replace('lgi-obolite-sk-prod-master:5-', ''),
|
||||
name: item.title
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.i) return {}
|
||||
const url = `${API_ENDPOINT}/listings/${item.i}`
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs(item.s)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs(item.e)
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
if (!content) return []
|
||||
const data = typeof content === 'string' ? JSON.parse(content) : content
|
||||
if (!data || !Array.isArray(data.entries)) return []
|
||||
const entity = data.entries.find(e => e.o === `lgi-obolite-sk-prod-master:${channel.site_id}`)
|
||||
return entity ? entity.l : []
|
||||
}
|
||||
|
||||
function parseDescription(detail) {
|
||||
if (!detail) return []
|
||||
if (!detail.program) return []
|
||||
return detail.program.longDescription || null
|
||||
}
|
||||
|
||||
function parseCategory(detail) {
|
||||
if (!detail) return []
|
||||
if (!detail.program) return []
|
||||
if (!detail.program.categories) return []
|
||||
let categories = []
|
||||
detail.program.categories.forEach(category => {
|
||||
categories.push(category.title)
|
||||
})
|
||||
return categories
|
||||
}
|
||||
|
||||
function parseSeason(detail) {
|
||||
if (!detail) return null
|
||||
if (!detail.program) return null
|
||||
if (!detail.program.seriesNumber) return null
|
||||
if (String(detail.program.seriesNumber).length > 2) return null
|
||||
return detail.program.seriesNumber
|
||||
}
|
||||
|
||||
function parseEpisode(detail) {
|
||||
if (!detail) return null
|
||||
if (!detail.program) return null
|
||||
if (!detail.program.seriesEpisodeNumber) return null
|
||||
if (String(detail.program.seriesEpisodeNumber).length > 3) return null
|
||||
return detail.program.seriesEpisodeNumber
|
||||
}
|
||||
|
||||
function parseDirectors(detail) {
|
||||
if (!detail) return []
|
||||
if (!detail.program) return []
|
||||
return detail.program.directors || []
|
||||
}
|
||||
|
||||
function parseActors(detail) {
|
||||
if (!detail) return []
|
||||
if (!detail.program) return []
|
||||
return detail.program.cast || []
|
||||
}
|
||||
|
||||
function parseYear(detail) {
|
||||
if (!detail) return null
|
||||
if (!detail.program) return null
|
||||
return detail.program.year || null
|
||||
}
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
|
||||
const API_ENDPOINT = 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web'
|
||||
|
||||
module.exports = {
|
||||
site: 'horizon.tv',
|
||||
days: 3,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 60 * 60 * 1000 // 1 hour
|
||||
}
|
||||
},
|
||||
url: function ({ date }) {
|
||||
return `${API_ENDPOINT}/programschedules/${date.format('YYYYMMDD')}/1`
|
||||
},
|
||||
async parser({ content, channel, date }) {
|
||||
let programs = []
|
||||
let items = parseItems(content, channel)
|
||||
if (!items.length) return programs
|
||||
const d = date.format('YYYYMMDD')
|
||||
const promises = [
|
||||
axios.get(`${API_ENDPOINT}/programschedules/${d}/2`),
|
||||
axios.get(`${API_ENDPOINT}/programschedules/${d}/3`),
|
||||
axios.get(`${API_ENDPOINT}/programschedules/${d}/4`)
|
||||
]
|
||||
await Promise.allSettled(promises)
|
||||
.then(results => {
|
||||
results.forEach(r => {
|
||||
if (r.status === 'fulfilled') {
|
||||
items = items.concat(parseItems(r.value.data, channel))
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(console.error)
|
||||
for (let item of items) {
|
||||
const detail = await loadProgramDetails(item)
|
||||
programs.push({
|
||||
title: item.t,
|
||||
description: parseDescription(detail),
|
||||
category: parseCategory(detail),
|
||||
season: parseSeason(detail),
|
||||
episode: parseEpisode(detail),
|
||||
actors: parseActors(detail),
|
||||
directors: parseDirectors(detail),
|
||||
date: parseYear(detail),
|
||||
start: parseStart(item),
|
||||
stop: parseStop(item)
|
||||
})
|
||||
}
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const data = await axios
|
||||
.get(`${API_ENDPOINT}/channels`)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
return data.channels.map(item => {
|
||||
return {
|
||||
lang: 'sk',
|
||||
site_id: item.id.replace('lgi-obolite-sk-prod-master:5-', ''),
|
||||
name: item.title
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProgramDetails(item) {
|
||||
if (!item.i) return {}
|
||||
const url = `${API_ENDPOINT}/listings/${item.i}`
|
||||
const data = await axios
|
||||
.get(url)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
return data || {}
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return dayjs(item.s)
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return dayjs(item.e)
|
||||
}
|
||||
|
||||
function parseItems(content, channel) {
|
||||
if (!content) return []
|
||||
const data = typeof content === 'string' ? JSON.parse(content) : content
|
||||
if (!data || !Array.isArray(data.entries)) return []
|
||||
const entity = data.entries.find(e => e.o === `lgi-obolite-sk-prod-master:${channel.site_id}`)
|
||||
return entity ? entity.l : []
|
||||
}
|
||||
|
||||
function parseDescription(detail) {
|
||||
if (!detail) return []
|
||||
if (!detail.program) return []
|
||||
return detail.program.longDescription || null
|
||||
}
|
||||
|
||||
function parseCategory(detail) {
|
||||
if (!detail) return []
|
||||
if (!detail.program) return []
|
||||
if (!detail.program.categories) return []
|
||||
let categories = []
|
||||
detail.program.categories.forEach(category => {
|
||||
categories.push(category.title)
|
||||
})
|
||||
return categories
|
||||
}
|
||||
|
||||
function parseSeason(detail) {
|
||||
if (!detail) return null
|
||||
if (!detail.program) return null
|
||||
if (!detail.program.seriesNumber) return null
|
||||
if (String(detail.program.seriesNumber).length > 2) return null
|
||||
return detail.program.seriesNumber
|
||||
}
|
||||
|
||||
function parseEpisode(detail) {
|
||||
if (!detail) return null
|
||||
if (!detail.program) return null
|
||||
if (!detail.program.seriesEpisodeNumber) return null
|
||||
if (String(detail.program.seriesEpisodeNumber).length > 3) return null
|
||||
return detail.program.seriesEpisodeNumber
|
||||
}
|
||||
|
||||
function parseDirectors(detail) {
|
||||
if (!detail) return []
|
||||
if (!detail.program) return []
|
||||
return detail.program.directors || []
|
||||
}
|
||||
|
||||
function parseActors(detail) {
|
||||
if (!detail) return []
|
||||
if (!detail.program) return []
|
||||
return detail.program.cast || []
|
||||
}
|
||||
|
||||
function parseYear(detail) {
|
||||
if (!detail) return null
|
||||
if (!detail.program) return null
|
||||
return detail.program.year || null
|
||||
}
|
||||
|
|
|
@ -1,266 +1,266 @@
|
|||
// npm run channels:parse -- --config=./sites/horizon.tv/horizon.tv.config.js --output=./sites/horizon.tv/horizon.tv.channels.xml
|
||||
// npm run grab -- --site=horizon.tv
|
||||
|
||||
const { parser, url } = require('./horizon.tv.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('2023-02-07', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '10024',
|
||||
xmltv_id: 'AMCCzechRepublic.cz'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date, channel })).toBe(
|
||||
'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/1'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
const content =
|
||||
'{"entryCount":184,"totalResults":184,"updated":1675790518889,"expires":1675791343825,"title":"EPG","periods":4,"periodStartTime":1675724400000,"periodEndTime":1675746000000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","t":"Avengement","s":1675719300000,"e":1675724700000,"c":"lgi-obolite-sk-prod-master:genre-9","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}'
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (
|
||||
url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/2'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"entryCount":184,"totalResults":184,"updated":1675790518889,"expires":1675791376097,"title":"EPG","periods":4,"periodStartTime":1675746000000,"periodEndTime":1675767600000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","t":"Zoom In","s":1675744500000,"e":1675746000000,"c":"lgi-obolite-sk-prod-master:genre-21","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/3'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"entryCount":184,"totalResults":184,"updated":1675789948804,"expires":1675791024984,"title":"EPG","periods":4,"periodStartTime":1675767600000,"periodEndTime":1675789200000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","t":"Studentka","s":1675761000000,"e":1675767600000,"c":"lgi-obolite-sk-prod-master:genre-14","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/4'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"entryCount":184,"totalResults":184,"updated":1675789948804,"expires":1675790973469,"title":"EPG","periods":4,"periodStartTime":1675789200000,"periodEndTime":1675810800000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","t":"Zilionáři","s":1675785900000,"e":1675791900000,"c":"lgi-obolite-sk-prod-master:genre-9","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url ===
|
||||
'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"id":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","startTime":1675719300000,"endTime":1675724700000,"actualStartTime":1675719300000,"actualEndTime":1675724700000,"expirationDate":1676324100000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","scCridImi":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F122941980","program":{"id":"crid:~~2F~~2Fport.cs~~2F122941980","title":"Avengement","description":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za...","longDescription":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za dosažením vytoužené pomsty na těch, kteří z něj udělali chladnokrevného vraha.","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-9","title":"Drama","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-33","title":"Akcia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"18","cast":["Scott Adkins","Craig Fairbrass","Thomas Turgoose","Nick Moran","Kierston Wareing","Leo Gregory","Mark Strange","Luke LaFontaine","Beau Fowler","Dan Styles","Christopher Sciueref","Matt Routledge","Jane Thorne","Louis Mandylor","Terence Maynard","Greg Burridge","Michael Higgs","Damian Gallagher","Daniel Adegboyega","John Ioannou","Sofie Golding-Spittle","Joe Egan","Darren Swain","Lee Charles","Dominic Kinnaird","Ross O\'Hennessy","Teresa Mahoney","Andrew Dunkelberger","Sam Hardy","Ivan Moy","Mark Sears","Phillip Ray Tommy"],"directors":["Jesse V. Johnson"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_3fa8387df870473fdacb1024635b52b2496b159c.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_19e3a660e637cd39e31046c284a66b3a95d698e4.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F122941980","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F122941980","shortDescription":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za...","mediaType":"FeatureFilm","year":"2019","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F122941980","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1676247300000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url ===
|
||||
'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"id":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","startTime":1675744500000,"endTime":1675746000000,"actualStartTime":1675744500000,"actualEndTime":1675746000000,"expirationDate":1676349300000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:e85129f9d1e211406a521df7a36f22237c22651b","scCridImi":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F41764266","program":{"id":"crid:~~2F~~2Fport.cs~~2F248281986","title":"Zoom In","description":"Film/Kino","longDescription":"Film/Kino","medium":"TV","categories":[{"id":"lgi-obolite-sk-prod-master:genre-21","title":"Hudba a umenie","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-14","title":"Film","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"9","cast":[],"directors":[],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_cbed64b557e83227a2292604cbcae2d193877b1c.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_cfe405e669385365846b69196e1e94caa3e60de0.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=180&h=260&mode=box"}],"parentId":"crid:~~2F~~2Fport.cs~~2F41764266_series","rootId":"crid:~~2F~~2Fport.cs~~2F41764266","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F41764266","shortDescription":"Film/Kino","mediaType":"Episode","year":"2010","seriesEpisodeNumber":"1302070535","seriesNumber":"1302080520","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"parentId":"crid:~~2F~~2Fport.cs~~2F41764266_series","rootId":"crid:~~2F~~2Fport.cs~~2F41764266","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1675746000000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url ===
|
||||
'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"id":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","startTime":1675761000000,"endTime":1675767600000,"actualStartTime":1675761000000,"actualEndTime":1675767600000,"expirationDate":1676365800000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","scCridImi":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F1379541","program":{"id":"crid:~~2F~~2Fport.cs~~2F1379541","title":"Studentka","description":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný...","longDescription":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný spánek a především a hlavně ... žádná láska! Věří, že jedině tak obstojí před zkušební komisí. Jednoho dne se však odehraje něco, s čím nepočítala. Potká charismatického hudebníka Neda - a bláznivě se zamiluje. V tuto chvíli stojí před osudovým rozhodnutím: zahodí roky obrovského studijního nasazení, nebo odmítne lásku? Nebo se snad dá obojí skloubit dohromady?","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-14","title":"Film","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-4","title":"Komédia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"9","cast":["Sophie Marceauová","Vincent Lindon","Elisabeth Vitali","Elena Pompei","Jean-Claude Leguay","Brigitte Chamarande","Christian Pereira","Gérard Dacier","Roberto Attias","Beppe Chierici","Nathalie Mann","Anne Macina","Janine Souchon","Virginie Demians","Hugues Leforestier","Jacqueline Noëlle","Marc-André Brunet","Isabelle Caubère","André Chazel","Med Salah Cheurfi","Guillaume Corea","Eric Denize","Gilles Gaston-Dreyfuss","Benoît Gourley","Marc Innocenti","Najim Laouriga","Laurent Ledermann","Philippe Maygal","Dominique Pifarely","Ysé Tran"],"directors":["Francis De Gueltz","Dominique Talmon","Claude Pinoteau"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_a8abceaa59bbb0aae8031dcdd5deba03aba8a100.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_72b11621270454812ac8474698fc75670db4a49d.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F1379541","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F1379541","shortDescription":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný...","mediaType":"FeatureFilm","year":"1988","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F1379541","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1675767600000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url ===
|
||||
'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"id":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","startTime":1675785900000,"endTime":1675791900000,"actualStartTime":1675785900000,"actualEndTime":1675791900000,"expirationDate":1676390700000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","scCridImi":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F71927954","program":{"id":"crid:~~2F~~2Fport.cs~~2F71927954","title":"Zilionáři","description":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným...","longDescription":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným vzrušujícím momentem v jeho životě je flirtování s kolegyní Kelly (Kristen Wiig), která ho však brzy zatáhne do těžko uvěřitelného dobrodružství. Skupinka nepříliš inteligentních loserů, pod vedením Steva (Owen Wilson), plánuje vyloupit banku a David jim v tom má samozřejmě pomoci. Navzdory absolutně amatérskému plánu se ale stane nemožné a oni mají najednou v kapse 17 miliónů dolarů. A protože tato partička je opravdu bláznivá, začne je hned ve velkém roztáčet. Peníze létají vzduchem za luxusní a kolikrát i zbytečné věci, ale nedochází jim, že pro policii tak zanechávají jasné stopy...","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-9","title":"Drama","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-33","title":"Akcia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"15","cast":["Zach Galifianakis","Kristen Wiigová","Owen Wilson","Kate McKinnon","Leslie Jones","Jason Sudeikis","Ross Kimball","Devin Ratray","Mary Elizabeth Ellisová","Jon Daly","Ken Marino","Daniel Zacapa","Tom Werme","Njema Williams","Nils Cruz","Michael Fraguada","Christian Gonzalez","Candace Blanchard","Karsten Friske","Dallas Edwards","Barry Ratcliffe","Shelton Grant","Laura Palka","Reegus Flenory","Wynn Reichert","Jill Jane Clements","Joseph S. Wilson","Jee An","Rhoda Griffisová","Nicole Dupre Sobchack"],"directors":["Scott August","Richard L. Fox","Michelle Malley-Campos","Sebastian Mazzola","Steven Ritzi","Pete Waterman","Jared Hess"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_fd098116bac1429318aaf5fdae498ce76e258782.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_6f857ae9375b3bcceb6353a5b35775f52cd85302.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F71927954","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F71927954","shortDescription":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným...","mediaType":"FeatureFilm","year":"2016","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F71927954","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1676187900000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}'
|
||||
)
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content, channel, date })
|
||||
.then(result => {
|
||||
result = result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2023-02-06T21:35:00.000Z',
|
||||
stop: '2023-02-06T23:05:00.000Z',
|
||||
title: 'Avengement',
|
||||
description:
|
||||
'Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za dosažením vytoužené pomsty na těch, kteří z něj udělali chladnokrevného vraha.',
|
||||
category: ['Drama', 'Akcia'],
|
||||
directors: ['Jesse V. Johnson'],
|
||||
actors: [
|
||||
'Scott Adkins',
|
||||
'Craig Fairbrass',
|
||||
'Thomas Turgoose',
|
||||
'Nick Moran',
|
||||
'Kierston Wareing',
|
||||
'Leo Gregory',
|
||||
'Mark Strange',
|
||||
'Luke LaFontaine',
|
||||
'Beau Fowler',
|
||||
'Dan Styles',
|
||||
'Christopher Sciueref',
|
||||
'Matt Routledge',
|
||||
'Jane Thorne',
|
||||
'Louis Mandylor',
|
||||
'Terence Maynard',
|
||||
'Greg Burridge',
|
||||
'Michael Higgs',
|
||||
'Damian Gallagher',
|
||||
'Daniel Adegboyega',
|
||||
'John Ioannou',
|
||||
'Sofie Golding-Spittle',
|
||||
'Joe Egan',
|
||||
'Darren Swain',
|
||||
'Lee Charles',
|
||||
'Dominic Kinnaird',
|
||||
"Ross O'Hennessy",
|
||||
'Teresa Mahoney',
|
||||
'Andrew Dunkelberger',
|
||||
'Sam Hardy',
|
||||
'Ivan Moy',
|
||||
'Mark Sears',
|
||||
'Phillip Ray Tommy'
|
||||
],
|
||||
date: '2019'
|
||||
},
|
||||
{
|
||||
start: '2023-02-07T04:35:00.000Z',
|
||||
stop: '2023-02-07T05:00:00.000Z',
|
||||
title: 'Zoom In',
|
||||
description: 'Film/Kino',
|
||||
category: ['Hudba a umenie', 'Film'],
|
||||
date: '2010'
|
||||
},
|
||||
{
|
||||
start: '2023-02-07T09:10:00.000Z',
|
||||
stop: '2023-02-07T11:00:00.000Z',
|
||||
title: 'Studentka',
|
||||
description:
|
||||
'Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný spánek a především a hlavně ... žádná láska! Věří, že jedině tak obstojí před zkušební komisí. Jednoho dne se však odehraje něco, s čím nepočítala. Potká charismatického hudebníka Neda - a bláznivě se zamiluje. V tuto chvíli stojí před osudovým rozhodnutím: zahodí roky obrovského studijního nasazení, nebo odmítne lásku? Nebo se snad dá obojí skloubit dohromady?',
|
||||
category: ['Film', 'Komédia'],
|
||||
actors: [
|
||||
'Sophie Marceauová',
|
||||
'Vincent Lindon',
|
||||
'Elisabeth Vitali',
|
||||
'Elena Pompei',
|
||||
'Jean-Claude Leguay',
|
||||
'Brigitte Chamarande',
|
||||
'Christian Pereira',
|
||||
'Gérard Dacier',
|
||||
'Roberto Attias',
|
||||
'Beppe Chierici',
|
||||
'Nathalie Mann',
|
||||
'Anne Macina',
|
||||
'Janine Souchon',
|
||||
'Virginie Demians',
|
||||
'Hugues Leforestier',
|
||||
'Jacqueline Noëlle',
|
||||
'Marc-André Brunet',
|
||||
'Isabelle Caubère',
|
||||
'André Chazel',
|
||||
'Med Salah Cheurfi',
|
||||
'Guillaume Corea',
|
||||
'Eric Denize',
|
||||
'Gilles Gaston-Dreyfuss',
|
||||
'Benoît Gourley',
|
||||
'Marc Innocenti',
|
||||
'Najim Laouriga',
|
||||
'Laurent Ledermann',
|
||||
'Philippe Maygal',
|
||||
'Dominique Pifarely',
|
||||
'Ysé Tran'
|
||||
],
|
||||
directors: ['Francis De Gueltz', 'Dominique Talmon', 'Claude Pinoteau'],
|
||||
date: '1988'
|
||||
},
|
||||
{
|
||||
start: '2023-02-07T16:05:00.000Z',
|
||||
stop: '2023-02-07T17:45:00.000Z',
|
||||
title: 'Zilionáři',
|
||||
description:
|
||||
'David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným vzrušujícím momentem v jeho životě je flirtování s kolegyní Kelly (Kristen Wiig), která ho však brzy zatáhne do těžko uvěřitelného dobrodružství. Skupinka nepříliš inteligentních loserů, pod vedením Steva (Owen Wilson), plánuje vyloupit banku a David jim v tom má samozřejmě pomoci. Navzdory absolutně amatérskému plánu se ale stane nemožné a oni mají najednou v kapse 17 miliónů dolarů. A protože tato partička je opravdu bláznivá, začne je hned ve velkém roztáčet. Peníze létají vzduchem za luxusní a kolikrát i zbytečné věci, ale nedochází jim, že pro policii tak zanechávají jasné stopy...',
|
||||
category: ['Drama', 'Akcia'],
|
||||
actors: [
|
||||
'Zach Galifianakis',
|
||||
'Kristen Wiigová',
|
||||
'Owen Wilson',
|
||||
'Kate McKinnon',
|
||||
'Leslie Jones',
|
||||
'Jason Sudeikis',
|
||||
'Ross Kimball',
|
||||
'Devin Ratray',
|
||||
'Mary Elizabeth Ellisová',
|
||||
'Jon Daly',
|
||||
'Ken Marino',
|
||||
'Daniel Zacapa',
|
||||
'Tom Werme',
|
||||
'Njema Williams',
|
||||
'Nils Cruz',
|
||||
'Michael Fraguada',
|
||||
'Christian Gonzalez',
|
||||
'Candace Blanchard',
|
||||
'Karsten Friske',
|
||||
'Dallas Edwards',
|
||||
'Barry Ratcliffe',
|
||||
'Shelton Grant',
|
||||
'Laura Palka',
|
||||
'Reegus Flenory',
|
||||
'Wynn Reichert',
|
||||
'Jill Jane Clements',
|
||||
'Joseph S. Wilson',
|
||||
'Jee An',
|
||||
'Rhoda Griffisová',
|
||||
'Nicole Dupre Sobchack'
|
||||
],
|
||||
directors: [
|
||||
'Scott August',
|
||||
'Richard L. Fox',
|
||||
'Michelle Malley-Campos',
|
||||
'Sebastian Mazzola',
|
||||
'Steven Ritzi',
|
||||
'Pete Waterman',
|
||||
'Jared Hess'
|
||||
],
|
||||
date: '2016'
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
content: '[{"type":"PATH_PARAM","code":"period","reason":"INVALID"}]',
|
||||
channel,
|
||||
date
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
// npm run channels:parse -- --config=./sites/horizon.tv/horizon.tv.config.js --output=./sites/horizon.tv/horizon.tv.channels.xml
|
||||
// npm run grab -- --site=horizon.tv
|
||||
|
||||
const { parser, url } = require('./horizon.tv.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('2023-02-07', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: '10024',
|
||||
xmltv_id: 'AMCCzechRepublic.cz'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ date, channel })).toBe(
|
||||
'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/1'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', done => {
|
||||
const content =
|
||||
'{"entryCount":184,"totalResults":184,"updated":1675790518889,"expires":1675791343825,"title":"EPG","periods":4,"periodStartTime":1675724400000,"periodEndTime":1675746000000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","t":"Avengement","s":1675719300000,"e":1675724700000,"c":"lgi-obolite-sk-prod-master:genre-9","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}'
|
||||
|
||||
axios.get.mockImplementation(url => {
|
||||
if (
|
||||
url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/2'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"entryCount":184,"totalResults":184,"updated":1675790518889,"expires":1675791376097,"title":"EPG","periods":4,"periodStartTime":1675746000000,"periodEndTime":1675767600000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","t":"Zoom In","s":1675744500000,"e":1675746000000,"c":"lgi-obolite-sk-prod-master:genre-21","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/3'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"entryCount":184,"totalResults":184,"updated":1675789948804,"expires":1675791024984,"title":"EPG","periods":4,"periodStartTime":1675767600000,"periodEndTime":1675789200000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","t":"Studentka","s":1675761000000,"e":1675767600000,"c":"lgi-obolite-sk-prod-master:genre-14","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/4'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"entryCount":184,"totalResults":184,"updated":1675789948804,"expires":1675790973469,"title":"EPG","periods":4,"periodStartTime":1675789200000,"periodEndTime":1675810800000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","t":"Zilionáři","s":1675785900000,"e":1675791900000,"c":"lgi-obolite-sk-prod-master:genre-9","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url ===
|
||||
'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"id":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","startTime":1675719300000,"endTime":1675724700000,"actualStartTime":1675719300000,"actualEndTime":1675724700000,"expirationDate":1676324100000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","scCridImi":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F122941980","program":{"id":"crid:~~2F~~2Fport.cs~~2F122941980","title":"Avengement","description":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za...","longDescription":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za dosažením vytoužené pomsty na těch, kteří z něj udělali chladnokrevného vraha.","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-9","title":"Drama","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-33","title":"Akcia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"18","cast":["Scott Adkins","Craig Fairbrass","Thomas Turgoose","Nick Moran","Kierston Wareing","Leo Gregory","Mark Strange","Luke LaFontaine","Beau Fowler","Dan Styles","Christopher Sciueref","Matt Routledge","Jane Thorne","Louis Mandylor","Terence Maynard","Greg Burridge","Michael Higgs","Damian Gallagher","Daniel Adegboyega","John Ioannou","Sofie Golding-Spittle","Joe Egan","Darren Swain","Lee Charles","Dominic Kinnaird","Ross O\'Hennessy","Teresa Mahoney","Andrew Dunkelberger","Sam Hardy","Ivan Moy","Mark Sears","Phillip Ray Tommy"],"directors":["Jesse V. Johnson"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_3fa8387df870473fdacb1024635b52b2496b159c.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_19e3a660e637cd39e31046c284a66b3a95d698e4.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F122941980","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F122941980","shortDescription":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za...","mediaType":"FeatureFilm","year":"2019","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F122941980","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1676247300000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url ===
|
||||
'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"id":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","startTime":1675744500000,"endTime":1675746000000,"actualStartTime":1675744500000,"actualEndTime":1675746000000,"expirationDate":1676349300000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:e85129f9d1e211406a521df7a36f22237c22651b","scCridImi":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F41764266","program":{"id":"crid:~~2F~~2Fport.cs~~2F248281986","title":"Zoom In","description":"Film/Kino","longDescription":"Film/Kino","medium":"TV","categories":[{"id":"lgi-obolite-sk-prod-master:genre-21","title":"Hudba a umenie","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-14","title":"Film","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"9","cast":[],"directors":[],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_cbed64b557e83227a2292604cbcae2d193877b1c.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_cfe405e669385365846b69196e1e94caa3e60de0.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=180&h=260&mode=box"}],"parentId":"crid:~~2F~~2Fport.cs~~2F41764266_series","rootId":"crid:~~2F~~2Fport.cs~~2F41764266","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F41764266","shortDescription":"Film/Kino","mediaType":"Episode","year":"2010","seriesEpisodeNumber":"1302070535","seriesNumber":"1302080520","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"parentId":"crid:~~2F~~2Fport.cs~~2F41764266_series","rootId":"crid:~~2F~~2Fport.cs~~2F41764266","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1675746000000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url ===
|
||||
'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"id":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","startTime":1675761000000,"endTime":1675767600000,"actualStartTime":1675761000000,"actualEndTime":1675767600000,"expirationDate":1676365800000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","scCridImi":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F1379541","program":{"id":"crid:~~2F~~2Fport.cs~~2F1379541","title":"Studentka","description":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný...","longDescription":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný spánek a především a hlavně ... žádná láska! Věří, že jedině tak obstojí před zkušební komisí. Jednoho dne se však odehraje něco, s čím nepočítala. Potká charismatického hudebníka Neda - a bláznivě se zamiluje. V tuto chvíli stojí před osudovým rozhodnutím: zahodí roky obrovského studijního nasazení, nebo odmítne lásku? Nebo se snad dá obojí skloubit dohromady?","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-14","title":"Film","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-4","title":"Komédia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"9","cast":["Sophie Marceauová","Vincent Lindon","Elisabeth Vitali","Elena Pompei","Jean-Claude Leguay","Brigitte Chamarande","Christian Pereira","Gérard Dacier","Roberto Attias","Beppe Chierici","Nathalie Mann","Anne Macina","Janine Souchon","Virginie Demians","Hugues Leforestier","Jacqueline Noëlle","Marc-André Brunet","Isabelle Caubère","André Chazel","Med Salah Cheurfi","Guillaume Corea","Eric Denize","Gilles Gaston-Dreyfuss","Benoît Gourley","Marc Innocenti","Najim Laouriga","Laurent Ledermann","Philippe Maygal","Dominique Pifarely","Ysé Tran"],"directors":["Francis De Gueltz","Dominique Talmon","Claude Pinoteau"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_a8abceaa59bbb0aae8031dcdd5deba03aba8a100.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_72b11621270454812ac8474698fc75670db4a49d.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F1379541","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F1379541","shortDescription":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný...","mediaType":"FeatureFilm","year":"1988","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F1379541","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1675767600000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}'
|
||||
)
|
||||
})
|
||||
} else if (
|
||||
url ===
|
||||
'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7'
|
||||
) {
|
||||
return Promise.resolve({
|
||||
data: JSON.parse(
|
||||
'{"id":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","startTime":1675785900000,"endTime":1675791900000,"actualStartTime":1675785900000,"actualEndTime":1675791900000,"expirationDate":1676390700000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","scCridImi":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F71927954","program":{"id":"crid:~~2F~~2Fport.cs~~2F71927954","title":"Zilionáři","description":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným...","longDescription":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným vzrušujícím momentem v jeho životě je flirtování s kolegyní Kelly (Kristen Wiig), která ho však brzy zatáhne do těžko uvěřitelného dobrodružství. Skupinka nepříliš inteligentních loserů, pod vedením Steva (Owen Wilson), plánuje vyloupit banku a David jim v tom má samozřejmě pomoci. Navzdory absolutně amatérskému plánu se ale stane nemožné a oni mají najednou v kapse 17 miliónů dolarů. A protože tato partička je opravdu bláznivá, začne je hned ve velkém roztáčet. Peníze létají vzduchem za luxusní a kolikrát i zbytečné věci, ale nedochází jim, že pro policii tak zanechávají jasné stopy...","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-9","title":"Drama","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-33","title":"Akcia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"15","cast":["Zach Galifianakis","Kristen Wiigová","Owen Wilson","Kate McKinnon","Leslie Jones","Jason Sudeikis","Ross Kimball","Devin Ratray","Mary Elizabeth Ellisová","Jon Daly","Ken Marino","Daniel Zacapa","Tom Werme","Njema Williams","Nils Cruz","Michael Fraguada","Christian Gonzalez","Candace Blanchard","Karsten Friske","Dallas Edwards","Barry Ratcliffe","Shelton Grant","Laura Palka","Reegus Flenory","Wynn Reichert","Jill Jane Clements","Joseph S. Wilson","Jee An","Rhoda Griffisová","Nicole Dupre Sobchack"],"directors":["Scott August","Richard L. Fox","Michelle Malley-Campos","Sebastian Mazzola","Steven Ritzi","Pete Waterman","Jared Hess"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_fd098116bac1429318aaf5fdae498ce76e258782.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_6f857ae9375b3bcceb6353a5b35775f52cd85302.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F71927954","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F71927954","shortDescription":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným...","mediaType":"FeatureFilm","year":"2016","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F71927954","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1676187900000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}'
|
||||
)
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve({ data: '' })
|
||||
}
|
||||
})
|
||||
|
||||
parser({ content, channel, date })
|
||||
.then(result => {
|
||||
result = result.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
return p
|
||||
})
|
||||
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
start: '2023-02-06T21:35:00.000Z',
|
||||
stop: '2023-02-06T23:05:00.000Z',
|
||||
title: 'Avengement',
|
||||
description:
|
||||
'Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za dosažením vytoužené pomsty na těch, kteří z něj udělali chladnokrevného vraha.',
|
||||
category: ['Drama', 'Akcia'],
|
||||
directors: ['Jesse V. Johnson'],
|
||||
actors: [
|
||||
'Scott Adkins',
|
||||
'Craig Fairbrass',
|
||||
'Thomas Turgoose',
|
||||
'Nick Moran',
|
||||
'Kierston Wareing',
|
||||
'Leo Gregory',
|
||||
'Mark Strange',
|
||||
'Luke LaFontaine',
|
||||
'Beau Fowler',
|
||||
'Dan Styles',
|
||||
'Christopher Sciueref',
|
||||
'Matt Routledge',
|
||||
'Jane Thorne',
|
||||
'Louis Mandylor',
|
||||
'Terence Maynard',
|
||||
'Greg Burridge',
|
||||
'Michael Higgs',
|
||||
'Damian Gallagher',
|
||||
'Daniel Adegboyega',
|
||||
'John Ioannou',
|
||||
'Sofie Golding-Spittle',
|
||||
'Joe Egan',
|
||||
'Darren Swain',
|
||||
'Lee Charles',
|
||||
'Dominic Kinnaird',
|
||||
"Ross O'Hennessy",
|
||||
'Teresa Mahoney',
|
||||
'Andrew Dunkelberger',
|
||||
'Sam Hardy',
|
||||
'Ivan Moy',
|
||||
'Mark Sears',
|
||||
'Phillip Ray Tommy'
|
||||
],
|
||||
date: '2019'
|
||||
},
|
||||
{
|
||||
start: '2023-02-07T04:35:00.000Z',
|
||||
stop: '2023-02-07T05:00:00.000Z',
|
||||
title: 'Zoom In',
|
||||
description: 'Film/Kino',
|
||||
category: ['Hudba a umenie', 'Film'],
|
||||
date: '2010'
|
||||
},
|
||||
{
|
||||
start: '2023-02-07T09:10:00.000Z',
|
||||
stop: '2023-02-07T11:00:00.000Z',
|
||||
title: 'Studentka',
|
||||
description:
|
||||
'Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný spánek a především a hlavně ... žádná láska! Věří, že jedině tak obstojí před zkušební komisí. Jednoho dne se však odehraje něco, s čím nepočítala. Potká charismatického hudebníka Neda - a bláznivě se zamiluje. V tuto chvíli stojí před osudovým rozhodnutím: zahodí roky obrovského studijního nasazení, nebo odmítne lásku? Nebo se snad dá obojí skloubit dohromady?',
|
||||
category: ['Film', 'Komédia'],
|
||||
actors: [
|
||||
'Sophie Marceauová',
|
||||
'Vincent Lindon',
|
||||
'Elisabeth Vitali',
|
||||
'Elena Pompei',
|
||||
'Jean-Claude Leguay',
|
||||
'Brigitte Chamarande',
|
||||
'Christian Pereira',
|
||||
'Gérard Dacier',
|
||||
'Roberto Attias',
|
||||
'Beppe Chierici',
|
||||
'Nathalie Mann',
|
||||
'Anne Macina',
|
||||
'Janine Souchon',
|
||||
'Virginie Demians',
|
||||
'Hugues Leforestier',
|
||||
'Jacqueline Noëlle',
|
||||
'Marc-André Brunet',
|
||||
'Isabelle Caubère',
|
||||
'André Chazel',
|
||||
'Med Salah Cheurfi',
|
||||
'Guillaume Corea',
|
||||
'Eric Denize',
|
||||
'Gilles Gaston-Dreyfuss',
|
||||
'Benoît Gourley',
|
||||
'Marc Innocenti',
|
||||
'Najim Laouriga',
|
||||
'Laurent Ledermann',
|
||||
'Philippe Maygal',
|
||||
'Dominique Pifarely',
|
||||
'Ysé Tran'
|
||||
],
|
||||
directors: ['Francis De Gueltz', 'Dominique Talmon', 'Claude Pinoteau'],
|
||||
date: '1988'
|
||||
},
|
||||
{
|
||||
start: '2023-02-07T16:05:00.000Z',
|
||||
stop: '2023-02-07T17:45:00.000Z',
|
||||
title: 'Zilionáři',
|
||||
description:
|
||||
'David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným vzrušujícím momentem v jeho životě je flirtování s kolegyní Kelly (Kristen Wiig), která ho však brzy zatáhne do těžko uvěřitelného dobrodružství. Skupinka nepříliš inteligentních loserů, pod vedením Steva (Owen Wilson), plánuje vyloupit banku a David jim v tom má samozřejmě pomoci. Navzdory absolutně amatérskému plánu se ale stane nemožné a oni mají najednou v kapse 17 miliónů dolarů. A protože tato partička je opravdu bláznivá, začne je hned ve velkém roztáčet. Peníze létají vzduchem za luxusní a kolikrát i zbytečné věci, ale nedochází jim, že pro policii tak zanechávají jasné stopy...',
|
||||
category: ['Drama', 'Akcia'],
|
||||
actors: [
|
||||
'Zach Galifianakis',
|
||||
'Kristen Wiigová',
|
||||
'Owen Wilson',
|
||||
'Kate McKinnon',
|
||||
'Leslie Jones',
|
||||
'Jason Sudeikis',
|
||||
'Ross Kimball',
|
||||
'Devin Ratray',
|
||||
'Mary Elizabeth Ellisová',
|
||||
'Jon Daly',
|
||||
'Ken Marino',
|
||||
'Daniel Zacapa',
|
||||
'Tom Werme',
|
||||
'Njema Williams',
|
||||
'Nils Cruz',
|
||||
'Michael Fraguada',
|
||||
'Christian Gonzalez',
|
||||
'Candace Blanchard',
|
||||
'Karsten Friske',
|
||||
'Dallas Edwards',
|
||||
'Barry Ratcliffe',
|
||||
'Shelton Grant',
|
||||
'Laura Palka',
|
||||
'Reegus Flenory',
|
||||
'Wynn Reichert',
|
||||
'Jill Jane Clements',
|
||||
'Joseph S. Wilson',
|
||||
'Jee An',
|
||||
'Rhoda Griffisová',
|
||||
'Nicole Dupre Sobchack'
|
||||
],
|
||||
directors: [
|
||||
'Scott August',
|
||||
'Richard L. Fox',
|
||||
'Michelle Malley-Campos',
|
||||
'Sebastian Mazzola',
|
||||
'Steven Ritzi',
|
||||
'Pete Waterman',
|
||||
'Jared Hess'
|
||||
],
|
||||
date: '2016'
|
||||
}
|
||||
])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('can handle empty guide', done => {
|
||||
parser({
|
||||
content: '[{"type":"PATH_PARAM","code":"period","reason":"INVALID"}]',
|
||||
channel,
|
||||
date
|
||||
})
|
||||
.then(result => {
|
||||
expect(result).toMatchObject([])
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
|
|
@ -1,107 +1,107 @@
|
|||
const dayjs = require('dayjs')
|
||||
const axios = require('axios')
|
||||
const parser = require('epg-parser')
|
||||
const isBetween = require('dayjs/plugin/isBetween')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(isBetween)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const API_ENDPOINT = 'https://raw.githubusercontent.com/matthuisman/i.mjh.nz/master'
|
||||
|
||||
module.exports = {
|
||||
site: 'i.mjh.nz',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 3 * 60 * 60 * 1000 // 3h
|
||||
},
|
||||
maxContentLength: 100 * 1024 * 1024 // 100Mb
|
||||
},
|
||||
url: function ({ channel }) {
|
||||
const [path] = channel.site_id.split('#')
|
||||
|
||||
return `${API_ENDPOINT}/${path}.xml`
|
||||
},
|
||||
parser: function ({ content, channel, date }) {
|
||||
const items = parseItems(content, channel, date)
|
||||
|
||||
let programs = items.map(item => {
|
||||
return {
|
||||
...item,
|
||||
title: getTitle(item),
|
||||
description: getDescription(item),
|
||||
categories: getCategories(item)
|
||||
}
|
||||
})
|
||||
|
||||
programs = mergeMovieParts(programs)
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ path, lang = 'en' }) {
|
||||
let xml = await axios
|
||||
.get(`${API_ENDPOINT}/${path}.xml`)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
let data = parser.parse(xml)
|
||||
|
||||
return data.channels.map(channel => {
|
||||
return {
|
||||
lang,
|
||||
site_id: `${path}#${channel.id}`,
|
||||
name: channel.name[0].value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function mergeMovieParts(programs) {
|
||||
let output = []
|
||||
|
||||
programs.forEach(prog => {
|
||||
let prev = output[output.length - 1]
|
||||
let found =
|
||||
prev &&
|
||||
prog.categories.includes('Movie') &&
|
||||
prev.title === prog.title &&
|
||||
prev.description === prog.description
|
||||
|
||||
if (found) {
|
||||
prev.stop = prog.stop
|
||||
} else {
|
||||
output.push(prog)
|
||||
}
|
||||
})
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
function getTitle(item) {
|
||||
return item.title.length ? item.title[0].value : null
|
||||
}
|
||||
|
||||
function getDescription(item) {
|
||||
return item.desc.length ? item.desc[0].value : null
|
||||
}
|
||||
|
||||
function getCategories(item) {
|
||||
return item.category.map(c => c.value)
|
||||
}
|
||||
|
||||
function parseItems(content, channel, date) {
|
||||
try {
|
||||
const curr_day = date
|
||||
const next_day = date.add(1, 'd')
|
||||
const [, site_id] = channel.site_id.split('#')
|
||||
const data = parser.parse(content)
|
||||
if (!data || !Array.isArray(data.programs)) return []
|
||||
|
||||
return data.programs.filter(
|
||||
p =>
|
||||
p.channel === site_id && dayjs(p.start, 'YYYYMMDDHHmmss ZZ').isBetween(curr_day, next_day)
|
||||
)
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
const dayjs = require('dayjs')
|
||||
const axios = require('axios')
|
||||
const parser = require('epg-parser')
|
||||
const isBetween = require('dayjs/plugin/isBetween')
|
||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||
|
||||
dayjs.extend(isBetween)
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const API_ENDPOINT = 'https://raw.githubusercontent.com/matthuisman/i.mjh.nz/master'
|
||||
|
||||
module.exports = {
|
||||
site: 'i.mjh.nz',
|
||||
days: 2,
|
||||
request: {
|
||||
cache: {
|
||||
ttl: 3 * 60 * 60 * 1000 // 3h
|
||||
},
|
||||
maxContentLength: 100 * 1024 * 1024 // 100Mb
|
||||
},
|
||||
url: function ({ channel }) {
|
||||
const [path] = channel.site_id.split('#')
|
||||
|
||||
return `${API_ENDPOINT}/${path}.xml`
|
||||
},
|
||||
parser: function ({ content, channel, date }) {
|
||||
const items = parseItems(content, channel, date)
|
||||
|
||||
let programs = items.map(item => {
|
||||
return {
|
||||
...item,
|
||||
title: getTitle(item),
|
||||
description: getDescription(item),
|
||||
categories: getCategories(item)
|
||||
}
|
||||
})
|
||||
|
||||
programs = mergeMovieParts(programs)
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels({ path, lang = 'en' }) {
|
||||
let xml = await axios
|
||||
.get(`${API_ENDPOINT}/${path}.xml`)
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
let data = parser.parse(xml)
|
||||
|
||||
return data.channels.map(channel => {
|
||||
return {
|
||||
lang,
|
||||
site_id: `${path}#${channel.id}`,
|
||||
name: channel.name[0].value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function mergeMovieParts(programs) {
|
||||
let output = []
|
||||
|
||||
programs.forEach(prog => {
|
||||
let prev = output[output.length - 1]
|
||||
let found =
|
||||
prev &&
|
||||
prog.categories.includes('Movie') &&
|
||||
prev.title === prog.title &&
|
||||
prev.description === prog.description
|
||||
|
||||
if (found) {
|
||||
prev.stop = prog.stop
|
||||
} else {
|
||||
output.push(prog)
|
||||
}
|
||||
})
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
function getTitle(item) {
|
||||
return item.title.length ? item.title[0].value : null
|
||||
}
|
||||
|
||||
function getDescription(item) {
|
||||
return item.desc.length ? item.desc[0].value : null
|
||||
}
|
||||
|
||||
function getCategories(item) {
|
||||
return item.category.map(c => c.value)
|
||||
}
|
||||
|
||||
function parseItems(content, channel, date) {
|
||||
try {
|
||||
const curr_day = date
|
||||
const next_day = date.add(1, 'd')
|
||||
const [, site_id] = channel.site_id.split('#')
|
||||
const data = parser.parse(content)
|
||||
if (!data || !Array.isArray(data.programs)) return []
|
||||
|
||||
return data.programs.filter(
|
||||
p =>
|
||||
p.channel === site_id && dayjs(p.start, 'YYYYMMDDHHmmss ZZ').isBetween(curr_day, next_day)
|
||||
)
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
// npm run channels:parse -- --config=./sites/i.mjh.nz/i.mjh.nz.config.js --output=./sites/i.mjh.nz/i.mjh.nz_pluto.channels.xml --set=path:PlutoTV/all
|
||||
// npm run grab -- --site=i.mjh.nz
|
||||
|
||||
const { parser, url } = require('./i.mjh.nz.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-23', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'Plex/all#5e20b730f2f8d5003d739db7-5eea605674085f0040ddc7a6',
|
||||
xmltv_id: 'DarkMatterTV.us',
|
||||
lang: 'en'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe(
|
||||
'https://raw.githubusercontent.com/matthuisman/i.mjh.nz/master/Plex/all.xml'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'))
|
||||
const results = parser({ content, channel, date })
|
||||
|
||||
expect(results.length).toBe(11)
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-23T07:14:32.000Z',
|
||||
stop: '2023-06-23T09:09:36.000Z',
|
||||
title: 'Killers Within',
|
||||
date: ['20180101'],
|
||||
description:
|
||||
'With her son being held captive by a criminal gang, police officer Amanda Doyle, together with her ex-husband and three unlikely allies, takes part in a desperate plot to hold a wealthy banker and his family to ransom. But this is no ordinary family.',
|
||||
icon: ['https://provider-static.plex.tv/epg/images/thumbnails/darkmatter-tv-fallback.jpg'],
|
||||
categories: ['Movie']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: '404: Not Found',
|
||||
channel,
|
||||
date
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run channels:parse -- --config=./sites/i.mjh.nz/i.mjh.nz.config.js --output=./sites/i.mjh.nz/i.mjh.nz_pluto.channels.xml --set=path:PlutoTV/all
|
||||
// npm run grab -- --site=i.mjh.nz
|
||||
|
||||
const { parser, url } = require('./i.mjh.nz.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-23', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'Plex/all#5e20b730f2f8d5003d739db7-5eea605674085f0040ddc7a6',
|
||||
xmltv_id: 'DarkMatterTV.us',
|
||||
lang: 'en'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe(
|
||||
'https://raw.githubusercontent.com/matthuisman/i.mjh.nz/master/Plex/all.xml'
|
||||
)
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'))
|
||||
const results = parser({ content, channel, date })
|
||||
|
||||
expect(results.length).toBe(11)
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-06-23T07:14:32.000Z',
|
||||
stop: '2023-06-23T09:09:36.000Z',
|
||||
title: 'Killers Within',
|
||||
date: ['20180101'],
|
||||
description:
|
||||
'With her son being held captive by a criminal gang, police officer Amanda Doyle, together with her ex-husband and three unlikely allies, takes part in a desperate plot to hold a wealthy banker and his family to ransom. But this is no ordinary family.',
|
||||
icon: ['https://provider-static.plex.tv/epg/images/thumbnails/darkmatter-tv-fallback.jpg'],
|
||||
categories: ['Movie']
|
||||
})
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: '404: Not Found',
|
||||
channel,
|
||||
date
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
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: 'i24news.tv',
|
||||
days: 2,
|
||||
url: function ({ channel }) {
|
||||
const [lang] = channel.site_id.split('#')
|
||||
|
||||
return `https://api.i24news.tv/v2/${lang}/schedules/world`
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
if (!item.show) return
|
||||
programs.push({
|
||||
title: item.show.title,
|
||||
description: item.show.body,
|
||||
icon: parseIcon(item),
|
||||
start: parseStart(item, date),
|
||||
stop: parseStop(item, date)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
return item.show.image ? item.show.image.href : null
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
if (!item.startHour) return null
|
||||
|
||||
return dayjs.tz(
|
||||
`${date.format('YYYY-MM-DD')} ${item.startHour}`,
|
||||
'YYYY-MM-DD HH:mm',
|
||||
'Asia/Jerusalem'
|
||||
)
|
||||
}
|
||||
|
||||
function parseStop(item, date) {
|
||||
if (!item.endHour) return null
|
||||
|
||||
return dayjs.tz(
|
||||
`${date.format('YYYY-MM-DD')} ${item.endHour}`,
|
||||
'YYYY-MM-DD HH:mm',
|
||||
'Asia/Jerusalem'
|
||||
)
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const data = JSON.parse(content)
|
||||
if (!Array.isArray(data)) return []
|
||||
let day = date.day() - 1
|
||||
day = day < 0 ? 6 : day
|
||||
|
||||
return data.filter(item => item.day === day)
|
||||
}
|
||||
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: 'i24news.tv',
|
||||
days: 2,
|
||||
url: function ({ channel }) {
|
||||
const [lang] = channel.site_id.split('#')
|
||||
|
||||
return `https://api.i24news.tv/v2/${lang}/schedules/world`
|
||||
},
|
||||
parser: function ({ content, date }) {
|
||||
let programs = []
|
||||
const items = parseItems(content, date)
|
||||
items.forEach(item => {
|
||||
if (!item.show) return
|
||||
programs.push({
|
||||
title: item.show.title,
|
||||
description: item.show.body,
|
||||
icon: parseIcon(item),
|
||||
start: parseStart(item, date),
|
||||
stop: parseStop(item, date)
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseIcon(item) {
|
||||
return item.show.image ? item.show.image.href : null
|
||||
}
|
||||
|
||||
function parseStart(item, date) {
|
||||
if (!item.startHour) return null
|
||||
|
||||
return dayjs.tz(
|
||||
`${date.format('YYYY-MM-DD')} ${item.startHour}`,
|
||||
'YYYY-MM-DD HH:mm',
|
||||
'Asia/Jerusalem'
|
||||
)
|
||||
}
|
||||
|
||||
function parseStop(item, date) {
|
||||
if (!item.endHour) return null
|
||||
|
||||
return dayjs.tz(
|
||||
`${date.format('YYYY-MM-DD')} ${item.endHour}`,
|
||||
'YYYY-MM-DD HH:mm',
|
||||
'Asia/Jerusalem'
|
||||
)
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const data = JSON.parse(content)
|
||||
if (!Array.isArray(data)) return []
|
||||
let day = date.day() - 1
|
||||
day = day < 0 ? 6 : day
|
||||
|
||||
return data.filter(item => item.day === day)
|
||||
}
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
// npm run grab -- --site=i24news.tv
|
||||
|
||||
const { parser, url } = require('./i24news.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 date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'ar#',
|
||||
xmltv_id: 'I24NewsArabic.il'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('https://api.i24news.tv/v2/ar/schedules/world')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content =
|
||||
'[{"id":348995,"startHour":"22:30","endHour":"23:00","day":5,"firstDiffusion":false,"override":false,"show":{"parsedBody":[{"type":"text","text":"Special Edition"}],"id":131,"title":"تغطية خاصة","body":"Special Edition","slug":"Special-Edition-تغطية-خاصة","visible":true,"image":{"id":1142467,"credit":"","legend":"","href":"https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png"}}},{"id":349023,"startHour":"15:00","endHour":"15:28","day":6,"firstDiffusion":false,"override":false,"show":{"parsedBody":[{"type":"text","text":"Special Edition"}],"id":131,"title":"تغطية خاصة","body":"Special Edition","slug":"Special-Edition-تغطية-خاصة","visible":true,"image":{"id":1142467,"credit":"","legend":"","href":"https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png"}}}]'
|
||||
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-06T13:00:00.000Z',
|
||||
stop: '2022-03-06T13:28:00.000Z',
|
||||
title: 'تغطية خاصة',
|
||||
description: 'Special Edition',
|
||||
icon: 'https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: '[]',
|
||||
date
|
||||
})
|
||||
expect(result).toMatchObject([])
|
||||
})
|
||||
// npm run grab -- --site=i24news.tv
|
||||
|
||||
const { parser, url } = require('./i24news.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 date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d')
|
||||
const channel = {
|
||||
site_id: 'ar#',
|
||||
xmltv_id: 'I24NewsArabic.il'
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url({ channel })).toBe('https://api.i24news.tv/v2/ar/schedules/world')
|
||||
})
|
||||
|
||||
it('can parse response', () => {
|
||||
const content =
|
||||
'[{"id":348995,"startHour":"22:30","endHour":"23:00","day":5,"firstDiffusion":false,"override":false,"show":{"parsedBody":[{"type":"text","text":"Special Edition"}],"id":131,"title":"تغطية خاصة","body":"Special Edition","slug":"Special-Edition-تغطية-خاصة","visible":true,"image":{"id":1142467,"credit":"","legend":"","href":"https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png"}}},{"id":349023,"startHour":"15:00","endHour":"15:28","day":6,"firstDiffusion":false,"override":false,"show":{"parsedBody":[{"type":"text","text":"Special Edition"}],"id":131,"title":"تغطية خاصة","body":"Special Edition","slug":"Special-Edition-تغطية-خاصة","visible":true,"image":{"id":1142467,"credit":"","legend":"","href":"https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png"}}}]'
|
||||
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-06T13:00:00.000Z',
|
||||
stop: '2022-03-06T13:28:00.000Z',
|
||||
title: 'تغطية خاصة',
|
||||
description: 'Special Edition',
|
||||
icon: 'https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('can handle empty guide', () => {
|
||||
const result = parser({
|
||||
content: '[]',
|
||||
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: 'indihometv.com',
|
||||
days: 2,
|
||||
url({ channel }) {
|
||||
return `https://www.indihometv.com/tvod/${channel.site_id}`
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const 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 && start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
let stop = parseStop($item, date)
|
||||
if (stop.isBefore(start)) {
|
||||
stop = stop.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const timeString = $item('p').text()
|
||||
const [, start] = timeString.match(/(\d{2}:\d{2}) -/) || [null, null]
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${start}`
|
||||
|
||||
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta')
|
||||
}
|
||||
|
||||
function parseStop($item, date) {
|
||||
const timeString = $item('p').text()
|
||||
const [, stop] = timeString.match(/- (\d{2}:\d{2})/) || [null, null]
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${stop}`
|
||||
|
||||
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('b').text()
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $(`#pills-${date.format('YYYY-MM-DD')} .schedule-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: 'indihometv.com',
|
||||
days: 2,
|
||||
url({ channel }) {
|
||||
return `https://www.indihometv.com/tvod/${channel.site_id}`
|
||||
},
|
||||
parser({ content, date }) {
|
||||
const 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 && start.isBefore(prev.start)) {
|
||||
start = start.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
let stop = parseStop($item, date)
|
||||
if (stop.isBefore(start)) {
|
||||
stop = stop.add(1, 'd')
|
||||
date = date.add(1, 'd')
|
||||
}
|
||||
programs.push({
|
||||
title: parseTitle($item),
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart($item, date) {
|
||||
const timeString = $item('p').text()
|
||||
const [, start] = timeString.match(/(\d{2}:\d{2}) -/) || [null, null]
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${start}`
|
||||
|
||||
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta')
|
||||
}
|
||||
|
||||
function parseStop($item, date) {
|
||||
const timeString = $item('p').text()
|
||||
const [, stop] = timeString.match(/- (\d{2}:\d{2})/) || [null, null]
|
||||
const dateString = `${date.format('YYYY-MM-DD')} ${stop}`
|
||||
|
||||
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta')
|
||||
}
|
||||
|
||||
function parseTitle($item) {
|
||||
return $item('b').text()
|
||||
}
|
||||
|
||||
function parseItems(content, date) {
|
||||
const $ = cheerio.load(content)
|
||||
|
||||
return $(`#pills-${date.format('YYYY-MM-DD')} .schedule-item`).toArray()
|
||||
}
|
||||
|
|
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