Fix linter issues in sites/

This commit is contained in:
freearhey 2025-01-01 12:27:22 +03:00
parent d6d20b6413
commit 5df982bb7c
129 changed files with 3316 additions and 3226 deletions

View file

@ -92,7 +92,7 @@ function parseItems(content, channel) {
const [, channelId] = channel.site_id.split('#')
const channelData = data.schedule.find(i => i.channel == channelId)
return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : []
} catch (err) {
} catch {
return []
}
}

View file

@ -1,4 +1,4 @@
const { parser, url, request } = require('./awilime.com.config.js')
const { parser, url } = require('./awilime.com.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')

View file

@ -1,5 +1,4 @@
const axios = require('axios')
const cheerio = require('cheerio')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
@ -62,7 +61,7 @@ function parseItems(content) {
let data
try {
data = JSON.parse(content)
} catch (error) {
} catch {
return []
}

View file

@ -1,6 +1,4 @@
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')

View file

@ -1,54 +1,55 @@
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: 'chada.ma',
channels: 'chada.ma.channels.xml',
days: 1,
request: {
cache: {
ttl: 60 * 60 * 1000 // 1 hour
}
},
url() {
return 'https://chada.ma/fr/chada-tv/grille-tv/';
},
parser: function ({ content }) {
const $ = cheerio.load(content);
const programs = [];
$('#stopfix .posts-area h2').each((i, element) => {
const timeRange = $(element).text().trim();
const [start, stop] = timeRange.split(' - ').map(t => parseProgramTime(t.trim()));
const titleElement = $(element).next('div').next('h3');
const title = titleElement.text().trim();
const description = titleElement.next('div').text().trim() || 'No description available';
programs.push({
title,
description,
start,
stop
});
});
return programs;
}
};
function parseProgramTime(timeStr) {
const timeZone = 'Africa/Casablanca';
const currentDate = dayjs().format('YYYY-MM-DD');
return dayjs.tz(`${currentDate} ${timeStr}`, 'YYYY-MM-DD HH:mm', timeZone).format('YYYY-MM-DDTHH:mm:ssZ');
}
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: 'chada.ma',
channels: 'chada.ma.channels.xml',
days: 1,
request: {
cache: {
ttl: 60 * 60 * 1000 // 1 hour
}
},
url() {
return 'https://chada.ma/fr/chada-tv/grille-tv/'
},
parser: function ({ content }) {
const $ = cheerio.load(content)
const programs = []
$('#stopfix .posts-area h2').each((i, element) => {
const timeRange = $(element).text().trim()
const [start, stop] = timeRange.split(' - ').map(t => parseProgramTime(t.trim()))
const titleElement = $(element).next('div').next('h3')
const title = titleElement.text().trim()
const description = titleElement.next('div').text().trim() || 'No description available'
programs.push({
title,
description,
start,
stop
})
})
return programs
}
}
function parseProgramTime(timeStr) {
const timeZone = 'Africa/Casablanca'
const currentDate = dayjs().format('YYYY-MM-DD')
return dayjs
.tz(`${currentDate} ${timeStr}`, 'YYYY-MM-DD HH:mm', timeZone)
.format('YYYY-MM-DDTHH:mm:ssZ')
}

View file

@ -1,60 +1,58 @@
const { parser, url } = require('./chada.ma.config.js')
const axios = require('axios')
const dayjs = require('dayjs')
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)
jest.mock('axios')
const mockHtmlContent = `
<div class="pm0 col-md-8" id="stopfix">
<h2 class="posts-date">Programmes d'Aujourd'hui</h2>
<div class="posts-area">
<h2> <i class="fas fa-circle"></i>00:00 - 09:00</h2>
<div class="relativeme">
<a href="https://chada.ma/fr/emissions/bloc-prime-clips/">
<img class="programthumb" src="https://chada.ma/wp-content/uploads/2023/11/Autres-slides-clips-la-couverture.jpg">
</a>
</div>
<h3>Bloc Prime + Clips</h3>
<div class="authorbox"></div>
<div class="ssprogramme row"></div>
</div>
</div>
`;
it('can generate valid url', () => {
expect(url()).toBe('https://chada.ma/fr/chada-tv/grille-tv/')
});
it('can parse response', () => {
const content = mockHtmlContent
const result = parser({ content }).map(p => {
p.start = dayjs(p.start).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
p.stop = dayjs(p.stop).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
return p
})
expect(result).toMatchObject([
{
title: "Bloc Prime + Clips",
description: "No description available",
start: dayjs.tz('00:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ'),
stop: dayjs.tz('09:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
}
])
})
it('can handle empty guide', () => {
const result = parser({
content: '<div class="pm0 col-md-8" id="stopfix"><div class="posts-area"></div></div>'
})
expect(result).toMatchObject([])
})
const { parser, url } = require('./chada.ma.config.js')
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)
jest.mock('axios')
const mockHtmlContent = `
<div class="pm0 col-md-8" id="stopfix">
<h2 class="posts-date">Programmes d'Aujourd'hui</h2>
<div class="posts-area">
<h2> <i class="fas fa-circle"></i>00:00 - 09:00</h2>
<div class="relativeme">
<a href="https://chada.ma/fr/emissions/bloc-prime-clips/">
<img class="programthumb" src="https://chada.ma/wp-content/uploads/2023/11/Autres-slides-clips-la-couverture.jpg">
</a>
</div>
<h3>Bloc Prime + Clips</h3>
<div class="authorbox"></div>
<div class="ssprogramme row"></div>
</div>
</div>
`
it('can generate valid url', () => {
expect(url()).toBe('https://chada.ma/fr/chada-tv/grille-tv/')
})
it('can parse response', () => {
const content = mockHtmlContent
const result = parser({ content }).map(p => {
p.start = dayjs(p.start).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
p.stop = dayjs(p.stop).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
return p
})
expect(result).toMatchObject([
{
title: 'Bloc Prime + Clips',
description: 'No description available',
start: dayjs.tz('00:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ'),
stop: dayjs.tz('09:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
}
])
})
it('can handle empty guide', () => {
const result = parser({
content: '<div class="pm0 col-md-8" id="stopfix"><div class="posts-area"></div></div>'
})
expect(result).toMatchObject([])
})

View file

@ -29,7 +29,7 @@ module.exports = {
},
async channels() {
const html = await axios
.get(`https://chaines-tv.orange.fr/programme-tv?filtres=all`)
.get('https://chaines-tv.orange.fr/programme-tv?filtres=all')
.then(r => r.data)
.catch(console.log)

View file

@ -40,7 +40,7 @@ module.exports = {
},
async channels() {
const data = await axios
.get(`https://contenthub-api.eco.astro.com.my/channel/all.json`)
.get('https://contenthub-api.eco.astro.com.my/channel/all.json')
.then(r => r.data)
.catch(console.log)
@ -85,7 +85,7 @@ function parseItems(content, date) {
const schedules = data.response.schedule
return schedules[date.format('YYYY-MM-DD')] || []
} catch (e) {
} catch {
return []
}
}

View file

@ -1,81 +1,85 @@
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(customParseFormat)
dayjs.extend(timezone)
module.exports = {
site: 'cosmotetv.gr',
days: 5,
request: {
cache: {
ttl: 60 * 60 * 1000 // 1 hour
},
method: 'GET',
headers: {
'referer': 'https://www.cosmotetv.gr/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Origin': 'https://www.cosmotetv.gr',
'Sec-Ch-Ua': '"Not.A/Brand";v="24", "Chromium";v="131", "Google Chrome";v="131"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site'
}
},
url: function ({date, channel}) {
const startOfDay = dayjs(date).startOf('day').utc().unix()
const endOfDay = dayjs(date).endOf('day').utc().unix()
return `https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false`
},
parser: function ({ date, content }) {
let programs = []
const data = JSON.parse(content)
data.channels.forEach(channel => {
channel.items.forEach(item => {
const start = dayjs(item.startTime).utc().toISOString()
const stop = dayjs(item.endTime).utc().toISOString()
programs.push({
title: item.title,
description: item.description || 'No description available',
category: item.qoe.genre,
image: item.thumbnails.standard,
start,
stop
})
})
})
return programs
},
async channels() {
const axios = require('axios')
try {
const response = await axios.get('https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/channels/all/el', {
headers: this.request.headers
})
const data = response.data
if (data && data.channels) {
return data.channels.map(item => ({
lang: 'el',
site_id: item.callSign,
name: item.title,
//logo: item.logos.square
}))
} else {
console.error('Unexpected response structure:', data)
return []
}
} catch (error) {
console.error('Error fetching channel data:', error)
return []
}
}
}
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(customParseFormat)
dayjs.extend(timezone)
module.exports = {
site: 'cosmotetv.gr',
days: 5,
request: {
cache: {
ttl: 60 * 60 * 1000 // 1 hour
},
method: 'GET',
headers: {
referer: 'https://www.cosmotetv.gr/',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
Accept: '*/*',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br, zstd',
Origin: 'https://www.cosmotetv.gr',
'Sec-Ch-Ua': '"Not.A/Brand";v="24", "Chromium";v="131", "Google Chrome";v="131"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site'
}
},
url: function ({ date, channel }) {
const startOfDay = dayjs(date).startOf('day').utc().unix()
const endOfDay = dayjs(date).endOf('day').utc().unix()
return `https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false`
},
parser: function ({ content }) {
let programs = []
const data = JSON.parse(content)
data.channels.forEach(channel => {
channel.items.forEach(item => {
const start = dayjs(item.startTime).utc().toISOString()
const stop = dayjs(item.endTime).utc().toISOString()
programs.push({
title: item.title,
description: item.description || 'No description available',
category: item.qoe.genre,
image: item.thumbnails.standard,
start,
stop
})
})
})
return programs
},
async channels() {
const axios = require('axios')
try {
const response = await axios.get(
'https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/channels/all/el',
{
headers: this.request.headers
}
)
const data = response.data
if (data && data.channels) {
return data.channels.map(item => ({
lang: 'el',
site_id: item.callSign,
name: item.title
//logo: item.logos.square
}))
} else {
console.error('Unexpected response structure:', data)
return []
}
} catch (error) {
console.error('Error fetching channel data:', error)
return []
}
}
}

View file

@ -1,81 +1,76 @@
const { parser, url, channels } = require('./cosmotetv.gr.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const timezone = require('dayjs/plugin/timezone')
const axios = require('axios')
dayjs.extend(utc)
dayjs.extend(customParseFormat)
dayjs.extend(timezone)
jest.mock('axios')
const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('d')
const channel = { site_id: 'vouli', xmltv_id: 'HellenicParliamentTV.gr' }
const mockChannelData = {
"channels": [
{
"guid": "XTV100000954",
"title": "ΒΟΥΛΗ HD",
"callSign": "vouli",
"logos": {
"square": "https://tr.static.cdn.cosmotetvott.gr/ote-prod/channel_logos/vouli1-normal.png",
"wide": "https://tr.static.cdn.cosmotetvott.gr/ote-prod/channel_logos/vouli1-wide.png"
}
}
]
}
const mockEpgData = {
"channels": [
{
"items": [
{
"startTime": "2024-12-26T23:00:00+00:00",
"endTime": "2024-12-27T00:00:00+00:00",
"title": "Τι Λέει ο Νόμος",
"description": "νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.",
"qoe": {
"genre": "Special"
},
"thumbnails": {
"standard": "https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg"
}
}
]
}
]
}
it('can generate valid url', () => {
const startOfDay = dayjs(date).startOf('day').utc().unix()
const endOfDay = dayjs(date).endOf('day').utc().unix()
expect(url({ date, channel })).toBe(`https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false`)
})
it('can parse response', () => {
const content = JSON.stringify(mockEpgData)
const result = parser({ date, content }).map(p => {
p.start = dayjs(p.start).toISOString()
p.stop = dayjs(p.stop).toISOString()
return p
})
expect(result).toMatchObject([
{
title: "Τι Λέει ο Νόμος",
description: "νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.",
category: "Special",
image: "https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg",
start: "2024-12-26T23:00:00.000Z",
stop: "2024-12-27T00:00:00.000Z"
}
])
})
it('can handle empty guide', () => {
const result = parser({ date, channel, content: '{"date":"2024-12-26","categories":[],"channels":[]}' });
expect(result).toMatchObject([])
})
const { parser, url } = require('./cosmotetv.gr.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(customParseFormat)
dayjs.extend(timezone)
jest.mock('axios')
const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('d')
const channel = { site_id: 'vouli', xmltv_id: 'HellenicParliamentTV.gr' }
const mockEpgData = {
channels: [
{
items: [
{
startTime: '2024-12-26T23:00:00+00:00',
endTime: '2024-12-27T00:00:00+00:00',
title: 'Τι Λέει ο Νόμος',
description:
'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.',
qoe: {
genre: 'Special'
},
thumbnails: {
standard:
'https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg'
}
}
]
}
]
}
it('can generate valid url', () => {
const startOfDay = dayjs(date).startOf('day').utc().unix()
const endOfDay = dayjs(date).endOf('day').utc().unix()
expect(url({ date, channel })).toBe(
`https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false`
)
})
it('can parse response', () => {
const content = JSON.stringify(mockEpgData)
const result = parser({ date, content }).map(p => {
p.start = dayjs(p.start).toISOString()
p.stop = dayjs(p.stop).toISOString()
return p
})
expect(result).toMatchObject([
{
title: 'Τι Λέει ο Νόμος',
description:
'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.',
category: 'Special',
image:
'https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg',
start: '2024-12-26T23:00:00.000Z',
stop: '2024-12-27T00:00:00.000Z'
}
])
})
it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: '{"date":"2024-12-26","categories":[],"channels":[]}'
})
expect(result).toMatchObject([])
})

View file

@ -1,102 +1,114 @@
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: 'cubmu.com',
days: 2,
url({ channel, date }) {
return `https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=${date.format('YYYY-MM-DD')}&channel_id=${channel.site_id}`
},
parser({ content, channel }) {
const programs = []
const items = parseItems(content)
items.forEach(item => {
programs.push({
title: parseTitle(item),
description: parseDescription(item, channel.lang),
episode: parseEpisode(item),
start: parseStart(item).toISOString(),
stop: parseStop(item).toISOString()
})
})
return programs
},
async channels({ lang = 'id' }) {
const axios = require('axios')
const cheerio = require('cheerio')
const result = await axios
.get('https://cubmu.com/live-tv')
.then(response => response.data)
.catch(console.error)
const $ = cheerio.load(result)
// retrieve service api data
const config = JSON.parse($('#__NEXT_DATA__').text()).runtimeConfig || {}
const options = {
headers: {
Origin: 'https://cubmu.com',
Referer: 'https://cubmu.com/live-tv'
}
}
// login to service bus
const token = await axios
.post(`https://servicebuss.transvision.co.id/tvs/login/external?email=${config.email}&password=${config.password}&deviceId=${config.deviceId}&deviceType=${config.deviceType}&deviceModel=${config.deviceModel}&deviceToken=&serial=&platformId=${config.platformId}`, options)
.then(response => response.data)
.catch(console.error)
// list channels
const subscribedChannels = await axios
.post(`https://servicebuss.transvision.co.id/tvs/subscribe_product/list?platformId=${config.platformId}`, options)
.then(response => response.data)
.catch(console.error)
const channels = []
const included = []
if (Array.isArray(subscribedChannels.channelPackageList)) {
subscribedChannels.channelPackageList.forEach(pkg => {
pkg.channelList.forEach(channel => {
if (included.indexOf(channel.id) < 0) {
included.push(channel.id)
channels.push({
lang,
site_id: channel.id,
name: channel.name
})
}
})
})
}
return channels
}
}
function parseItems(content) {
return content ? JSON.parse(content.trim()).result || [] : []
}
function parseTitle(item) {
return item.scehedule_title
}
function parseDescription(item, lang = 'id') {
return lang === 'id' ? item.schedule_json.primarySynopsis : item.schedule_json.secondarySynopsis
}
function parseEpisode(item) {
return item.schedule_json.episodeName
}
function parseStart(item) {
return dayjs.tz(item.schedule_date, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
}
function parseStop(item) {
return dayjs.tz([item.schedule_date.split(' ')[0], item.schedule_end_time].join(' '), '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: 'cubmu.com',
days: 2,
url({ channel, date }) {
return `https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=${date.format(
'YYYY-MM-DD'
)}&channel_id=${channel.site_id}`
},
parser({ content, channel }) {
const programs = []
const items = parseItems(content)
items.forEach(item => {
programs.push({
title: parseTitle(item),
description: parseDescription(item, channel.lang),
episode: parseEpisode(item),
start: parseStart(item).toISOString(),
stop: parseStop(item).toISOString()
})
})
return programs
},
async channels({ lang = 'id' }) {
const axios = require('axios')
const cheerio = require('cheerio')
const result = await axios
.get('https://cubmu.com/live-tv')
.then(response => response.data)
.catch(console.error)
const $ = cheerio.load(result)
// retrieve service api data
const config = JSON.parse($('#__NEXT_DATA__').text()).runtimeConfig || {}
const options = {
headers: {
Origin: 'https://cubmu.com',
Referer: 'https://cubmu.com/live-tv'
}
}
// login to service bus
await axios
.post(
`https://servicebuss.transvision.co.id/tvs/login/external?email=${config.email}&password=${config.password}&deviceId=${config.deviceId}&deviceType=${config.deviceType}&deviceModel=${config.deviceModel}&deviceToken=&serial=&platformId=${config.platformId}`,
options
)
.then(response => response.data)
.catch(console.error)
// list channels
const subscribedChannels = await axios
.post(
`https://servicebuss.transvision.co.id/tvs/subscribe_product/list?platformId=${config.platformId}`,
options
)
.then(response => response.data)
.catch(console.error)
const channels = []
const included = []
if (Array.isArray(subscribedChannels.channelPackageList)) {
subscribedChannels.channelPackageList.forEach(pkg => {
pkg.channelList.forEach(channel => {
if (included.indexOf(channel.id) < 0) {
included.push(channel.id)
channels.push({
lang,
site_id: channel.id,
name: channel.name
})
}
})
})
}
return channels
}
}
function parseItems(content) {
return content ? JSON.parse(content.trim()).result || [] : []
}
function parseTitle(item) {
return item.scehedule_title
}
function parseDescription(item, lang = 'id') {
return lang === 'id' ? item.schedule_json.primarySynopsis : item.schedule_json.secondarySynopsis
}
function parseEpisode(item) {
return item.schedule_json.episodeName
}
function parseStart(item) {
return dayjs.tz(item.schedule_date, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
}
function parseStop(item) {
return dayjs.tz(
[item.schedule_date.split(' ')[0], item.schedule_end_time].join(' '),
'YYYY-MM-DD HH:mm:ss',
'Asia/Jakarta'
)
}

View file

@ -1,47 +1,47 @@
const { url, parser } = require('./cubmu.com.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const date = dayjs.utc('2023-11-05', 'DD/MM/YYYY').startOf('d')
const channel = { site_id: '4028c68574537fcd0174be43042758d8', xmltv_id: 'TransTV.id', lang: 'id' }
const channelEn = Object.assign({}, channel, { lang: 'en' })
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
'https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=2023-11-05&channel_id=4028c68574537fcd0174be43042758d8'
)
})
it('can parse response', () => {
const content =
'{"result":[{"channel_id":"4028c68574537fcd0174be43042758d8","channel_name":"Trans TV","scehedule_title":"CNN Tech News","schedule_date":"2023-11-05 01:30:00","schedule_end_time":"02:00:00","schedule_json":{"availability":0,"channelId":"4028c68574537fcd0174be43042758d8","channelName":"Trans TV","duration":1800,"editable":true,"episodeName":"","imageUrl":"https://cdnjkt2.transvision.co.id:1001/catchup/schedule/thumbnail/4028c68574537fcd0174be43042758d8/4028c6858b8b3621018b9330e3701a7e/458x640","imageUrlWide":"https://cdnjkt2.transvision.co.id:1001/catchup/schedule/thumbnail/4028c68574537fcd0174be43042758d8/4028c6858b8b3621018b9330e3701a7e/320x180","name":"CNN Tech News","ottImageUrl":"","primarySynopsis":"CNN Indonesia Tech News adalah berita teknologi yang membawa pemirsa ke dunia teknologi yang penuh dengan informasi, pendidikan, hiburan sampai informasi kesehatan terkini.","scheduleId":"4028c6858b8b3621018b9330e3701a7e","scheduleTime":"18:30:00","secondarySynopsis":"CNN Indonesia Tech News is tech news brings viewers into the world of technology that provides information, education, entertainment to the latest health information.","startDt":"20231104183000","url":""},"schedule_start_time":"01:30:00"}]}'
const idResults = parser({ content, channel })
expect(idResults).toMatchObject([
{
start: '2023-11-04T18:30:00.000Z',
stop: '2023-11-04T19:00:00.000Z',
title: 'CNN Tech News',
description:
'CNN Indonesia Tech News adalah berita teknologi yang membawa pemirsa ke dunia teknologi yang penuh dengan informasi, pendidikan, hiburan sampai informasi kesehatan terkini.'
}
])
const enResults = parser({ content, channel: channelEn })
expect(enResults).toMatchObject([
{
start: '2023-11-04T18:30:00.000Z',
stop: '2023-11-04T19:00:00.000Z',
title: 'CNN Tech News',
description:
'CNN Indonesia Tech News is tech news brings viewers into the world of technology that provides information, education, entertainment to the latest health information.'
}
])
})
it('can handle empty guide', () => {
const results = parser({ content: '' })
expect(results).toMatchObject([])
})
const { url, parser } = require('./cubmu.com.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const date = dayjs.utc('2023-11-05', 'DD/MM/YYYY').startOf('d')
const channel = { site_id: '4028c68574537fcd0174be43042758d8', xmltv_id: 'TransTV.id', lang: 'id' }
const channelEn = Object.assign({}, channel, { lang: 'en' })
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
'https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=2023-11-05&channel_id=4028c68574537fcd0174be43042758d8'
)
})
it('can parse response', () => {
const content =
'{"result":[{"channel_id":"4028c68574537fcd0174be43042758d8","channel_name":"Trans TV","scehedule_title":"CNN Tech News","schedule_date":"2023-11-05 01:30:00","schedule_end_time":"02:00:00","schedule_json":{"availability":0,"channelId":"4028c68574537fcd0174be43042758d8","channelName":"Trans TV","duration":1800,"editable":true,"episodeName":"","imageUrl":"https://cdnjkt2.transvision.co.id:1001/catchup/schedule/thumbnail/4028c68574537fcd0174be43042758d8/4028c6858b8b3621018b9330e3701a7e/458x640","imageUrlWide":"https://cdnjkt2.transvision.co.id:1001/catchup/schedule/thumbnail/4028c68574537fcd0174be43042758d8/4028c6858b8b3621018b9330e3701a7e/320x180","name":"CNN Tech News","ottImageUrl":"","primarySynopsis":"CNN Indonesia Tech News adalah berita teknologi yang membawa pemirsa ke dunia teknologi yang penuh dengan informasi, pendidikan, hiburan sampai informasi kesehatan terkini.","scheduleId":"4028c6858b8b3621018b9330e3701a7e","scheduleTime":"18:30:00","secondarySynopsis":"CNN Indonesia Tech News is tech news brings viewers into the world of technology that provides information, education, entertainment to the latest health information.","startDt":"20231104183000","url":""},"schedule_start_time":"01:30:00"}]}'
const idResults = parser({ content, channel })
expect(idResults).toMatchObject([
{
start: '2023-11-04T18:30:00.000Z',
stop: '2023-11-04T19:00:00.000Z',
title: 'CNN Tech News',
description:
'CNN Indonesia Tech News adalah berita teknologi yang membawa pemirsa ke dunia teknologi yang penuh dengan informasi, pendidikan, hiburan sampai informasi kesehatan terkini.'
}
])
const enResults = parser({ content, channel: channelEn })
expect(enResults).toMatchObject([
{
start: '2023-11-04T18:30:00.000Z',
stop: '2023-11-04T19:00:00.000Z',
title: 'CNN Tech News',
description:
'CNN Indonesia Tech News is tech news brings viewers into the world of technology that provides information, education, entertainment to the latest health information.'
}
])
})
it('can handle empty guide', () => {
const results = parser({ content: '' })
expect(results).toMatchObject([])
})

View file

@ -13,9 +13,9 @@ module.exports = {
site: 'dens.tv',
days: 2,
url({ channel, date }) {
return `https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=${date.format('YYYY-MM-DD')}&id_channel=${
channel.site_id
}&app_type=10`
return `https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=${date.format(
'YYYY-MM-DD'
)}&id_channel=${channel.site_id}&app_type=10`
},
parser({ content }) {
// parsing
@ -25,8 +25,9 @@ module.exports = {
if (Array.isArray(response?.data)) {
response.data.forEach(item => {
const title = item.title
const [, , , season, , , episode] = title.match(/( (Season |Season|S)(\d+))?( (Episode|Ep) (\d+))/) ||
[null, null, null, null, null, null, null]
const [, , , season, , , episode] = title.match(
/( (Season |Season|S)(\d+))?( (Episode|Ep) (\d+))/
) || [null, null, null, null, null, null, null]
programs.push({
title,
description: item.description,
@ -52,7 +53,7 @@ module.exports = {
const channels = []
for (const id_category of Object.values(categories)) {
const data = await axios
.get(`https://www.dens.tv/api/dens3/tv/TvChannels/listByCategory`, {
.get('https://www.dens.tv/api/dens3/tv/TvChannels/listByCategory', {
params: { id_category }
})
.then(r => r.data)

View file

@ -10,7 +10,9 @@ const date = dayjs.utc('2024-11-24').startOf('d')
const channel = { site_id: '38', xmltv_id: 'AniplusAsia.sg', lang: 'id' }
it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=2024-11-24&id_channel=38&app_type=10')
expect(url({ channel, date })).toBe(
'https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=2024-11-24&id_channel=38&app_type=10'
)
})
it('can parse response', () => {

View file

@ -65,7 +65,7 @@ module.exports = {
const cheerio = require('cheerio')
const data = await axios
.get(`https://www.digiturk.com.tr/`, {
.get('https://www.digiturk.com.tr/', {
headers: {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'

View file

@ -60,7 +60,7 @@ module.exports = {
$('.pgrid').each((i, el) => {
const onclick = $(el).find('.chnl-logo').attr('onclick')
const number = $(el).find('.cnl-fav > a > span').text().trim()
const [, name, site_id] = onclick.match(/ShowChannelGuid\('([^']+)','([^']+)'/) || [
const [, , site_id] = onclick.match(/ShowChannelGuid\('([^']+)','([^']+)'/) || [
null,
'',
''

View file

@ -49,7 +49,7 @@ module.exports = {
const channels = []
for (let provider of providers) {
const data = await axios
.post(`https://www.guida.tv/guide/schedule`, null, {
.post('https://www.guida.tv/guide/schedule', null, {
params: {
provider,
region: 'Italy',
@ -81,7 +81,7 @@ module.exports = {
}
}
function parseStart($item, date, channel) {
function parseStart($item, date) {
const timeString = $item('td:eq(0)').text().trim()
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`

View file

@ -35,7 +35,7 @@ module.exports = {
const cheerio = require('cheerio')
const data = await axios
.get(`https://guidatv.sky.it/canali`)
.get('https://guidatv.sky.it/canali')
.then(r => r.data)
.catch(console.log)

View file

@ -1,63 +1,63 @@
const axios = require('axios')
const convert = require('xml-js')
const dayjs = require('dayjs')
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(timezone)
module.exports = {
site: 'hoy.tv',
days: 2,
request: {
cache: {
ttl: 60 * 60 * 1000 // 1h
}
},
url: function ({ channel, date }) {
return `https://epg-file.hoy.tv/hoy/OTT${channel.site_id}${date.format('YYYYMMDD')}.xml`
},
parser({ content, channel, date }) {
const data = convert.xml2js(content, {
compact: true,
ignoreDeclaration: true,
ignoreAttributes: true
})
const programs = []
for (let item of data.ProgramGuide.Channel.EpgItem) {
const start = dayjs.tz(item.EpgStartDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong')
if (! date.isSame(start, 'day')) {
continue
}
const epIndex = item.EpisodeInfo.EpisodeIndex._text
const subtitle = parseInt(epIndex) > 0 ? `${epIndex}` : undefined
programs.push({
title: `${item.ComScore.ns_st_pr._text}${item.EpgOtherInfo?._text || ''}`,
sub_title: subtitle,
description: item.EpisodeInfo.EpisodeLongDescription._text,
start,
stop: dayjs.tz(item.EpgEndDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong'),
})
}
return programs
},
async channels({ lang }) {
const data = await axios
.get('https://api2.hoy.tv/api/v2/a/channel')
.then(r => r.data)
.catch(console.error)
return data.data.map(c => {
return {
site_id: c.videos.id,
name: c.name.zh_hk,
lang: 'zh',
}
})
}
}
const axios = require('axios')
const convert = require('xml-js')
const dayjs = require('dayjs')
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(timezone)
module.exports = {
site: 'hoy.tv',
days: 2,
request: {
cache: {
ttl: 60 * 60 * 1000 // 1h
}
},
url: function ({ channel, date }) {
return `https://epg-file.hoy.tv/hoy/OTT${channel.site_id}${date.format('YYYYMMDD')}.xml`
},
parser({ content, date }) {
const data = convert.xml2js(content, {
compact: true,
ignoreDeclaration: true,
ignoreAttributes: true
})
const programs = []
for (let item of data.ProgramGuide.Channel.EpgItem) {
const start = dayjs.tz(item.EpgStartDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong')
if (!date.isSame(start, 'day')) {
continue
}
const epIndex = item.EpisodeInfo.EpisodeIndex._text
const subtitle = parseInt(epIndex) > 0 ? `${epIndex}` : undefined
programs.push({
title: `${item.ComScore.ns_st_pr._text}${item.EpgOtherInfo?._text || ''}`,
sub_title: subtitle,
description: item.EpisodeInfo.EpisodeLongDescription._text,
start,
stop: dayjs.tz(item.EpgEndDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong')
})
}
return programs
},
async channels() {
const data = await axios
.get('https://api2.hoy.tv/api/v2/a/channel')
.then(r => r.data)
.catch(console.error)
return data.data.map(c => {
return {
site_id: c.videos.id,
name: c.name.zh_hk,
lang: 'zh'
}
})
}
}

View file

@ -1,116 +1,115 @@
const { parser, url } = require('./hoy.tv.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
jest.mock('axios')
const date = dayjs.utc('2024-09-13', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: '76',
xmltv_id: 'HOYIBC.hk',
lang: 'zh'
}
const content = `<?xml version="1.0" encoding="UTF-8" ?>
<ProgramGuide>
<Channel id="76">
<EpgItem>
<EpgStartDateTime>2024-09-13 11:30:00</EpgStartDateTime>
<EpgEndDateTime>2024-09-13 12:30:00</EpgEndDateTime>
<EpgOtherInfo>[PG]</EpgOtherInfo>
<DisableLive>false</DisableLive>
<DisableVod>false</DisableVod>
<VODLicPeriod>2024-09-27 11:30:00</VODLicPeriod>
<ProgramInfo>
<ProgramId>0</ProgramId>
<ProgramTitle></ProgramTitle>
<ProgramPos>0</ProgramPos>
<FirstRunDateTime></FirstRunDateTime>
<ProgramThumbnailUrl>http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg</ProgramThumbnailUrl>
</ProgramInfo>
<EpisodeInfo>
<EpisodeId>EQ00135</EpisodeId>
<EpisodeIndex>46</EpisodeIndex>
<EpisodeShortDescription>點講都係一家人</EpisodeShortDescription>
<EpisodeLongDescription></EpisodeLongDescription>
<EpisodeThumbnailUrl>http://tv.fantv.hk/images/nosuchthumbnail.jpg</EpisodeThumbnailUrl>
</EpisodeInfo>
<ComScore>
<ns_st_stc></ns_st_stc>
<ns_st_pr>點講都係一家人</ns_st_pr>
<ns_st_tpr>0</ns_st_tpr>
<ns_st_tep>EQ00135</ns_st_tep>
<ns_st_ep>點講都係一家人 Episode 46</ns_st_ep>
<ns_st_li>1</ns_st_li>
<ns_st_tdt>20240913</ns_st_tdt>
<ns_st_tm>1130</ns_st_tm>
<ns_st_ty>0001</ns_st_ty>
<ns_st_cl>3704000</ns_st_cl>
</ComScore>
</EpgItem>
<EpgItem>
<EpgStartDateTime>2024-09-13 12:30:00</EpgStartDateTime>
<EpgEndDateTime>2024-09-13 13:30:00</EpgEndDateTime>
<EpgOtherInfo></EpgOtherInfo>
<DisableLive>false</DisableLive>
<DisableVod>false</DisableVod>
<VODLicPeriod>2024-09-27 12:30:00</VODLicPeriod>
<ProgramInfo>
<ProgramId>0</ProgramId>
<ProgramTitle></ProgramTitle>
<ProgramPos>0</ProgramPos>
<FirstRunDateTime></FirstRunDateTime>
<ProgramThumbnailUrl>http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg</ProgramThumbnailUrl>
</ProgramInfo>
<EpisodeInfo>
<EpisodeId>ED00311</EpisodeId>
<EpisodeIndex>0</EpisodeIndex>
<EpisodeShortDescription>麝香之路</EpisodeShortDescription>
<EpisodeLongDescription>Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world</EpisodeLongDescription>
<EpisodeThumbnailUrl>http://tv.fantv.hk/images/nosuchthumbnail.jpg</EpisodeThumbnailUrl>
</EpisodeInfo>
<ComScore>
<ns_st_stc></ns_st_stc>
<ns_st_pr>麝香之路</ns_st_pr>
<ns_st_tpr>0</ns_st_tpr>
<ns_st_tep>ED00311</ns_st_tep>
<ns_st_ep>麝香之路 2024-09-13</ns_st_ep>
<ns_st_li>1</ns_st_li>
<ns_st_tdt>20240913</ns_st_tdt>
<ns_st_tm>1230</ns_st_tm>
<ns_st_ty>0001</ns_st_ty>
<ns_st_cl>3704000</ns_st_cl>
</ComScore>
</EpgItem>
</Channel>
</ProgramGuide>`
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
'https://epg-file.hoy.tv/hoy/OTT7620240913.xml'
)
})
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: '2024-09-13T03:30:00.000Z',
stop: '2024-09-13T04:30:00.000Z',
title: '點講都係一家人[PG]',
sub_title: '第46集',
},
{
start: '2024-09-13T04:30:00.000Z',
stop: '2024-09-13T05:30:00.000Z',
title: '麝香之路',
description: 'Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world',
}
])
})
const { parser, url } = require('./hoy.tv.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
jest.mock('axios')
const date = dayjs.utc('2024-09-13', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: '76',
xmltv_id: 'HOYIBC.hk',
lang: 'zh'
}
const content = `<?xml version="1.0" encoding="UTF-8" ?>
<ProgramGuide>
<Channel id="76">
<EpgItem>
<EpgStartDateTime>2024-09-13 11:30:00</EpgStartDateTime>
<EpgEndDateTime>2024-09-13 12:30:00</EpgEndDateTime>
<EpgOtherInfo>[PG]</EpgOtherInfo>
<DisableLive>false</DisableLive>
<DisableVod>false</DisableVod>
<VODLicPeriod>2024-09-27 11:30:00</VODLicPeriod>
<ProgramInfo>
<ProgramId>0</ProgramId>
<ProgramTitle></ProgramTitle>
<ProgramPos>0</ProgramPos>
<FirstRunDateTime></FirstRunDateTime>
<ProgramThumbnailUrl>http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg</ProgramThumbnailUrl>
</ProgramInfo>
<EpisodeInfo>
<EpisodeId>EQ00135</EpisodeId>
<EpisodeIndex>46</EpisodeIndex>
<EpisodeShortDescription>點講都係一家人</EpisodeShortDescription>
<EpisodeLongDescription></EpisodeLongDescription>
<EpisodeThumbnailUrl>http://tv.fantv.hk/images/nosuchthumbnail.jpg</EpisodeThumbnailUrl>
</EpisodeInfo>
<ComScore>
<ns_st_stc></ns_st_stc>
<ns_st_pr>點講都係一家人</ns_st_pr>
<ns_st_tpr>0</ns_st_tpr>
<ns_st_tep>EQ00135</ns_st_tep>
<ns_st_ep>點講都係一家人 Episode 46</ns_st_ep>
<ns_st_li>1</ns_st_li>
<ns_st_tdt>20240913</ns_st_tdt>
<ns_st_tm>1130</ns_st_tm>
<ns_st_ty>0001</ns_st_ty>
<ns_st_cl>3704000</ns_st_cl>
</ComScore>
</EpgItem>
<EpgItem>
<EpgStartDateTime>2024-09-13 12:30:00</EpgStartDateTime>
<EpgEndDateTime>2024-09-13 13:30:00</EpgEndDateTime>
<EpgOtherInfo></EpgOtherInfo>
<DisableLive>false</DisableLive>
<DisableVod>false</DisableVod>
<VODLicPeriod>2024-09-27 12:30:00</VODLicPeriod>
<ProgramInfo>
<ProgramId>0</ProgramId>
<ProgramTitle></ProgramTitle>
<ProgramPos>0</ProgramPos>
<FirstRunDateTime></FirstRunDateTime>
<ProgramThumbnailUrl>http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg</ProgramThumbnailUrl>
</ProgramInfo>
<EpisodeInfo>
<EpisodeId>ED00311</EpisodeId>
<EpisodeIndex>0</EpisodeIndex>
<EpisodeShortDescription>麝香之路</EpisodeShortDescription>
<EpisodeLongDescription>Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world</EpisodeLongDescription>
<EpisodeThumbnailUrl>http://tv.fantv.hk/images/nosuchthumbnail.jpg</EpisodeThumbnailUrl>
</EpisodeInfo>
<ComScore>
<ns_st_stc></ns_st_stc>
<ns_st_pr>麝香之路</ns_st_pr>
<ns_st_tpr>0</ns_st_tpr>
<ns_st_tep>ED00311</ns_st_tep>
<ns_st_ep>麝香之路 2024-09-13</ns_st_ep>
<ns_st_li>1</ns_st_li>
<ns_st_tdt>20240913</ns_st_tdt>
<ns_st_tm>1230</ns_st_tm>
<ns_st_ty>0001</ns_st_ty>
<ns_st_cl>3704000</ns_st_cl>
</ComScore>
</EpgItem>
</Channel>
</ProgramGuide>`
it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://epg-file.hoy.tv/hoy/OTT7620240913.xml')
})
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: '2024-09-13T03:30:00.000Z',
stop: '2024-09-13T04:30:00.000Z',
title: '點講都係一家人[PG]',
sub_title: '第46集'
},
{
start: '2024-09-13T04:30:00.000Z',
stop: '2024-09-13T05:30:00.000Z',
title: '麝香之路',
description:
'Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world'
}
])
})

View file

@ -172,14 +172,17 @@ function parseItems(content, channel, date) {
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))
.filter(
p =>
p.channel === site_id && dayjs(p.start, 'YYYYMMDDHHmmss ZZ').isBetween(curr_day, next_day)
)
.map(p => {
if (Array.isArray(p.date) && p.date.length) {
p.date = p.date[0]
}
return p
})
} catch (error) {
} catch {
return []
}
}

View file

@ -1,73 +1,80 @@
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: 'ipko.tv',
timezone: 'Europe/Belgrade',
days: 5,
url({ date, channel }) { return 'https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData' },
request: {
method: 'POST',
headers: {
'Host': 'stargate.ipko.tv',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
'Content-Type': 'application/json',
'X-AppLayout': '1',
'x-language': 'sq',
'Origin': 'https://ipko.tv',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
'Sec-GPC': '1',
'Connection': 'keep-alive'
},
data({ channel, date }) {
const todayEpoch = date.startOf('day').unix();
const nextDayEpoch = date.add(1, 'day').startOf('day').unix();
return JSON.stringify({
ch_ext_id: channel.site_id,
from: todayEpoch,
to: nextDayEpoch
})
}
},
parser: function ({ content }) {
const programs = [];
const data = JSON.parse(content);
data.shows.forEach(show => {
const start = dayjs.unix(show.show_start).utc();
const stop = dayjs.unix(show.show_end).utc();
const programData = {
title: show.title,
description: show.summary || 'No description available',
start: start.toISOString(),
stop: stop.toISOString(),
thumbnail: show.thumbnail
}
programs.push(programData)
})
return programs
},
async channels() {
const response = await axios.post('https://stargate.ipko.tv/api/titan.tv.WebEpg/ZapList', JSON.stringify({ includeRadioStations: true }), {
headers: this.request.headers
});
const data = response.data.data;
return data.map(item => ({
lang: 'sq',
name: String(item.channel.title),
site_id: String(item.channel.id),
//logo: String(item.channel.logo)
}))
}
}
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: 'ipko.tv',
timezone: 'Europe/Belgrade',
days: 5,
url() {
return 'https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData'
},
request: {
method: 'POST',
headers: {
Host: 'stargate.ipko.tv',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
'Content-Type': 'application/json',
'X-AppLayout': '1',
'x-language': 'sq',
Origin: 'https://ipko.tv',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
'Sec-GPC': '1',
Connection: 'keep-alive'
},
data({ channel, date }) {
const todayEpoch = date.startOf('day').unix()
const nextDayEpoch = date.add(1, 'day').startOf('day').unix()
return JSON.stringify({
ch_ext_id: channel.site_id,
from: todayEpoch,
to: nextDayEpoch
})
}
},
parser: function ({ content }) {
const programs = []
const data = JSON.parse(content)
data.shows.forEach(show => {
const start = dayjs.unix(show.show_start).utc()
const stop = dayjs.unix(show.show_end).utc()
const programData = {
title: show.title,
description: show.summary || 'No description available',
start: start.toISOString(),
stop: stop.toISOString(),
thumbnail: show.thumbnail
}
programs.push(programData)
})
return programs
},
async channels() {
const response = await axios.post(
'https://stargate.ipko.tv/api/titan.tv.WebEpg/ZapList',
JSON.stringify({ includeRadioStations: true }),
{
headers: this.request.headers
}
)
const data = response.data.data
return data.map(item => ({
lang: 'sq',
name: String(item.channel.title),
site_id: String(item.channel.id)
//logo: String(item.channel.logo)
}))
}
}

View file

@ -1,115 +1,111 @@
const { parser, url } = require('./ipko.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('2024-12-24', 'YYYY-MM-DD').startOf('day')
const channel = {
site_id: 'ipko-promo',
xmltv_id: 'IPKOPROMO'
}
it('can generate valid url', () => {
expect(url({ date, channel })).toBe('https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData')
})
it('can parse response', () => {
const content = `
{
"shows": [
{
"title": "IPKO Promo",
"show_start": 1735012800,
"show_end": 1735020000,
"timestamp": "5:00 - 7:00",
"show_id": "EPG_TvProfil_IPKOPROMO_296105567",
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
"is_adult": false,
"friendly_id": "ipko_promo_4cf3",
"pg": "",
"genres": [],
"year": 0,
"summary": "",
"categories": "Other",
"stb_only": false,
"is_live": false,
"original_title": "IPKO Promo"
},
{
"title": "IPKO Promo",
"show_start": 1735020000,
"show_end": 1735027200,
"timestamp": "7:00 - 9:00",
"show_id": "EPG_TvProfil_IPKOPROMO_296105568",
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
"is_adult": false,
"friendly_id": "ipko_promo_416b",
"pg": "",
"genres": [],
"year": 0,
"summary": "",
"categories": "Other",
"stb_only": false,
"is_live": false,
"original_title": "IPKO Promo"
},
{
"title": "IPKO Promo",
"show_start": 1735027200,
"show_end": 1735034400,
"timestamp": "9:00 - 11:00",
"show_id": "EPG_TvProfil_IPKOPROMO_296105569",
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
"is_adult": false,
"friendly_id": "ipko_promo_2e23",
"pg": "",
"genres": [],
"year": 0,
"summary": "",
"categories": "Other",
"stb_only": false,
"is_live": false,
"original_title": "IPKO Promo"
}
]
}`
const result = parser({ content, channel }).map(p => {
p.start = p.start
p.stop = p.stop
return p
})
expect(result).toMatchObject([
{
title: "IPKO Promo",
description: "No description available",
start: "2024-12-24T04:00:00.000Z",
stop: "2024-12-24T06:00:00.000Z",
thumbnail: "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg"
},
{
title: "IPKO Promo",
description: "No description available",
start: "2024-12-24T06:00:00.000Z",
stop: "2024-12-24T08:00:00.000Z",
thumbnail: "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg"
},
{
title: "IPKO Promo",
description: "No description available",
start: "2024-12-24T08:00:00.000Z",
stop: "2024-12-24T10:00:00.000Z",
thumbnail: "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg"
}
])
})
it('can handle empty guide', () => {
const result = parser({
content: '{"shows":[]}'
})
expect(result).toMatchObject([])
})
const { parser, url } = require('./ipko.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('2024-12-24', 'YYYY-MM-DD').startOf('day')
const channel = {
site_id: 'ipko-promo',
xmltv_id: 'IPKOPROMO'
}
it('can generate valid url', () => {
expect(url({ date, channel })).toBe('https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData')
})
it('can parse response', () => {
const content = `
{
"shows": [
{
"title": "IPKO Promo",
"show_start": 1735012800,
"show_end": 1735020000,
"timestamp": "5:00 - 7:00",
"show_id": "EPG_TvProfil_IPKOPROMO_296105567",
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
"is_adult": false,
"friendly_id": "ipko_promo_4cf3",
"pg": "",
"genres": [],
"year": 0,
"summary": "",
"categories": "Other",
"stb_only": false,
"is_live": false,
"original_title": "IPKO Promo"
},
{
"title": "IPKO Promo",
"show_start": 1735020000,
"show_end": 1735027200,
"timestamp": "7:00 - 9:00",
"show_id": "EPG_TvProfil_IPKOPROMO_296105568",
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
"is_adult": false,
"friendly_id": "ipko_promo_416b",
"pg": "",
"genres": [],
"year": 0,
"summary": "",
"categories": "Other",
"stb_only": false,
"is_live": false,
"original_title": "IPKO Promo"
},
{
"title": "IPKO Promo",
"show_start": 1735027200,
"show_end": 1735034400,
"timestamp": "9:00 - 11:00",
"show_id": "EPG_TvProfil_IPKOPROMO_296105569",
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
"is_adult": false,
"friendly_id": "ipko_promo_2e23",
"pg": "",
"genres": [],
"year": 0,
"summary": "",
"categories": "Other",
"stb_only": false,
"is_live": false,
"original_title": "IPKO Promo"
}
]
}`
const result = parser({ content, channel })
expect(result).toMatchObject([
{
title: 'IPKO Promo',
description: 'No description available',
start: '2024-12-24T04:00:00.000Z',
stop: '2024-12-24T06:00:00.000Z',
thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
},
{
title: 'IPKO Promo',
description: 'No description available',
start: '2024-12-24T06:00:00.000Z',
stop: '2024-12-24T08:00:00.000Z',
thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
},
{
title: 'IPKO Promo',
description: 'No description available',
start: '2024-12-24T08:00:00.000Z',
stop: '2024-12-24T10:00:00.000Z',
thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
}
])
})
it('can handle empty guide', () => {
const result = parser({
content: '{"shows":[]}'
})
expect(result).toMatchObject([])
})

View file

@ -38,7 +38,7 @@ module.exports = {
async channels() {
const axios = require('axios')
const data = await axios
.get(`https://m.tv.sms.cz/?zmen_stanice=true`)
.get('https://m.tv.sms.cz/?zmen_stanice=true')
.then(r => r.data)
.catch(console.log)

View file

@ -77,8 +77,8 @@ function parseItems(content) {
let data
try {
data = JSON.parse(content)
} catch (error) {
console.log(error.message)
} catch {
return []
}
if (!data || !Array.isArray(data)) return []

View file

@ -32,7 +32,7 @@ module.exports = {
async channels() {
const axios = require('axios')
const data = await axios
.get(`https://player.maxtvtogo.tportal.hr:8082/OTT4Proxy/proxy/epg/channels`)
.get('https://player.maxtvtogo.tportal.hr:8082/OTT4Proxy/proxy/epg/channels')
.then(r => r.data)
.catch(console.log)

View file

@ -56,7 +56,7 @@ function parseStop($item) {
try {
return dayjs(timeString, 'YYYY-MM-DD HH:mm:ssZZ')
} catch (err) {
} catch {
return null
}
}

View file

@ -1,93 +1,97 @@
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(customParseFormat)
dayjs.extend(timezone)
module.exports = {
site: 'mediasetinfinity.mediaset.it',
days: 2,
url: function ({date, channel}) {
// Get the epoch timestamp
const todayEpoch = date.startOf('day').utc().valueOf()
// Get the epoch timestamp for the next day
const nextDayEpoch = date.add(1, 'day').startOf('day').utc().valueOf()
return `https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=${todayEpoch}~${nextDayEpoch}&byCallSign=${channel.site_id}`
},
parser: function ({content}) {
const programs = []
const data = JSON.parse(content)
if (!data.response || !data.response.entries || !data.response.entries[0] || !data.response.entries[0].listings) {
// If the structure is not as expected, return an empty array
return programs
}
const listings = data.response.entries[0].listings
listings.forEach((listing) => {
const title = listing.mediasetlisting$epgTitle
const subTitle = listing.program.title
const season = parseSeason(listing)
const episode = parseEpisode(listing)
if (listing.program.title && listing.startTime && listing.endTime) {
programs.push({
title: title || subTitle,
sub_title: title && title != subTitle ? subTitle : null,
description: listing.program.description || null,
category: listing.program.mediasetprogram$skyGenre || null,
season: episode && !season ? '0' : season,
episode: episode,
start: parseTime(listing.startTime),
stop: parseTime(listing.endTime),
image: getMaxResolutionThumbnails(listing)
})
}
})
return programs
}
}
function parseTime(timestamp) {
return dayjs(timestamp).utc().format('YYYY-MM-DD HH:mm')
}
function parseSeason(item) {
if (!item.mediasetlisting$shortDescription) return null
const season = item.mediasetlisting$shortDescription.match(/S(\d+)\s/)
return season ? season[1] : null
}
function parseEpisode(item) {
if (!item.mediasetlisting$shortDescription) return null
const episode = item.mediasetlisting$shortDescription.match(/Ep(\d+)\s/)
return episode ? episode[1] : null
}
function getMaxResolutionThumbnails(item) {
const thumbnails = item.program.thumbnails || null
const maxResolutionThumbnails = {}
for (const key in thumbnails) {
const type = key.split('-')[0] // Estrarre il tipo di thumbnail
const {width, height, url, title} = thumbnails[key]
if (!maxResolutionThumbnails[type] ||
(width * height > maxResolutionThumbnails[type].width * maxResolutionThumbnails[type].height)) {
maxResolutionThumbnails[type] = {width, height, url, title}
}
}
if (maxResolutionThumbnails.image_keyframe_poster)
return maxResolutionThumbnails.image_keyframe_poster.url
else if (maxResolutionThumbnails.image_header_poster)
return maxResolutionThumbnails.image_header_poster.url
else
return null
}
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(customParseFormat)
dayjs.extend(timezone)
module.exports = {
site: 'mediasetinfinity.mediaset.it',
days: 2,
url: function ({ date, channel }) {
// Get the epoch timestamp
const todayEpoch = date.startOf('day').utc().valueOf()
// Get the epoch timestamp for the next day
const nextDayEpoch = date.add(1, 'day').startOf('day').utc().valueOf()
return `https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=${todayEpoch}~${nextDayEpoch}&byCallSign=${channel.site_id}`
},
parser: function ({ content }) {
const programs = []
const data = JSON.parse(content)
if (
!data.response ||
!data.response.entries ||
!data.response.entries[0] ||
!data.response.entries[0].listings
) {
// If the structure is not as expected, return an empty array
return programs
}
const listings = data.response.entries[0].listings
listings.forEach(listing => {
const title = listing.mediasetlisting$epgTitle
const subTitle = listing.program.title
const season = parseSeason(listing)
const episode = parseEpisode(listing)
if (listing.program.title && listing.startTime && listing.endTime) {
programs.push({
title: title || subTitle,
sub_title: title && title != subTitle ? subTitle : null,
description: listing.program.description || null,
category: listing.program.mediasetprogram$skyGenre || null,
season: episode && !season ? '0' : season,
episode: episode,
start: parseTime(listing.startTime),
stop: parseTime(listing.endTime),
image: getMaxResolutionThumbnails(listing)
})
}
})
return programs
}
}
function parseTime(timestamp) {
return dayjs(timestamp).utc().format('YYYY-MM-DD HH:mm')
}
function parseSeason(item) {
if (!item.mediasetlisting$shortDescription) return null
const season = item.mediasetlisting$shortDescription.match(/S(\d+)\s/)
return season ? season[1] : null
}
function parseEpisode(item) {
if (!item.mediasetlisting$shortDescription) return null
const episode = item.mediasetlisting$shortDescription.match(/Ep(\d+)\s/)
return episode ? episode[1] : null
}
function getMaxResolutionThumbnails(item) {
const thumbnails = item.program.thumbnails || null
const maxResolutionThumbnails = {}
for (const key in thumbnails) {
const type = key.split('-')[0] // Estrarre il tipo di thumbnail
const { width, height, url, title } = thumbnails[key]
if (
!maxResolutionThumbnails[type] ||
width * height > maxResolutionThumbnails[type].width * maxResolutionThumbnails[type].height
) {
maxResolutionThumbnails[type] = { width, height, url, title }
}
}
if (maxResolutionThumbnails.image_keyframe_poster)
return maxResolutionThumbnails.image_keyframe_poster.url
else if (maxResolutionThumbnails.image_header_poster)
return maxResolutionThumbnails.image_header_poster.url
else return null
}

View file

@ -1,46 +1,53 @@
const {parser, url} = require('./mediasetinfinity.mediaset.it.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('2024-01-20', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: 'LB', xmltv_id: '20.it'
}
it('can generate valid url', () => {
expect(url({
channel,
date
})).toBe('https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=1705708800000~1705795200000&byCallSign=LB')
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')
const results = parser({content, date}).map(p => {
return p
})
expect(results[3]).toMatchObject({
start: '2024-01-20 02:14',
stop: '2024-01-20 02:54',
title: 'Chicago Fire',
sub_title: 'Ep. 22 - Io non ti lascio',
description: 'Severide e Kidd continuano a indagare su un vecchio caso doloso di Benny. Notizie inaspettate portano Brett a meditare su una grande decisione.',
category: 'Intrattenimento',
season: '7',
episode: '22',
image: 'https://static2.mediasetplay.mediaset.it/Mediaset_Italia_Production_-_Main/F309370301002204/media/0/0/1ef76b73-3173-43bd-9c16-73986a0ec131/46896726-11e7-4438-b947-d2ae53f58c0b.jpg'
})
})
it('can handle empty guide', () => {
const result = parser({
content: '[]'
})
expect(result).toMatchObject([])
})
const { parser, url } = require('./mediasetinfinity.mediaset.it.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('2024-01-20', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: 'LB',
xmltv_id: '20.it'
}
it('can generate valid url', () => {
expect(
url({
channel,
date
})
).toBe(
'https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=1705708800000~1705795200000&byCallSign=LB'
)
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')
const results = parser({ content, date }).map(p => {
return p
})
expect(results[3]).toMatchObject({
start: '2024-01-20 02:14',
stop: '2024-01-20 02:54',
title: 'Chicago Fire',
sub_title: 'Ep. 22 - Io non ti lascio',
description:
'Severide e Kidd continuano a indagare su un vecchio caso doloso di Benny. Notizie inaspettate portano Brett a meditare su una grande decisione.',
category: 'Intrattenimento',
season: '7',
episode: '22',
image:
'https://static2.mediasetplay.mediaset.it/Mediaset_Italia_Production_-_Main/F309370301002204/media/0/0/1ef76b73-3173-43bd-9c16-73986a0ec131/46896726-11e7-4438-b947-d2ae53f58c0b.jpg'
})
})
it('can handle empty guide', () => {
const result = parser({
content: '[]'
})
expect(result).toMatchObject([])
})

View file

@ -40,7 +40,7 @@ module.exports = {
async channels() {
const axios = require('axios')
const data = await axios
.post(`https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getGridAnon`, null, {
.post('https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getGridAnon', null, {
headers: {
Origin: 'https://www.meo.pt'
}

View file

@ -1,101 +1,105 @@
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: 'meuguia.tv',
days: 2,
url({ channel }) {
return `https://meuguia.tv/programacao/canal/${channel.site_id}`
},
parser({ content, date }) {
const programs = []
parseItems(content, date).forEach(item => {
if (dayjs.utc(item.start).isSame(date, 'day')) {
programs.push(item)
}
})
return programs
},
async channels() {
const channels = []
const axios = require('axios')
const baseUrl = 'https://meuguia.tv'
let seq = 0
const queues = [baseUrl]
while (true) {
if (!queues.length) {
break
}
const url = queues.shift()
const content = await axios
.get(url)
.then(response => response.data)
.catch(console.error)
if (content) {
const [ $, items ] = getItems(content)
if (seq === 0) {
queues.push(...items.map(category => baseUrl + $(category).attr('href')))
} else {
items.forEach(item => {
const href = $(item).attr('href')
channels.push({
lang: 'pt',
site_id: href.substr(href.lastIndexOf('/') + 1),
name: $(item).find('.licontent h2').text().trim()
})
})
}
}
seq++
}
return channels
}
}
function getItems(content) {
const $ = cheerio.load(content)
return [$, $('div.mw ul li a').toArray()]
}
function parseItems(content, date) {
const result = []
const $ = cheerio.load(content)
let lastDate
for (const item of $('ul.mw li').toArray()) {
const $item = $(item)
if ($item.hasClass('subheader')) {
lastDate = `${$item.text().split(', ')[1]}/${date.format('YYYY')}`
} else if ($item.hasClass('divider')) {
// ignore
} else if (lastDate) {
const data = { title: $item.find('a').attr('title').trim() }
const ep = data.title.match(/T(\d+) EP(\d+)/)
if (ep) {
data.season = parseInt(ep[1])
data.episode = parseInt(ep[2])
}
data.start = dayjs.tz(`${lastDate} ${$item.find('.time').text()}`, 'DD/MM/YYYY HH:mm', 'America/Sao_Paulo')
result.push(data)
}
}
// use stop time from next item
if (result.length > 1) {
for (let i = 0; i < result.length - 1; i++) {
result[i].stop = result[i + 1].start
}
}
return result
}
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: 'meuguia.tv',
days: 2,
url({ channel }) {
return `https://meuguia.tv/programacao/canal/${channel.site_id}`
},
parser({ content, date }) {
const programs = []
parseItems(content, date).forEach(item => {
if (dayjs.utc(item.start).isSame(date, 'day')) {
programs.push(item)
}
})
return programs
},
async channels() {
const channels = []
const axios = require('axios')
const baseUrl = 'https://meuguia.tv'
let seq = 0
const queues = [baseUrl]
while (true) {
if (!queues.length) {
break
}
const url = queues.shift()
const content = await axios
.get(url)
.then(response => response.data)
.catch(console.error)
if (content) {
const [$, items] = getItems(content)
if (seq === 0) {
queues.push(...items.map(category => baseUrl + $(category).attr('href')))
} else {
items.forEach(item => {
const href = $(item).attr('href')
channels.push({
lang: 'pt',
site_id: href.substr(href.lastIndexOf('/') + 1),
name: $(item).find('.licontent h2').text().trim()
})
})
}
}
seq++
}
return channels
}
}
function getItems(content) {
const $ = cheerio.load(content)
return [$, $('div.mw ul li a').toArray()]
}
function parseItems(content, date) {
const result = []
const $ = cheerio.load(content)
let lastDate
for (const item of $('ul.mw li').toArray()) {
const $item = $(item)
if ($item.hasClass('subheader')) {
lastDate = `${$item.text().split(', ')[1]}/${date.format('YYYY')}`
} else if ($item.hasClass('divider')) {
// ignore
} else if (lastDate) {
const data = { title: $item.find('a').attr('title').trim() }
const ep = data.title.match(/T(\d+) EP(\d+)/)
if (ep) {
data.season = parseInt(ep[1])
data.episode = parseInt(ep[2])
}
data.start = dayjs.tz(
`${lastDate} ${$item.find('.time').text()}`,
'DD/MM/YYYY HH:mm',
'America/Sao_Paulo'
)
result.push(data)
}
}
// use stop time from next item
if (result.length > 1) {
for (let i = 0; i < result.length - 1; i++) {
result[i].stop = result[i + 1].start
}
}
return result
}

View file

@ -1,60 +1,60 @@
const { parser, url } = require('./meuguia.tv.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const date = dayjs.utc('2023-11-21').startOf('d')
const channel = {
site_id: 'AXN',
xmltv_id: 'AXN.id'
}
it('can generate valid url', () => {
expect(url({ channel })).toBe('https://meuguia.tv/programacao/canal/AXN')
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
const result = parser({ content, channel, date }).map(p => {
p.start = p.start.toJSON()
if (p.stop) {
p.stop = p.stop.toJSON()
}
return p
})
expect(result).toMatchObject([
{
title: 'Hawaii Five-0 : T10 EP4 - Tiny Is the Flower, Yet It Scents the Grasses Around It',
start: '2023-11-21T21:20:00.000Z',
stop: '2023-11-21T22:15:00.000Z',
season: 10,
episode: 4
},
{
title:
"Hawaii Five-0 : T10 EP5 - Don't Blame Ghosts and Spirits for One's Troubles; A Human Is Responsible",
start: '2023-11-21T22:15:00.000Z',
stop: '2023-11-21T23:10:00.000Z',
season: 10,
episode: 5
},
{
title: 'NCIS : T5 EP15 - In the Zone',
start: '2023-11-21T23:10:00.000Z',
season: 5,
episode: 15
}
])
})
it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: '<!DOCTYPE html><html><head></head><body></body></html>'
})
expect(result).toMatchObject([])
})
const { parser, url } = require('./meuguia.tv.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const date = dayjs.utc('2023-11-21').startOf('d')
const channel = {
site_id: 'AXN',
xmltv_id: 'AXN.id'
}
it('can generate valid url', () => {
expect(url({ channel })).toBe('https://meuguia.tv/programacao/canal/AXN')
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
const result = parser({ content, channel, date }).map(p => {
p.start = p.start.toJSON()
if (p.stop) {
p.stop = p.stop.toJSON()
}
return p
})
expect(result).toMatchObject([
{
title: 'Hawaii Five-0 : T10 EP4 - Tiny Is the Flower, Yet It Scents the Grasses Around It',
start: '2023-11-21T21:20:00.000Z',
stop: '2023-11-21T22:15:00.000Z',
season: 10,
episode: 4
},
{
title:
"Hawaii Five-0 : T10 EP5 - Don't Blame Ghosts and Spirits for One's Troubles; A Human Is Responsible",
start: '2023-11-21T22:15:00.000Z',
stop: '2023-11-21T23:10:00.000Z',
season: 10,
episode: 5
},
{
title: 'NCIS : T5 EP15 - In the Zone',
start: '2023-11-21T23:10:00.000Z',
season: 5,
episode: 15
}
])
})
it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: '<!DOCTYPE html><html><head></head><body></body></html>'
})
expect(result).toMatchObject([])
})

View file

@ -42,7 +42,7 @@ module.exports = {
const axios = require('axios')
const cheerio = require('cheerio')
const data = await axios
.get(`https://www.mewatch.sg/channel-guide`)
.get('https://www.mewatch.sg/channel-guide')
.then(r => r.data)
.catch(console.log)

View file

@ -11,9 +11,7 @@ dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
doFetch
.setCheckResult(false)
.setDebugger(debug)
doFetch.setCheckResult(false).setDebugger(debug)
const languages = { en: 'english', id: 'indonesia' }
const cookies = {}
@ -125,7 +123,7 @@ async function parseItems(content, date, cookies) {
const url = $item.find('a').attr('href')
const headers = {
'X-Requested-With': 'XMLHttpRequest',
Cookie: cookies,
Cookie: cookies
}
queues.push({ i: $item, url, params: { headers, timeout } })
}

View file

@ -48,8 +48,10 @@ function parseItems(context) {
schDayPrograms.forEach((program, i) => {
const itemDay = {
progStart: parseStart($(schDayMonth), $(program)),
progStop: parseStop($(schDayMonth), schDayPrograms[i + 1] ?
$(schDayPrograms[i + 1]) : null),
progStop: parseStop(
$(schDayMonth),
schDayPrograms[i + 1] ? $(schDayPrograms[i + 1]) : null
),
progTitle: parseTitle($(program)),
progDesc: parseDescription($(program))
}
@ -91,7 +93,9 @@ function parseStop(schDayMonth, itemNext) {
)
} else {
return dayjs.tz(
`${currentYear}-${monthDate[0]}-${(parseInt(monthDate[1]) + 1).toString().padStart(2, '0')} 00:00`,
`${currentYear}-${monthDate[0]}-${(parseInt(monthDate[1]) + 1)
.toString()
.padStart(2, '0')} 00:00`,
'YYYY-MMM-DD HH:mm',
tz
)

File diff suppressed because one or more lines are too long

View file

@ -41,7 +41,7 @@ module.exports = {
const pages = Array.from(Array(totalPages).keys())
for (let page of pages) {
const data = await axios
.get(`https://mtel.ba/oec/epg/program`, {
.get('https://mtel.ba/oec/epg/program', {
params: { page, date: dayjs().format('YYYY-MM-DD') },
headers: {
'X-Requested-With': 'XMLHttpRequest'
@ -65,7 +65,7 @@ module.exports = {
async function getTotalPageCount() {
const data = await axios
.get(`https://mtel.ba/oec/epg/program`, {
.get('https://mtel.ba/oec/epg/program', {
params: { page: 0, date: dayjs().format('YYYY-MM-DD') },
headers: {
'X-Requested-With': 'XMLHttpRequest'

View file

@ -43,7 +43,7 @@ module.exports = {
const pages = Array.from(Array(totalPages).keys())
for (let page of pages) {
const data = await axios
.get(`https://mts.rs/oec/epg/program`, {
.get('https://mts.rs/oec/epg/program', {
params: { page, date: dayjs().format('YYYY-MM-DD') },
headers: {
'X-Requested-With': 'XMLHttpRequest'
@ -67,7 +67,7 @@ module.exports = {
async function getTotalPageCount() {
const data = await axios
.get(`https://mts.rs/oec/epg/program`, {
.get('https://mts.rs/oec/epg/program', {
params: { page: 0, date: dayjs().format('YYYY-MM-DD') },
headers: {
'X-Requested-With': 'XMLHttpRequest'
@ -84,8 +84,8 @@ function parseContent(content, channel) {
let data
try {
data = JSON.parse(content)
} catch (error) {
console.log(error)
} catch {
return []
}
if (!data || !data.channels || !data.channels.length) return null

View file

@ -47,7 +47,7 @@ module.exports = {
const data = await axios
.post(
`https://services.mujtvprogram.cz/tvprogram2services/services/tvchannellist_mobile.php`,
'https://services.mujtvprogram.cz/tvprogram2services/services/tvchannellist_mobile.php',
params,
{
headers: {
@ -86,7 +86,7 @@ function parseItems(content) {
if (!data) return []
const programmes = data['tv-program-programmes'].programme
return programmes && Array.isArray(programmes) ? programmes : []
} catch (err) {
} catch {
return []
}
}

View file

@ -9,7 +9,7 @@ dayjs.extend(customParseFormat)
const headers = {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0'
}
module.exports = {

View file

@ -26,12 +26,11 @@ it('can generate valid url for today', () => {
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
})
const results = parser({ content, date }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results[0]).toMatchObject({
start: '2022-11-19T23:00:00.000Z',

View file

@ -23,14 +23,15 @@ module.exports = {
channel.site_id
}.html?dt=${date.format('YYYY-MM-DD')}`
},
async parser({ content, date, channel }) {
async parser({ content, date }) {
const programs = []
if (content) {
const queues = []
const $ = cheerio.load(content)
$('table.table > tbody > tr').toArray()
$('table.table > tbody > tr')
.toArray()
.forEach(el => {
const td = $(el).find('td:eq(1)')
const title = td.find('h5 a')
@ -66,12 +67,16 @@ module.exports = {
const subTitle = parseText($('.tab-pane > h5 > strong'))
const description = parseText($('.tab-pane > .tvbody > p'))
const image = $('.program-media-image img').attr('src')
const category = $('.schedule-attributes-genres span').toArray()
const category = $('.schedule-attributes-genres span')
.toArray()
.map(el => $(el).text())
const casts = $('.single-cast-head:not([id])').toArray()
const casts = $('.single-cast-head:not([id])')
.toArray()
.map(el => {
const cast = { name: parseText($(el).find('a')) }
const [, role] = $(el).text().match(/\((.*)\)/) || [null, null]
const [, role] = $(el)
.text()
.match(/\((.*)\)/) || [null, null]
if (role) {
cast.role = role
}
@ -102,7 +107,7 @@ module.exports = {
start,
stop
})
})
})
}
}
@ -115,11 +120,17 @@ module.exports = {
// process form -> provider
if (queue.t === 'p') {
const $ = cheerio.load(res)
$('#guide_provider option').toArray()
$('#guide_provider option')
.toArray()
.forEach(el => {
const opt = $(el)
const provider = opt.attr('value')
queues.push({ t: 'r', method: 'post', url: 'https://www.mytelly.co.uk/getregions', params: { provider } })
queues.push({
t: 'r',
method: 'post',
url: 'https://www.mytelly.co.uk/getregions',
params: { provider }
})
})
}
// process provider -> region
@ -135,26 +146,30 @@ module.exports = {
u_time: now.format('HHmm'),
is_mobile: 1
}
queues.push({ t: 's', method: 'post', url: 'https://www.mytelly.co.uk/tv-guide/schedule', params })
queues.push({
t: 's',
method: 'post',
url: 'https://www.mytelly.co.uk/tv-guide/schedule',
params
})
}
}
// process schedule -> channels
if (queue.t === 's') {
const $ = cheerio.load(res)
$('.channelname')
.each((i, el) => {
const name = $(el).find('center > a:eq(1)').text()
const url = $(el).find('center > a:eq(1)').attr('href')
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)
const site_id = `${number}/${slug}`
if (channels[site_id] === undefined) {
channels[site_id] = {
lang: 'en',
site_id,
name
}
$('.channelname').each((i, el) => {
const name = $(el).find('center > a:eq(1)').text()
const url = $(el).find('center > a:eq(1)').attr('href')
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)
const site_id = `${number}/${slug}`
if (channels[site_id] === undefined) {
channels[site_id] = {
lang: 'en',
site_id,
name
}
})
}
})
}
})
@ -178,13 +193,10 @@ function parseTime(date, time) {
}
function parseText($item) {
let text = $item.text()
.replace(/\t/g, '')
.replace(/\n/g, ' ')
.trim()
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
while (true) {
if (text.match(/ /)) {
text = text.replace(/ /g, ' ')
if (text.match(/\s\s/)) {
text = text.replace(/\s\s/g, ' ')
continue
}
break

View file

@ -17,16 +17,18 @@ const channel = {
xmltv_id: 'BBCOneLondon.uk'
}
axios.get.mockImplementation((url, opts) => {
axios.get.mockImplementation(url => {
if (
url === 'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=1906433&tm=2024-12-07+00%3A00%3A00'
url ===
'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=1906433&tm=2024-12-07+00%3A00%3A00'
) {
return Promise.resolve({
data: fs.readFileSync(path.join(__dirname, '__data__', 'programme.html'))
})
}
if (
url === 'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=5656624&tm=2024-12-07+23%3A35%3A00'
url ===
'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=5656624&tm=2024-12-07+23%3A35%3A00'
) {
return Promise.resolve({
data: fs.readFileSync(path.join(__dirname, '__data__', 'programme2.html'))
@ -57,7 +59,8 @@ it('can parse response', async () => {
title: 'Captain Phillips',
description:
'An American cargo ship sets a dangerous course around the coast of Somalia, while inland, four men are pressed into service as pirates by the local warlords. The captain is taken hostage when the raiding party hijacks the vessel, resulting in a tense five-day crisis. Fact-based thriller, starring Tom Hanks and Barkhad Abdi',
image: 'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/c44ce7b0d3ae602c0c93ece5af140815.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4dsylOCGGE7OWlqwSWt0cd0Qtrin4DkEMC0Zzdp8ZeNk2vNIQzjMF0DG0h3IeTR5NM%3D',
image:
'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/c44ce7b0d3ae602c0c93ece5af140815.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4dsylOCGGE7OWlqwSWt0cd0Qtrin4DkEMC0Zzdp8ZeNk2vNIQzjMF0DG0h3IeTR5NM%3D',
category: ['Factual', 'Movie/Drama', 'Thriller']
})
expect(results[1]).toMatchObject({
@ -67,7 +70,8 @@ it('can parse response', async () => {
subTitle: 'Past and Pressure Season 6, Episode 5',
description:
'The artists are tasked with writing a song about their heritage. For some, the pressure of the competition proves too much for them to match. In their final challenge, they are put face to face with industry experts who grill them about their plans after the competition. Some impress, while others leave the mentors confused',
image: 'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/2039278182b27cc279570b9ab9b89379.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4cDhR7jXTNFW3tgwQCdOPUobhXwlT81mIsqOe93HPusDG6tw1aoeYOgafojtynNWxc%3D',
image:
'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/2039278182b27cc279570b9ab9b89379.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4cDhR7jXTNFW3tgwQCdOPUobhXwlT81mIsqOe93HPusDG6tw1aoeYOgafojtynNWxc%3D',
category: ['Challenge/Reality Show', 'Show/Game Show'],
season: 6,
episode: 5

View file

@ -1,73 +1,80 @@
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: 'neo.io',
timezone: 'Europe/Ljubljana',
days: 5,
url({ date, channel }) { return 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData' },
request: {
method: 'POST',
headers: {
'Host': 'stargate.telekom.si',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
'Content-Type': 'application/json',
'X-AppLayout': '1',
'x-language': 'sl',
'Origin': 'https://neo.io',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
'Sec-GPC': '1',
'Connection': 'keep-alive'
},
data({ channel, date }) {
const todayEpoch = date.startOf('day').unix();
const nextDayEpoch = date.add(1, 'day').startOf('day').unix();
return JSON.stringify({
ch_ext_id: channel.site_id,
from: todayEpoch,
to: nextDayEpoch
})
}
},
parser: function ({ content }) {
const programs = [];
const data = JSON.parse(content);
data.shows.forEach(show => {
const start = dayjs.unix(show.show_start).utc();
const stop = dayjs.unix(show.show_end).utc();
const programData = {
title: show.title,
description: show.summary || 'No description available',
start: start.toISOString(),
stop: stop.toISOString(),
thumbnail: show.thumbnail
}
programs.push(programData)
})
return programs
},
async channels() {
const response = await axios.post('https://stargate.telekom.si/api/titan.tv.WebEpg/ZapList', JSON.stringify({ includeRadioStations: true }), {
headers: this.request.headers
});
const data = response.data.data;
return data.map(item => ({
lang: 'sq',
name: String(item.channel.title),
site_id: String(item.channel.id),
//logo: String(item.channel.logo)
}))
}
}
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: 'neo.io',
timezone: 'Europe/Ljubljana',
days: 5,
url() {
return 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData'
},
request: {
method: 'POST',
headers: {
Host: 'stargate.telekom.si',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
'Content-Type': 'application/json',
'X-AppLayout': '1',
'x-language': 'sl',
Origin: 'https://neo.io',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
'Sec-GPC': '1',
Connection: 'keep-alive'
},
data({ channel, date }) {
const todayEpoch = date.startOf('day').unix()
const nextDayEpoch = date.add(1, 'day').startOf('day').unix()
return JSON.stringify({
ch_ext_id: channel.site_id,
from: todayEpoch,
to: nextDayEpoch
})
}
},
parser: function ({ content }) {
const programs = []
const data = JSON.parse(content)
data.shows.forEach(show => {
const start = dayjs.unix(show.show_start).utc()
const stop = dayjs.unix(show.show_end).utc()
const programData = {
title: show.title,
description: show.summary || 'No description available',
start: start.toISOString(),
stop: stop.toISOString(),
thumbnail: show.thumbnail
}
programs.push(programData)
})
return programs
},
async channels() {
const response = await axios.post(
'https://stargate.telekom.si/api/titan.tv.WebEpg/ZapList',
JSON.stringify({ includeRadioStations: true }),
{
headers: this.request.headers
}
)
const data = response.data.data
return data.map(item => ({
lang: 'sq',
name: String(item.channel.title),
site_id: String(item.channel.id)
//logo: String(item.channel.logo)
}))
}
}

View file

@ -1,121 +1,124 @@
const { parser, url } = require('./neo.io.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('2024-12-26', 'YYYY-MM-DD').startOf('day')
const channel = {
site_id: 'tv-slo-1',
xmltv_id: 'TVSLO1.si'
}
it('can generate valid url', () => {
expect(url({ date, channel })).toBe('https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData')
})
it('can parse response', () => {
const content = `
{
"shows": [
{
"title": "Napovedujemo",
"show_start": 1735185900,
"show_end": 1735192200,
"timestamp": "5:05 - 6:50",
"show_id": "CUP_IECOM_SLO1_10004660",
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg",
"is_adult": false,
"friendly_id": "napovedujemo_db48",
"pg": "",
"genres": [
"napovednik"
],
"year": 0,
"summary": "Vabilo k ogledu naših oddaj.",
"categories": "Ostalo",
"stb_only": false,
"is_live": false,
"original_title": "Napovedujemo"
},
{
"title": "S0E0 - Hrabri zajčki: Prvi sneg",
"show_start": 1735192200,
"show_end": 1735192800,
"timestamp": "6:50 - 7:00",
"show_id": "CUP_IECOM_SLO1_79637910",
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg",
"is_adult": false,
"friendly_id": "hrabri_zajcki_prvi_sneg_1619",
"pg": "",
"genres": [
"risanka"
],
"year": 2020,
"summary": "Hrabri zajčki so prispeli v borov gozd in izkusili prvi sneg. Bob in Bu še nikoli nista videla snega. Mami kuha korenčkov kakav, Bu in Bob pa kmalu spoznata novega prijatelja, losa Danija.",
"categories": "Otroški/Mladinski",
"stb_only": false,
"is_live": false,
"original_title": "S0E0 - Brave Bunnies"
},
{
"title": "Dobro jutro",
"show_start": 1735192800,
"show_end": 1735203900,
"timestamp": "7:00 - 10:05",
"show_id": "CUP_IECOM_SLO1_79637911",
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg",
"is_adult": false,
"friendly_id": "dobro_jutro_2f10",
"pg": "",
"genres": [
"zabavna oddaja"
],
"year": 2024,
"summary": "Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.",
"categories": "Razvedrilni program",
"stb_only": false,
"is_live": false,
"original_title": "Dobro jutro"
}
]
}`
const result = parser({ content, channel }).map(p => {
p.start = p.start
p.stop = p.stop
return p
})
expect(result).toMatchObject([
{
title: "Napovedujemo",
description: "Vabilo k ogledu naših oddaj.",
start: "2024-12-26T04:05:00.000Z",
stop: "2024-12-26T05:50:00.000Z",
thumbnail: "https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg"
},
{
title: "S0E0 - Hrabri zajčki: Prvi sneg",
description: "Hrabri zajčki so prispeli v borov gozd in izkusili prvi sneg. Bob in Bu še nikoli nista videla snega. Mami kuha korenčkov kakav, Bu in Bob pa kmalu spoznata novega prijatelja, losa Danija.",
start: "2024-12-26T05:50:00.000Z",
stop: "2024-12-26T06:00:00.000Z",
thumbnail: "https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg"
},
{
title: "Dobro jutro",
description: "Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.",
start: "2024-12-26T06:00:00.000Z",
stop: "2024-12-26T09:05:00.000Z",
thumbnail: "https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg"
}
])
})
it('can handle empty guide', () => {
const result = parser({
content: '{"shows":[]}'
})
expect(result).toMatchObject([])
})
const { parser, url } = require('./neo.io.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('2024-12-26', 'YYYY-MM-DD').startOf('day')
const channel = {
site_id: 'tv-slo-1',
xmltv_id: 'TVSLO1.si'
}
it('can generate valid url', () => {
expect(url({ date, channel })).toBe(
'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData'
)
})
it('can parse response', () => {
const content = `
{
"shows": [
{
"title": "Napovedujemo",
"show_start": 1735185900,
"show_end": 1735192200,
"timestamp": "5:05 - 6:50",
"show_id": "CUP_IECOM_SLO1_10004660",
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg",
"is_adult": false,
"friendly_id": "napovedujemo_db48",
"pg": "",
"genres": [
"napovednik"
],
"year": 0,
"summary": "Vabilo k ogledu naših oddaj.",
"categories": "Ostalo",
"stb_only": false,
"is_live": false,
"original_title": "Napovedujemo"
},
{
"title": "S0E0 - Hrabri zajčki: Prvi sneg",
"show_start": 1735192200,
"show_end": 1735192800,
"timestamp": "6:50 - 7:00",
"show_id": "CUP_IECOM_SLO1_79637910",
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg",
"is_adult": false,
"friendly_id": "hrabri_zajcki_prvi_sneg_1619",
"pg": "",
"genres": [
"risanka"
],
"year": 2020,
"summary": "Hrabri zajčki so prispeli v borov gozd in izkusili prvi sneg. Bob in Bu še nikoli nista videla snega. Mami kuha korenčkov kakav, Bu in Bob pa kmalu spoznata novega prijatelja, losa Danija.",
"categories": "Otroški/Mladinski",
"stb_only": false,
"is_live": false,
"original_title": "S0E0 - Brave Bunnies"
},
{
"title": "Dobro jutro",
"show_start": 1735192800,
"show_end": 1735203900,
"timestamp": "7:00 - 10:05",
"show_id": "CUP_IECOM_SLO1_79637911",
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg",
"is_adult": false,
"friendly_id": "dobro_jutro_2f10",
"pg": "",
"genres": [
"zabavna oddaja"
],
"year": 2024,
"summary": "Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.",
"categories": "Razvedrilni program",
"stb_only": false,
"is_live": false,
"original_title": "Dobro jutro"
}
]
}`
const result = parser({ content, channel })
expect(result).toMatchObject([
{
title: 'Napovedujemo',
description: 'Vabilo k ogledu naših oddaj.',
start: '2024-12-26T04:05:00.000Z',
stop: '2024-12-26T05:50:00.000Z',
thumbnail:
'https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg'
},
{
title: 'S0E0 - Hrabri zajčki: Prvi sneg',
description:
'Hrabri zajčki so prispeli v borov gozd in izkusili prvi sneg. Bob in Bu še nikoli nista videla snega. Mami kuha korenčkov kakav, Bu in Bob pa kmalu spoznata novega prijatelja, losa Danija.',
start: '2024-12-26T05:50:00.000Z',
stop: '2024-12-26T06:00:00.000Z',
thumbnail:
'https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg'
},
{
title: 'Dobro jutro',
description:
'Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.',
start: '2024-12-26T06:00:00.000Z',
stop: '2024-12-26T09:05:00.000Z',
thumbnail:
'https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg'
}
])
})
it('can handle empty guide', () => {
const result = parser({
content: '{"shows":[]}'
})
expect(result).toMatchObject([])
})

View file

@ -50,7 +50,7 @@ function parseItems(content, date) {
if (!data || !data.item || !Array.isArray(data.item.episodes)) return []
return data.item.episodes.filter(ep => ep.schedule.startsWith(date.format('YYYY-MM-DD')))
} catch (err) {
} catch {
return []
}
}

View file

@ -1,45 +1,46 @@
const dayjs = require('dayjs')
module.exports = {
site: 'nhl.com',
// I'm not sure what `endDate` represents but they only return 1 day of
// results, with `endTime`s ocassionally in the following day.
days: 1,
url: ({ date }) => `https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split("T")[0]}`,
parser({ content }) {
const programs = []
const items = parseItems(content)
for (const item of items) {
programs.push({
title: item.title,
description: item.description === item.title ? undefined : item.description,
category: "Sports",
// image: parseImage(item),
start: parseStart(item),
stop: parseStop(item)
})
}
return programs
}
}
// Unfortunately I couldn't determine how these are
// supposed to be formatted. Pointers appreciated!
// function parseImage(item) {
// const uri = item.broadcastImageUrl
// return uri ? `https://???/${uri}` : null
// }
function parseStart(item) {
return dayjs(item.startTime)
}
function parseStop(item) {
return dayjs(item.endTime)
}
function parseItems(content) {
return JSON.parse(content).broadcasts
}
const dayjs = require('dayjs')
module.exports = {
site: 'nhl.com',
// I'm not sure what `endDate` represents but they only return 1 day of
// results, with `endTime`s ocassionally in the following day.
days: 1,
url: ({ date }) =>
`https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split('T')[0]}`,
parser({ content }) {
const programs = []
const items = parseItems(content)
for (const item of items) {
programs.push({
title: item.title,
description: item.description === item.title ? undefined : item.description,
category: 'Sports',
// image: parseImage(item),
start: parseStart(item),
stop: parseStop(item)
})
}
return programs
}
}
// Unfortunately I couldn't determine how these are
// supposed to be formatted. Pointers appreciated!
// function parseImage(item) {
// const uri = item.broadcastImageUrl
// return uri ? `https://???/${uri}` : null
// }
function parseStart(item) {
return dayjs(item.startTime)
}
function parseStop(item) {
return dayjs(item.endTime)
}
function parseItems(content) {
return JSON.parse(content).broadcasts
}

View file

@ -1,44 +1,44 @@
const { parser, url } = require('./nhl.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('2024-11-21', 'YYYY-MM-DD').startOf('d')
it('can generate valid url', () => {
expect(url({ date })).toBe(
'https://api-web.nhle.com/v1/network/tv-schedule/2024-11-21'
)
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
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: '2024-11-21T12:00:00.000Z',
stop: '2024-11-21T13:00:00.000Z',
title: 'On The Fly',
category: 'Sports',
})
})
it('can handle empty guide', () => {
const results = parser({ content: JSON.stringify({
// extra props not necessary but they form a valid response
date: "2024-11-21",
startDate: "2024-11-07",
endDate: "2024-12-05",
broadcasts: [],
}) })
expect(results).toMatchObject([])
})
const { parser, url } = require('./nhl.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('2024-11-21', 'YYYY-MM-DD').startOf('d')
it('can generate valid url', () => {
expect(url({ date })).toBe('https://api-web.nhle.com/v1/network/tv-schedule/2024-11-21')
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
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: '2024-11-21T12:00:00.000Z',
stop: '2024-11-21T13:00:00.000Z',
title: 'On The Fly',
category: 'Sports'
})
})
it('can handle empty guide', () => {
const results = parser({
content: JSON.stringify({
// extra props not necessary but they form a valid response
date: '2024-11-21',
startDate: '2024-11-07',
endDate: '2024-12-05',
broadcasts: []
})
})
expect(results).toMatchObject([])
})

View file

@ -1,68 +1,68 @@
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const headers = {
'X-Apikey': 'xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI',
'X-Core-Appversion': '2.14.0.1',
'X-Core-Contentratinglimit': '0',
'X-Core-Deviceid': '',
'X-Core-Devicetype': 'web',
Origin: 'https://nostv.pt',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
module.exports = {
site: 'nostv.pt',
days: 2,
url({ channel, date }) {
return `https://tyr-prod.apigee.net/nostv/ott/schedule/range/contents/guest?channels=${
channel.site_id
}&minDate=${date.format('YYYY-MM-DD')}T00:00:00Z&maxDate=${date.format(
'YYYY-MM-DD'
)}T23:59:59Z&isDateInclusive=true&client_id=${headers['X-Apikey']}`
},
request: { headers },
parser({ content }) {
const programs = []
if (content) {
const items = Array.isArray(content) ? content : JSON.parse(content)
items.forEach(item => {
programs.push({
title: item.Metadata?.Title,
sub_title: item.Metadata?.SubTitle ? item.Metadata?.SubTitle : null,
description: item.Metadata?.Description,
season: item.Metadata?.Season,
episode: item.Metadata?.Episode,
image: item.Images
? `https://mage.stream.nos.pt/v1/nostv_mage/Images?sourceUri=${item.Images[0].Url}&profile=ott_1_452x340&client_id=${headers['X-Apikey']}`
: null,
start: dayjs.utc(item.UtcDateTimeStart),
stop: dayjs.utc(item.UtcDateTimeEnd)
})
})
}
return programs
},
async channels() {
const result = await axios
.get(
`https://tyr-prod.apigee.net/nostv/ott/channels/guest?client_id=${headers['X-Apikey']}`,
{ headers }
)
.then(r => r.data)
.catch(console.error)
return result.map(item => {
return {
lang: 'pt',
site_id: item.ServiceId,
name: item.Name
}
})
}
}
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const headers = {
'X-Apikey': 'xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI',
'X-Core-Appversion': '2.14.0.1',
'X-Core-Contentratinglimit': '0',
'X-Core-Deviceid': '',
'X-Core-Devicetype': 'web',
Origin: 'https://nostv.pt',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
module.exports = {
site: 'nostv.pt',
days: 2,
url({ channel, date }) {
return `https://tyr-prod.apigee.net/nostv/ott/schedule/range/contents/guest?channels=${
channel.site_id
}&minDate=${date.format('YYYY-MM-DD')}T00:00:00Z&maxDate=${date.format(
'YYYY-MM-DD'
)}T23:59:59Z&isDateInclusive=true&client_id=${headers['X-Apikey']}`
},
request: { headers },
parser({ content }) {
const programs = []
if (content) {
const items = Array.isArray(content) ? content : JSON.parse(content)
items.forEach(item => {
programs.push({
title: item.Metadata?.Title,
sub_title: item.Metadata?.SubTitle ? item.Metadata?.SubTitle : null,
description: item.Metadata?.Description,
season: item.Metadata?.Season,
episode: item.Metadata?.Episode,
image: item.Images
? `https://mage.stream.nos.pt/v1/nostv_mage/Images?sourceUri=${item.Images[0].Url}&profile=ott_1_452x340&client_id=${headers['X-Apikey']}`
: null,
start: dayjs.utc(item.UtcDateTimeStart),
stop: dayjs.utc(item.UtcDateTimeEnd)
})
})
}
return programs
},
async channels() {
const result = await axios
.get(
`https://tyr-prod.apigee.net/nostv/ott/channels/guest?client_id=${headers['X-Apikey']}`,
{ headers }
)
.then(r => r.data)
.catch(console.error)
return result.map(item => {
return {
lang: 'pt',
site_id: item.ServiceId,
name: item.Name
}
})
}
}

View file

@ -1,51 +1,51 @@
const { parser, url } = require('./nostv.pt.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-12-11').startOf('d')
const channel = {
site_id: '510',
xmltv_id: 'SPlus.pt'
}
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
'https://tyr-prod.apigee.net/nostv/ott/schedule/range/contents/guest?channels=510&minDate=2023-12-11T00:00:00Z&maxDate=2023-12-11T23:59:59Z&isDateInclusive=true&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI'
)
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.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-12-11T16:30:00.000Z',
stop: '2023-12-11T17:00:00.000Z',
title: 'Village Vets',
description:
'A história de dois melhores amigos veterinários e o seu extraordinário trabalho na Austrália.',
season: 1,
episode: 12,
image:
'https://mage.stream.nos.pt/v1/nostv_mage/Images?sourceUri=http://vip.pam.local.internal/PAM.Images/Store/8329ed1aec5d4c0faa2056972256ff9f&profile=ott_1_452x340&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI'
})
})
it('can handle empty guide', async () => {
const results = await parser({
date,
content: '[]'
})
expect(results).toMatchObject([])
})
const { parser, url } = require('./nostv.pt.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-12-11').startOf('d')
const channel = {
site_id: '510',
xmltv_id: 'SPlus.pt'
}
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
'https://tyr-prod.apigee.net/nostv/ott/schedule/range/contents/guest?channels=510&minDate=2023-12-11T00:00:00Z&maxDate=2023-12-11T23:59:59Z&isDateInclusive=true&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI'
)
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.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-12-11T16:30:00.000Z',
stop: '2023-12-11T17:00:00.000Z',
title: 'Village Vets',
description:
'A história de dois melhores amigos veterinários e o seu extraordinário trabalho na Austrália.',
season: 1,
episode: 12,
image:
'https://mage.stream.nos.pt/v1/nostv_mage/Images?sourceUri=http://vip.pam.local.internal/PAM.Images/Store/8329ed1aec5d4c0faa2056972256ff9f&profile=ott_1_452x340&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI'
})
})
it('can handle empty guide', async () => {
const results = await parser({
date,
content: '[]'
})
expect(results).toMatchObject([])
})

View file

@ -55,7 +55,7 @@ module.exports = {
.map(function () {
return {
lang: 'es',
site_id: $(this).attr('alt').replace(/\&/gi, '&amp;'),
site_id: $(this).attr('alt').replace(/&/gi, '&amp;'),
name: $(this).attr('alt')
}
})

View file

@ -1,81 +1,81 @@
const parser = require('epg-parser')
module.exports = {
site: 'nzxmltv.com',
days: 2,
request: {
cache: {
ttl: 3600000 // 1 hour
},
maxContentLength: 104857600 // 100 MB
},
url({ channel }) {
const [path] = channel.site_id.split('#')
return `https://nzxmltv.com/${path}.xml`
},
parser({ content, channel, date }) {
const programs = []
parseItems(content, channel, date).forEach(item => {
const program = {
title: item.title?.[0]?.value,
description: item.desc?.[0]?.value,
icon: item.icon?.[0],
start: item.start,
stop: item.stop
}
if (item.episodeNum) {
item.episodeNum.forEach(ep => {
if (ep.system === 'xmltv_ns') {
const [season, episode, _] = ep.value.split('.')
program.season = parseInt(season) + 1
program.episode = parseInt(episode) + 1
return true
}
})
}
programs.push(program)
})
return programs
},
async channels({ provider }) {
const axios = require('axios')
const cheerio = require('cheerio')
const providers = {
freeview: 'xmltv/guide',
sky: 'sky/guide',
redbull: 'iptv/redbull',
pluto: 'iptv/plutotv'
}
const channels = []
const path = providers[provider]
const xml = await axios
.get(`https://nzxmltv.com/${path}.xml`)
.then(r => r.data)
.catch(console.error)
const $ = cheerio.load(xml)
$('tv channel').each((i, el) => {
const disp = $(el).find('display-name')
const channelId = $(el).attr('id')
channels.push({
lang: disp.attr('lang').substr(0, 2),
site_id: `${path}#${channelId}`,
name: disp.text().trim()
})
})
return channels
}
}
function parseItems(content, channel, date) {
const { programs } = parser.parse(content)
const [, channelId] = channel.site_id.split('#')
return programs.filter(p => p.channel === channelId && date.isSame(p.start, 'day'))
}
const parser = require('epg-parser')
module.exports = {
site: 'nzxmltv.com',
days: 2,
request: {
cache: {
ttl: 3600000 // 1 hour
},
maxContentLength: 104857600 // 100 MB
},
url({ channel }) {
const [path] = channel.site_id.split('#')
return `https://nzxmltv.com/${path}.xml`
},
parser({ content, channel, date }) {
const programs = []
parseItems(content, channel, date).forEach(item => {
const program = {
title: item.title?.[0]?.value,
description: item.desc?.[0]?.value,
icon: item.icon?.[0],
start: item.start,
stop: item.stop
}
if (item.episodeNum) {
item.episodeNum.forEach(ep => {
if (ep.system === 'xmltv_ns') {
const [season, episode] = ep.value.split('.')
program.season = parseInt(season) + 1
program.episode = parseInt(episode) + 1
return true
}
})
}
programs.push(program)
})
return programs
},
async channels({ provider }) {
const axios = require('axios')
const cheerio = require('cheerio')
const providers = {
freeview: 'xmltv/guide',
sky: 'sky/guide',
redbull: 'iptv/redbull',
pluto: 'iptv/plutotv'
}
const channels = []
const path = providers[provider]
const xml = await axios
.get(`https://nzxmltv.com/${path}.xml`)
.then(r => r.data)
.catch(console.error)
const $ = cheerio.load(xml)
$('tv channel').each((i, el) => {
const disp = $(el).find('display-name')
const channelId = $(el).attr('id')
channels.push({
lang: disp.attr('lang').substr(0, 2),
site_id: `${path}#${channelId}`,
name: disp.text().trim()
})
})
return channels
}
}
function parseItems(content, channel, date) {
const { programs } = parser.parse(content)
const [, channelId] = channel.site_id.split('#')
return programs.filter(p => p.channel === channelId && date.isSame(p.start, 'day'))
}

View file

@ -1,40 +1,40 @@
const { parser, url } = require('./nzxmltv.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-11-21').startOf('d')
const channel = {
site_id: 'xmltv/guide#1',
xmltv_id: 'TVNZ1.nz'
}
it('can generate valid url', () => {
expect(url({ channel })).toBe('https://nzxmltv.com/xmltv/guide.xml')
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'))
const results = parser({ content, channel, date })
expect(results[0]).toMatchObject({
start: '2023-11-21T10:30:00.000Z',
stop: '2023-11-21T11:25:00.000Z',
title: 'Sunday',
description:
'On Sunday, an unmissable show with stories about divorce, weight loss, and the incomprehensible devastation of Gaza.',
season: 2023,
episode: 37,
icon: 'https://www.thetvdb.com/banners/posters/5dbebff2986f2.jpg'
})
})
it('can handle empty guide', () => {
const result = parser({ content: '', channel, date })
expect(result).toMatchObject([])
})
const { parser, url } = require('./nzxmltv.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-11-21').startOf('d')
const channel = {
site_id: 'xmltv/guide#1',
xmltv_id: 'TVNZ1.nz'
}
it('can generate valid url', () => {
expect(url({ channel })).toBe('https://nzxmltv.com/xmltv/guide.xml')
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'))
const results = parser({ content, channel, date })
expect(results[0]).toMatchObject({
start: '2023-11-21T10:30:00.000Z',
stop: '2023-11-21T11:25:00.000Z',
title: 'Sunday',
description:
'On Sunday, an unmissable show with stories about divorce, weight loss, and the incomprehensible devastation of Gaza.',
season: 2023,
episode: 37,
icon: 'https://www.thetvdb.com/banners/posters/5dbebff2986f2.jpg'
})
})
it('can handle empty guide', () => {
const result = parser({ content: '', channel, date })
expect(result).toMatchObject([])
})

View file

@ -132,7 +132,7 @@ module.exports = {
const $ = cheerio.load(data)
$('.channelname').each((i, el) => {
let name = $(el).find('center > a:eq(1)').text()
name = name.replace(/\-\-/gi, '-')
name = name.replace(/--/gi, '-')
const url = $(el).find('center > a:eq(1)').attr('href')
if (!url) return
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)

View file

@ -1,113 +1,108 @@
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const axios = require('axios')
dayjs.extend(utc)
const API_PROGRAM_ENDPOINT = 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO'
const API_CHANNEL_ENDPOINT = 'https://pc.orangetv.orange.es/pc/api/rtv/v1/GetChannelList?bouquet_id=1&model_external_id=PC&filter_unsupported_channels=false&client=json'
const API_IMAGE_ENDPOINT = 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images'
module.exports = {
site: 'orangetv.orange.es',
days: 2,
request: {
cache: {
ttl: 60 * 60 * 1000 // 1 hour
}
},
url({ date }) {
return `${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`
},
async parser({ content, channel, date }) {
let programs = []
let items = parseItems(content, channel)
if (!items.length) return programs
const promises = [
axios.get(
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`,
),
axios.get(
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_2.json`,
),
axios.get(
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_3.json`,
),
]
await Promise.allSettled(promises)
.then(results => {
results.forEach(r => {
if (r.status === 'fulfilled') {
const parsed = parseItems(r.value.data, channel)
items = items.filter((item, index) => items.findIndex(oi => oi.id === item.id) === index).concat(parsed)
}
})
})
.catch(console.error)
items.forEach(item => {
programs.push({
title: item.name,
description: item.description,
category: parseGenres(item),
season: item.seriesSeason || null,
episode: item.episodeId || null,
icon: parseIcon(item),
start: dayjs.utc(item.startDate) || null,
stop: dayjs.utc(item.endDate) || null,
})
})
return programs
},
async channels() {
const axios = require('axios')
const data = await axios
.get(API_CHANNEL_ENDPOINT)
.then(r => r.data)
.catch(console.log)
return data.response.map(item => {
return {
lang: 'es',
name: item.name,
site_id: item.externalChannelId
}
})
}
}
function parseIcon(item){
if(item.attachments.length > 0){
const cover = item.attachments.find(i => i.name === "COVER" || i.name === "cover")
if(cover)
{
return `${API_IMAGE_ENDPOINT}${cover.value}`;
}
}
return ''
}
function parseGenres(item){
return item.genres.map(i => i.name);
}
function parseItems(content, channel) {
const json = typeof content === 'string' ? JSON.parse(content) : Array.isArray(content) ? content : []
if (!Array.isArray(json)) {
return [];
}
const channelData = json.find(i => i.channelExternalId == channel.site_id);
if(!channelData)
return [];
return channelData.programs;
}
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const axios = require('axios')
dayjs.extend(utc)
const API_PROGRAM_ENDPOINT = 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO'
const API_CHANNEL_ENDPOINT =
'https://pc.orangetv.orange.es/pc/api/rtv/v1/GetChannelList?bouquet_id=1&model_external_id=PC&filter_unsupported_channels=false&client=json'
const API_IMAGE_ENDPOINT = 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images'
module.exports = {
site: 'orangetv.orange.es',
days: 2,
request: {
cache: {
ttl: 60 * 60 * 1000 // 1 hour
}
},
url({ date }) {
return `${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`
},
async parser({ content, channel, date }) {
let programs = []
let items = parseItems(content, channel)
if (!items.length) return programs
const promises = [
axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`),
axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_2.json`),
axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_3.json`)
]
await Promise.allSettled(promises)
.then(results => {
results.forEach(r => {
if (r.status === 'fulfilled') {
const parsed = parseItems(r.value.data, channel)
items = items
.filter((item, index) => items.findIndex(oi => oi.id === item.id) === index)
.concat(parsed)
}
})
})
.catch(console.error)
items.forEach(item => {
programs.push({
title: item.name,
description: item.description,
category: parseGenres(item),
season: item.seriesSeason || null,
episode: item.episodeId || null,
icon: parseIcon(item),
start: dayjs.utc(item.startDate) || null,
stop: dayjs.utc(item.endDate) || null
})
})
return programs
},
async channels() {
const axios = require('axios')
const data = await axios
.get(API_CHANNEL_ENDPOINT)
.then(r => r.data)
.catch(console.log)
return data.response.map(item => {
return {
lang: 'es',
name: item.name,
site_id: item.externalChannelId
}
})
}
}
function parseIcon(item) {
if (item.attachments.length > 0) {
const cover = item.attachments.find(i => i.name === 'COVER' || i.name === 'cover')
if (cover) {
return `${API_IMAGE_ENDPOINT}${cover.value}`
}
}
return ''
}
function parseGenres(item) {
return item.genres.map(i => i.name)
}
function parseItems(content, channel) {
const json =
typeof content === 'string' ? JSON.parse(content) : Array.isArray(content) ? content : []
if (!Array.isArray(json)) {
return []
}
const channelData = json.find(i => i.channelExternalId == channel.site_id)
if (!channelData) return []
return channelData.programs
}

View file

@ -1,49 +1,54 @@
const { parser, url } = require('./orangetv.orange.es.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 path = require('path')
const fs = require('fs')
const date = dayjs.utc('2024-12-01', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: '1010',
xmltv_id: 'La1.es'
}
it('can generate valid url', () => {
expect(url({ date })).toBe(`https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/${date.format('YYYYMMDD')}_8h_1.json`)
})
it('can parse response', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')).toString()
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(4)
var sampleResult = results[0];
expect(sampleResult).toMatchObject({
start: '2024-11-30T22:36:51.000Z',
stop: '2024-11-30T23:57:25.000Z',
category: ['Cine', 'Romance', 'Comedia', 'Comedia Romántica'],
description: 'Charlie trabaja como director en una escuela de primaria y goza de una placentera existencia junto a sus amigos. A pesar de ello, no es feliz porque cada vez que se enamora pierde la cordura.',
title: 'Loco de amor'
})
})
it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: '{}'
})
expect(result).toMatchObject({})
})
const { parser, url } = require('./orangetv.orange.es.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 path = require('path')
const fs = require('fs')
const date = dayjs.utc('2024-12-01', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: '1010',
xmltv_id: 'La1.es'
}
it('can generate valid url', () => {
expect(url({ date })).toBe(
`https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/${date.format(
'YYYYMMDD'
)}_8h_1.json`
)
})
it('can parse response', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')).toString()
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(4)
var sampleResult = results[0]
expect(sampleResult).toMatchObject({
start: '2024-11-30T22:36:51.000Z',
stop: '2024-11-30T23:57:25.000Z',
category: ['Cine', 'Romance', 'Comedia', 'Comedia Romántica'],
description:
'Charlie trabaja como director en una escuela de primaria y goza de una placentera existencia junto a sus amigos. A pesar de ello, no es feliz porque cada vez que se enamora pierde la cordura.',
title: 'Loco de amor'
})
})
it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: '{}'
})
expect(result).toMatchObject({})
})

View file

@ -5,7 +5,12 @@ const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(timezone)
const packages = { 'OSNTV CONNECT': 3720, 'OSNTV PRIME': 3733, 'ALFA': 1281, 'OSN PINOY PLUS EXTRA': 3519 }
const packages = {
'OSNTV CONNECT': 3720,
'OSNTV PRIME': 3733,
ALFA: 1281,
'OSN PINOY PLUS EXTRA': 3519
}
const country = 'AE'
const tz = 'Asia/Dubai'
@ -13,11 +18,9 @@ module.exports = {
site: 'osn.com',
days: 2,
url({ channel, date }) {
return `https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=${
encodeURIComponent(date.format('MM/DD/YYYY'))
}&co=${country}&ch=${
channel.site_id
}&mo=false&hr=0`
return `https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=${encodeURIComponent(
date.format('MM/DD/YYYY')
)}&co=${country}&ch=${channel.site_id}&mo=false&hr=0`
},
request: {
headers({ channel }) {
@ -46,7 +49,9 @@ module.exports = {
const axios = require('axios')
for (const pkg of Object.values(packages)) {
const channels = await axios
.get(`https://www.osn.com/api/tvchannels.ashx?culture=en-US&packageId=${pkg}&country=${country}`)
.get(
`https://www.osn.com/api/tvchannels.ashx?culture=en-US&packageId=${pkg}&country=${country}`
)
.then(response => response.data)
.catch(console.error)

View file

@ -28,32 +28,30 @@ it('can generate valid url', () => {
})
it('can parse response (ar)', () => {
const result = parser({ date, channel: channelAR, content })
.map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
const result = parser({ date, channel: channelAR, content }).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
expect(result.length).toBe(29)
expect(result[1]).toMatchObject({
start: '2024-11-26T20:50:00.000Z',
stop: '2024-11-26T21:45:00.000Z',
title: 'بيت الحلويات: الحلقة 3',
title: 'بيت الحلويات: الحلقة 3'
})
})
it('can parse response (en)', () => {
const result = parser({ date, channel: channelEN, content })
.map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
const result = parser({ date, channel: channelEN, content }).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
expect(result.length).toBe(29)
expect(result[1]).toMatchObject({
start: '2024-11-26T20:50:00.000Z',
stop: '2024-11-26T21:45:00.000Z',
title: 'House Of Desserts: Episode 3',
title: 'House Of Desserts: Episode 3'
})
})

View file

@ -27,7 +27,7 @@ function parseItems(content, date) {
let data
try {
data = JSON.parse(json)
} catch (error) {
} catch {
return []
}

View file

@ -1,174 +1,172 @@
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
let apiVersion
dayjs.extend(utc)
module.exports = {
site: 'pickx.be',
days: 2,
setApiVersion: function (version) {
apiVersion = version
},
getApiVersion: function () {
return apiVersion
},
fetchApiVersion: fetchApiVersion,
url: async function ({ channel, date }) {
if (!apiVersion) {
await fetchApiVersion()
}
return `https://px-epg.azureedge.net/airings/${apiVersion}/${date.format(
'YYYY-MM-DD'
)}/channel/${channel.site_id}?timezone=Europe%2FBrussels`
},
request: {
headers: {
Origin: 'https://www.pickx.be',
Referer: 'https://www.pickx.be/'
}
},
parser({ channel, content }) {
const programs = []
if (content) {
const items = JSON.parse(content)
items.forEach(item => {
programs.push({
title: item.program.title,
sub_title: item.program.episodeTitle,
description: item.program.description,
category: item.program.translatedCategory?.[channel.lang]
? item.program.translatedCategory[channel.lang]
: item.program.category.split('.')[1],
image: item.program.posterFileName
? `https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/${item.program.posterFileName}`
: null,
season: item.program.seasonNumber,
episode: item.program.episodeNumber,
actors: item.program.actors,
director: item.program.director ? [item.program.director] : null,
start: dayjs.utc(item.programScheduleStart),
stop: dayjs.utc(item.programScheduleEnd)
})
})
}
return programs
},
async channels({ lang = '' }) {
const query = {
operationName: 'getChannels',
variables: {
language: lang,
queryParams: {},
id: '0',
params: {
shouldReadFromCache: true
}
},
query: `query getChannels($language: String!, $queryParams: ChannelQueryParams, $id: String, $params: ChannelParams) {
channels(language: $language, queryParams: $queryParams, id: $id, params: $params) {
id
channelReferenceNumber
name
callLetter
number
logo {
key
url
__typename
}
language
hd
radio
replayable
ottReplayable
playable
ottPlayable
recordable
subscribed
cloudRecordable
catchUpWindowInHours
isOttNPVREnabled
ottNPVRStart
subscription {
channelRef
subscribed
upselling {
upsellable
packages
__typename
}
__typename
}
packages
__typename
}
}`
}
const result = await axios
.post('https://api.proximusmwc.be/tiams/v3/graphql', query)
.then(r => r.data)
.catch(console.error)
return (
result?.data?.channels
.filter(
channel =>
!channel.radio && (!lang || channel.language === (lang === 'de' ? 'ger' : lang))
)
.map(channel => {
return {
lang: channel.language === 'ger' ? 'de' : channel.language,
site_id: channel.id,
name: channel.name
}
}) || []
)
}
}
function fetchApiVersion() {
return new Promise(async (resolve, reject) => {
try {
// you'll never find what happened here :)
// load the pickx page and get the hash from the MWC configuration.
// it's not the best way to get the version but it's the only way to get it.
const hashUrl = 'https://www.pickx.be/nl/televisie/tv-gids';
const hashData = await axios.get(hashUrl)
.then(r => {
const re = /"hashes":\["(.*)"\]/
const match = r.data.match(re)
if (match && match[1]) {
return match[1]
} else {
throw new Error('React app version hash not found')
}
})
.catch(console.error);
const versionUrl = `https://www.pickx.be/api/s-${hashData}`
const response = await axios.get(versionUrl, {
headers: {
Origin: 'https://www.pickx.be',
Referer: 'https://www.pickx.be/'
}
})
if (response.status === 200) {
apiVersion = response.data.version
resolve()
} else {
console.error(`Failed to fetch API version. Status: ${response.status}`)
reject(`Failed to fetch API version. Status: ${response.status}`)
}
} catch (error) {
console.error('Error during fetchApiVersion:', error)
reject(error)
}
})
}
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
let apiVersion
dayjs.extend(utc)
module.exports = {
site: 'pickx.be',
days: 2,
setApiVersion: function (version) {
apiVersion = version
},
getApiVersion: function () {
return apiVersion
},
fetchApiVersion: fetchApiVersion,
url: async function ({ channel, date }) {
if (!apiVersion) {
await fetchApiVersion()
}
return `https://px-epg.azureedge.net/airings/${apiVersion}/${date.format(
'YYYY-MM-DD'
)}/channel/${channel.site_id}?timezone=Europe%2FBrussels`
},
request: {
headers: {
Origin: 'https://www.pickx.be',
Referer: 'https://www.pickx.be/'
}
},
parser({ channel, content }) {
const programs = []
if (content) {
const items = JSON.parse(content)
items.forEach(item => {
programs.push({
title: item.program.title,
sub_title: item.program.episodeTitle,
description: item.program.description,
category: item.program.translatedCategory?.[channel.lang]
? item.program.translatedCategory[channel.lang]
: item.program.category.split('.')[1],
image: item.program.posterFileName
? `https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/${item.program.posterFileName}`
: null,
season: item.program.seasonNumber,
episode: item.program.episodeNumber,
actors: item.program.actors,
director: item.program.director ? [item.program.director] : null,
start: dayjs.utc(item.programScheduleStart),
stop: dayjs.utc(item.programScheduleEnd)
})
})
}
return programs
},
async channels({ lang = '' }) {
const query = {
operationName: 'getChannels',
variables: {
language: lang,
queryParams: {},
id: '0',
params: {
shouldReadFromCache: true
}
},
query: `query getChannels($language: String!, $queryParams: ChannelQueryParams, $id: String, $params: ChannelParams) {
channels(language: $language, queryParams: $queryParams, id: $id, params: $params) {
id
channelReferenceNumber
name
callLetter
number
logo {
key
url
__typename
}
language
hd
radio
replayable
ottReplayable
playable
ottPlayable
recordable
subscribed
cloudRecordable
catchUpWindowInHours
isOttNPVREnabled
ottNPVRStart
subscription {
channelRef
subscribed
upselling {
upsellable
packages
__typename
}
__typename
}
packages
__typename
}
}`
}
const result = await axios
.post('https://api.proximusmwc.be/tiams/v3/graphql', query)
.then(r => r.data)
.catch(console.error)
return (
result?.data?.channels
.filter(
channel =>
!channel.radio && (!lang || channel.language === (lang === 'de' ? 'ger' : lang))
)
.map(channel => {
return {
lang: channel.language === 'ger' ? 'de' : channel.language,
site_id: channel.id,
name: channel.name
}
}) || []
)
}
}
async function fetchApiVersion() {
// you'll never find what happened here :)
// load the pickx page and get the hash from the MWC configuration.
// it's not the best way to get the version but it's the only way to get it.
const hashUrl = 'https://www.pickx.be/nl/televisie/tv-gids'
const hashData = await axios
.get(hashUrl)
.then(r => {
const re = /"hashes":\["(.*)"\]/
const match = r.data.match(re)
if (match && match[1]) {
return match[1]
} else {
throw new Error('React app version hash not found')
}
})
.catch(console.error)
const versionUrl = `https://www.pickx.be/api/s-${hashData}`
const response = await axios.get(versionUrl, {
headers: {
Origin: 'https://www.pickx.be',
Referer: 'https://www.pickx.be/'
}
})
return new Promise((resolve, reject) => {
try {
if (response.status === 200) {
apiVersion = response.data.version
resolve()
} else {
console.error(`Failed to fetch API version. Status: ${response.status}`)
reject(`Failed to fetch API version. Status: ${response.status}`)
}
} catch (error) {
console.error('Error during fetchApiVersion:', error)
reject(error)
}
})
}

View file

@ -1,76 +1,69 @@
jest.mock('./pickx.be.config.js', () => {
const originalModule = jest.requireActual('./pickx.be.config.js')
return {
...originalModule,
fetchApiVersion: jest.fn(() => Promise.resolve())
}
})
const {
parser,
url,
request,
fetchApiVersion,
setApiVersion,
getApiVersion
} = require('./pickx.be.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const date = dayjs.utc('2023-12-13').startOf('d')
const channel = {
lang: 'fr',
site_id: 'UID0118',
xmltv_id: 'Vedia.be'
}
beforeEach(() => {
setApiVersion('mockedApiVersion')
})
it('can generate valid url', async () => {
const generatedUrl = await url({ channel, date })
expect(generatedUrl).toBe(
`https://px-epg.azureedge.net/airings/mockedApiVersion/2023-12-13/channel/UID0118?timezone=Europe%2FBrussels`
)
})
it('can generate valid request headers', () => {
expect(request.headers).toMatchObject({
Origin: 'https://www.pickx.be',
Referer: 'https://www.pickx.be/'
})
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
const result = parser({ content, channel, date }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(result[0]).toMatchObject({
start: '2023-12-12T23:55:00.000Z',
stop: '2023-12-13T00:15:00.000Z',
title: 'Le 22h30',
description: 'Le journal de vivre ici.',
category: 'Info',
image:
'https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/250_250_4B990CC58066A7B2A660AFA0BDDE5C41.jpg'
})
})
it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: ''
})
expect(result).toMatchObject([])
})
jest.mock('./pickx.be.config.js', () => {
const originalModule = jest.requireActual('./pickx.be.config.js')
return {
...originalModule,
fetchApiVersion: jest.fn(() => Promise.resolve())
}
})
const { parser, url, request, setApiVersion } = require('./pickx.be.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const date = dayjs.utc('2023-12-13').startOf('d')
const channel = {
lang: 'fr',
site_id: 'UID0118',
xmltv_id: 'Vedia.be'
}
beforeEach(() => {
setApiVersion('mockedApiVersion')
})
it('can generate valid url', async () => {
const generatedUrl = await url({ channel, date })
expect(generatedUrl).toBe(
'https://px-epg.azureedge.net/airings/mockedApiVersion/2023-12-13/channel/UID0118?timezone=Europe%2FBrussels'
)
})
it('can generate valid request headers', () => {
expect(request.headers).toMatchObject({
Origin: 'https://www.pickx.be',
Referer: 'https://www.pickx.be/'
})
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
const result = parser({ content, channel, date }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(result[0]).toMatchObject({
start: '2023-12-12T23:55:00.000Z',
stop: '2023-12-13T00:15:00.000Z',
title: 'Le 22h30',
description: 'Le journal de vivre ici.',
category: 'Info',
image:
'https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/250_250_4B990CC58066A7B2A660AFA0BDDE5C41.jpg'
})
})
it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: ''
})
expect(result).toMatchObject([])
})

View file

@ -1,102 +1,104 @@
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
module.exports = {
site: 'player.ee.co.uk',
days: 2,
url({ date, channel, hour = 0 }) {
return `https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=${
encodeURIComponent(channel.site_id)
}&interval=${date.format('YYYY-MM-DD')}T${hour.toString().padStart(2,'0')}Z/PT12H`
},
request: {
headers: {
Referer: 'https://player.ee.co.uk/'
}
},
async parser({ content, channel, date }) {
const programs = []
if (content) {
const schedule = JSON.parse(content)
// fetch next 12 hours schedule
const { url, request } = module.exports
const nextSchedule = await axios
.get(url({ channel, date, hour: 12 }), { headers: request.headers })
.then(response => response.data)
.catch(console.error)
if (schedule?.items) {
// merge schedules
if (nextSchedule?.items) {
schedule.items.push(...nextSchedule.items)
}
schedule.items.forEach(item => {
let season, episode
const start = dayjs.utc(item.publishedStartTime)
const stop = start.add(item.publishedDuration, 's')
const description = item.synopsis
if (description) {
const matches = description.trim().match(/\(?S(\d+)[\/\s]Ep(\d+)\)?/)
if (matches) {
if (matches[1]) {
season = parseInt(matches[1])
}
if (matches[2]) {
episode = parseInt(matches[2])
}
}
}
programs.push({
title: item.title,
description,
season,
episode,
start,
stop
})
})
}
}
return programs
},
async channels() {
const token =
'eyJkaXNjb3ZlcnlVc2VyR3JvdXBzIjpbIkFMTFVTRVJTIiwiYWxsIiwiaHR0cDovL3JlZmRhd' +
'GEueW91dmlldy5jb20vbXBlZzdjcy9Zb3VWaWV3QXBwbGljYXRpb25QbGF5ZXJDUy8yMDIxLT' +
'A5LTEwI2FuZHJvaWRfcnVudGltZS1wcm9maWxlMSIsInRhZzpidC5jb20sMjAxOC0wNy0xMTp' +
'1c2VyZ3JvdXAjR0JSLWJ0X25vd1RWX211bHRpY2FzdCIsInRhZzpidC5jb20sMjAyMS0xMC0y' +
'NTp1c2VyZ3JvdXAjR0JSLWJ0X2V1cm9zcG9ydCJdLCJyZWdpb25zIjpbIkFMTFJFR0lPTlMiL' +
'CJHQlIiLCJHQlItRU5HIiwiR0JSLUVORy1sb25kb24iLCJhbGwiXSwic3Vic2V0IjoiMy41Lj' +
'EvYW5kcm9pZF9ydW50aW1lLXByb2ZpbGUxL0JST0FEQ0FTVF9JUC9HQlItYnRfYnJvYWRiYW5' +
'kIiwic3Vic2V0cyI6WyIvLy8iLCIvL0JST0FEQ0FTVF9JUC8iLCIzLjUvLy8iXX0='
const extensions = [
'LinearCategoriesExtension',
'LogicalChannelNumberExtension',
'BTSubscriptionCodesExtension'
]
const result = await axios
.get(`https://api.youview.tv/metadata/linear/v2/linear-services`, {
params: {
contentTargetingToken: token,
extensions: extensions.join(',')
},
headers: module.exports.request.headers
})
.then(response => response.data)
.catch(console.error)
return result?.items
.filter(channel => channel.contentTypes.indexOf('tv') >= 0)
.map(channel => {
return {
lang: 'en',
site_id: channel.serviceLocator,
name: channel.fullName
}
}) || []
}
}
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
module.exports = {
site: 'player.ee.co.uk',
days: 2,
url({ date, channel, hour = 0 }) {
return `https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=${encodeURIComponent(
channel.site_id
)}&interval=${date.format('YYYY-MM-DD')}T${hour.toString().padStart(2, '0')}Z/PT12H`
},
request: {
headers: {
Referer: 'https://player.ee.co.uk/'
}
},
async parser({ content, channel, date }) {
const programs = []
if (content) {
const schedule = JSON.parse(content)
// fetch next 12 hours schedule
const { url, request } = module.exports
const nextSchedule = await axios
.get(url({ channel, date, hour: 12 }), { headers: request.headers })
.then(response => response.data)
.catch(console.error)
if (schedule?.items) {
// merge schedules
if (nextSchedule?.items) {
schedule.items.push(...nextSchedule.items)
}
schedule.items.forEach(item => {
let season, episode
const start = dayjs.utc(item.publishedStartTime)
const stop = start.add(item.publishedDuration, 's')
const description = item.synopsis
if (description) {
const matches = description.trim().match(/\(?S(\d+)[/\s]Ep(\d+)\)?/)
if (matches) {
if (matches[1]) {
season = parseInt(matches[1])
}
if (matches[2]) {
episode = parseInt(matches[2])
}
}
}
programs.push({
title: item.title,
description,
season,
episode,
start,
stop
})
})
}
}
return programs
},
async channels() {
const token =
'eyJkaXNjb3ZlcnlVc2VyR3JvdXBzIjpbIkFMTFVTRVJTIiwiYWxsIiwiaHR0cDovL3JlZmRhd' +
'GEueW91dmlldy5jb20vbXBlZzdjcy9Zb3VWaWV3QXBwbGljYXRpb25QbGF5ZXJDUy8yMDIxLT' +
'A5LTEwI2FuZHJvaWRfcnVudGltZS1wcm9maWxlMSIsInRhZzpidC5jb20sMjAxOC0wNy0xMTp' +
'1c2VyZ3JvdXAjR0JSLWJ0X25vd1RWX211bHRpY2FzdCIsInRhZzpidC5jb20sMjAyMS0xMC0y' +
'NTp1c2VyZ3JvdXAjR0JSLWJ0X2V1cm9zcG9ydCJdLCJyZWdpb25zIjpbIkFMTFJFR0lPTlMiL' +
'CJHQlIiLCJHQlItRU5HIiwiR0JSLUVORy1sb25kb24iLCJhbGwiXSwic3Vic2V0IjoiMy41Lj' +
'EvYW5kcm9pZF9ydW50aW1lLXByb2ZpbGUxL0JST0FEQ0FTVF9JUC9HQlItYnRfYnJvYWRiYW5' +
'kIiwic3Vic2V0cyI6WyIvLy8iLCIvL0JST0FEQ0FTVF9JUC8iLCIzLjUvLy8iXX0='
const extensions = [
'LinearCategoriesExtension',
'LogicalChannelNumberExtension',
'BTSubscriptionCodesExtension'
]
const result = await axios
.get('https://api.youview.tv/metadata/linear/v2/linear-services', {
params: {
contentTargetingToken: token,
extensions: extensions.join(',')
},
headers: module.exports.request.headers
})
.then(response => response.data)
.catch(console.error)
return (
result?.items
.filter(channel => channel.contentTypes.indexOf('tv') >= 0)
.map(channel => {
return {
lang: 'en',
site_id: channel.serviceLocator,
name: channel.fullName
}
}) || []
)
}
}

View file

@ -1,72 +1,74 @@
const { parser, url } = require('./player.ee.co.uk.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.utc('2023-12-13').startOf('d')
const channel = {
site_id: 'dvb://233a..6d60',
xmltv_id: 'HGTV.uk'
}
axios.get.mockImplementation((url, opts) => {
if (url === 'https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=dvb%3A%2F%2F233a..6d60&interval=2023-12-13T12Z/PT12H') {
return Promise.resolve({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/data1.json')))
})
}
return Promise.resolve({ data: '' })
})
it('can generate valid url', () => {
expect(url({ date, channel })).toBe(
'https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=dvb%3A%2F%2F233a..6d60&interval=2023-12-13T00Z/PT12H'
)
})
it('can parse response', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
const result = (await parser({ content, channel, date }))
.map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(result).toMatchObject([
{
title: 'Bargain Mansions',
description:
'Tamara and her dad help a recent widow who loves to cook for her family design her dream kitchen, perfect for entertaining and large gatherings. S4/Ep1',
season: 4,
episode: 1,
start: '2023-12-13T13:00:00.000Z',
stop: '2023-12-13T14:00:00.000Z'
},
{
title: 'Flip Or Flop',
description:
'Tarek and Christina are contacted by a cash strapped flipper who needs to unload a project house. S2/Ep2',
season: 2,
episode: 2,
start: '2023-12-13T14:00:00.000Z',
stop: '2023-12-13T14:30:00.000Z'
}
])
})
it('can handle empty guide', async () => {
const result = await parser({
channel,
date,
content: ''
})
expect(result).toMatchObject([])
})
const { parser, url } = require('./player.ee.co.uk.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.utc('2023-12-13').startOf('d')
const channel = {
site_id: 'dvb://233a..6d60',
xmltv_id: 'HGTV.uk'
}
axios.get.mockImplementation(url => {
if (
url ===
'https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=dvb%3A%2F%2F233a..6d60&interval=2023-12-13T12Z/PT12H'
) {
return Promise.resolve({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/data1.json')))
})
}
return Promise.resolve({ data: '' })
})
it('can generate valid url', () => {
expect(url({ date, channel })).toBe(
'https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=dvb%3A%2F%2F233a..6d60&interval=2023-12-13T00Z/PT12H'
)
})
it('can parse response', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
const result = (await parser({ content, channel, date })).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(result).toMatchObject([
{
title: 'Bargain Mansions',
description:
'Tamara and her dad help a recent widow who loves to cook for her family design her dream kitchen, perfect for entertaining and large gatherings. S4/Ep1',
season: 4,
episode: 1,
start: '2023-12-13T13:00:00.000Z',
stop: '2023-12-13T14:00:00.000Z'
},
{
title: 'Flip Or Flop',
description:
'Tarek and Christina are contacted by a cash strapped flipper who needs to unload a project house. S2/Ep2',
season: 2,
episode: 2,
start: '2023-12-13T14:00:00.000Z',
stop: '2023-12-13T14:30:00.000Z'
}
])
})
it('can handle empty guide', async () => {
const result = await parser({
channel,
date,
content: ''
})
expect(result).toMatchObject([])
})

View file

@ -43,7 +43,7 @@ module.exports = {
const axios = require('axios')
const data = await axios
.post(
`https://playtv.unifi.com.my:7053/VSP/V3/QueryAllChannel`,
'https://playtv.unifi.com.my:7053/VSP/V3/QueryAllChannel',
{ isReturnAllMedia: '0' },
{
params: {
@ -74,7 +74,7 @@ function parseItems(content, channel) {
const channelData = data.find(i => i.id == channel.site_id)
return channelData.items && Array.isArray(channelData.items) ? channelData.items : []
} catch (err) {
} catch {
return []
}
}

View file

@ -46,10 +46,10 @@ module.exports = {
return programs
},
async channels({ country, lang }) {
async channels() {
const axios = require('axios')
const data = await axios
.get(`https://www.programetv.ro/api/station/index/`)
.get('https://www.programetv.ro/api/station/index/')
.then(r => r.data)
.catch(console.log)

View file

@ -62,7 +62,7 @@ module.exports = {
$('.channelList-listItemsLink').each((i, el) => {
const name = $(el).attr('title')
const url = $(el).attr('href')
const [, site_id] = url.match(/\/programme\-(.*)\.html$/i)
const [, site_id] = url.match(/\/programme-(.*)\.html$/i)
channels.push({
lang: 'fr',

View file

@ -51,7 +51,7 @@ module.exports = {
return data.programmes.map(item => {
const site_id = item.url.replace('/', '')
const name = site_id.replace(/\-/gi, ' ')
const name = site_id.replace(/-/gi, ' ')
return {
lang: 'fr',

View file

@ -13,19 +13,17 @@ module.exports = {
site: 'programme.tvb.com',
days: 2,
url({ channel, date, time = null }) {
return `https://programme.tvb.com/api/schedule?input_date=${
date.format('YYYYMMDD')
}&network_code=${channel.site_id}&_t=${time ? time : parseInt(Date.now() / 1000)}`
return `https://programme.tvb.com/api/schedule?input_date=${date.format(
'YYYYMMDD'
)}&network_code=${channel.site_id}&_t=${time ? time : parseInt(Date.now() / 1000)}`
},
parser({ content, channel, date }) {
const programs = []
const data = content ? JSON.parse(content) : {}
if (Array.isArray(data.data?.list)) {
const dt = date.format('YYYY-MM-DD')
for (const d of data.data.list) {
if (Array.isArray(d.schedules)) {
const schedules = d.schedules
.filter(s => s.network_code === channel.site_id)
const schedules = d.schedules.filter(s => s.network_code === channel.site_id)
schedules.forEach((s, i) => {
const start = dayjs.tz(s.event_datetime, 'YYYY-MM-DD HH:mm:ss', tz)
let stop
@ -64,7 +62,7 @@ module.exports = {
if (assets) {
queues.push(...assets.map(a => base + '/' + a))
} else {
const metadata = content.match(/e\=(\[(.*?)\])/)
const metadata = content.match(/e=(\[(.*?)\])/)
if (metadata) {
const infos = eval(metadata[1])
if (Array.isArray(infos)) {

View file

@ -34,7 +34,7 @@ it('can parse response (en)', () => {
expect(results[1]).toMatchObject({
start: '2024-12-06T15:55:00.000Z',
stop: '2024-12-06T16:55:00.000Z',
title: 'Line Walker: Bull Fight#16[Can][PG]',
title: 'Line Walker: Bull Fight#16[Can][PG]'
})
})
@ -51,7 +51,7 @@ it('can parse response (zh)', () => {
stop: '2024-12-06T16:55:00.000Z',
title: '使徒行者3#16[粵][PG]',
description:
'文鼎從淑梅手上救走大聖爺兒子,大聖爺還恩於歡喜,答允支持九指強。崇聯社定下選舉日子,恰巧是韋傑出獄之日,頭目們顧念舊日恩義,紛紛轉投浩洋。浩洋帶亞希逛傢俬店,憧憬二人未來。亞希向家強承認愛上浩洋,要求退出臥底任務。作榮與歡喜暗中會面,將國際犯罪組織「永恆幫」情報交給他。阿火遭家強出賣,到沐足店搶錢。家強逮住阿火,惟被合星誤會而受拘捕。家強把正植遺下的頸鏈和學生證交還,合星意識到家強已知悉正植身世。',
'文鼎從淑梅手上救走大聖爺兒子,大聖爺還恩於歡喜,答允支持九指強。崇聯社定下選舉日子,恰巧是韋傑出獄之日,頭目們顧念舊日恩義,紛紛轉投浩洋。浩洋帶亞希逛傢俬店,憧憬二人未來。亞希向家強承認愛上浩洋,要求退出臥底任務。作榮與歡喜暗中會面,將國際犯罪組織「永恆幫」情報交給他。阿火遭家強出賣,到沐足店搶錢。家強逮住阿火,惟被合星誤會而受拘捕。家強把正植遺下的頸鏈和學生證交還,合星意識到家強已知悉正植身世。'
})
})

View file

@ -50,7 +50,7 @@ module.exports = {
$('ul.channelList a').each((i, el) => {
const name = $(el).text()
const url = $(el).attr('href')
const [, site_id] = url.match(/^\/program\-tv\/(.*)$/i)
const [, site_id] = url.match(/^\/program-tv\/(.*)$/i)
channels.push({
lang: 'pl',

View file

@ -58,7 +58,7 @@ function parseItems(content, channel) {
let data
try {
data = JSON.parse(content)
} catch (error) {
} catch {
return []
}

View file

@ -11,9 +11,7 @@ dayjs.extend(timezone)
dayjs.extend(utc)
dayjs.extend(customParseFormat)
doFetch
.setCheckResult(false)
.setDebugger(debug)
doFetch.setCheckResult(false).setDebugger(debug)
const tz = 'Asia/Riyadh'
const defaultHeaders = {
@ -47,7 +45,7 @@ module.exports = {
headers: {
...defaultHeaders,
'X-Requested-With': 'XMLHttpRequest',
cookie: cookies[channel.lang],
cookie: cookies[channel.lang]
}
}
queues.push({ i: item, url, params })
@ -61,7 +59,7 @@ module.exports = {
},
async channels({ lang = 'en' }) {
const result = await axios
.get(`https://rotana.net/api/channels`)
.get('https://rotana.net/api/channels')
.then(response => response.data)
.catch(console.error)
@ -88,34 +86,37 @@ function parseProgram(item, result) {
item.description = desc
}
}
break;
break
case 'Element':
if (el.name === 'span') {
const [k, v] = $(el).text().split(':').map(a => a.trim())
const [k, v] = $(el)
.text()
.split(':')
.map(a => a.trim())
switch (k) {
case 'Category':
case 'التصنيف':
item.category = v;
break;
item.category = v
break
case 'Country':
case 'البلد':
item.country = v;
break;
item.country = v
break
case 'Director':
case 'المخرج':
item.director = v;
break;
item.director = v
break
case 'Language':
case 'اللغة':
item.language = v;
break;
item.language = v
break
case 'Release Year':
case 'سنة الإصدار':
item.date = v;
break;
item.date = v
break
}
}
break;
break
}
}
}
@ -142,7 +143,9 @@ function parseItems(content, date) {
const heading = top.find('.iq-accordion-title .big-title')
if (heading.length) {
const progId = top.attr('id')
const title = heading.find('span:eq(1)').text()
const title = heading
.find('span:eq(1)')
.text()
.split('\n')
.map(a => a.trim())
.join(' ')
@ -151,7 +154,7 @@ function parseItems(content, date) {
items.push({
program: progId.substr(progId.indexOf('-') + 1),
title: title ? title.trim() : title,
start: `${y}-${m}-${d} ${time.trim()}`,
start: `${y}-${m}-${d} ${time.trim()}`
})
}
}

View file

@ -19,7 +19,7 @@ const channel = {
}
const channelAr = Object.assign({}, channel, { lang: 'ar' })
axios.get.mockImplementation((url, opts) => {
axios.get.mockImplementation(url => {
if (url === 'https://rotana.net/en/streams?channel=439&itemId=736970') {
return Promise.resolve({
data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html'))
@ -52,11 +52,13 @@ it('can generate valid arabic url', () => {
})
it('can parse english response', async () => {
const result = (await parser({
channel,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html'))
})).map(a => {
const result = (
await parser({
channel,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html'))
})
).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
@ -69,17 +71,20 @@ it('can parse english response', async () => {
title: 'Khiyana Mashroua',
description:
'Hisham knows that his father has given all his wealth to his elder brother. This leads him to plan to kill his brother to make it look like a defense of honor, which he does by killing his wife along...',
image: 'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
image:
'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
category: 'Movie'
})
})
it('can parse arabic response', async () => {
const result = (await parser({
channel: channelAr,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html'))
})).map(a => {
const result = (
await parser({
channel: channelAr,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html'))
})
).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
@ -92,7 +97,8 @@ it('can parse arabic response', async () => {
title: 'خيانة مشروعة',
description:
'يعلم هشام البحيري أن والده قد حرمه من الميراث، ووهب كل ثروته لشقيقه اﻷكبر، وهو ما يدفعه لتدبير جريمة قتل شقيقه لتبدو وكأنها دفاع عن الشرف، وذلك حين يقتل هشام زوجته مع شقيقه.',
image: 'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
image:
'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
category: 'فيلم'
})
})

View file

@ -53,7 +53,7 @@ async function parseItems(buffer) {
let data
try {
data = await pdf(buffer)
} catch (err) {
} catch {
return []
}

View file

@ -1,5 +1,4 @@
const _ = require('lodash')
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')

View file

@ -1,5 +1,5 @@
const dayjs = require('dayjs')
const duration = require("dayjs/plugin/duration")
const duration = require('dayjs/plugin/duration')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
@ -10,72 +10,74 @@ dayjs.extend(customParseFormat)
dayjs.extend(duration)
module.exports = {
site: 's.mxtv.jp',
days: 1,
lang: 'ja',
url: function ({ date, channel }) {
const id = `SV${channel.site_id}EPG${date.format('YYYYMMDD')}`
return `https://s.mxtv.jp/bangumi_file/json01/${id}.json`
},
parser: function ({ content }) {
let programs = []
const items = parseItems(content)
items.forEach(item => {
programs.push({
title: item.Event_name,
description: item.Event_text,
category: parseCategory(item),
image: parseImage(item),
start: parseStart(item),
stop: parseStop(item)
})
})
return programs
},
channels() {
return [
{
lang: 'ja',
site_id: '1',
name: 'Tokyo MX1',
xmltv_id: 'TokyoMX1.jp'
},
{
lang: 'ja',
site_id: '2',
name: 'Tokyo MX2',
xmltv_id: 'TokyoMX2.jp'
}
]
}
site: 's.mxtv.jp',
days: 1,
lang: 'ja',
url: function ({ date, channel }) {
const id = `SV${channel.site_id}EPG${date.format('YYYYMMDD')}`
return `https://s.mxtv.jp/bangumi_file/json01/${id}.json`
},
parser: function ({ content }) {
let programs = []
const items = parseItems(content)
items.forEach(item => {
programs.push({
title: item.Event_name,
description: item.Event_text,
category: parseCategory(item),
image: parseImage(item),
start: parseStart(item),
stop: parseStop(item)
})
})
return programs
},
channels() {
return [
{
lang: 'ja',
site_id: '1',
name: 'Tokyo MX1',
xmltv_id: 'TokyoMX1.jp'
},
{
lang: 'ja',
site_id: '2',
name: 'Tokyo MX2',
xmltv_id: 'TokyoMX2.jp'
}
]
}
}
function parseImage(item) {
// Should return a string if we can output an image URL
// Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ?
return null
function parseImage() {
// Should return a string if we can output an image URL
// Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ?
return null
}
function parseCategory(item) {
// Should return a string if we can determine the category
// Might be done with `https://s.mxtv.jp/index_set/csv/ranking_bangumi_allU.csv` ?
return null
function parseCategory() {
// Should return a string if we can determine the category
// Might be done with `https://s.mxtv.jp/index_set/csv/ranking_bangumi_allU.csv` ?
return null
}
function parseStart(item) {
return dayjs.tz(item.Start_time.toString(), 'YYYY年MM月DD日HH時mm分ss秒', 'Asia/Tokyo')
return dayjs.tz(item.Start_time.toString(), 'YYYY年MM月DD日HH時mm分ss秒', 'Asia/Tokyo')
}
function parseStop(item) {
// Add the duration to the start time
const durationDate = dayjs(item.Duration, 'HH:mm:ss');
return parseStart(item).add(dayjs.duration({
hours: durationDate.hour(),
minutes: durationDate.minute(),
seconds: durationDate.second()
}))
// Add the duration to the start time
const durationDate = dayjs(item.Duration, 'HH:mm:ss')
return parseStart(item).add(
dayjs.duration({
hours: durationDate.hour(),
minutes: durationDate.minute(),
seconds: durationDate.second()
})
)
}
function parseItems(content) {
return JSON.parse(content) || []
return JSON.parse(content) || []
}

View file

@ -11,7 +11,8 @@ const channel = {
name: 'Tokyo MX2',
xmltv_id: 'TokyoMX2.jp'
}
const content = `[{ "Event_id": "0x6a57", "Start_time": "2024年07月27日05時00分00秒", "Duration": "01:00:00", "Event_name": "ヒーリングタイム&ヘッドラインニュース", "Event_text": "ねこの足跡", "Component": "480i 16:9 パンベクトルなし", "Sound": "ステレオ", "Event_detail": ""}]`
const content =
'[{ "Event_id": "0x6a57", "Start_time": "2024年07月27日05時00分00秒", "Duration": "01:00:00", "Event_name": "ヒーリングタイム&ヘッドラインニュース", "Event_text": "ねこの足跡", "Component": "480i 16:9 パンベクトルなし", "Sound": "ステレオ", "Event_detail": ""}]'
it('can generate valid url', () => {
const result = url({ date, channel })

View file

@ -114,7 +114,7 @@ module.exports = {
const $ = cheerio.load(data)
$('.main-container-channels-events > .container-channel-events').each((i, el) => {
const name = $(el).find('.channel-title').text().trim()
const channelId = name.replace(/\s\&\s/gi, ' &amp; ')
const channelId = name.replace(/\s&\s/gi, ' &amp; ')
if (!name) return

View file

@ -1,74 +1,79 @@
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: 'shahid.mbc.net',
days: 2,
url({ channel, date}) {
return `https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${channel.site_id}&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format('YYYY-MM-DD')}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}`
},
parser({ content, channel }) {
const programs = parseItems(content, channel)
.map(item => {
return {
title: item.title,
description: item.description,
session: item.seasonNumber,
episode: item.episodeNumber,
start: dayjs.tz(item.actualFrom, 'Asia/Riyadh').toISOString(),
stop: dayjs.tz(item.actualTo, 'Asia/Riyadh').toISOString()
}
})
return programs
},
async channels({lang = 'en'}) {
const axios = require('axios')
const items = []
let page = 0
while (true) {
const result = await axios
.get(`https://api2.shahid.net/proxy/v2.1/product/filter?filter=%7B"pageNumber":${page},"pageSize":100,"productType":"LIVESTREAM","productSubType":"LIVE_CHANNEL"%7D&country=SA&language=${lang}&Accept-Language=${lang}`)
.then(response => response.data)
.catch(console.error)
if (result.productList) {
items.push(...result.productList.products)
if (result.productList.hasMore) {
page++
continue
}
}
break;
}
const channels = items.map(channel => {
return {
lang,
site_id: channel.id,
name: channel.title
}
})
return channels
}
}
function parseItems(content, channel) {
const items = []
content = content ? JSON.parse(content) : []
if (content.items) {
content.items.forEach(schedules => {
if (schedules.channelId == channel.site_id) {
items.push(...schedules.items)
return true
}
})
}
return items
}
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: 'shahid.mbc.net',
days: 2,
url({ channel, date }) {
return `https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${
channel.site_id
}&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format(
'YYYY-MM-DD'
)}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}`
},
parser({ content, channel }) {
const programs = parseItems(content, channel).map(item => {
return {
title: item.title,
description: item.description,
session: item.seasonNumber,
episode: item.episodeNumber,
start: dayjs.tz(item.actualFrom, 'Asia/Riyadh').toISOString(),
stop: dayjs.tz(item.actualTo, 'Asia/Riyadh').toISOString()
}
})
return programs
},
async channels({ lang = 'en' }) {
const axios = require('axios')
const items = []
let page = 0
while (true) {
const result = await axios
.get(
`https://api2.shahid.net/proxy/v2.1/product/filter?filter=%7B"pageNumber":${page},"pageSize":100,"productType":"LIVESTREAM","productSubType":"LIVE_CHANNEL"%7D&country=SA&language=${lang}&Accept-Language=${lang}`
)
.then(response => response.data)
.catch(console.error)
if (result.productList) {
items.push(...result.productList.products)
if (result.productList.hasMore) {
page++
continue
}
}
break
}
const channels = items.map(channel => {
return {
lang,
site_id: channel.id,
name: channel.title
}
})
return channels
}
}
function parseItems(content, channel) {
const items = []
content = content ? JSON.parse(content) : []
if (content.items) {
content.items.forEach(schedules => {
if (schedules.channelId == channel.site_id) {
items.push(...schedules.items)
return true
}
})
}
return items
}

View file

@ -1,36 +1,40 @@
const { url, parser } = require('./shahid.mbc.net.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const date = dayjs.utc('2023-11-11').startOf('d')
const channel = { site_id: '996520', xmltv_id: 'AlAanTV.ae', lang: 'en' }
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
`https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${channel.site_id}&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format('YYYY-MM-DD')}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}`
)
})
it('can parse response', () => {
const content =
'{"items":[{"channelId":"996520","items":[{"actualFrom":"2023-11-11T00:00:00.000+00:00","actualTo":"2023-11-11T00:30:00.000+00:00","description":"The presenter reviews the most prominent episodes of news programs produced by the channel\'s team on a weekly basis, which include the most important global updates and developments at all levels.","duration":null,"emptySlot":false,"episodeNumber":194,"from":"2023-11-11T00:00:00.000+00:00","genres":["TV Show"],"productId":null,"productionYear":null,"productPoster":"https://imagesmbc.whatsonindia.com/dasimages/landscape/1920x1080/F968D4A39DB25793E9EED1BDAFBAD2EA8A8F9B30Z.jpg","productSubType":null,"productType":null,"replay":false,"restritectContent":null,"seasonId":null,"seasonNumber":"1","showId":null,"streamInfo":null,"title":"Menassaatona Fi Osboo\'","to":"2023-11-11T00:30:00.000+00:00"}]}]}'
const result = parser({ content, channel, date })
expect(result).toMatchObject([
{
start: '2023-11-10T21:00:00.000Z',
stop: '2023-11-10T21:30:00.000Z',
title: 'Menassaatona Fi Osboo\'',
description:
'The presenter reviews the most prominent episodes of news programs produced by the channel\'s team on a weekly basis, which include the most important global updates and developments at all levels.'
}
])
})
it('can handle empty guide', () => {
const result = parser({ content: '' })
expect(result).toMatchObject([])
})
const { url, parser } = require('./shahid.mbc.net.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const date = dayjs.utc('2023-11-11').startOf('d')
const channel = { site_id: '996520', xmltv_id: 'AlAanTV.ae', lang: 'en' }
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
`https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${
channel.site_id
}&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format(
'YYYY-MM-DD'
)}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}`
)
})
it('can parse response', () => {
const content =
'{"items":[{"channelId":"996520","items":[{"actualFrom":"2023-11-11T00:00:00.000+00:00","actualTo":"2023-11-11T00:30:00.000+00:00","description":"The presenter reviews the most prominent episodes of news programs produced by the channel\'s team on a weekly basis, which include the most important global updates and developments at all levels.","duration":null,"emptySlot":false,"episodeNumber":194,"from":"2023-11-11T00:00:00.000+00:00","genres":["TV Show"],"productId":null,"productionYear":null,"productPoster":"https://imagesmbc.whatsonindia.com/dasimages/landscape/1920x1080/F968D4A39DB25793E9EED1BDAFBAD2EA8A8F9B30Z.jpg","productSubType":null,"productType":null,"replay":false,"restritectContent":null,"seasonId":null,"seasonNumber":"1","showId":null,"streamInfo":null,"title":"Menassaatona Fi Osboo\'","to":"2023-11-11T00:30:00.000+00:00"}]}]}'
const result = parser({ content, channel, date })
expect(result).toMatchObject([
{
start: '2023-11-10T21:00:00.000Z',
stop: '2023-11-10T21:30:00.000Z',
title: "Menassaatona Fi Osboo'",
description:
"The presenter reviews the most prominent episodes of news programs produced by the channel's team on a weekly basis, which include the most important global updates and developments at all levels."
}
])
})
it('can handle empty guide', () => {
const result = parser({ content: '' })
expect(result).toMatchObject([])
})

View file

@ -40,7 +40,7 @@ module.exports = {
const cheerio = require('cheerio')
const data = await axios
.get(`https://www.singtel.com/personal/products-services/tv/tv-programme-guide`)
.get('https://www.singtel.com/personal/products-services/tv/tv-programme-guide')
.then(r => r.data)
.catch(console.log)
@ -62,7 +62,7 @@ function parseItems(content, channel) {
try {
const data = JSON.parse(content)
return data && data[channel.site_id] ? data[channel.site_id] : []
} catch (err) {
} catch {
return []
}
}

View file

@ -44,7 +44,7 @@ module.exports = {
const cheerio = require('cheerio')
const data = await axios
.get(`https://sjonvarp.is/`)
.get('https://sjonvarp.is/')
.then(r => r.data)
.catch(console.log)

View file

@ -12,9 +12,7 @@ module.exports = {
site: 'sky.com',
days: 2,
url({ date, channel }) {
return `https://awk.epgsky.com/hawk/linear/schedule/${
date.format('YYYYMMDD')
}/${
return `https://awk.epgsky.com/hawk/linear/schedule/${date.format('YYYYMMDD')}/${
channel.site_id
}`
},
@ -27,19 +25,18 @@ module.exports = {
.filter(schedule => schedule.sid === channel.site_id)
.forEach(schedule => {
if (Array.isArray(schedule.events)) {
schedule.events
.forEach(event => {
const start = dayjs.utc(event.st * 1000)
const stop = start.add(event.d, 's')
programs.push({
title: event.t,
description: event.sy,
season: event.seasonnumber,
episode: event.episodenumber,
start,
stop
})
schedule.events.forEach(event => {
const start = dayjs.utc(event.st * 1000)
const stop = start.add(event.d, 's')
programs.push({
title: event.t,
description: event.sy,
season: event.seasonnumber,
episode: event.episodenumber,
start,
stop
})
})
}
})
}
@ -55,10 +52,12 @@ module.exports = {
if (queue.t === 'r') {
const $ = cheerio.load(res)
const initialData = JSON.parse(decodeURIComponent($('#initialData').text()))
initialData.state.epgData.regions
.forEach(region => {
queues.push({ t: 'c', url: `https://awk.epgsky.com/hawk/linear/services/${region.bouquet}/${region.subBouquet}` })
initialData.state.epgData.regions.forEach(region => {
queues.push({
t: 'c',
url: `https://awk.epgsky.com/hawk/linear/services/${region.bouquet}/${region.subBouquet}`
})
})
}
// process channels
if (queue.t === 'c') {

View file

@ -15,9 +15,7 @@ const channel = {
}
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
'https://awk.epgsky.com/hawk/linear/schedule/20241214/4086'
)
expect(url({ channel, date })).toBe('https://awk.epgsky.com/hawk/linear/schedule/20241214/4086')
})
it('can parse response', () => {
@ -34,7 +32,7 @@ it('can parse response', () => {
stop: '2024-12-13T23:00:00.000Z',
title: 'The UnXplained With...',
description:
'The Hunt for Jack the Ripper: Jack the Ripper\'s identity has eluded police, historians and armchair detectives for over a century. What do we know about the notorious killer? (S3, ep 21)',
"The Hunt for Jack the Ripper: Jack the Ripper's identity has eluded police, historians and armchair detectives for over a century. What do we know about the notorious killer? (S3, ep 21)",
season: 4,
episode: 14
})

View file

@ -1,4 +1,4 @@
const { parser, url, request } = require('./skylife.co.kr.config.js')
const { parser, url } = require('./skylife.co.kr.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -12,103 +12,110 @@ dayjs.extend(customParseFormat)
dayjs.extend(duration)
const exported = {
site: 'skyperfectv.co.jp',
days: 1,
lang: 'ja',
url: function ({ date, channel }) {
let [type, ...code] = channel.site_id.split('_')
code = code.join('_')
return `https://www.skyperfectv.co.jp/program/schedule/${type}/channel:${code}/date:${date.format('YYMMDD')}`
},
logo: function ({ channel }) {
return `https://www.skyperfectv.co.jp/library/common/img/channel/icon/basic/m_${channel.site_id.toLowerCase()}.gif`
},
// Specific function that permits to gather NSFW channels (needs confirmation)
async fetchSchedule({ date, channel }) {
const url = exported.url({ date, channel })
const response = await axios.get(url, {
headers: {
'Cookie': 'adult_auth=true'
}
site: 'skyperfectv.co.jp',
days: 1,
lang: 'ja',
url: function ({ date, channel }) {
let [type, ...code] = channel.site_id.split('_')
code = code.join('_')
return `https://www.skyperfectv.co.jp/program/schedule/${type}/channel:${code}/date:${date.format(
'YYMMDD'
)}`
},
logo: function ({ channel }) {
return `https://www.skyperfectv.co.jp/library/common/img/channel/icon/basic/m_${channel.site_id.toLowerCase()}.gif`
},
// Specific function that permits to gather NSFW channels (needs confirmation)
async fetchSchedule({ date, channel }) {
const url = exported.url({ date, channel })
const response = await axios.get(url, {
headers: {
Cookie: 'adult_auth=true'
}
})
return response.data
},
parser({ content, date }) {
const $ = cheerio.load(content)
const programs = []
const sections = [
{ id: 'js-am', addition: 0 },
{ id: 'js-pm', addition: 0 },
{ id: 'js-md', addition: 1 }
]
sections.forEach(({ id, addition }) => {
$(`#${id} > td`).each((index, element) => {
// `td` is a column for a day
// the next `td` will be the next day
const today = date.add(index + addition, 'd').tz('Asia/Tokyo')
const parseTime = timeString => {
// timeString is in the format "HH:mm"
// replace `today` with the time from timeString
const [hour, minute] = timeString.split(':').map(Number)
return today.hour(hour).minute(minute)
}
const $element = $(element) // Wrap element with Cheerio
$element.find('.p-program__item').each((itemIndex, itemElement) => {
const $itemElement = $(itemElement) // Wrap itemElement with Cheerio
const [start, stop] = $itemElement
.find('.p-program__range')
.first()
.text()
.split('〜')
.map(parseTime)
const title = $itemElement.find('.p-program__name').first().text()
const image = $itemElement.find('.js-program_thumbnail').first().attr('data-lazysrc')
programs.push({
title,
start,
stop,
image
})
})
return response.data
},
parser({ content, date }) {
const $ = cheerio.load(content)
const programs = []
})
})
const sections = [
{ id: 'js-am', addition: 0 },
{ id: 'js-pm', addition: 0 },
{ id: 'js-md', addition: 1 }
]
return programs
},
async channels() {
const pageParser = (content, type) => {
// type: "basic" | "premium"
// Returns an array of channel objects
sections.forEach(({ id, addition }) => {
$(`#${id} > td`).each((index, element) => {
// `td` is a column for a day
// the next `td` will be the next day
const today = date.add(index + addition, 'd').tz('Asia/Tokyo')
const $ = cheerio.load(content)
const channels = []
const parseTime = (timeString) => {
// timeString is in the format "HH:mm"
// replace `today` with the time from timeString
const [hour, minute] = timeString.split(':').map(Number)
return today.hour(hour).minute(minute)
}
$('.p-channel').each((index, element) => {
const site_id = `${type}_${$(element).find('.p-channel__id').text()}`
const name = $(element).find('.p-channel__name').text()
channels.push({ site_id, name, lang: 'ja' })
})
const $element = $(element) // Wrap element with Cheerio
$element.find('.p-program__item').each((itemIndex, itemElement) => {
const $itemElement = $(itemElement) // Wrap itemElement with Cheerio
const [start, stop] = $itemElement.find('.p-program__range').first().text().split('〜').map(parseTime)
const title = $itemElement.find('.p-program__name').first().text()
const image = $itemElement.find('.js-program_thumbnail').first().attr('data-lazysrc')
programs.push({
title,
start,
stop,
image
})
})
})
})
return programs
},
async channels() {
const pageParser = (content, type) => {
// type: "basic" | "premium"
// Returns an array of channel objects
const $ = cheerio.load(content)
const channels = []
$('.p-channel').each((index, element) => {
const site_id = `${type}_${$(element).find('.p-channel__id').text()}`
const name = $(element).find('.p-channel__name').text()
channels.push({ site_id, name, lang: 'ja' })
})
return channels
}
const getChannels = async (type) => {
const response = await axios.get(`https://www.skyperfectv.co.jp/program/schedule/${type}/`, {
headers: {
'Cookie': 'adult_auth=true;'
}
})
return pageParser(response.data, type)
}
const fetchAllChannels = async () => {
const basicChannels = await getChannels('basic')
const premiumChannels = await getChannels('premium')
const results = [...basicChannels, ...premiumChannels]
return results
}
return await fetchAllChannels()
return channels
}
const getChannels = async type => {
const response = await axios.get(`https://www.skyperfectv.co.jp/program/schedule/${type}/`, {
headers: {
Cookie: 'adult_auth=true;'
}
})
return pageParser(response.data, type)
}
const fetchAllChannels = async () => {
const basicChannels = await getChannels('basic')
const premiumChannels = await getChannels('premium')
const results = [...basicChannels, ...premiumChannels]
return results
}
return await fetchAllChannels()
}
}
module.exports = exported
module.exports = exported

File diff suppressed because one or more lines are too long

View file

@ -1,97 +1,86 @@
const cheerio = require('cheerio')
const { DateTime } = require('luxon')
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 channel = [{ site_id: '1208', xmltv_id: 'AlAoula.ma', lang: 'ar' },
{ site_id: '4069', xmltv_id: 'Laayoune.ma', lang: 'ar' },
{ site_id: '4070', xmltv_id: 'Arryadia.ma', lang: 'ar' },
{ site_id: '4071', xmltv_id: 'Athaqafia.ma', lang: 'ar' },
{ site_id: '4072', xmltv_id: 'AlMaghribia.ma', lang: 'ar' },
{ site_id: '4073', xmltv_id: 'Assadissa.ma', lang: 'ar' },
{ site_id: '4075', xmltv_id: 'Tamazight.ma', lang: 'ar' }]
module.exports = {
site: 'snrt.ma',
channels: 'snrt.ma.channels.xml',
days: 2,
url: function ({ channel }) {
return `https://www.snrt.ma/ar/node/${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('data-date', date.format('YYYYMMDD'))
params.append('current_date', date.format('YYYYMMDD'))
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),
description: parseDescription($item),
category: parseCategory($item),
start,
stop
})
})
return programs
}
}
function parseStart($item, date) {
const timeString = $item('.grille-time').text().trim()
const [hours, minutes] = timeString.split('H').map(Number)
const formattedTime = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:00`
const dateString = `${date.format('YYYY-MM-DD')} ${formattedTime}`
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm:ss', 'Africa/Casablanca')
}
function parseTitle($item) {
return $item('.program-title-sm').text().trim()
}
function parseDescription($item) {
return $item('.program-description-sm').text().trim()
}
function parseCategory($item) {
return $item('.genre-first').text().trim()
}
function parseItems(content) {
const $ = cheerio.load(content)
return $('.grille-line').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: 'snrt.ma',
channels: 'snrt.ma.channels.xml',
days: 2,
url: function ({ channel }) {
return `https://www.snrt.ma/ar/node/${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('data-date', date.format('YYYYMMDD'))
params.append('current_date', date.format('YYYYMMDD'))
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),
description: parseDescription($item),
category: parseCategory($item),
start,
stop
})
})
return programs
}
}
function parseStart($item, date) {
const timeString = $item('.grille-time').text().trim()
const [hours, minutes] = timeString.split('H').map(Number)
const formattedTime = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:00`
const dateString = `${date.format('YYYY-MM-DD')} ${formattedTime}`
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm:ss', 'Africa/Casablanca')
}
function parseTitle($item) {
return $item('.program-title-sm').text().trim()
}
function parseDescription($item) {
return $item('.program-description-sm').text().trim()
}
function parseCategory($item) {
return $item('.genre-first').text().trim()
}
function parseItems(content) {
const $ = cheerio.load(content)
return $('.grille-line').toArray()
}

View file

@ -1,6 +1,4 @@
const { parser, url } = require('./snrt.ma.config.js')
const cheerio = require('cheerio')
const { DateTime } = require('luxon')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
@ -25,16 +23,14 @@ it('can parse response', () => {
})
expect(results[0]).toMatchObject({
"category": "القرآن الكريم",
"description": "",
"start": "2024-12-19T06:00:00.000Z",
"stop": "2024-12-19T06:10:00.000Z",
"stop": "2024-12-19T06:30:00.000Z",
"title": "ﺍﻟﺴﻼﻡ ﺍﻟﻮﻃﻨﻲ + ﺍﻟﻘﺮﺁﻥ ﺍﻟﻜﺮﻳﻢ"
category: 'القرآن الكريم',
description: '',
start: '2024-12-19T06:00:00.000Z',
stop: '2024-12-19T06:30:00.000Z',
title: 'ﺍﻟﺴﻼﻡ ﺍﻟﻮﻃﻨﻲ + ﺍﻟﻘﺮﺁﻥ ﺍﻟﻜﺮﻳﻢ'
})
})
it('can handle empty guide', () => {
const result = parser({
date,
@ -42,4 +38,4 @@ it('can handle empty guide', () => {
content: '<!DOCTYPE html><html lang="ar" dir="rtl"><head></head><body></body></html>'
})
expect(result).toMatchObject([])
})
})

View file

@ -9,15 +9,9 @@ module.exports = {
url({ date, channel }) {
return `https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/schedules?locale=${
languages[channel.lang]
}&locale_default=${
languages[channel.lang]
}&device=1&in_channel_id=${
}&locale_default=${languages[channel.lang]}&device=1&in_channel_id=${
channel.site_id
}&gt_end=${
date.unix()
}&lt_start=${
date.add(1, 'd').unix()
}&limit=100&page=1`
}&gt_end=${date.unix()}&lt_start=${date.add(1, 'd').unix()}&limit=100&page=1`
},
async parser({ content, date, channel }) {
const programs = []
@ -29,7 +23,11 @@ module.exports = {
}
if (res.page && res.page.current < res.page.total) {
res = await axios
.get(module.exports.url({ date, channel }).replace(/page=(\d+)/, `page=${res.page.current + 1}`))
.get(
module.exports
.url({ date, channel })
.replace(/page=(\d+)/, `page=${res.page.current + 1}`)
)
.then(r => r.data)
.catch(console.error)
} else {
@ -39,7 +37,7 @@ module.exports = {
}
const season = s => {
if (s) {
const [ , , n ] = s.match(/(S|Season )(\d+)/) || [null, null, null]
const [, , n] = s.match(/(S|Season )(\d+)/) || [null, null, null]
if (n) {
return parseInt(n)
}
@ -66,11 +64,9 @@ module.exports = {
let page = 1
while (true) {
const items = await axios
.get(`https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/channels?locale=${
languages[lang]
}&locale_default=${
languages[lang]
}&device=1&limit=50&page=${page}`)
.get(
`https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/channels?locale=${languages[lang]}&locale_default=${languages[lang]}&device=1&limit=50&page=${page}`
)
.then(r => r.data)
.catch(console.error)
if (items.resources) {

View file

@ -1,4 +1,4 @@
const { parser, url, request } = require('./starhubtvplus.com.config.js')
const { parser, url } = require('./starhubtvplus.com.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
@ -36,9 +36,12 @@ it('can parse response', async () => {
title: 'Northern Rexposure',
subTitle: 'Hudson & Rex (Season 5)',
description:
'When Jesse\'s sister contacts him for help, he, Sarah and Rex head to Northern Ontario and find themselves in the middle of a deadly situation.',
"When Jesse's sister contacts him for help, he, Sarah and Rex head to Northern Ontario and find themselves in the middle of a deadly situation.",
category: ['Drama'],
image: ['https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=960&h=540', 'https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=341&h=192'],
image: [
'https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=960&h=540',
'https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=341&h=192'
],
season: 5,
episode: 15,
rating: 'PG13'

View file

@ -8,9 +8,7 @@ const debug = require('debug')('site:startimestv.com')
dayjs.extend(utc)
dayjs.extend(customParseFormat)
doFetch
.setDebugger(debug)
.setMaxWorker(5)
doFetch.setDebugger(debug).setMaxWorker(5)
module.exports = {
site: 'startimestv.com',
@ -24,7 +22,8 @@ module.exports = {
const programs = []
if (content) {
const $ = cheerio.load(content)
$('.box .mask').toArray()
$('.box .mask')
.toArray()
.forEach(el => {
let title = parseText($(el).find('h4'))
const [s, e] = title.substr(0, title.indexOf(' ')).split('-') || [null, null]
@ -53,7 +52,8 @@ module.exports = {
// process area-id
if (queue.t === 'a') {
const $ = cheerio.load(res)
$('dd.update-areaID').toArray()
$('dd.update-areaID')
.toArray()
.forEach(el => {
const dd = $(el)
const areaId = dd.attr('area-id')
@ -72,7 +72,8 @@ module.exports = {
if (queue.t === 's') {
if (res) {
const $ = cheerio.load(res)
$(`.channl .c`).toArray()
$('.channl .c')
.toArray()
.forEach(el => {
// only process channel with schedule only
const clazz = $(el).attr('class')
@ -98,13 +99,10 @@ module.exports = {
}
function parseText($item) {
let text = $item.text()
.replace(/\t/g, '')
.replace(/\n/g, ' ')
.trim()
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
while (true) {
if (text.match(/ /)) {
text = text.replace(/ /g, ' ')
if (text.match(/\s\s/)) {
text = text.replace(/\s\s/g, ' ')
continue
}
break

View file

@ -33,10 +33,10 @@ module.exports = {
return programs
},
async channels({ country, lang }) {
async channels() {
const axios = require('axios')
const data = await axios
.get(`https://streamingtvguides.com/Preferences`)
.get('https://streamingtvguides.com/Preferences')
.then(r => r.data)
.catch(console.log)

View file

@ -3,7 +3,7 @@ const dayjs = require('dayjs')
const API_STATIC_ENDPOINT = 'https://static.spark.telenet.tv/eng/web/epg-service-lite/be'
const API_PROD_ENDPOINT = 'https://spark-prod-be.gnp.cloud.telenet.tv/eng/web/linear-service/v2'
const API_IMAGE_ENDPOINT = 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service';
const API_IMAGE_ENDPOINT = 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service'
module.exports = {
site: 'telenet.tv',
@ -94,7 +94,7 @@ module.exports = {
async function loadProgramDetails(item, channel) {
if (!item.id) return {}
const url = `${API_PROD_ENDPOINT}/replayEvent/${item.id}?returnLinearContent=true&language=${channel.lang}`
const url = `${API_PROD_ENDPOINT}/replayEvent/${item.id}?returnLinearContent=true&language=${channel.lang}`
const data = await axios
.get(url)
.then(r => r.data)
@ -134,5 +134,5 @@ function parseEpisode(detail) {
}
function parseIcon(item) {
return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile`;
}
return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile`
}

View file

@ -27,10 +27,10 @@ module.exports = {
return programs
},
async channels({ lang }) {
async channels() {
const axios = require('axios')
const data = await axios
.get(`https://telkussa.fi/API/Channels`)
.get('https://telkussa.fi/API/Channels')
.then(r => r.data)
.catch(console.log)

View file

@ -1,132 +1,134 @@
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')
const doFetch = require('@ntlab/sfetch')
const debug = require('debug')('site:tivie.id')
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
doFetch
.setDebugger(debug)
const tz = 'Asia/Jakarta'
module.exports = {
site: 'tivie.id',
days: 2,
url({ channel, date }) {
return `https://tivie.id/channel/${
channel.site_id
}/${
date.format('YYYYMMDD')
}`
},
async parser({ content, date }) {
const programs = []
if (content) {
const $ = cheerio.load(content)
const items = $('ul[x-data] > li[id*="event-"] > div.w-full').toArray()
.map(item => {
const $item = $(item)
const time = $item.find('div:nth-child(1) span:nth-child(1)')
const info = $item.find('div:nth-child(2) h5')
const detail = info.find('a')
const p = {
start: dayjs.tz(`${date.format('YYYY-MM-DD')} ${time.html()}`, 'YYYY-MM-DD HH:mm', tz)
}
if (detail.length) {
const subtitle = detail.find('div')
p.title = parseText(subtitle.length ? subtitle : detail)
p.url = detail.attr('href')
} else {
p.title = parseText(info)
}
if (p.title) {
const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [null, null, null, null]
if (season) {
p.season = parseInt(season)
}
if (episode) {
p.episode = parseInt(episode)
}
}
return p
})
// fetch detailed guide if necessary
const queues = items
.filter(i => i.url)
.map(i => {
const url = i.url
delete i.url
return {i, url}
})
if (queues.length) {
await doFetch(queues, (queue, res) => {
const $ = cheerio.load(res)
const img = $('#main-content > div > div:nth-child(1) img')
const info = $('#main-content > div > div:nth-child(2)')
const title = parseText(info.find('h2:nth-child(2)'))
if (!queue.i.title.startsWith(title)) {
queue.i.subTitle = parseText(info.find('h2:nth-child(2)'))
}
queue.i.description = parseText(info.find('div[class=""]:nth-child(4)'))
queue.i.date = parseText(info.find('h2:nth-child(3)'))
queue.i.image = img.length ? img.attr('src') : null
})
}
// fill start-stop
for (let i = 0; i < items.length; i++) {
if (i < items.length - 1) {
items[i].stop = items[i + 1].start
} else {
items[i].stop = dayjs.tz(`${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`, 'YYYY-MM-DD HH:mm', tz)
}
}
// add programs
programs.push(...items)
}
return programs
},
async channels({ lang = 'id' }) {
const result = await axios
.get('https://tivie.id/channel')
.then(response => response.data)
.catch(console.error)
const $ = cheerio.load(result)
const items = $('ul[x-data] li[x-data] div header h2 a').toArray()
const channels = items.map(item => {
const $item = $(item)
const url = $item.attr('href')
return {
lang,
site_id: url.substr(url.lastIndexOf('/') + 1),
name: $item.find('strong').text()
}
})
return channels
}
}
function parseText($item) {
let text = $item.text()
.replace(/\t/g, '')
.replace(/\n/g, ' ')
.trim()
while (true) {
if (text.match(/ /)) {
text = text.replace(/ /g, ' ')
continue
}
break
}
return text
}
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')
const doFetch = require('@ntlab/sfetch')
const debug = require('debug')('site:tivie.id')
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
doFetch.setDebugger(debug)
const tz = 'Asia/Jakarta'
module.exports = {
site: 'tivie.id',
days: 2,
url({ channel, date }) {
return `https://tivie.id/channel/${channel.site_id}/${date.format('YYYYMMDD')}`
},
async parser({ content, date }) {
const programs = []
if (content) {
const $ = cheerio.load(content)
const items = $('ul[x-data] > li[id*="event-"] > div.w-full')
.toArray()
.map(item => {
const $item = $(item)
const time = $item.find('div:nth-child(1) span:nth-child(1)')
const info = $item.find('div:nth-child(2) h5')
const detail = info.find('a')
const p = {
start: dayjs.tz(`${date.format('YYYY-MM-DD')} ${time.html()}`, 'YYYY-MM-DD HH:mm', tz)
}
if (detail.length) {
const subtitle = detail.find('div')
p.title = parseText(subtitle.length ? subtitle : detail)
p.url = detail.attr('href')
} else {
p.title = parseText(info)
}
if (p.title) {
const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [
null,
null,
null,
null
]
if (season) {
p.season = parseInt(season)
}
if (episode) {
p.episode = parseInt(episode)
}
}
return p
})
// fetch detailed guide if necessary
const queues = items
.filter(i => i.url)
.map(i => {
const url = i.url
delete i.url
return { i, url }
})
if (queues.length) {
await doFetch(queues, (queue, res) => {
const $ = cheerio.load(res)
const img = $('#main-content > div > div:nth-child(1) img')
const info = $('#main-content > div > div:nth-child(2)')
const title = parseText(info.find('h2:nth-child(2)'))
if (!queue.i.title.startsWith(title)) {
queue.i.subTitle = parseText(info.find('h2:nth-child(2)'))
}
queue.i.description = parseText(info.find('div[class=""]:nth-child(4)'))
queue.i.date = parseText(info.find('h2:nth-child(3)'))
queue.i.image = img.length ? img.attr('src') : null
})
}
// fill start-stop
for (let i = 0; i < items.length; i++) {
if (i < items.length - 1) {
items[i].stop = items[i + 1].start
} else {
items[i].stop = dayjs.tz(
`${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`,
'YYYY-MM-DD HH:mm',
tz
)
}
}
// add programs
programs.push(...items)
}
return programs
},
async channels({ lang = 'id' }) {
const result = await axios
.get('https://tivie.id/channel')
.then(response => response.data)
.catch(console.error)
const $ = cheerio.load(result)
const items = $('ul[x-data] li[x-data] div header h2 a').toArray()
const channels = items.map(item => {
const $item = $(item)
const url = $item.attr('href')
return {
lang,
site_id: url.substr(url.lastIndexOf('/') + 1),
name: $item.find('strong').text()
}
})
return channels
}
}
function parseText($item) {
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
while (true) {
if (text.match(/\s\s/)) {
text = text.replace(/\s\s/g, ' ')
continue
}
break
}
return text
}

View file

@ -1,77 +1,75 @@
const { parser, url } = require('./tivie.id.config')
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('2024-12-31').startOf('d')
const channel = {
site_id: 'axn',
xmltv_id: 'AXN.id',
lang: 'id'
}
axios.get.mockImplementation(url => {
const urls = {
'https://tivie.id/film/white-house-down-nwzDnwz9nAv6':
'program01.html',
'https://tivie.id/program/hudson-rex-s6-e14-nwzDnwvBmQr9':
'program02.html',
}
let data = ''
if (urls[url] !== undefined) {
data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString()
}
return Promise.resolve({ data })
})
it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://tivie.id/channel/axn/20241231')
})
it('can parse response', async () => {
const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html'))
const results = (
await parser({ date, content, channel })
).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results.length).toBe(27)
expect(results[0]).toMatchObject({
start: '2024-12-30T17:00:00.000Z',
stop: '2024-12-30T17:05:00.000Z',
title: 'White House Down',
description:
'Saat melakukan tur di Gedung Putih bersama putrinya yang masih kecil, seorang perwira polisi beraksi untuk melindungi anaknya dan presiden dari sekelompok penjajah paramiliter bersenjata lengkap.',
image: 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2023/09/65116c78791c2-1695640694.jpg?resize=480,270',
})
expect(results[2]).toMatchObject({
start: '2024-12-30T18:00:00.000Z',
stop: '2024-12-30T18:55:00.000Z',
title: 'Hudson & Rex S6, Ep. 14',
description:
'Saat guru musik Jesse terbunuh di studio rekamannya, Charlie dan Rex menghubungkan kejahatan tersebut dengan pembunuhan yang tampaknya tak ada hubungannya.',
image: 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2024/07/668b7ced47b25-1720417517.jpg?resize=480,270',
season: 6,
episode: 14,
})
})
it('can handle empty guide', async () => {
const results = await parser({
date,
channel,
content: '',
})
expect(results).toMatchObject([])
})
const { parser, url } = require('./tivie.id.config')
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('2024-12-31').startOf('d')
const channel = {
site_id: 'axn',
xmltv_id: 'AXN.id',
lang: 'id'
}
axios.get.mockImplementation(url => {
const urls = {
'https://tivie.id/film/white-house-down-nwzDnwz9nAv6': 'program01.html',
'https://tivie.id/program/hudson-rex-s6-e14-nwzDnwvBmQr9': 'program02.html'
}
let data = ''
if (urls[url] !== undefined) {
data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString()
}
return Promise.resolve({ data })
})
it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://tivie.id/channel/axn/20241231')
})
it('can parse response', async () => {
const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html'))
const results = (await parser({ date, content, channel })).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results.length).toBe(27)
expect(results[0]).toMatchObject({
start: '2024-12-30T17:00:00.000Z',
stop: '2024-12-30T17:05:00.000Z',
title: 'White House Down',
description:
'Saat melakukan tur di Gedung Putih bersama putrinya yang masih kecil, seorang perwira polisi beraksi untuk melindungi anaknya dan presiden dari sekelompok penjajah paramiliter bersenjata lengkap.',
image:
'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2023/09/65116c78791c2-1695640694.jpg?resize=480,270'
})
expect(results[2]).toMatchObject({
start: '2024-12-30T18:00:00.000Z',
stop: '2024-12-30T18:55:00.000Z',
title: 'Hudson & Rex S6, Ep. 14',
description:
'Saat guru musik Jesse terbunuh di studio rekamannya, Charlie dan Rex menghubungkan kejahatan tersebut dengan pembunuhan yang tampaknya tak ada hubungannya.',
image:
'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2024/07/668b7ced47b25-1720417517.jpg?resize=480,270',
season: 6,
episode: 14
})
})
it('can handle empty guide', async () => {
const results = await parser({
date,
channel,
content: ''
})
expect(results).toMatchObject([])
})

View file

@ -73,7 +73,7 @@ function parseItems(content, channel) {
let parsed
try {
parsed = JSON.parse(content)
} catch (error) {
} catch {
return []
}
if (!parsed || !parsed.k) return []

Some files were not shown because too many files have changed in this diff Show more