mirror of
https://github.com/iptv-org/epg.git
synced 2025-05-09 08:30:06 -04:00
Fix linter issues in sites/
This commit is contained in:
parent
d6d20b6413
commit
5df982bb7c
129 changed files with 3316 additions and 3226 deletions
|
@ -92,7 +92,7 @@ function parseItems(content, channel) {
|
||||||
const [, channelId] = channel.site_id.split('#')
|
const [, channelId] = channel.site_id.split('#')
|
||||||
const channelData = data.schedule.find(i => i.channel == channelId)
|
const channelData = data.schedule.find(i => i.channel == channelId)
|
||||||
return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : []
|
return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : []
|
||||||
} catch (err) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
|
@ -62,7 +61,7 @@ function parseItems(content) {
|
||||||
let data
|
let data
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(content)
|
data = JSON.parse(content)
|
||||||
} catch (error) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
const { parser, url } = require('./beinsports.com.config.js')
|
const { parser, url } = require('./beinsports.com.config.js')
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
|
@ -1,54 +1,55 @@
|
||||||
const axios = require('axios');
|
const cheerio = require('cheerio')
|
||||||
const cheerio = require('cheerio');
|
const dayjs = require('dayjs')
|
||||||
const dayjs = require('dayjs');
|
const utc = require('dayjs/plugin/utc')
|
||||||
const utc = require('dayjs/plugin/utc');
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const timezone = require('dayjs/plugin/timezone');
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat');
|
|
||||||
|
dayjs.extend(utc)
|
||||||
dayjs.extend(utc);
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(customParseFormat);
|
|
||||||
|
module.exports = {
|
||||||
module.exports = {
|
site: 'chada.ma',
|
||||||
site: 'chada.ma',
|
channels: 'chada.ma.channels.xml',
|
||||||
channels: 'chada.ma.channels.xml',
|
days: 1,
|
||||||
days: 1,
|
request: {
|
||||||
request: {
|
cache: {
|
||||||
cache: {
|
ttl: 60 * 60 * 1000 // 1 hour
|
||||||
ttl: 60 * 60 * 1000 // 1 hour
|
}
|
||||||
}
|
},
|
||||||
},
|
url() {
|
||||||
url() {
|
return 'https://chada.ma/fr/chada-tv/grille-tv/'
|
||||||
return 'https://chada.ma/fr/chada-tv/grille-tv/';
|
},
|
||||||
},
|
parser: function ({ content }) {
|
||||||
parser: function ({ content }) {
|
const $ = cheerio.load(content)
|
||||||
const $ = cheerio.load(content);
|
const programs = []
|
||||||
const programs = [];
|
|
||||||
|
$('#stopfix .posts-area h2').each((i, element) => {
|
||||||
$('#stopfix .posts-area h2').each((i, element) => {
|
const timeRange = $(element).text().trim()
|
||||||
const timeRange = $(element).text().trim();
|
const [start, stop] = timeRange.split(' - ').map(t => parseProgramTime(t.trim()))
|
||||||
const [start, stop] = timeRange.split(' - ').map(t => parseProgramTime(t.trim()));
|
|
||||||
|
const titleElement = $(element).next('div').next('h3')
|
||||||
const titleElement = $(element).next('div').next('h3');
|
const title = titleElement.text().trim()
|
||||||
const title = titleElement.text().trim();
|
|
||||||
|
const description = titleElement.next('div').text().trim() || 'No description available'
|
||||||
const description = titleElement.next('div').text().trim() || 'No description available';
|
|
||||||
|
programs.push({
|
||||||
programs.push({
|
title,
|
||||||
title,
|
description,
|
||||||
description,
|
start,
|
||||||
start,
|
stop
|
||||||
stop
|
})
|
||||||
});
|
})
|
||||||
});
|
|
||||||
|
return programs
|
||||||
return programs;
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
function parseProgramTime(timeStr) {
|
||||||
function parseProgramTime(timeStr) {
|
const timeZone = 'Africa/Casablanca'
|
||||||
const timeZone = 'Africa/Casablanca';
|
const currentDate = dayjs().format('YYYY-MM-DD')
|
||||||
const currentDate = dayjs().format('YYYY-MM-DD');
|
|
||||||
|
return dayjs
|
||||||
return dayjs.tz(`${currentDate} ${timeStr}`, 'YYYY-MM-DD HH:mm', timeZone).format('YYYY-MM-DDTHH:mm:ssZ');
|
.tz(`${currentDate} ${timeStr}`, 'YYYY-MM-DD HH:mm', timeZone)
|
||||||
}
|
.format('YYYY-MM-DDTHH:mm:ssZ')
|
||||||
|
}
|
||||||
|
|
|
@ -1,60 +1,58 @@
|
||||||
const { parser, url } = require('./chada.ma.config.js')
|
const { parser, url } = require('./chada.ma.config.js')
|
||||||
const axios = require('axios')
|
const dayjs = require('dayjs')
|
||||||
const dayjs = require('dayjs')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const cheerio = require('cheerio')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
dayjs.extend(utc)
|
||||||
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(timezone)
|
|
||||||
dayjs.extend(customParseFormat)
|
jest.mock('axios')
|
||||||
|
|
||||||
jest.mock('axios')
|
const mockHtmlContent = `
|
||||||
|
<div class="pm0 col-md-8" id="stopfix">
|
||||||
const mockHtmlContent = `
|
<h2 class="posts-date">Programmes d'Aujourd'hui</h2>
|
||||||
<div class="pm0 col-md-8" id="stopfix">
|
<div class="posts-area">
|
||||||
<h2 class="posts-date">Programmes d'Aujourd'hui</h2>
|
<h2> <i class="fas fa-circle"></i>00:00 - 09:00</h2>
|
||||||
<div class="posts-area">
|
<div class="relativeme">
|
||||||
<h2> <i class="fas fa-circle"></i>00:00 - 09:00</h2>
|
<a href="https://chada.ma/fr/emissions/bloc-prime-clips/">
|
||||||
<div class="relativeme">
|
<img class="programthumb" src="https://chada.ma/wp-content/uploads/2023/11/Autres-slides-clips-la-couverture.jpg">
|
||||||
<a href="https://chada.ma/fr/emissions/bloc-prime-clips/">
|
</a>
|
||||||
<img class="programthumb" src="https://chada.ma/wp-content/uploads/2023/11/Autres-slides-clips-la-couverture.jpg">
|
</div>
|
||||||
</a>
|
<h3>Bloc Prime + Clips</h3>
|
||||||
</div>
|
<div class="authorbox"></div>
|
||||||
<h3>Bloc Prime + Clips</h3>
|
<div class="ssprogramme row"></div>
|
||||||
<div class="authorbox"></div>
|
</div>
|
||||||
<div class="ssprogramme row"></div>
|
</div>
|
||||||
</div>
|
`
|
||||||
</div>
|
|
||||||
`;
|
it('can generate valid url', () => {
|
||||||
|
expect(url()).toBe('https://chada.ma/fr/chada-tv/grille-tv/')
|
||||||
it('can generate valid url', () => {
|
})
|
||||||
expect(url()).toBe('https://chada.ma/fr/chada-tv/grille-tv/')
|
|
||||||
});
|
it('can parse response', () => {
|
||||||
|
const content = mockHtmlContent
|
||||||
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')
|
||||||
const result = parser({ content }).map(p => {
|
p.stop = dayjs(p.stop).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
|
||||||
p.start = dayjs(p.start).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
|
return p
|
||||||
p.stop = dayjs(p.stop).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
|
})
|
||||||
return p
|
|
||||||
})
|
expect(result).toMatchObject([
|
||||||
|
{
|
||||||
expect(result).toMatchObject([
|
title: 'Bloc Prime + Clips',
|
||||||
{
|
description: 'No description available',
|
||||||
title: "Bloc Prime + Clips",
|
start: dayjs.tz('00:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ'),
|
||||||
description: "No description available",
|
stop: dayjs.tz('09:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
|
||||||
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({
|
||||||
it('can handle empty guide', () => {
|
content: '<div class="pm0 col-md-8" id="stopfix"><div class="posts-area"></div></div>'
|
||||||
const result = parser({
|
})
|
||||||
content: '<div class="pm0 col-md-8" id="stopfix"><div class="posts-area"></div></div>'
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
|
||||||
})
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const html = await axios
|
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)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const data = await axios
|
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)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ function parseItems(content, date) {
|
||||||
const schedules = data.response.schedule
|
const schedules = data.response.schedule
|
||||||
|
|
||||||
return schedules[date.format('YYYY-MM-DD')] || []
|
return schedules[date.format('YYYY-MM-DD')] || []
|
||||||
} catch (e) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +1,85 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'cosmotetv.gr',
|
site: 'cosmotetv.gr',
|
||||||
days: 5,
|
days: 5,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 60 * 60 * 1000 // 1 hour
|
ttl: 60 * 60 * 1000 // 1 hour
|
||||||
},
|
},
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'referer': 'https://www.cosmotetv.gr/',
|
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',
|
'User-Agent':
|
||||||
'Accept': '*/*',
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||||
'Accept-Language': 'en-US,en;q=0.9',
|
Accept: '*/*',
|
||||||
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
'Accept-Language': 'en-US,en;q=0.9',
|
||||||
'Origin': 'https://www.cosmotetv.gr',
|
'Accept-Encoding': 'gzip, deflate, br, zstd',
|
||||||
'Sec-Ch-Ua': '"Not.A/Brand";v="24", "Chromium";v="131", "Google Chrome";v="131"',
|
Origin: 'https://www.cosmotetv.gr',
|
||||||
'Sec-Ch-Ua-Mobile': '?0',
|
'Sec-Ch-Ua': '"Not.A/Brand";v="24", "Chromium";v="131", "Google Chrome";v="131"',
|
||||||
'Sec-Ch-Ua-Platform': '"Windows"',
|
'Sec-Ch-Ua-Mobile': '?0',
|
||||||
'Sec-Fetch-Dest': 'empty',
|
'Sec-Ch-Ua-Platform': '"Windows"',
|
||||||
'Sec-Fetch-Mode': 'cors',
|
'Sec-Fetch-Dest': 'empty',
|
||||||
'Sec-Fetch-Site': 'cross-site'
|
'Sec-Fetch-Mode': 'cors',
|
||||||
}
|
'Sec-Fetch-Site': 'cross-site'
|
||||||
},
|
}
|
||||||
url: function ({date, channel}) {
|
},
|
||||||
const startOfDay = dayjs(date).startOf('day').utc().unix()
|
url: function ({ date, channel }) {
|
||||||
const endOfDay = dayjs(date).endOf('day').utc().unix()
|
const startOfDay = dayjs(date).startOf('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`
|
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 = []
|
parser: function ({ content }) {
|
||||||
const data = JSON.parse(content)
|
let programs = []
|
||||||
data.channels.forEach(channel => {
|
const data = JSON.parse(content)
|
||||||
channel.items.forEach(item => {
|
data.channels.forEach(channel => {
|
||||||
const start = dayjs(item.startTime).utc().toISOString()
|
channel.items.forEach(item => {
|
||||||
const stop = dayjs(item.endTime).utc().toISOString()
|
const start = dayjs(item.startTime).utc().toISOString()
|
||||||
programs.push({
|
const stop = dayjs(item.endTime).utc().toISOString()
|
||||||
title: item.title,
|
programs.push({
|
||||||
description: item.description || 'No description available',
|
title: item.title,
|
||||||
category: item.qoe.genre,
|
description: item.description || 'No description available',
|
||||||
image: item.thumbnails.standard,
|
category: item.qoe.genre,
|
||||||
start,
|
image: item.thumbnails.standard,
|
||||||
stop
|
start,
|
||||||
})
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return programs
|
})
|
||||||
},
|
return programs
|
||||||
async channels() {
|
},
|
||||||
const axios = require('axios')
|
async channels() {
|
||||||
try {
|
const axios = require('axios')
|
||||||
const response = await axios.get('https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/channels/all/el', {
|
try {
|
||||||
headers: this.request.headers
|
const response = await axios.get(
|
||||||
})
|
'https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/channels/all/el',
|
||||||
const data = response.data
|
{
|
||||||
|
headers: this.request.headers
|
||||||
if (data && data.channels) {
|
}
|
||||||
return data.channels.map(item => ({
|
)
|
||||||
lang: 'el',
|
const data = response.data
|
||||||
site_id: item.callSign,
|
|
||||||
name: item.title,
|
if (data && data.channels) {
|
||||||
//logo: item.logos.square
|
return data.channels.map(item => ({
|
||||||
}))
|
lang: 'el',
|
||||||
} else {
|
site_id: item.callSign,
|
||||||
console.error('Unexpected response structure:', data)
|
name: item.title
|
||||||
return []
|
//logo: item.logos.square
|
||||||
}
|
}))
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error('Error fetching channel data:', error)
|
console.error('Unexpected response structure:', data)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
} catch (error) {
|
||||||
}
|
console.error('Error fetching channel data:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,81 +1,76 @@
|
||||||
const { parser, url, channels } = require('./cosmotetv.gr.config.js')
|
const { parser, url } = require('./cosmotetv.gr.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const axios = require('axios')
|
|
||||||
|
dayjs.extend(utc)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(timezone)
|
|
||||||
|
jest.mock('axios')
|
||||||
jest.mock('axios')
|
|
||||||
|
const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('d')
|
||||||
const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('d')
|
const channel = { site_id: 'vouli', xmltv_id: 'HellenicParliamentTV.gr' }
|
||||||
const channel = { site_id: 'vouli', xmltv_id: 'HellenicParliamentTV.gr' }
|
|
||||||
|
const mockEpgData = {
|
||||||
const mockChannelData = {
|
channels: [
|
||||||
"channels": [
|
{
|
||||||
{
|
items: [
|
||||||
"guid": "XTV100000954",
|
{
|
||||||
"title": "ΒΟΥΛΗ HD",
|
startTime: '2024-12-26T23:00:00+00:00',
|
||||||
"callSign": "vouli",
|
endTime: '2024-12-27T00:00:00+00:00',
|
||||||
"logos": {
|
title: 'Τι Λέει ο Νόμος',
|
||||||
"square": "https://tr.static.cdn.cosmotetvott.gr/ote-prod/channel_logos/vouli1-normal.png",
|
description:
|
||||||
"wide": "https://tr.static.cdn.cosmotetvott.gr/ote-prod/channel_logos/vouli1-wide.png"
|
'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.',
|
||||||
}
|
qoe: {
|
||||||
}
|
genre: 'Special'
|
||||||
]
|
},
|
||||||
}
|
thumbnails: {
|
||||||
|
standard:
|
||||||
const mockEpgData = {
|
'https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg'
|
||||||
"channels": [
|
}
|
||||||
{
|
}
|
||||||
"items": [
|
]
|
||||||
{
|
}
|
||||||
"startTime": "2024-12-26T23:00:00+00:00",
|
]
|
||||||
"endTime": "2024-12-27T00:00:00+00:00",
|
}
|
||||||
"title": "Τι Λέει ο Νόμος",
|
|
||||||
"description": "νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.",
|
it('can generate valid url', () => {
|
||||||
"qoe": {
|
const startOfDay = dayjs(date).startOf('day').utc().unix()
|
||||||
"genre": "Special"
|
const endOfDay = dayjs(date).endOf('day').utc().unix()
|
||||||
},
|
expect(url({ date, channel })).toBe(
|
||||||
"thumbnails": {
|
`https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false`
|
||||||
"standard": "https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg"
|
)
|
||||||
}
|
})
|
||||||
}
|
|
||||||
]
|
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()
|
||||||
it('can generate valid url', () => {
|
return p
|
||||||
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`)
|
expect(result).toMatchObject([
|
||||||
})
|
{
|
||||||
|
title: 'Τι Λέει ο Νόμος',
|
||||||
it('can parse response', () => {
|
description:
|
||||||
const content = JSON.stringify(mockEpgData)
|
'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.',
|
||||||
const result = parser({ date, content }).map(p => {
|
category: 'Special',
|
||||||
p.start = dayjs(p.start).toISOString()
|
image:
|
||||||
p.stop = dayjs(p.stop).toISOString()
|
'https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg',
|
||||||
return p
|
start: '2024-12-26T23:00:00.000Z',
|
||||||
})
|
stop: '2024-12-27T00:00:00.000Z'
|
||||||
|
}
|
||||||
expect(result).toMatchObject([
|
])
|
||||||
{
|
})
|
||||||
title: "Τι Λέει ο Νόμος",
|
|
||||||
description: "νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.",
|
it('can handle empty guide', () => {
|
||||||
category: "Special",
|
const result = parser({
|
||||||
image: "https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg",
|
date,
|
||||||
start: "2024-12-26T23:00:00.000Z",
|
channel,
|
||||||
stop: "2024-12-27T00:00:00.000Z"
|
content: '{"date":"2024-12-26","categories":[],"channels":[]}'
|
||||||
}
|
})
|
||||||
])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
|
||||||
const result = parser({ date, channel, content: '{"date":"2024-12-26","categories":[],"channels":[]}' });
|
|
||||||
expect(result).toMatchObject([])
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,102 +1,114 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'cubmu.com',
|
site: 'cubmu.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel, date }) {
|
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}`
|
return `https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=${date.format(
|
||||||
},
|
'YYYY-MM-DD'
|
||||||
parser({ content, channel }) {
|
)}&channel_id=${channel.site_id}`
|
||||||
const programs = []
|
},
|
||||||
const items = parseItems(content)
|
parser({ content, channel }) {
|
||||||
items.forEach(item => {
|
const programs = []
|
||||||
programs.push({
|
const items = parseItems(content)
|
||||||
title: parseTitle(item),
|
items.forEach(item => {
|
||||||
description: parseDescription(item, channel.lang),
|
programs.push({
|
||||||
episode: parseEpisode(item),
|
title: parseTitle(item),
|
||||||
start: parseStart(item).toISOString(),
|
description: parseDescription(item, channel.lang),
|
||||||
stop: parseStop(item).toISOString()
|
episode: parseEpisode(item),
|
||||||
})
|
start: parseStart(item).toISOString(),
|
||||||
})
|
stop: parseStop(item).toISOString()
|
||||||
|
})
|
||||||
return programs
|
})
|
||||||
},
|
|
||||||
async channels({ lang = 'id' }) {
|
return programs
|
||||||
const axios = require('axios')
|
},
|
||||||
const cheerio = require('cheerio')
|
async channels({ lang = 'id' }) {
|
||||||
const result = await axios
|
const axios = require('axios')
|
||||||
.get('https://cubmu.com/live-tv')
|
const cheerio = require('cheerio')
|
||||||
.then(response => response.data)
|
const result = await axios
|
||||||
.catch(console.error)
|
.get('https://cubmu.com/live-tv')
|
||||||
|
.then(response => response.data)
|
||||||
const $ = cheerio.load(result)
|
.catch(console.error)
|
||||||
|
|
||||||
// retrieve service api data
|
const $ = cheerio.load(result)
|
||||||
const config = JSON.parse($('#__NEXT_DATA__').text()).runtimeConfig || {}
|
|
||||||
|
// retrieve service api data
|
||||||
const options = {
|
const config = JSON.parse($('#__NEXT_DATA__').text()).runtimeConfig || {}
|
||||||
headers: {
|
|
||||||
Origin: 'https://cubmu.com',
|
const options = {
|
||||||
Referer: 'https://cubmu.com/live-tv'
|
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)
|
// login to service bus
|
||||||
.then(response => response.data)
|
await axios
|
||||||
.catch(console.error)
|
.post(
|
||||||
// list channels
|
`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}`,
|
||||||
const subscribedChannels = await axios
|
options
|
||||||
.post(`https://servicebuss.transvision.co.id/tvs/subscribe_product/list?platformId=${config.platformId}`, options)
|
)
|
||||||
.then(response => response.data)
|
.then(response => response.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
// list channels
|
||||||
const channels = []
|
const subscribedChannels = await axios
|
||||||
const included = []
|
.post(
|
||||||
if (Array.isArray(subscribedChannels.channelPackageList)) {
|
`https://servicebuss.transvision.co.id/tvs/subscribe_product/list?platformId=${config.platformId}`,
|
||||||
subscribedChannels.channelPackageList.forEach(pkg => {
|
options
|
||||||
pkg.channelList.forEach(channel => {
|
)
|
||||||
if (included.indexOf(channel.id) < 0) {
|
.then(response => response.data)
|
||||||
included.push(channel.id)
|
.catch(console.error)
|
||||||
channels.push({
|
|
||||||
lang,
|
const channels = []
|
||||||
site_id: channel.id,
|
const included = []
|
||||||
name: channel.name
|
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,
|
||||||
return channels
|
site_id: channel.id,
|
||||||
}
|
name: channel.name
|
||||||
}
|
})
|
||||||
|
}
|
||||||
function parseItems(content) {
|
})
|
||||||
return content ? JSON.parse(content.trim()).result || [] : []
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTitle(item) {
|
return channels
|
||||||
return item.scehedule_title
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDescription(item, lang = 'id') {
|
function parseItems(content) {
|
||||||
return lang === 'id' ? item.schedule_json.primarySynopsis : item.schedule_json.secondarySynopsis
|
return content ? JSON.parse(content.trim()).result || [] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseEpisode(item) {
|
function parseTitle(item) {
|
||||||
return item.schedule_json.episodeName
|
return item.scehedule_title
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
function parseDescription(item, lang = 'id') {
|
||||||
return dayjs.tz(item.schedule_date, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
|
return lang === 'id' ? item.schedule_json.primarySynopsis : item.schedule_json.secondarySynopsis
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStop(item) {
|
function parseEpisode(item) {
|
||||||
return dayjs.tz([item.schedule_date.split(' ')[0], item.schedule_end_time].join(' '), 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
|
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'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,47 +1,47 @@
|
||||||
const { url, parser } = require('./cubmu.com.config.js')
|
const { url, parser } = require('./cubmu.com.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-11-05', 'DD/MM/YYYY').startOf('d')
|
const date = dayjs.utc('2023-11-05', 'DD/MM/YYYY').startOf('d')
|
||||||
const channel = { site_id: '4028c68574537fcd0174be43042758d8', xmltv_id: 'TransTV.id', lang: 'id' }
|
const channel = { site_id: '4028c68574537fcd0174be43042758d8', xmltv_id: 'TransTV.id', lang: 'id' }
|
||||||
const channelEn = Object.assign({}, channel, { lang: 'en' })
|
const channelEn = Object.assign({}, channel, { lang: 'en' })
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe(
|
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'
|
'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', () => {
|
it('can parse response', () => {
|
||||||
const content =
|
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"}]}'
|
'{"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 })
|
const idResults = parser({ content, channel })
|
||||||
expect(idResults).toMatchObject([
|
expect(idResults).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2023-11-04T18:30:00.000Z',
|
start: '2023-11-04T18:30:00.000Z',
|
||||||
stop: '2023-11-04T19:00:00.000Z',
|
stop: '2023-11-04T19:00:00.000Z',
|
||||||
title: 'CNN Tech News',
|
title: 'CNN Tech News',
|
||||||
description:
|
description:
|
||||||
'CNN Indonesia Tech News adalah berita teknologi yang membawa pemirsa ke dunia teknologi yang penuh dengan informasi, pendidikan, hiburan sampai informasi kesehatan terkini.'
|
'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 })
|
const enResults = parser({ content, channel: channelEn })
|
||||||
expect(enResults).toMatchObject([
|
expect(enResults).toMatchObject([
|
||||||
{
|
{
|
||||||
start: '2023-11-04T18:30:00.000Z',
|
start: '2023-11-04T18:30:00.000Z',
|
||||||
stop: '2023-11-04T19:00:00.000Z',
|
stop: '2023-11-04T19:00:00.000Z',
|
||||||
title: 'CNN Tech News',
|
title: 'CNN Tech News',
|
||||||
description:
|
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.'
|
'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', () => {
|
it('can handle empty guide', () => {
|
||||||
const results = parser({ content: '' })
|
const results = parser({ content: '' })
|
||||||
|
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,9 +13,9 @@ module.exports = {
|
||||||
site: 'dens.tv',
|
site: 'dens.tv',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel, date }) {
|
url({ channel, date }) {
|
||||||
return `https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=${date.format('YYYY-MM-DD')}&id_channel=${
|
return `https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=${date.format(
|
||||||
channel.site_id
|
'YYYY-MM-DD'
|
||||||
}&app_type=10`
|
)}&id_channel=${channel.site_id}&app_type=10`
|
||||||
},
|
},
|
||||||
parser({ content }) {
|
parser({ content }) {
|
||||||
// parsing
|
// parsing
|
||||||
|
@ -25,8 +25,9 @@ module.exports = {
|
||||||
if (Array.isArray(response?.data)) {
|
if (Array.isArray(response?.data)) {
|
||||||
response.data.forEach(item => {
|
response.data.forEach(item => {
|
||||||
const title = item.title
|
const title = item.title
|
||||||
const [, , , season, , , episode] = title.match(/( (Season |Season|S)(\d+))?( (Episode|Ep) (\d+))/) ||
|
const [, , , season, , , episode] = title.match(
|
||||||
[null, null, null, null, null, null, null]
|
/( (Season |Season|S)(\d+))?( (Episode|Ep) (\d+))/
|
||||||
|
) || [null, null, null, null, null, null, null]
|
||||||
programs.push({
|
programs.push({
|
||||||
title,
|
title,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
|
@ -52,7 +53,7 @@ module.exports = {
|
||||||
const channels = []
|
const channels = []
|
||||||
for (const id_category of Object.values(categories)) {
|
for (const id_category of Object.values(categories)) {
|
||||||
const data = await axios
|
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 }
|
params: { id_category }
|
||||||
})
|
})
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
|
|
|
@ -10,7 +10,9 @@ const date = dayjs.utc('2024-11-24').startOf('d')
|
||||||
const channel = { site_id: '38', xmltv_id: 'AniplusAsia.sg', lang: 'id' }
|
const channel = { site_id: '38', xmltv_id: 'AniplusAsia.sg', lang: 'id' }
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
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', () => {
|
it('can parse response', () => {
|
||||||
|
|
|
@ -65,7 +65,7 @@ module.exports = {
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
|
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(`https://www.digiturk.com.tr/`, {
|
.get('https://www.digiturk.com.tr/', {
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent':
|
'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'
|
'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'
|
||||||
|
|
|
@ -60,7 +60,7 @@ module.exports = {
|
||||||
$('.pgrid').each((i, el) => {
|
$('.pgrid').each((i, el) => {
|
||||||
const onclick = $(el).find('.chnl-logo').attr('onclick')
|
const onclick = $(el).find('.chnl-logo').attr('onclick')
|
||||||
const number = $(el).find('.cnl-fav > a > span').text().trim()
|
const number = $(el).find('.cnl-fav > a > span').text().trim()
|
||||||
const [, name, site_id] = onclick.match(/ShowChannelGuid\('([^']+)','([^']+)'/) || [
|
const [, , site_id] = onclick.match(/ShowChannelGuid\('([^']+)','([^']+)'/) || [
|
||||||
null,
|
null,
|
||||||
'',
|
'',
|
||||||
''
|
''
|
||||||
|
|
|
@ -49,7 +49,7 @@ module.exports = {
|
||||||
const channels = []
|
const channels = []
|
||||||
for (let provider of providers) {
|
for (let provider of providers) {
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.post(`https://www.guida.tv/guide/schedule`, null, {
|
.post('https://www.guida.tv/guide/schedule', null, {
|
||||||
params: {
|
params: {
|
||||||
provider,
|
provider,
|
||||||
region: 'Italy',
|
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 timeString = $item('td:eq(0)').text().trim()
|
||||||
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ module.exports = {
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
|
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(`https://guidatv.sky.it/canali`)
|
.get('https://guidatv.sky.it/canali')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
|
|
@ -1,63 +1,63 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const convert = require('xml-js')
|
const convert = require('xml-js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
|
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'hoy.tv',
|
site: 'hoy.tv',
|
||||||
days: 2,
|
days: 2,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 60 * 60 * 1000 // 1h
|
ttl: 60 * 60 * 1000 // 1h
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
url: function ({ channel, date }) {
|
url: function ({ channel, date }) {
|
||||||
return `https://epg-file.hoy.tv/hoy/OTT${channel.site_id}${date.format('YYYYMMDD')}.xml`
|
return `https://epg-file.hoy.tv/hoy/OTT${channel.site_id}${date.format('YYYYMMDD')}.xml`
|
||||||
},
|
},
|
||||||
parser({ content, channel, date }) {
|
parser({ content, date }) {
|
||||||
const data = convert.xml2js(content, {
|
const data = convert.xml2js(content, {
|
||||||
compact: true,
|
compact: true,
|
||||||
ignoreDeclaration: true,
|
ignoreDeclaration: true,
|
||||||
ignoreAttributes: true
|
ignoreAttributes: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const programs = []
|
const programs = []
|
||||||
|
|
||||||
for (let item of data.ProgramGuide.Channel.EpgItem) {
|
for (let item of data.ProgramGuide.Channel.EpgItem) {
|
||||||
const start = dayjs.tz(item.EpgStartDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong')
|
const start = dayjs.tz(item.EpgStartDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong')
|
||||||
|
|
||||||
if (! date.isSame(start, 'day')) {
|
if (!date.isSame(start, 'day')) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const epIndex = item.EpisodeInfo.EpisodeIndex._text
|
const epIndex = item.EpisodeInfo.EpisodeIndex._text
|
||||||
const subtitle = parseInt(epIndex) > 0 ? `第${epIndex}集` : undefined
|
const subtitle = parseInt(epIndex) > 0 ? `第${epIndex}集` : undefined
|
||||||
|
|
||||||
programs.push({
|
programs.push({
|
||||||
title: `${item.ComScore.ns_st_pr._text}${item.EpgOtherInfo?._text || ''}`,
|
title: `${item.ComScore.ns_st_pr._text}${item.EpgOtherInfo?._text || ''}`,
|
||||||
sub_title: subtitle,
|
sub_title: subtitle,
|
||||||
description: item.EpisodeInfo.EpisodeLongDescription._text,
|
description: item.EpisodeInfo.EpisodeLongDescription._text,
|
||||||
start,
|
start,
|
||||||
stop: dayjs.tz(item.EpgEndDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong'),
|
stop: dayjs.tz(item.EpgEndDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ lang }) {
|
async channels() {
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get('https://api2.hoy.tv/api/v2/a/channel')
|
.get('https://api2.hoy.tv/api/v2/a/channel')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
return data.data.map(c => {
|
return data.data.map(c => {
|
||||||
return {
|
return {
|
||||||
site_id: c.videos.id,
|
site_id: c.videos.id,
|
||||||
name: c.name.zh_hk,
|
name: c.name.zh_hk,
|
||||||
lang: 'zh',
|
lang: 'zh'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,116 +1,115 @@
|
||||||
const { parser, url } = require('./hoy.tv.config.js')
|
const { parser, url } = require('./hoy.tv.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const date = dayjs.utc('2024-09-13', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2024-09-13', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '76',
|
site_id: '76',
|
||||||
xmltv_id: 'HOYIBC.hk',
|
xmltv_id: 'HOYIBC.hk',
|
||||||
lang: 'zh'
|
lang: 'zh'
|
||||||
}
|
}
|
||||||
const content = `<?xml version="1.0" encoding="UTF-8" ?>
|
const content = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<ProgramGuide>
|
<ProgramGuide>
|
||||||
<Channel id="76">
|
<Channel id="76">
|
||||||
<EpgItem>
|
<EpgItem>
|
||||||
<EpgStartDateTime>2024-09-13 11:30:00</EpgStartDateTime>
|
<EpgStartDateTime>2024-09-13 11:30:00</EpgStartDateTime>
|
||||||
<EpgEndDateTime>2024-09-13 12:30:00</EpgEndDateTime>
|
<EpgEndDateTime>2024-09-13 12:30:00</EpgEndDateTime>
|
||||||
<EpgOtherInfo>[PG]</EpgOtherInfo>
|
<EpgOtherInfo>[PG]</EpgOtherInfo>
|
||||||
<DisableLive>false</DisableLive>
|
<DisableLive>false</DisableLive>
|
||||||
<DisableVod>false</DisableVod>
|
<DisableVod>false</DisableVod>
|
||||||
<VODLicPeriod>2024-09-27 11:30:00</VODLicPeriod>
|
<VODLicPeriod>2024-09-27 11:30:00</VODLicPeriod>
|
||||||
<ProgramInfo>
|
<ProgramInfo>
|
||||||
<ProgramId>0</ProgramId>
|
<ProgramId>0</ProgramId>
|
||||||
<ProgramTitle></ProgramTitle>
|
<ProgramTitle></ProgramTitle>
|
||||||
<ProgramPos>0</ProgramPos>
|
<ProgramPos>0</ProgramPos>
|
||||||
<FirstRunDateTime></FirstRunDateTime>
|
<FirstRunDateTime></FirstRunDateTime>
|
||||||
<ProgramThumbnailUrl>http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg</ProgramThumbnailUrl>
|
<ProgramThumbnailUrl>http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg</ProgramThumbnailUrl>
|
||||||
</ProgramInfo>
|
</ProgramInfo>
|
||||||
<EpisodeInfo>
|
<EpisodeInfo>
|
||||||
<EpisodeId>EQ00135</EpisodeId>
|
<EpisodeId>EQ00135</EpisodeId>
|
||||||
<EpisodeIndex>46</EpisodeIndex>
|
<EpisodeIndex>46</EpisodeIndex>
|
||||||
<EpisodeShortDescription>點講都係一家人</EpisodeShortDescription>
|
<EpisodeShortDescription>點講都係一家人</EpisodeShortDescription>
|
||||||
<EpisodeLongDescription></EpisodeLongDescription>
|
<EpisodeLongDescription></EpisodeLongDescription>
|
||||||
<EpisodeThumbnailUrl>http://tv.fantv.hk/images/nosuchthumbnail.jpg</EpisodeThumbnailUrl>
|
<EpisodeThumbnailUrl>http://tv.fantv.hk/images/nosuchthumbnail.jpg</EpisodeThumbnailUrl>
|
||||||
</EpisodeInfo>
|
</EpisodeInfo>
|
||||||
<ComScore>
|
<ComScore>
|
||||||
<ns_st_stc></ns_st_stc>
|
<ns_st_stc></ns_st_stc>
|
||||||
<ns_st_pr>點講都係一家人</ns_st_pr>
|
<ns_st_pr>點講都係一家人</ns_st_pr>
|
||||||
<ns_st_tpr>0</ns_st_tpr>
|
<ns_st_tpr>0</ns_st_tpr>
|
||||||
<ns_st_tep>EQ00135</ns_st_tep>
|
<ns_st_tep>EQ00135</ns_st_tep>
|
||||||
<ns_st_ep>點講都係一家人 Episode 46</ns_st_ep>
|
<ns_st_ep>點講都係一家人 Episode 46</ns_st_ep>
|
||||||
<ns_st_li>1</ns_st_li>
|
<ns_st_li>1</ns_st_li>
|
||||||
<ns_st_tdt>20240913</ns_st_tdt>
|
<ns_st_tdt>20240913</ns_st_tdt>
|
||||||
<ns_st_tm>1130</ns_st_tm>
|
<ns_st_tm>1130</ns_st_tm>
|
||||||
<ns_st_ty>0001</ns_st_ty>
|
<ns_st_ty>0001</ns_st_ty>
|
||||||
<ns_st_cl>3704000</ns_st_cl>
|
<ns_st_cl>3704000</ns_st_cl>
|
||||||
</ComScore>
|
</ComScore>
|
||||||
</EpgItem>
|
</EpgItem>
|
||||||
<EpgItem>
|
<EpgItem>
|
||||||
<EpgStartDateTime>2024-09-13 12:30:00</EpgStartDateTime>
|
<EpgStartDateTime>2024-09-13 12:30:00</EpgStartDateTime>
|
||||||
<EpgEndDateTime>2024-09-13 13:30:00</EpgEndDateTime>
|
<EpgEndDateTime>2024-09-13 13:30:00</EpgEndDateTime>
|
||||||
<EpgOtherInfo></EpgOtherInfo>
|
<EpgOtherInfo></EpgOtherInfo>
|
||||||
<DisableLive>false</DisableLive>
|
<DisableLive>false</DisableLive>
|
||||||
<DisableVod>false</DisableVod>
|
<DisableVod>false</DisableVod>
|
||||||
<VODLicPeriod>2024-09-27 12:30:00</VODLicPeriod>
|
<VODLicPeriod>2024-09-27 12:30:00</VODLicPeriod>
|
||||||
<ProgramInfo>
|
<ProgramInfo>
|
||||||
<ProgramId>0</ProgramId>
|
<ProgramId>0</ProgramId>
|
||||||
<ProgramTitle></ProgramTitle>
|
<ProgramTitle></ProgramTitle>
|
||||||
<ProgramPos>0</ProgramPos>
|
<ProgramPos>0</ProgramPos>
|
||||||
<FirstRunDateTime></FirstRunDateTime>
|
<FirstRunDateTime></FirstRunDateTime>
|
||||||
<ProgramThumbnailUrl>http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg</ProgramThumbnailUrl>
|
<ProgramThumbnailUrl>http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg</ProgramThumbnailUrl>
|
||||||
</ProgramInfo>
|
</ProgramInfo>
|
||||||
<EpisodeInfo>
|
<EpisodeInfo>
|
||||||
<EpisodeId>ED00311</EpisodeId>
|
<EpisodeId>ED00311</EpisodeId>
|
||||||
<EpisodeIndex>0</EpisodeIndex>
|
<EpisodeIndex>0</EpisodeIndex>
|
||||||
<EpisodeShortDescription>麝香之路</EpisodeShortDescription>
|
<EpisodeShortDescription>麝香之路</EpisodeShortDescription>
|
||||||
<EpisodeLongDescription>Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world</EpisodeLongDescription>
|
<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>
|
<EpisodeThumbnailUrl>http://tv.fantv.hk/images/nosuchthumbnail.jpg</EpisodeThumbnailUrl>
|
||||||
</EpisodeInfo>
|
</EpisodeInfo>
|
||||||
<ComScore>
|
<ComScore>
|
||||||
<ns_st_stc></ns_st_stc>
|
<ns_st_stc></ns_st_stc>
|
||||||
<ns_st_pr>麝香之路</ns_st_pr>
|
<ns_st_pr>麝香之路</ns_st_pr>
|
||||||
<ns_st_tpr>0</ns_st_tpr>
|
<ns_st_tpr>0</ns_st_tpr>
|
||||||
<ns_st_tep>ED00311</ns_st_tep>
|
<ns_st_tep>ED00311</ns_st_tep>
|
||||||
<ns_st_ep>麝香之路 2024-09-13</ns_st_ep>
|
<ns_st_ep>麝香之路 2024-09-13</ns_st_ep>
|
||||||
<ns_st_li>1</ns_st_li>
|
<ns_st_li>1</ns_st_li>
|
||||||
<ns_st_tdt>20240913</ns_st_tdt>
|
<ns_st_tdt>20240913</ns_st_tdt>
|
||||||
<ns_st_tm>1230</ns_st_tm>
|
<ns_st_tm>1230</ns_st_tm>
|
||||||
<ns_st_ty>0001</ns_st_ty>
|
<ns_st_ty>0001</ns_st_ty>
|
||||||
<ns_st_cl>3704000</ns_st_cl>
|
<ns_st_cl>3704000</ns_st_cl>
|
||||||
</ComScore>
|
</ComScore>
|
||||||
</EpgItem>
|
</EpgItem>
|
||||||
</Channel>
|
</Channel>
|
||||||
</ProgramGuide>`
|
</ProgramGuide>`
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe('https://epg-file.hoy.tv/hoy/OTT7620240913.xml')
|
||||||
'https://epg-file.hoy.tv/hoy/OTT7620240913.xml'
|
})
|
||||||
)
|
|
||||||
})
|
it('can parse response', () => {
|
||||||
|
const result = parser({ content, channel, date }).map(p => {
|
||||||
it('can parse response', () => {
|
p.start = p.start.toJSON()
|
||||||
const result = parser({ content, channel, date }).map(p => {
|
p.stop = p.stop.toJSON()
|
||||||
p.start = p.start.toJSON()
|
return p
|
||||||
p.stop = p.stop.toJSON()
|
})
|
||||||
return p
|
|
||||||
})
|
expect(result).toMatchObject([
|
||||||
|
{
|
||||||
expect(result).toMatchObject([
|
start: '2024-09-13T03:30:00.000Z',
|
||||||
{
|
stop: '2024-09-13T04:30:00.000Z',
|
||||||
start: '2024-09-13T03:30:00.000Z',
|
title: '點講都係一家人[PG]',
|
||||||
stop: '2024-09-13T04:30:00.000Z',
|
sub_title: '第46集'
|
||||||
title: '點講都係一家人[PG]',
|
},
|
||||||
sub_title: '第46集',
|
{
|
||||||
},
|
start: '2024-09-13T04:30:00.000Z',
|
||||||
{
|
stop: '2024-09-13T05:30:00.000Z',
|
||||||
start: '2024-09-13T04:30:00.000Z',
|
title: '麝香之路',
|
||||||
stop: '2024-09-13T05:30:00.000Z',
|
description:
|
||||||
title: '麝香之路',
|
'Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world'
|
||||||
description: 'Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world',
|
}
|
||||||
}
|
])
|
||||||
])
|
})
|
||||||
})
|
|
||||||
|
|
|
@ -172,14 +172,17 @@ function parseItems(content, channel, date) {
|
||||||
if (!data || !Array.isArray(data.programs)) return []
|
if (!data || !Array.isArray(data.programs)) return []
|
||||||
|
|
||||||
return data.programs
|
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 => {
|
.map(p => {
|
||||||
if (Array.isArray(p.date) && p.date.length) {
|
if (Array.isArray(p.date) && p.date.length) {
|
||||||
p.date = p.date[0]
|
p.date = p.date[0]
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +1,80 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'ipko.tv',
|
site: 'ipko.tv',
|
||||||
timezone: 'Europe/Belgrade',
|
timezone: 'Europe/Belgrade',
|
||||||
days: 5,
|
days: 5,
|
||||||
url({ date, channel }) { return 'https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData' },
|
url() {
|
||||||
request: {
|
return 'https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData'
|
||||||
method: 'POST',
|
},
|
||||||
headers: {
|
request: {
|
||||||
'Host': 'stargate.ipko.tv',
|
method: 'POST',
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
|
headers: {
|
||||||
'Accept': 'application/json, text/plain, */*',
|
Host: 'stargate.ipko.tv',
|
||||||
'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
|
'User-Agent':
|
||||||
'Content-Type': 'application/json',
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
|
||||||
'X-AppLayout': '1',
|
Accept: 'application/json, text/plain, */*',
|
||||||
'x-language': 'sq',
|
'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
|
||||||
'Origin': 'https://ipko.tv',
|
'Content-Type': 'application/json',
|
||||||
'Sec-Fetch-Dest': 'empty',
|
'X-AppLayout': '1',
|
||||||
'Sec-Fetch-Mode': 'cors',
|
'x-language': 'sq',
|
||||||
'Sec-Fetch-Site': 'cross-site',
|
Origin: 'https://ipko.tv',
|
||||||
'Sec-GPC': '1',
|
'Sec-Fetch-Dest': 'empty',
|
||||||
'Connection': 'keep-alive'
|
'Sec-Fetch-Mode': 'cors',
|
||||||
},
|
'Sec-Fetch-Site': 'cross-site',
|
||||||
data({ channel, date }) {
|
'Sec-GPC': '1',
|
||||||
const todayEpoch = date.startOf('day').unix();
|
Connection: 'keep-alive'
|
||||||
const nextDayEpoch = date.add(1, 'day').startOf('day').unix();
|
},
|
||||||
return JSON.stringify({
|
data({ channel, date }) {
|
||||||
ch_ext_id: channel.site_id,
|
const todayEpoch = date.startOf('day').unix()
|
||||||
from: todayEpoch,
|
const nextDayEpoch = date.add(1, 'day').startOf('day').unix()
|
||||||
to: nextDayEpoch
|
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 => {
|
parser: function ({ content }) {
|
||||||
const start = dayjs.unix(show.show_start).utc();
|
const programs = []
|
||||||
const stop = dayjs.unix(show.show_end).utc();
|
const data = JSON.parse(content)
|
||||||
const programData = {
|
data.shows.forEach(show => {
|
||||||
title: show.title,
|
const start = dayjs.unix(show.show_start).utc()
|
||||||
description: show.summary || 'No description available',
|
const stop = dayjs.unix(show.show_end).utc()
|
||||||
start: start.toISOString(),
|
const programData = {
|
||||||
stop: stop.toISOString(),
|
title: show.title,
|
||||||
thumbnail: show.thumbnail
|
description: show.summary || 'No description available',
|
||||||
}
|
start: start.toISOString(),
|
||||||
programs.push(programData)
|
stop: stop.toISOString(),
|
||||||
})
|
thumbnail: show.thumbnail
|
||||||
return programs
|
}
|
||||||
},
|
programs.push(programData)
|
||||||
async channels() {
|
})
|
||||||
const response = await axios.post('https://stargate.ipko.tv/api/titan.tv.WebEpg/ZapList', JSON.stringify({ includeRadioStations: true }), {
|
return programs
|
||||||
headers: this.request.headers
|
},
|
||||||
});
|
async channels() {
|
||||||
|
const response = await axios.post(
|
||||||
const data = response.data.data;
|
'https://stargate.ipko.tv/api/titan.tv.WebEpg/ZapList',
|
||||||
return data.map(item => ({
|
JSON.stringify({ includeRadioStations: true }),
|
||||||
lang: 'sq',
|
{
|
||||||
name: String(item.channel.title),
|
headers: this.request.headers
|
||||||
site_id: String(item.channel.id),
|
}
|
||||||
//logo: String(item.channel.logo)
|
)
|
||||||
}))
|
|
||||||
}
|
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)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,115 +1,111 @@
|
||||||
const { parser, url } = require('./ipko.tv.config.js')
|
const { parser, url } = require('./ipko.tv.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2024-12-24', 'YYYY-MM-DD').startOf('day')
|
const date = dayjs.utc('2024-12-24', 'YYYY-MM-DD').startOf('day')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'ipko-promo',
|
site_id: 'ipko-promo',
|
||||||
xmltv_id: 'IPKOPROMO'
|
xmltv_id: 'IPKOPROMO'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ date, channel })).toBe('https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData')
|
expect(url({ date, channel })).toBe('https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = `
|
const content = `
|
||||||
{
|
{
|
||||||
"shows": [
|
"shows": [
|
||||||
{
|
{
|
||||||
"title": "IPKO Promo",
|
"title": "IPKO Promo",
|
||||||
"show_start": 1735012800,
|
"show_start": 1735012800,
|
||||||
"show_end": 1735020000,
|
"show_end": 1735020000,
|
||||||
"timestamp": "5:00 - 7:00",
|
"timestamp": "5:00 - 7:00",
|
||||||
"show_id": "EPG_TvProfil_IPKOPROMO_296105567",
|
"show_id": "EPG_TvProfil_IPKOPROMO_296105567",
|
||||||
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
|
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
|
||||||
"is_adult": false,
|
"is_adult": false,
|
||||||
"friendly_id": "ipko_promo_4cf3",
|
"friendly_id": "ipko_promo_4cf3",
|
||||||
"pg": "",
|
"pg": "",
|
||||||
"genres": [],
|
"genres": [],
|
||||||
"year": 0,
|
"year": 0,
|
||||||
"summary": "",
|
"summary": "",
|
||||||
"categories": "Other",
|
"categories": "Other",
|
||||||
"stb_only": false,
|
"stb_only": false,
|
||||||
"is_live": false,
|
"is_live": false,
|
||||||
"original_title": "IPKO Promo"
|
"original_title": "IPKO Promo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "IPKO Promo",
|
"title": "IPKO Promo",
|
||||||
"show_start": 1735020000,
|
"show_start": 1735020000,
|
||||||
"show_end": 1735027200,
|
"show_end": 1735027200,
|
||||||
"timestamp": "7:00 - 9:00",
|
"timestamp": "7:00 - 9:00",
|
||||||
"show_id": "EPG_TvProfil_IPKOPROMO_296105568",
|
"show_id": "EPG_TvProfil_IPKOPROMO_296105568",
|
||||||
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
|
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
|
||||||
"is_adult": false,
|
"is_adult": false,
|
||||||
"friendly_id": "ipko_promo_416b",
|
"friendly_id": "ipko_promo_416b",
|
||||||
"pg": "",
|
"pg": "",
|
||||||
"genres": [],
|
"genres": [],
|
||||||
"year": 0,
|
"year": 0,
|
||||||
"summary": "",
|
"summary": "",
|
||||||
"categories": "Other",
|
"categories": "Other",
|
||||||
"stb_only": false,
|
"stb_only": false,
|
||||||
"is_live": false,
|
"is_live": false,
|
||||||
"original_title": "IPKO Promo"
|
"original_title": "IPKO Promo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "IPKO Promo",
|
"title": "IPKO Promo",
|
||||||
"show_start": 1735027200,
|
"show_start": 1735027200,
|
||||||
"show_end": 1735034400,
|
"show_end": 1735034400,
|
||||||
"timestamp": "9:00 - 11:00",
|
"timestamp": "9:00 - 11:00",
|
||||||
"show_id": "EPG_TvProfil_IPKOPROMO_296105569",
|
"show_id": "EPG_TvProfil_IPKOPROMO_296105569",
|
||||||
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
|
"thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg",
|
||||||
"is_adult": false,
|
"is_adult": false,
|
||||||
"friendly_id": "ipko_promo_2e23",
|
"friendly_id": "ipko_promo_2e23",
|
||||||
"pg": "",
|
"pg": "",
|
||||||
"genres": [],
|
"genres": [],
|
||||||
"year": 0,
|
"year": 0,
|
||||||
"summary": "",
|
"summary": "",
|
||||||
"categories": "Other",
|
"categories": "Other",
|
||||||
"stb_only": false,
|
"stb_only": false,
|
||||||
"is_live": false,
|
"is_live": false,
|
||||||
"original_title": "IPKO Promo"
|
"original_title": "IPKO Promo"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}`
|
}`
|
||||||
|
|
||||||
const result = parser({ content, channel }).map(p => {
|
const result = parser({ content, channel })
|
||||||
p.start = p.start
|
|
||||||
p.stop = p.stop
|
expect(result).toMatchObject([
|
||||||
return p
|
{
|
||||||
})
|
title: 'IPKO Promo',
|
||||||
|
description: 'No description available',
|
||||||
expect(result).toMatchObject([
|
start: '2024-12-24T04:00:00.000Z',
|
||||||
{
|
stop: '2024-12-24T06:00:00.000Z',
|
||||||
title: "IPKO Promo",
|
thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
|
||||||
description: "No description available",
|
},
|
||||||
start: "2024-12-24T04:00:00.000Z",
|
{
|
||||||
stop: "2024-12-24T06:00:00.000Z",
|
title: 'IPKO Promo',
|
||||||
thumbnail: "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg"
|
description: 'No description available',
|
||||||
},
|
start: '2024-12-24T06:00:00.000Z',
|
||||||
{
|
stop: '2024-12-24T08:00:00.000Z',
|
||||||
title: "IPKO Promo",
|
thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
|
||||||
description: "No description available",
|
},
|
||||||
start: "2024-12-24T06:00:00.000Z",
|
{
|
||||||
stop: "2024-12-24T08:00:00.000Z",
|
title: 'IPKO Promo',
|
||||||
thumbnail: "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg"
|
description: 'No description available',
|
||||||
},
|
start: '2024-12-24T08:00:00.000Z',
|
||||||
{
|
stop: '2024-12-24T10:00:00.000Z',
|
||||||
title: "IPKO Promo",
|
thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
|
||||||
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":[]}'
|
||||||
|
})
|
||||||
it('can handle empty guide', () => {
|
expect(result).toMatchObject([])
|
||||||
const result = parser({
|
})
|
||||||
content: '{"shows":[]}'
|
|
||||||
})
|
|
||||||
expect(result).toMatchObject([])
|
|
||||||
})
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ module.exports = {
|
||||||
async channels() {
|
async channels() {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const data = await 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)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
|
|
@ -77,8 +77,8 @@ function parseItems(content) {
|
||||||
let data
|
let data
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(content)
|
data = JSON.parse(content)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.log(error.message)
|
return []
|
||||||
}
|
}
|
||||||
if (!data || !Array.isArray(data)) return []
|
if (!data || !Array.isArray(data)) return []
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ module.exports = {
|
||||||
async channels() {
|
async channels() {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const data = await 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)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ function parseStop($item) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return dayjs(timeString, 'YYYY-MM-DD HH:mm:ssZZ')
|
return dayjs(timeString, 'YYYY-MM-DD HH:mm:ssZZ')
|
||||||
} catch (err) {
|
} catch {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,93 +1,97 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'mediasetinfinity.mediaset.it',
|
site: 'mediasetinfinity.mediaset.it',
|
||||||
days: 2,
|
days: 2,
|
||||||
url: function ({date, channel}) {
|
url: function ({ date, channel }) {
|
||||||
// Get the epoch timestamp
|
// Get the epoch timestamp
|
||||||
const todayEpoch = date.startOf('day').utc().valueOf()
|
const todayEpoch = date.startOf('day').utc().valueOf()
|
||||||
// Get the epoch timestamp for the next day
|
// Get the epoch timestamp for the next day
|
||||||
const nextDayEpoch = date.add(1, 'day').startOf('day').utc().valueOf()
|
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}`
|
return `https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=${todayEpoch}~${nextDayEpoch}&byCallSign=${channel.site_id}`
|
||||||
},
|
},
|
||||||
parser: function ({content}) {
|
parser: function ({ content }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
|
|
||||||
if (!data.response || !data.response.entries || !data.response.entries[0] || !data.response.entries[0].listings) {
|
if (
|
||||||
// If the structure is not as expected, return an empty array
|
!data.response ||
|
||||||
return programs
|
!data.response.entries ||
|
||||||
}
|
!data.response.entries[0] ||
|
||||||
|
!data.response.entries[0].listings
|
||||||
const listings = data.response.entries[0].listings
|
) {
|
||||||
|
// If the structure is not as expected, return an empty array
|
||||||
listings.forEach((listing) => {
|
return programs
|
||||||
const title = listing.mediasetlisting$epgTitle
|
}
|
||||||
const subTitle = listing.program.title
|
|
||||||
const season = parseSeason(listing)
|
const listings = data.response.entries[0].listings
|
||||||
const episode = parseEpisode(listing)
|
|
||||||
|
listings.forEach(listing => {
|
||||||
|
const title = listing.mediasetlisting$epgTitle
|
||||||
if (listing.program.title && listing.startTime && listing.endTime) {
|
const subTitle = listing.program.title
|
||||||
programs.push({
|
const season = parseSeason(listing)
|
||||||
title: title || subTitle,
|
const episode = parseEpisode(listing)
|
||||||
sub_title: title && title != subTitle ? subTitle : null,
|
|
||||||
description: listing.program.description || null,
|
if (listing.program.title && listing.startTime && listing.endTime) {
|
||||||
category: listing.program.mediasetprogram$skyGenre || null,
|
programs.push({
|
||||||
season: episode && !season ? '0' : season,
|
title: title || subTitle,
|
||||||
episode: episode,
|
sub_title: title && title != subTitle ? subTitle : null,
|
||||||
start: parseTime(listing.startTime),
|
description: listing.program.description || null,
|
||||||
stop: parseTime(listing.endTime),
|
category: listing.program.mediasetprogram$skyGenre || null,
|
||||||
image: getMaxResolutionThumbnails(listing)
|
season: episode && !season ? '0' : season,
|
||||||
})
|
episode: episode,
|
||||||
}
|
start: parseTime(listing.startTime),
|
||||||
})
|
stop: parseTime(listing.endTime),
|
||||||
|
image: getMaxResolutionThumbnails(listing)
|
||||||
return programs
|
})
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
|
return programs
|
||||||
function parseTime(timestamp) {
|
}
|
||||||
return dayjs(timestamp).utc().format('YYYY-MM-DD HH:mm')
|
}
|
||||||
}
|
|
||||||
|
function parseTime(timestamp) {
|
||||||
function parseSeason(item) {
|
return dayjs(timestamp).utc().format('YYYY-MM-DD HH:mm')
|
||||||
if (!item.mediasetlisting$shortDescription) return null
|
}
|
||||||
const season = item.mediasetlisting$shortDescription.match(/S(\d+)\s/)
|
|
||||||
return season ? season[1] : null
|
function parseSeason(item) {
|
||||||
}
|
if (!item.mediasetlisting$shortDescription) return null
|
||||||
|
const season = item.mediasetlisting$shortDescription.match(/S(\d+)\s/)
|
||||||
function parseEpisode(item) {
|
return season ? season[1] : null
|
||||||
if (!item.mediasetlisting$shortDescription) return null
|
}
|
||||||
const episode = item.mediasetlisting$shortDescription.match(/Ep(\d+)\s/)
|
|
||||||
return episode ? episode[1] : null
|
function parseEpisode(item) {
|
||||||
}
|
if (!item.mediasetlisting$shortDescription) return null
|
||||||
|
const episode = item.mediasetlisting$shortDescription.match(/Ep(\d+)\s/)
|
||||||
function getMaxResolutionThumbnails(item) {
|
return episode ? episode[1] : null
|
||||||
const thumbnails = item.program.thumbnails || null
|
}
|
||||||
const maxResolutionThumbnails = {}
|
|
||||||
|
function getMaxResolutionThumbnails(item) {
|
||||||
for (const key in thumbnails) {
|
const thumbnails = item.program.thumbnails || null
|
||||||
const type = key.split('-')[0] // Estrarre il tipo di thumbnail
|
const maxResolutionThumbnails = {}
|
||||||
const {width, height, url, title} = thumbnails[key]
|
|
||||||
|
for (const key in thumbnails) {
|
||||||
if (!maxResolutionThumbnails[type] ||
|
const type = key.split('-')[0] // Estrarre il tipo di thumbnail
|
||||||
(width * height > maxResolutionThumbnails[type].width * maxResolutionThumbnails[type].height)) {
|
const { width, height, url, title } = thumbnails[key]
|
||||||
maxResolutionThumbnails[type] = {width, height, url, title}
|
|
||||||
}
|
if (
|
||||||
}
|
!maxResolutionThumbnails[type] ||
|
||||||
if (maxResolutionThumbnails.image_keyframe_poster)
|
width * height > maxResolutionThumbnails[type].width * maxResolutionThumbnails[type].height
|
||||||
return maxResolutionThumbnails.image_keyframe_poster.url
|
) {
|
||||||
else if (maxResolutionThumbnails.image_header_poster)
|
maxResolutionThumbnails[type] = { width, height, url, title }
|
||||||
return maxResolutionThumbnails.image_header_poster.url
|
}
|
||||||
else
|
}
|
||||||
return null
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -1,46 +1,53 @@
|
||||||
const {parser, url} = require('./mediasetinfinity.mediaset.it.config.js')
|
const { parser, url } = require('./mediasetinfinity.mediaset.it.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2024-01-20', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2024-01-20', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'LB', xmltv_id: '20.it'
|
site_id: 'LB',
|
||||||
}
|
xmltv_id: '20.it'
|
||||||
|
}
|
||||||
it('can generate valid url', () => {
|
|
||||||
expect(url({
|
it('can generate valid url', () => {
|
||||||
channel,
|
expect(
|
||||||
date
|
url({
|
||||||
})).toBe('https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=1705708800000~1705795200000&byCallSign=LB')
|
channel,
|
||||||
})
|
date
|
||||||
|
})
|
||||||
it('can parse response', () => {
|
).toBe(
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')
|
'https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=1705708800000~1705795200000&byCallSign=LB'
|
||||||
const results = parser({content, date}).map(p => {
|
)
|
||||||
return p
|
})
|
||||||
})
|
|
||||||
|
it('can parse response', () => {
|
||||||
expect(results[3]).toMatchObject({
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')
|
||||||
start: '2024-01-20 02:14',
|
const results = parser({ content, date }).map(p => {
|
||||||
stop: '2024-01-20 02:54',
|
return p
|
||||||
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.',
|
expect(results[3]).toMatchObject({
|
||||||
category: 'Intrattenimento',
|
start: '2024-01-20 02:14',
|
||||||
season: '7',
|
stop: '2024-01-20 02:54',
|
||||||
episode: '22',
|
title: 'Chicago Fire',
|
||||||
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'
|
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',
|
||||||
it('can handle empty guide', () => {
|
season: '7',
|
||||||
const result = parser({
|
episode: '22',
|
||||||
content: '[]'
|
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'
|
||||||
expect(result).toMatchObject([])
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('can handle empty guide', () => {
|
||||||
|
const result = parser({
|
||||||
|
content: '[]'
|
||||||
|
})
|
||||||
|
expect(result).toMatchObject([])
|
||||||
|
})
|
||||||
|
|
|
@ -40,7 +40,7 @@ module.exports = {
|
||||||
async channels() {
|
async channels() {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const data = await 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: {
|
headers: {
|
||||||
Origin: 'https://www.meo.pt'
|
Origin: 'https://www.meo.pt'
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,101 +1,105 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'meuguia.tv',
|
site: 'meuguia.tv',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel }) {
|
url({ channel }) {
|
||||||
return `https://meuguia.tv/programacao/canal/${channel.site_id}`
|
return `https://meuguia.tv/programacao/canal/${channel.site_id}`
|
||||||
},
|
},
|
||||||
parser({ content, date }) {
|
parser({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
parseItems(content, date).forEach(item => {
|
parseItems(content, date).forEach(item => {
|
||||||
if (dayjs.utc(item.start).isSame(date, 'day')) {
|
if (dayjs.utc(item.start).isSame(date, 'day')) {
|
||||||
programs.push(item)
|
programs.push(item)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const channels = []
|
const channels = []
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const baseUrl = 'https://meuguia.tv'
|
const baseUrl = 'https://meuguia.tv'
|
||||||
|
|
||||||
let seq = 0
|
let seq = 0
|
||||||
const queues = [baseUrl]
|
const queues = [baseUrl]
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!queues.length) {
|
if (!queues.length) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
const url = queues.shift()
|
const url = queues.shift()
|
||||||
const content = await axios
|
const content = await axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(response => response.data)
|
.then(response => response.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
if (content) {
|
if (content) {
|
||||||
const [ $, items ] = getItems(content)
|
const [$, items] = getItems(content)
|
||||||
if (seq === 0) {
|
if (seq === 0) {
|
||||||
queues.push(...items.map(category => baseUrl + $(category).attr('href')))
|
queues.push(...items.map(category => baseUrl + $(category).attr('href')))
|
||||||
} else {
|
} else {
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const href = $(item).attr('href')
|
const href = $(item).attr('href')
|
||||||
channels.push({
|
channels.push({
|
||||||
lang: 'pt',
|
lang: 'pt',
|
||||||
site_id: href.substr(href.lastIndexOf('/') + 1),
|
site_id: href.substr(href.lastIndexOf('/') + 1),
|
||||||
name: $(item).find('.licontent h2').text().trim()
|
name: $(item).find('.licontent h2').text().trim()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
seq++
|
seq++
|
||||||
}
|
}
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItems(content) {
|
function getItems(content) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
return [$, $('div.mw ul li a').toArray()]
|
return [$, $('div.mw ul li a').toArray()]
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, date) {
|
function parseItems(content, date) {
|
||||||
const result = []
|
const result = []
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
let lastDate
|
let lastDate
|
||||||
for (const item of $('ul.mw li').toArray()) {
|
for (const item of $('ul.mw li').toArray()) {
|
||||||
const $item = $(item)
|
const $item = $(item)
|
||||||
if ($item.hasClass('subheader')) {
|
if ($item.hasClass('subheader')) {
|
||||||
lastDate = `${$item.text().split(', ')[1]}/${date.format('YYYY')}`
|
lastDate = `${$item.text().split(', ')[1]}/${date.format('YYYY')}`
|
||||||
} else if ($item.hasClass('divider')) {
|
} else if ($item.hasClass('divider')) {
|
||||||
// ignore
|
// ignore
|
||||||
} else if (lastDate) {
|
} else if (lastDate) {
|
||||||
const data = { title: $item.find('a').attr('title').trim() }
|
const data = { title: $item.find('a').attr('title').trim() }
|
||||||
const ep = data.title.match(/T(\d+) EP(\d+)/)
|
const ep = data.title.match(/T(\d+) EP(\d+)/)
|
||||||
if (ep) {
|
if (ep) {
|
||||||
data.season = parseInt(ep[1])
|
data.season = parseInt(ep[1])
|
||||||
data.episode = parseInt(ep[2])
|
data.episode = parseInt(ep[2])
|
||||||
}
|
}
|
||||||
data.start = dayjs.tz(`${lastDate} ${$item.find('.time').text()}`, 'DD/MM/YYYY HH:mm', 'America/Sao_Paulo')
|
data.start = dayjs.tz(
|
||||||
result.push(data)
|
`${lastDate} ${$item.find('.time').text()}`,
|
||||||
}
|
'DD/MM/YYYY HH:mm',
|
||||||
}
|
'America/Sao_Paulo'
|
||||||
// use stop time from next item
|
)
|
||||||
if (result.length > 1) {
|
result.push(data)
|
||||||
for (let i = 0; i < result.length - 1; i++) {
|
}
|
||||||
result[i].stop = result[i + 1].start
|
}
|
||||||
}
|
// use stop time from next item
|
||||||
}
|
if (result.length > 1) {
|
||||||
|
for (let i = 0; i < result.length - 1; i++) {
|
||||||
return result
|
result[i].stop = result[i + 1].start
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
@ -1,60 +1,60 @@
|
||||||
const { parser, url } = require('./meuguia.tv.config.js')
|
const { parser, url } = require('./meuguia.tv.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-11-21').startOf('d')
|
const date = dayjs.utc('2023-11-21').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'AXN',
|
site_id: 'AXN',
|
||||||
xmltv_id: 'AXN.id'
|
xmltv_id: 'AXN.id'
|
||||||
}
|
}
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel })).toBe('https://meuguia.tv/programacao/canal/AXN')
|
expect(url({ channel })).toBe('https://meuguia.tv/programacao/canal/AXN')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||||
const result = parser({ content, channel, date }).map(p => {
|
const result = parser({ content, channel, date }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
if (p.stop) {
|
if (p.stop) {
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toMatchObject([
|
expect(result).toMatchObject([
|
||||||
{
|
{
|
||||||
title: 'Hawaii Five-0 : T10 EP4 - Tiny Is the Flower, Yet It Scents the Grasses Around It',
|
title: 'Hawaii Five-0 : T10 EP4 - Tiny Is the Flower, Yet It Scents the Grasses Around It',
|
||||||
start: '2023-11-21T21:20:00.000Z',
|
start: '2023-11-21T21:20:00.000Z',
|
||||||
stop: '2023-11-21T22:15:00.000Z',
|
stop: '2023-11-21T22:15:00.000Z',
|
||||||
season: 10,
|
season: 10,
|
||||||
episode: 4
|
episode: 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title:
|
title:
|
||||||
"Hawaii Five-0 : T10 EP5 - Don't Blame Ghosts and Spirits for One's Troubles; A Human Is Responsible",
|
"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',
|
start: '2023-11-21T22:15:00.000Z',
|
||||||
stop: '2023-11-21T23:10:00.000Z',
|
stop: '2023-11-21T23:10:00.000Z',
|
||||||
season: 10,
|
season: 10,
|
||||||
episode: 5
|
episode: 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'NCIS : T5 EP15 - In the Zone',
|
title: 'NCIS : T5 EP15 - In the Zone',
|
||||||
start: '2023-11-21T23:10:00.000Z',
|
start: '2023-11-21T23:10:00.000Z',
|
||||||
season: 5,
|
season: 5,
|
||||||
episode: 15
|
episode: 15
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
channel,
|
channel,
|
||||||
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
content: '<!DOCTYPE html><html><head></head><body></body></html>'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -42,7 +42,7 @@ module.exports = {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(`https://www.mewatch.sg/channel-guide`)
|
.get('https://www.mewatch.sg/channel-guide')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,7 @@ dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
doFetch
|
doFetch.setCheckResult(false).setDebugger(debug)
|
||||||
.setCheckResult(false)
|
|
||||||
.setDebugger(debug)
|
|
||||||
|
|
||||||
const languages = { en: 'english', id: 'indonesia' }
|
const languages = { en: 'english', id: 'indonesia' }
|
||||||
const cookies = {}
|
const cookies = {}
|
||||||
|
@ -125,7 +123,7 @@ async function parseItems(content, date, cookies) {
|
||||||
const url = $item.find('a').attr('href')
|
const url = $item.find('a').attr('href')
|
||||||
const headers = {
|
const headers = {
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
Cookie: cookies,
|
Cookie: cookies
|
||||||
}
|
}
|
||||||
queues.push({ i: $item, url, params: { headers, timeout } })
|
queues.push({ i: $item, url, params: { headers, timeout } })
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,8 +48,10 @@ function parseItems(context) {
|
||||||
schDayPrograms.forEach((program, i) => {
|
schDayPrograms.forEach((program, i) => {
|
||||||
const itemDay = {
|
const itemDay = {
|
||||||
progStart: parseStart($(schDayMonth), $(program)),
|
progStart: parseStart($(schDayMonth), $(program)),
|
||||||
progStop: parseStop($(schDayMonth), schDayPrograms[i + 1] ?
|
progStop: parseStop(
|
||||||
$(schDayPrograms[i + 1]) : null),
|
$(schDayMonth),
|
||||||
|
schDayPrograms[i + 1] ? $(schDayPrograms[i + 1]) : null
|
||||||
|
),
|
||||||
progTitle: parseTitle($(program)),
|
progTitle: parseTitle($(program)),
|
||||||
progDesc: parseDescription($(program))
|
progDesc: parseDescription($(program))
|
||||||
}
|
}
|
||||||
|
@ -91,7 +93,9 @@ function parseStop(schDayMonth, itemNext) {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return dayjs.tz(
|
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',
|
'YYYY-MMM-DD HH:mm',
|
||||||
tz
|
tz
|
||||||
)
|
)
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -41,7 +41,7 @@ module.exports = {
|
||||||
const pages = Array.from(Array(totalPages).keys())
|
const pages = Array.from(Array(totalPages).keys())
|
||||||
for (let page of pages) {
|
for (let page of pages) {
|
||||||
const data = await axios
|
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') },
|
params: { page, date: dayjs().format('YYYY-MM-DD') },
|
||||||
headers: {
|
headers: {
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
@ -65,7 +65,7 @@ module.exports = {
|
||||||
|
|
||||||
async function getTotalPageCount() {
|
async function getTotalPageCount() {
|
||||||
const data = await axios
|
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') },
|
params: { page: 0, date: dayjs().format('YYYY-MM-DD') },
|
||||||
headers: {
|
headers: {
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
|
|
@ -43,7 +43,7 @@ module.exports = {
|
||||||
const pages = Array.from(Array(totalPages).keys())
|
const pages = Array.from(Array(totalPages).keys())
|
||||||
for (let page of pages) {
|
for (let page of pages) {
|
||||||
const data = await axios
|
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') },
|
params: { page, date: dayjs().format('YYYY-MM-DD') },
|
||||||
headers: {
|
headers: {
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
@ -67,7 +67,7 @@ module.exports = {
|
||||||
|
|
||||||
async function getTotalPageCount() {
|
async function getTotalPageCount() {
|
||||||
const data = await axios
|
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') },
|
params: { page: 0, date: dayjs().format('YYYY-MM-DD') },
|
||||||
headers: {
|
headers: {
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
@ -84,8 +84,8 @@ function parseContent(content, channel) {
|
||||||
let data
|
let data
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(content)
|
data = JSON.parse(content)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.log(error)
|
return []
|
||||||
}
|
}
|
||||||
if (!data || !data.channels || !data.channels.length) return null
|
if (!data || !data.channels || !data.channels.length) return null
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ module.exports = {
|
||||||
|
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.post(
|
.post(
|
||||||
`https://services.mujtvprogram.cz/tvprogram2services/services/tvchannellist_mobile.php`,
|
'https://services.mujtvprogram.cz/tvprogram2services/services/tvchannellist_mobile.php',
|
||||||
params,
|
params,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -86,7 +86,7 @@ function parseItems(content) {
|
||||||
if (!data) return []
|
if (!data) return []
|
||||||
const programmes = data['tv-program-programmes'].programme
|
const programmes = data['tv-program-programmes'].programme
|
||||||
return programmes && Array.isArray(programmes) ? programmes : []
|
return programmes && Array.isArray(programmes) ? programmes : []
|
||||||
} catch (err) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
'User-Agent':
|
'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 = {
|
module.exports = {
|
||||||
|
|
|
@ -26,12 +26,11 @@ it('can generate valid url for today', () => {
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
|
||||||
const results = parser({ content, date })
|
const results = parser({ content, date }).map(p => {
|
||||||
.map(p => {
|
p.start = p.start.toJSON()
|
||||||
p.start = p.start.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
return p
|
||||||
return p
|
})
|
||||||
})
|
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2022-11-19T23:00:00.000Z',
|
start: '2022-11-19T23:00:00.000Z',
|
||||||
|
|
|
@ -23,14 +23,15 @@ module.exports = {
|
||||||
channel.site_id
|
channel.site_id
|
||||||
}.html?dt=${date.format('YYYY-MM-DD')}`
|
}.html?dt=${date.format('YYYY-MM-DD')}`
|
||||||
},
|
},
|
||||||
async parser({ content, date, channel }) {
|
async parser({ content, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
|
|
||||||
if (content) {
|
if (content) {
|
||||||
const queues = []
|
const queues = []
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
$('table.table > tbody > tr').toArray()
|
$('table.table > tbody > tr')
|
||||||
|
.toArray()
|
||||||
.forEach(el => {
|
.forEach(el => {
|
||||||
const td = $(el).find('td:eq(1)')
|
const td = $(el).find('td:eq(1)')
|
||||||
const title = td.find('h5 a')
|
const title = td.find('h5 a')
|
||||||
|
@ -66,12 +67,16 @@ module.exports = {
|
||||||
const subTitle = parseText($('.tab-pane > h5 > strong'))
|
const subTitle = parseText($('.tab-pane > h5 > strong'))
|
||||||
const description = parseText($('.tab-pane > .tvbody > p'))
|
const description = parseText($('.tab-pane > .tvbody > p'))
|
||||||
const image = $('.program-media-image img').attr('src')
|
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())
|
.map(el => $(el).text())
|
||||||
const casts = $('.single-cast-head:not([id])').toArray()
|
const casts = $('.single-cast-head:not([id])')
|
||||||
|
.toArray()
|
||||||
.map(el => {
|
.map(el => {
|
||||||
const cast = { name: parseText($(el).find('a')) }
|
const cast = { name: parseText($(el).find('a')) }
|
||||||
const [, role] = $(el).text().match(/\((.*)\)/) || [null, null]
|
const [, role] = $(el)
|
||||||
|
.text()
|
||||||
|
.match(/\((.*)\)/) || [null, null]
|
||||||
if (role) {
|
if (role) {
|
||||||
cast.role = role
|
cast.role = role
|
||||||
}
|
}
|
||||||
|
@ -102,7 +107,7 @@ module.exports = {
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,11 +120,17 @@ module.exports = {
|
||||||
// process form -> provider
|
// process form -> provider
|
||||||
if (queue.t === 'p') {
|
if (queue.t === 'p') {
|
||||||
const $ = cheerio.load(res)
|
const $ = cheerio.load(res)
|
||||||
$('#guide_provider option').toArray()
|
$('#guide_provider option')
|
||||||
|
.toArray()
|
||||||
.forEach(el => {
|
.forEach(el => {
|
||||||
const opt = $(el)
|
const opt = $(el)
|
||||||
const provider = opt.attr('value')
|
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
|
// process provider -> region
|
||||||
|
@ -135,26 +146,30 @@ module.exports = {
|
||||||
u_time: now.format('HHmm'),
|
u_time: now.format('HHmm'),
|
||||||
is_mobile: 1
|
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
|
// process schedule -> channels
|
||||||
if (queue.t === 's') {
|
if (queue.t === 's') {
|
||||||
const $ = cheerio.load(res)
|
const $ = cheerio.load(res)
|
||||||
$('.channelname')
|
$('.channelname').each((i, el) => {
|
||||||
.each((i, el) => {
|
const name = $(el).find('center > a:eq(1)').text()
|
||||||
const name = $(el).find('center > a:eq(1)').text()
|
const url = $(el).find('center > a:eq(1)').attr('href')
|
||||||
const url = $(el).find('center > a:eq(1)').attr('href')
|
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)
|
||||||
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)
|
const site_id = `${number}/${slug}`
|
||||||
const site_id = `${number}/${slug}`
|
if (channels[site_id] === undefined) {
|
||||||
if (channels[site_id] === undefined) {
|
channels[site_id] = {
|
||||||
channels[site_id] = {
|
lang: 'en',
|
||||||
lang: 'en',
|
site_id,
|
||||||
site_id,
|
name
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -178,13 +193,10 @@ function parseTime(date, time) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseText($item) {
|
function parseText($item) {
|
||||||
let text = $item.text()
|
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
|
||||||
.replace(/\t/g, '')
|
|
||||||
.replace(/\n/g, ' ')
|
|
||||||
.trim()
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (text.match(/ /)) {
|
if (text.match(/\s\s/)) {
|
||||||
text = text.replace(/ /g, ' ')
|
text = text.replace(/\s\s/g, ' ')
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
@ -17,16 +17,18 @@ const channel = {
|
||||||
xmltv_id: 'BBCOneLondon.uk'
|
xmltv_id: 'BBCOneLondon.uk'
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.get.mockImplementation((url, opts) => {
|
axios.get.mockImplementation(url => {
|
||||||
if (
|
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({
|
return Promise.resolve({
|
||||||
data: fs.readFileSync(path.join(__dirname, '__data__', 'programme.html'))
|
data: fs.readFileSync(path.join(__dirname, '__data__', 'programme.html'))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (
|
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({
|
return Promise.resolve({
|
||||||
data: fs.readFileSync(path.join(__dirname, '__data__', 'programme2.html'))
|
data: fs.readFileSync(path.join(__dirname, '__data__', 'programme2.html'))
|
||||||
|
@ -57,7 +59,8 @@ it('can parse response', async () => {
|
||||||
title: 'Captain Phillips',
|
title: 'Captain Phillips',
|
||||||
description:
|
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',
|
'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']
|
category: ['Factual', 'Movie/Drama', 'Thriller']
|
||||||
})
|
})
|
||||||
expect(results[1]).toMatchObject({
|
expect(results[1]).toMatchObject({
|
||||||
|
@ -67,7 +70,8 @@ it('can parse response', async () => {
|
||||||
subTitle: 'Past and Pressure Season 6, Episode 5',
|
subTitle: 'Past and Pressure Season 6, Episode 5',
|
||||||
description:
|
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',
|
'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'],
|
category: ['Challenge/Reality Show', 'Show/Game Show'],
|
||||||
season: 6,
|
season: 6,
|
||||||
episode: 5
|
episode: 5
|
||||||
|
|
|
@ -1,73 +1,80 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'neo.io',
|
site: 'neo.io',
|
||||||
timezone: 'Europe/Ljubljana',
|
timezone: 'Europe/Ljubljana',
|
||||||
days: 5,
|
days: 5,
|
||||||
url({ date, channel }) { return 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData' },
|
url() {
|
||||||
request: {
|
return 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData'
|
||||||
method: 'POST',
|
},
|
||||||
headers: {
|
request: {
|
||||||
'Host': 'stargate.telekom.si',
|
method: 'POST',
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
|
headers: {
|
||||||
'Accept': 'application/json, text/plain, */*',
|
Host: 'stargate.telekom.si',
|
||||||
'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
|
'User-Agent':
|
||||||
'Content-Type': 'application/json',
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
|
||||||
'X-AppLayout': '1',
|
Accept: 'application/json, text/plain, */*',
|
||||||
'x-language': 'sl',
|
'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
|
||||||
'Origin': 'https://neo.io',
|
'Content-Type': 'application/json',
|
||||||
'Sec-Fetch-Dest': 'empty',
|
'X-AppLayout': '1',
|
||||||
'Sec-Fetch-Mode': 'cors',
|
'x-language': 'sl',
|
||||||
'Sec-Fetch-Site': 'cross-site',
|
Origin: 'https://neo.io',
|
||||||
'Sec-GPC': '1',
|
'Sec-Fetch-Dest': 'empty',
|
||||||
'Connection': 'keep-alive'
|
'Sec-Fetch-Mode': 'cors',
|
||||||
},
|
'Sec-Fetch-Site': 'cross-site',
|
||||||
data({ channel, date }) {
|
'Sec-GPC': '1',
|
||||||
const todayEpoch = date.startOf('day').unix();
|
Connection: 'keep-alive'
|
||||||
const nextDayEpoch = date.add(1, 'day').startOf('day').unix();
|
},
|
||||||
return JSON.stringify({
|
data({ channel, date }) {
|
||||||
ch_ext_id: channel.site_id,
|
const todayEpoch = date.startOf('day').unix()
|
||||||
from: todayEpoch,
|
const nextDayEpoch = date.add(1, 'day').startOf('day').unix()
|
||||||
to: nextDayEpoch
|
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 => {
|
parser: function ({ content }) {
|
||||||
const start = dayjs.unix(show.show_start).utc();
|
const programs = []
|
||||||
const stop = dayjs.unix(show.show_end).utc();
|
const data = JSON.parse(content)
|
||||||
const programData = {
|
data.shows.forEach(show => {
|
||||||
title: show.title,
|
const start = dayjs.unix(show.show_start).utc()
|
||||||
description: show.summary || 'No description available',
|
const stop = dayjs.unix(show.show_end).utc()
|
||||||
start: start.toISOString(),
|
const programData = {
|
||||||
stop: stop.toISOString(),
|
title: show.title,
|
||||||
thumbnail: show.thumbnail
|
description: show.summary || 'No description available',
|
||||||
}
|
start: start.toISOString(),
|
||||||
programs.push(programData)
|
stop: stop.toISOString(),
|
||||||
})
|
thumbnail: show.thumbnail
|
||||||
return programs
|
}
|
||||||
},
|
programs.push(programData)
|
||||||
async channels() {
|
})
|
||||||
const response = await axios.post('https://stargate.telekom.si/api/titan.tv.WebEpg/ZapList', JSON.stringify({ includeRadioStations: true }), {
|
return programs
|
||||||
headers: this.request.headers
|
},
|
||||||
});
|
async channels() {
|
||||||
|
const response = await axios.post(
|
||||||
const data = response.data.data;
|
'https://stargate.telekom.si/api/titan.tv.WebEpg/ZapList',
|
||||||
return data.map(item => ({
|
JSON.stringify({ includeRadioStations: true }),
|
||||||
lang: 'sq',
|
{
|
||||||
name: String(item.channel.title),
|
headers: this.request.headers
|
||||||
site_id: String(item.channel.id),
|
}
|
||||||
//logo: String(item.channel.logo)
|
)
|
||||||
}))
|
|
||||||
}
|
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)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,121 +1,124 @@
|
||||||
const { parser, url } = require('./neo.io.config.js')
|
const { parser, url } = require('./neo.io.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('day')
|
const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('day')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'tv-slo-1',
|
site_id: 'tv-slo-1',
|
||||||
xmltv_id: 'TVSLO1.si'
|
xmltv_id: 'TVSLO1.si'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ date, channel })).toBe('https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData')
|
expect(url({ date, channel })).toBe(
|
||||||
})
|
'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData'
|
||||||
|
)
|
||||||
it('can parse response', () => {
|
})
|
||||||
const content = `
|
|
||||||
{
|
it('can parse response', () => {
|
||||||
"shows": [
|
const content = `
|
||||||
{
|
{
|
||||||
"title": "Napovedujemo",
|
"shows": [
|
||||||
"show_start": 1735185900,
|
{
|
||||||
"show_end": 1735192200,
|
"title": "Napovedujemo",
|
||||||
"timestamp": "5:05 - 6:50",
|
"show_start": 1735185900,
|
||||||
"show_id": "CUP_IECOM_SLO1_10004660",
|
"show_end": 1735192200,
|
||||||
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg",
|
"timestamp": "5:05 - 6:50",
|
||||||
"is_adult": false,
|
"show_id": "CUP_IECOM_SLO1_10004660",
|
||||||
"friendly_id": "napovedujemo_db48",
|
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg",
|
||||||
"pg": "",
|
"is_adult": false,
|
||||||
"genres": [
|
"friendly_id": "napovedujemo_db48",
|
||||||
"napovednik"
|
"pg": "",
|
||||||
],
|
"genres": [
|
||||||
"year": 0,
|
"napovednik"
|
||||||
"summary": "Vabilo k ogledu naših oddaj.",
|
],
|
||||||
"categories": "Ostalo",
|
"year": 0,
|
||||||
"stb_only": false,
|
"summary": "Vabilo k ogledu naših oddaj.",
|
||||||
"is_live": false,
|
"categories": "Ostalo",
|
||||||
"original_title": "Napovedujemo"
|
"stb_only": false,
|
||||||
},
|
"is_live": false,
|
||||||
{
|
"original_title": "Napovedujemo"
|
||||||
"title": "S0E0 - Hrabri zajčki: Prvi sneg",
|
},
|
||||||
"show_start": 1735192200,
|
{
|
||||||
"show_end": 1735192800,
|
"title": "S0E0 - Hrabri zajčki: Prvi sneg",
|
||||||
"timestamp": "6:50 - 7:00",
|
"show_start": 1735192200,
|
||||||
"show_id": "CUP_IECOM_SLO1_79637910",
|
"show_end": 1735192800,
|
||||||
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg",
|
"timestamp": "6:50 - 7:00",
|
||||||
"is_adult": false,
|
"show_id": "CUP_IECOM_SLO1_79637910",
|
||||||
"friendly_id": "hrabri_zajcki_prvi_sneg_1619",
|
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg",
|
||||||
"pg": "",
|
"is_adult": false,
|
||||||
"genres": [
|
"friendly_id": "hrabri_zajcki_prvi_sneg_1619",
|
||||||
"risanka"
|
"pg": "",
|
||||||
],
|
"genres": [
|
||||||
"year": 2020,
|
"risanka"
|
||||||
"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",
|
"year": 2020,
|
||||||
"stb_only": false,
|
"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.",
|
||||||
"is_live": false,
|
"categories": "Otroški/Mladinski",
|
||||||
"original_title": "S0E0 - Brave Bunnies"
|
"stb_only": false,
|
||||||
},
|
"is_live": false,
|
||||||
{
|
"original_title": "S0E0 - Brave Bunnies"
|
||||||
"title": "Dobro jutro",
|
},
|
||||||
"show_start": 1735192800,
|
{
|
||||||
"show_end": 1735203900,
|
"title": "Dobro jutro",
|
||||||
"timestamp": "7:00 - 10:05",
|
"show_start": 1735192800,
|
||||||
"show_id": "CUP_IECOM_SLO1_79637911",
|
"show_end": 1735203900,
|
||||||
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg",
|
"timestamp": "7:00 - 10:05",
|
||||||
"is_adult": false,
|
"show_id": "CUP_IECOM_SLO1_79637911",
|
||||||
"friendly_id": "dobro_jutro_2f10",
|
"thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg",
|
||||||
"pg": "",
|
"is_adult": false,
|
||||||
"genres": [
|
"friendly_id": "dobro_jutro_2f10",
|
||||||
"zabavna oddaja"
|
"pg": "",
|
||||||
],
|
"genres": [
|
||||||
"year": 2024,
|
"zabavna oddaja"
|
||||||
"summary": "Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.",
|
],
|
||||||
"categories": "Razvedrilni program",
|
"year": 2024,
|
||||||
"stb_only": false,
|
"summary": "Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.",
|
||||||
"is_live": false,
|
"categories": "Razvedrilni program",
|
||||||
"original_title": "Dobro jutro"
|
"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
|
const result = parser({ content, channel })
|
||||||
return p
|
|
||||||
})
|
expect(result).toMatchObject([
|
||||||
|
{
|
||||||
expect(result).toMatchObject([
|
title: 'Napovedujemo',
|
||||||
{
|
description: 'Vabilo k ogledu naših oddaj.',
|
||||||
title: "Napovedujemo",
|
start: '2024-12-26T04:05:00.000Z',
|
||||||
description: "Vabilo k ogledu naših oddaj.",
|
stop: '2024-12-26T05:50:00.000Z',
|
||||||
start: "2024-12-26T04:05:00.000Z",
|
thumbnail:
|
||||||
stop: "2024-12-26T05:50:00.000Z",
|
'https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg'
|
||||||
thumbnail: "https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg"
|
},
|
||||||
},
|
{
|
||||||
{
|
title: 'S0E0 - Hrabri zajčki: Prvi sneg',
|
||||||
title: "S0E0 - Hrabri zajčki: Prvi sneg",
|
description:
|
||||||
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.",
|
'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",
|
start: '2024-12-26T05:50:00.000Z',
|
||||||
stop: "2024-12-26T06:00: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"
|
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.",
|
title: 'Dobro jutro',
|
||||||
start: "2024-12-26T06:00:00.000Z",
|
description:
|
||||||
stop: "2024-12-26T09:05:00.000Z",
|
'Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.',
|
||||||
thumbnail: "https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg"
|
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":[]}'
|
|
||||||
})
|
it('can handle empty guide', () => {
|
||||||
expect(result).toMatchObject([])
|
const result = parser({
|
||||||
})
|
content: '{"shows":[]}'
|
||||||
|
})
|
||||||
|
expect(result).toMatchObject([])
|
||||||
|
})
|
||||||
|
|
|
@ -50,7 +50,7 @@ function parseItems(content, date) {
|
||||||
if (!data || !data.item || !Array.isArray(data.item.episodes)) return []
|
if (!data || !data.item || !Array.isArray(data.item.episodes)) return []
|
||||||
|
|
||||||
return data.item.episodes.filter(ep => ep.schedule.startsWith(date.format('YYYY-MM-DD')))
|
return data.item.episodes.filter(ep => ep.schedule.startsWith(date.format('YYYY-MM-DD')))
|
||||||
} catch (err) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,46 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'nhl.com',
|
site: 'nhl.com',
|
||||||
// I'm not sure what `endDate` represents but they only return 1 day of
|
// I'm not sure what `endDate` represents but they only return 1 day of
|
||||||
// results, with `endTime`s ocassionally in the following day.
|
// results, with `endTime`s ocassionally in the following day.
|
||||||
days: 1,
|
days: 1,
|
||||||
url: ({ date }) => `https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split("T")[0]}`,
|
url: ({ date }) =>
|
||||||
parser({ content }) {
|
`https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split('T')[0]}`,
|
||||||
const programs = []
|
parser({ content }) {
|
||||||
const items = parseItems(content)
|
const programs = []
|
||||||
for (const item of items) {
|
const items = parseItems(content)
|
||||||
programs.push({
|
for (const item of items) {
|
||||||
title: item.title,
|
programs.push({
|
||||||
description: item.description === item.title ? undefined : item.description,
|
title: item.title,
|
||||||
category: "Sports",
|
description: item.description === item.title ? undefined : item.description,
|
||||||
// image: parseImage(item),
|
category: 'Sports',
|
||||||
start: parseStart(item),
|
// image: parseImage(item),
|
||||||
stop: parseStop(item)
|
start: parseStart(item),
|
||||||
})
|
stop: parseStop(item)
|
||||||
}
|
})
|
||||||
|
}
|
||||||
return programs
|
|
||||||
}
|
return programs
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Unfortunately I couldn't determine how these are
|
|
||||||
// supposed to be formatted. Pointers appreciated!
|
// Unfortunately I couldn't determine how these are
|
||||||
// function parseImage(item) {
|
// supposed to be formatted. Pointers appreciated!
|
||||||
// const uri = item.broadcastImageUrl
|
// function parseImage(item) {
|
||||||
|
// const uri = item.broadcastImageUrl
|
||||||
// return uri ? `https://???/${uri}` : null
|
|
||||||
// }
|
// return uri ? `https://???/${uri}` : null
|
||||||
|
// }
|
||||||
function parseStart(item) {
|
|
||||||
return dayjs(item.startTime)
|
function parseStart(item) {
|
||||||
}
|
return dayjs(item.startTime)
|
||||||
|
}
|
||||||
function parseStop(item) {
|
|
||||||
return dayjs(item.endTime)
|
function parseStop(item) {
|
||||||
}
|
return dayjs(item.endTime)
|
||||||
|
}
|
||||||
function parseItems(content) {
|
|
||||||
return JSON.parse(content).broadcasts
|
function parseItems(content) {
|
||||||
}
|
return JSON.parse(content).broadcasts
|
||||||
|
}
|
||||||
|
|
|
@ -1,44 +1,44 @@
|
||||||
const { parser, url } = require('./nhl.com.config.js')
|
const { parser, url } = require('./nhl.com.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2024-11-21', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2024-11-21', 'YYYY-MM-DD').startOf('d')
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ date })).toBe(
|
expect(url({ date })).toBe('https://api-web.nhle.com/v1/network/tv-schedule/2024-11-21')
|
||||||
'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'))
|
||||||
it('can parse response', () => {
|
let results = parser({ content, date })
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
results = results.map(p => {
|
||||||
let results = parser({ content, date })
|
p.start = p.start.toJSON()
|
||||||
results = results.map(p => {
|
p.stop = p.stop.toJSON()
|
||||||
p.start = p.start.toJSON()
|
return p
|
||||||
p.stop = p.stop.toJSON()
|
})
|
||||||
return p
|
|
||||||
})
|
expect(results[0]).toMatchObject({
|
||||||
|
start: '2024-11-21T12:00:00.000Z',
|
||||||
expect(results[0]).toMatchObject({
|
stop: '2024-11-21T13:00:00.000Z',
|
||||||
start: '2024-11-21T12:00:00.000Z',
|
title: 'On The Fly',
|
||||||
stop: '2024-11-21T13:00:00.000Z',
|
category: 'Sports'
|
||||||
title: 'On The Fly',
|
})
|
||||||
category: 'Sports',
|
})
|
||||||
})
|
|
||||||
})
|
it('can handle empty guide', () => {
|
||||||
|
const results = parser({
|
||||||
it('can handle empty guide', () => {
|
content: JSON.stringify({
|
||||||
const results = parser({ content: JSON.stringify({
|
// extra props not necessary but they form a valid response
|
||||||
// extra props not necessary but they form a valid response
|
date: '2024-11-21',
|
||||||
date: "2024-11-21",
|
startDate: '2024-11-07',
|
||||||
startDate: "2024-11-07",
|
endDate: '2024-12-05',
|
||||||
endDate: "2024-12-05",
|
broadcasts: []
|
||||||
broadcasts: [],
|
})
|
||||||
}) })
|
})
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,68 +1,68 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
'X-Apikey': 'xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI',
|
'X-Apikey': 'xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI',
|
||||||
'X-Core-Appversion': '2.14.0.1',
|
'X-Core-Appversion': '2.14.0.1',
|
||||||
'X-Core-Contentratinglimit': '0',
|
'X-Core-Contentratinglimit': '0',
|
||||||
'X-Core-Deviceid': '',
|
'X-Core-Deviceid': '',
|
||||||
'X-Core-Devicetype': 'web',
|
'X-Core-Devicetype': 'web',
|
||||||
Origin: 'https://nostv.pt',
|
Origin: 'https://nostv.pt',
|
||||||
'User-Agent':
|
'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'
|
'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 = {
|
module.exports = {
|
||||||
site: 'nostv.pt',
|
site: 'nostv.pt',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel, date }) {
|
url({ channel, date }) {
|
||||||
return `https://tyr-prod.apigee.net/nostv/ott/schedule/range/contents/guest?channels=${
|
return `https://tyr-prod.apigee.net/nostv/ott/schedule/range/contents/guest?channels=${
|
||||||
channel.site_id
|
channel.site_id
|
||||||
}&minDate=${date.format('YYYY-MM-DD')}T00:00:00Z&maxDate=${date.format(
|
}&minDate=${date.format('YYYY-MM-DD')}T00:00:00Z&maxDate=${date.format(
|
||||||
'YYYY-MM-DD'
|
'YYYY-MM-DD'
|
||||||
)}T23:59:59Z&isDateInclusive=true&client_id=${headers['X-Apikey']}`
|
)}T23:59:59Z&isDateInclusive=true&client_id=${headers['X-Apikey']}`
|
||||||
},
|
},
|
||||||
request: { headers },
|
request: { headers },
|
||||||
parser({ content }) {
|
parser({ content }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
if (content) {
|
if (content) {
|
||||||
const items = Array.isArray(content) ? content : JSON.parse(content)
|
const items = Array.isArray(content) ? content : JSON.parse(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.Metadata?.Title,
|
title: item.Metadata?.Title,
|
||||||
sub_title: item.Metadata?.SubTitle ? item.Metadata?.SubTitle : null,
|
sub_title: item.Metadata?.SubTitle ? item.Metadata?.SubTitle : null,
|
||||||
description: item.Metadata?.Description,
|
description: item.Metadata?.Description,
|
||||||
season: item.Metadata?.Season,
|
season: item.Metadata?.Season,
|
||||||
episode: item.Metadata?.Episode,
|
episode: item.Metadata?.Episode,
|
||||||
image: item.Images
|
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']}`
|
? `https://mage.stream.nos.pt/v1/nostv_mage/Images?sourceUri=${item.Images[0].Url}&profile=ott_1_452x340&client_id=${headers['X-Apikey']}`
|
||||||
: null,
|
: null,
|
||||||
start: dayjs.utc(item.UtcDateTimeStart),
|
start: dayjs.utc(item.UtcDateTimeStart),
|
||||||
stop: dayjs.utc(item.UtcDateTimeEnd)
|
stop: dayjs.utc(item.UtcDateTimeEnd)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const result = await axios
|
const result = await axios
|
||||||
.get(
|
.get(
|
||||||
`https://tyr-prod.apigee.net/nostv/ott/channels/guest?client_id=${headers['X-Apikey']}`,
|
`https://tyr-prod.apigee.net/nostv/ott/channels/guest?client_id=${headers['X-Apikey']}`,
|
||||||
{ headers }
|
{ headers }
|
||||||
)
|
)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
return result.map(item => {
|
return result.map(item => {
|
||||||
return {
|
return {
|
||||||
lang: 'pt',
|
lang: 'pt',
|
||||||
site_id: item.ServiceId,
|
site_id: item.ServiceId,
|
||||||
name: item.Name
|
name: item.Name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,51 @@
|
||||||
const { parser, url } = require('./nostv.pt.config.js')
|
const { parser, url } = require('./nostv.pt.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-12-11').startOf('d')
|
const date = dayjs.utc('2023-12-11').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '510',
|
site_id: '510',
|
||||||
xmltv_id: 'SPlus.pt'
|
xmltv_id: 'SPlus.pt'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe(
|
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'
|
'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', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
|
||||||
const results = parser({ content }).map(p => {
|
const results = parser({ content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-12-11T16:30:00.000Z',
|
start: '2023-12-11T16:30:00.000Z',
|
||||||
stop: '2023-12-11T17:00:00.000Z',
|
stop: '2023-12-11T17:00:00.000Z',
|
||||||
title: 'Village Vets',
|
title: 'Village Vets',
|
||||||
description:
|
description:
|
||||||
'A história de dois melhores amigos veterinários e o seu extraordinário trabalho na Austrália.',
|
'A história de dois melhores amigos veterinários e o seu extraordinário trabalho na Austrália.',
|
||||||
season: 1,
|
season: 1,
|
||||||
episode: 12,
|
episode: 12,
|
||||||
image:
|
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'
|
'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 () => {
|
it('can handle empty guide', async () => {
|
||||||
const results = await parser({
|
const results = await parser({
|
||||||
date,
|
date,
|
||||||
content: '[]'
|
content: '[]'
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -55,7 +55,7 @@ module.exports = {
|
||||||
.map(function () {
|
.map(function () {
|
||||||
return {
|
return {
|
||||||
lang: 'es',
|
lang: 'es',
|
||||||
site_id: $(this).attr('alt').replace(/\&/gi, '&'),
|
site_id: $(this).attr('alt').replace(/&/gi, '&'),
|
||||||
name: $(this).attr('alt')
|
name: $(this).attr('alt')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,81 +1,81 @@
|
||||||
const parser = require('epg-parser')
|
const parser = require('epg-parser')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'nzxmltv.com',
|
site: 'nzxmltv.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
request: {
|
request: {
|
||||||
cache: {
|
cache: {
|
||||||
ttl: 3600000 // 1 hour
|
ttl: 3600000 // 1 hour
|
||||||
},
|
},
|
||||||
maxContentLength: 104857600 // 100 MB
|
maxContentLength: 104857600 // 100 MB
|
||||||
},
|
},
|
||||||
url({ channel }) {
|
url({ channel }) {
|
||||||
const [path] = channel.site_id.split('#')
|
const [path] = channel.site_id.split('#')
|
||||||
|
|
||||||
return `https://nzxmltv.com/${path}.xml`
|
return `https://nzxmltv.com/${path}.xml`
|
||||||
},
|
},
|
||||||
parser({ content, channel, date }) {
|
parser({ content, channel, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
parseItems(content, channel, date).forEach(item => {
|
parseItems(content, channel, date).forEach(item => {
|
||||||
const program = {
|
const program = {
|
||||||
title: item.title?.[0]?.value,
|
title: item.title?.[0]?.value,
|
||||||
description: item.desc?.[0]?.value,
|
description: item.desc?.[0]?.value,
|
||||||
icon: item.icon?.[0],
|
icon: item.icon?.[0],
|
||||||
start: item.start,
|
start: item.start,
|
||||||
stop: item.stop
|
stop: item.stop
|
||||||
}
|
}
|
||||||
if (item.episodeNum) {
|
if (item.episodeNum) {
|
||||||
item.episodeNum.forEach(ep => {
|
item.episodeNum.forEach(ep => {
|
||||||
if (ep.system === 'xmltv_ns') {
|
if (ep.system === 'xmltv_ns') {
|
||||||
const [season, episode, _] = ep.value.split('.')
|
const [season, episode] = ep.value.split('.')
|
||||||
program.season = parseInt(season) + 1
|
program.season = parseInt(season) + 1
|
||||||
program.episode = parseInt(episode) + 1
|
program.episode = parseInt(episode) + 1
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
programs.push(program)
|
programs.push(program)
|
||||||
})
|
})
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ provider }) {
|
async channels({ provider }) {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
|
|
||||||
const providers = {
|
const providers = {
|
||||||
freeview: 'xmltv/guide',
|
freeview: 'xmltv/guide',
|
||||||
sky: 'sky/guide',
|
sky: 'sky/guide',
|
||||||
redbull: 'iptv/redbull',
|
redbull: 'iptv/redbull',
|
||||||
pluto: 'iptv/plutotv'
|
pluto: 'iptv/plutotv'
|
||||||
}
|
}
|
||||||
|
|
||||||
const channels = []
|
const channels = []
|
||||||
const path = providers[provider]
|
const path = providers[provider]
|
||||||
const xml = await axios
|
const xml = await axios
|
||||||
.get(`https://nzxmltv.com/${path}.xml`)
|
.get(`https://nzxmltv.com/${path}.xml`)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
const $ = cheerio.load(xml)
|
const $ = cheerio.load(xml)
|
||||||
$('tv channel').each((i, el) => {
|
$('tv channel').each((i, el) => {
|
||||||
const disp = $(el).find('display-name')
|
const disp = $(el).find('display-name')
|
||||||
const channelId = $(el).attr('id')
|
const channelId = $(el).attr('id')
|
||||||
|
|
||||||
channels.push({
|
channels.push({
|
||||||
lang: disp.attr('lang').substr(0, 2),
|
lang: disp.attr('lang').substr(0, 2),
|
||||||
site_id: `${path}#${channelId}`,
|
site_id: `${path}#${channelId}`,
|
||||||
name: disp.text().trim()
|
name: disp.text().trim()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content, channel, date) {
|
function parseItems(content, channel, date) {
|
||||||
const { programs } = parser.parse(content)
|
const { programs } = parser.parse(content)
|
||||||
const [, channelId] = channel.site_id.split('#')
|
const [, channelId] = channel.site_id.split('#')
|
||||||
|
|
||||||
return programs.filter(p => p.channel === channelId && date.isSame(p.start, 'day'))
|
return programs.filter(p => p.channel === channelId && date.isSame(p.start, 'day'))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
const { parser, url } = require('./nzxmltv.com.config.js')
|
const { parser, url } = require('./nzxmltv.com.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-11-21').startOf('d')
|
const date = dayjs.utc('2023-11-21').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'xmltv/guide#1',
|
site_id: 'xmltv/guide#1',
|
||||||
xmltv_id: 'TVNZ1.nz'
|
xmltv_id: 'TVNZ1.nz'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel })).toBe('https://nzxmltv.com/xmltv/guide.xml')
|
expect(url({ channel })).toBe('https://nzxmltv.com/xmltv/guide.xml')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'))
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'))
|
||||||
const results = parser({ content, channel, date })
|
const results = parser({ content, channel, date })
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
start: '2023-11-21T10:30:00.000Z',
|
start: '2023-11-21T10:30:00.000Z',
|
||||||
stop: '2023-11-21T11:25:00.000Z',
|
stop: '2023-11-21T11:25:00.000Z',
|
||||||
title: 'Sunday',
|
title: 'Sunday',
|
||||||
description:
|
description:
|
||||||
'On Sunday, an unmissable show with stories about divorce, weight loss, and the incomprehensible devastation of Gaza.',
|
'On Sunday, an unmissable show with stories about divorce, weight loss, and the incomprehensible devastation of Gaza.',
|
||||||
season: 2023,
|
season: 2023,
|
||||||
episode: 37,
|
episode: 37,
|
||||||
icon: 'https://www.thetvdb.com/banners/posters/5dbebff2986f2.jpg'
|
icon: 'https://www.thetvdb.com/banners/posters/5dbebff2986f2.jpg'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({ content: '', channel, date })
|
const result = parser({ content: '', channel, date })
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -132,7 +132,7 @@ module.exports = {
|
||||||
const $ = cheerio.load(data)
|
const $ = cheerio.load(data)
|
||||||
$('.channelname').each((i, el) => {
|
$('.channelname').each((i, el) => {
|
||||||
let name = $(el).find('center > a:eq(1)').text()
|
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')
|
const url = $(el).find('center > a:eq(1)').attr('href')
|
||||||
if (!url) return
|
if (!url) return
|
||||||
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)
|
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)
|
||||||
|
|
|
@ -1,113 +1,108 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const API_PROGRAM_ENDPOINT = 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO'
|
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_CHANNEL_ENDPOINT =
|
||||||
const API_IMAGE_ENDPOINT = 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images'
|
'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',
|
module.exports = {
|
||||||
days: 2,
|
site: 'orangetv.orange.es',
|
||||||
request: {
|
days: 2,
|
||||||
cache: {
|
request: {
|
||||||
ttl: 60 * 60 * 1000 // 1 hour
|
cache: {
|
||||||
}
|
ttl: 60 * 60 * 1000 // 1 hour
|
||||||
},
|
}
|
||||||
url({ date }) {
|
},
|
||||||
return `${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`
|
url({ date }) {
|
||||||
},
|
return `${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`
|
||||||
async parser({ content, channel, date }) {
|
},
|
||||||
let programs = []
|
async parser({ content, channel, date }) {
|
||||||
let items = parseItems(content, channel)
|
let programs = []
|
||||||
if (!items.length) return programs
|
let items = parseItems(content, channel)
|
||||||
|
if (!items.length) return programs
|
||||||
const promises = [
|
|
||||||
axios.get(
|
const promises = [
|
||||||
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`,
|
axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`),
|
||||||
),
|
axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_2.json`),
|
||||||
axios.get(
|
axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_3.json`)
|
||||||
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_2.json`,
|
]
|
||||||
),
|
|
||||||
axios.get(
|
await Promise.allSettled(promises)
|
||||||
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_3.json`,
|
.then(results => {
|
||||||
),
|
results.forEach(r => {
|
||||||
]
|
if (r.status === 'fulfilled') {
|
||||||
|
const parsed = parseItems(r.value.data, channel)
|
||||||
await Promise.allSettled(promises)
|
|
||||||
.then(results => {
|
items = items
|
||||||
results.forEach(r => {
|
.filter((item, index) => items.findIndex(oi => oi.id === item.id) === index)
|
||||||
if (r.status === 'fulfilled') {
|
.concat(parsed)
|
||||||
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 => {
|
||||||
.catch(console.error)
|
programs.push({
|
||||||
|
title: item.name,
|
||||||
items.forEach(item => {
|
description: item.description,
|
||||||
programs.push({
|
category: parseGenres(item),
|
||||||
title: item.name,
|
season: item.seriesSeason || null,
|
||||||
description: item.description,
|
episode: item.episodeId || null,
|
||||||
category: parseGenres(item),
|
icon: parseIcon(item),
|
||||||
season: item.seriesSeason || null,
|
start: dayjs.utc(item.startDate) || null,
|
||||||
episode: item.episodeId || null,
|
stop: dayjs.utc(item.endDate) || null
|
||||||
icon: parseIcon(item),
|
})
|
||||||
start: dayjs.utc(item.startDate) || null,
|
})
|
||||||
stop: dayjs.utc(item.endDate) || null,
|
|
||||||
})
|
return programs
|
||||||
})
|
},
|
||||||
|
async channels() {
|
||||||
return programs
|
const axios = require('axios')
|
||||||
},
|
const data = await axios
|
||||||
async channels() {
|
.get(API_CHANNEL_ENDPOINT)
|
||||||
const axios = require('axios')
|
.then(r => r.data)
|
||||||
const data = await axios
|
.catch(console.log)
|
||||||
.get(API_CHANNEL_ENDPOINT)
|
return data.response.map(item => {
|
||||||
.then(r => r.data)
|
return {
|
||||||
.catch(console.log)
|
lang: 'es',
|
||||||
return data.response.map(item => {
|
name: item.name,
|
||||||
return {
|
site_id: item.externalChannelId
|
||||||
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')
|
||||||
function parseIcon(item){
|
|
||||||
|
if (cover) {
|
||||||
if(item.attachments.length > 0){
|
return `${API_IMAGE_ENDPOINT}${cover.value}`
|
||||||
const cover = item.attachments.find(i => i.name === "COVER" || i.name === "cover")
|
}
|
||||||
|
}
|
||||||
if(cover)
|
|
||||||
{
|
return ''
|
||||||
return `${API_IMAGE_ENDPOINT}${cover.value}`;
|
}
|
||||||
}
|
|
||||||
}
|
function parseGenres(item) {
|
||||||
|
return item.genres.map(i => i.name)
|
||||||
return ''
|
}
|
||||||
}
|
|
||||||
|
function parseItems(content, channel) {
|
||||||
function parseGenres(item){
|
const json =
|
||||||
return item.genres.map(i => i.name);
|
typeof content === 'string' ? JSON.parse(content) : Array.isArray(content) ? content : []
|
||||||
}
|
|
||||||
|
if (!Array.isArray(json)) {
|
||||||
function parseItems(content, channel) {
|
return []
|
||||||
const json = typeof content === 'string' ? JSON.parse(content) : Array.isArray(content) ? content : []
|
}
|
||||||
|
|
||||||
if (!Array.isArray(json)) {
|
const channelData = json.find(i => i.channelExternalId == channel.site_id)
|
||||||
return [];
|
|
||||||
}
|
if (!channelData) return []
|
||||||
|
|
||||||
const channelData = json.find(i => i.channelExternalId == channel.site_id);
|
return channelData.programs
|
||||||
|
}
|
||||||
if(!channelData)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
return channelData.programs;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,49 +1,54 @@
|
||||||
const { parser, url } = require('./orangetv.orange.es.config.js')
|
const { parser, url } = require('./orangetv.orange.es.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
const date = dayjs.utc('2024-12-01', 'YYYY-MM-DD').startOf('d')
|
const date = dayjs.utc('2024-12-01', 'YYYY-MM-DD').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: '1010',
|
site_id: '1010',
|
||||||
xmltv_id: 'La1.es'
|
xmltv_id: 'La1.es'
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
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`)
|
expect(url({ date })).toBe(
|
||||||
})
|
`https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/${date.format(
|
||||||
|
'YYYYMMDD'
|
||||||
it('can parse response', async () => {
|
)}_8h_1.json`
|
||||||
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()
|
it('can parse response', async () => {
|
||||||
p.stop = p.stop.toJSON()
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')).toString()
|
||||||
return p
|
let results = await parser({ content, channel, date })
|
||||||
})
|
results = results.map(p => {
|
||||||
|
p.start = p.start.toJSON()
|
||||||
expect(results.length).toBe(4)
|
p.stop = p.stop.toJSON()
|
||||||
|
return p
|
||||||
var sampleResult = results[0];
|
})
|
||||||
|
|
||||||
expect(sampleResult).toMatchObject({
|
expect(results.length).toBe(4)
|
||||||
start: '2024-11-30T22:36:51.000Z',
|
|
||||||
stop: '2024-11-30T23:57:25.000Z',
|
var sampleResult = results[0]
|
||||||
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.',
|
expect(sampleResult).toMatchObject({
|
||||||
title: 'Loco de amor'
|
start: '2024-11-30T22:36:51.000Z',
|
||||||
})
|
stop: '2024-11-30T23:57:25.000Z',
|
||||||
})
|
category: ['Cine', 'Romance', 'Comedia', 'Comedia Romántica'],
|
||||||
|
description:
|
||||||
it('can handle empty guide', () => {
|
'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.',
|
||||||
const result = parser({
|
title: 'Loco de amor'
|
||||||
date,
|
})
|
||||||
channel,
|
})
|
||||||
content: '{}'
|
|
||||||
})
|
it('can handle empty guide', () => {
|
||||||
expect(result).toMatchObject({})
|
const result = parser({
|
||||||
})
|
date,
|
||||||
|
channel,
|
||||||
|
content: '{}'
|
||||||
|
})
|
||||||
|
expect(result).toMatchObject({})
|
||||||
|
})
|
||||||
|
|
|
@ -5,7 +5,12 @@ const timezone = require('dayjs/plugin/timezone')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
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 country = 'AE'
|
||||||
const tz = 'Asia/Dubai'
|
const tz = 'Asia/Dubai'
|
||||||
|
|
||||||
|
@ -13,11 +18,9 @@ module.exports = {
|
||||||
site: 'osn.com',
|
site: 'osn.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel, date }) {
|
url({ channel, date }) {
|
||||||
return `https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=${
|
return `https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=${encodeURIComponent(
|
||||||
encodeURIComponent(date.format('MM/DD/YYYY'))
|
date.format('MM/DD/YYYY')
|
||||||
}&co=${country}&ch=${
|
)}&co=${country}&ch=${channel.site_id}&mo=false&hr=0`
|
||||||
channel.site_id
|
|
||||||
}&mo=false&hr=0`
|
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
headers({ channel }) {
|
headers({ channel }) {
|
||||||
|
@ -46,7 +49,9 @@ module.exports = {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
for (const pkg of Object.values(packages)) {
|
for (const pkg of Object.values(packages)) {
|
||||||
const channels = await axios
|
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)
|
.then(response => response.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
|
|
|
@ -28,32 +28,30 @@ it('can generate valid url', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response (ar)', () => {
|
it('can parse response (ar)', () => {
|
||||||
const result = parser({ date, channel: channelAR, content })
|
const result = parser({ date, channel: channelAR, content }).map(a => {
|
||||||
.map(a => {
|
a.start = a.start.toJSON()
|
||||||
a.start = a.start.toJSON()
|
a.stop = a.stop.toJSON()
|
||||||
a.stop = a.stop.toJSON()
|
return a
|
||||||
return a
|
})
|
||||||
})
|
|
||||||
expect(result.length).toBe(29)
|
expect(result.length).toBe(29)
|
||||||
expect(result[1]).toMatchObject({
|
expect(result[1]).toMatchObject({
|
||||||
start: '2024-11-26T20:50:00.000Z',
|
start: '2024-11-26T20:50:00.000Z',
|
||||||
stop: '2024-11-26T21:45:00.000Z',
|
stop: '2024-11-26T21:45:00.000Z',
|
||||||
title: 'بيت الحلويات: الحلقة 3',
|
title: 'بيت الحلويات: الحلقة 3'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response (en)', () => {
|
it('can parse response (en)', () => {
|
||||||
const result = parser({ date, channel: channelEN, content })
|
const result = parser({ date, channel: channelEN, content }).map(a => {
|
||||||
.map(a => {
|
a.start = a.start.toJSON()
|
||||||
a.start = a.start.toJSON()
|
a.stop = a.stop.toJSON()
|
||||||
a.stop = a.stop.toJSON()
|
return a
|
||||||
return a
|
})
|
||||||
})
|
|
||||||
expect(result.length).toBe(29)
|
expect(result.length).toBe(29)
|
||||||
expect(result[1]).toMatchObject({
|
expect(result[1]).toMatchObject({
|
||||||
start: '2024-11-26T20:50:00.000Z',
|
start: '2024-11-26T20:50:00.000Z',
|
||||||
stop: '2024-11-26T21:45:00.000Z',
|
stop: '2024-11-26T21:45:00.000Z',
|
||||||
title: 'House Of Desserts: Episode 3',
|
title: 'House Of Desserts: Episode 3'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ function parseItems(content, date) {
|
||||||
let data
|
let data
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(json)
|
data = JSON.parse(json)
|
||||||
} catch (error) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,174 +1,172 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
let apiVersion
|
let apiVersion
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'pickx.be',
|
site: 'pickx.be',
|
||||||
days: 2,
|
days: 2,
|
||||||
setApiVersion: function (version) {
|
setApiVersion: function (version) {
|
||||||
apiVersion = version
|
apiVersion = version
|
||||||
},
|
},
|
||||||
getApiVersion: function () {
|
getApiVersion: function () {
|
||||||
return apiVersion
|
return apiVersion
|
||||||
},
|
},
|
||||||
fetchApiVersion: fetchApiVersion,
|
fetchApiVersion: fetchApiVersion,
|
||||||
url: async function ({ channel, date }) {
|
url: async function ({ channel, date }) {
|
||||||
if (!apiVersion) {
|
if (!apiVersion) {
|
||||||
await fetchApiVersion()
|
await fetchApiVersion()
|
||||||
}
|
}
|
||||||
return `https://px-epg.azureedge.net/airings/${apiVersion}/${date.format(
|
return `https://px-epg.azureedge.net/airings/${apiVersion}/${date.format(
|
||||||
'YYYY-MM-DD'
|
'YYYY-MM-DD'
|
||||||
)}/channel/${channel.site_id}?timezone=Europe%2FBrussels`
|
)}/channel/${channel.site_id}?timezone=Europe%2FBrussels`
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
headers: {
|
headers: {
|
||||||
Origin: 'https://www.pickx.be',
|
Origin: 'https://www.pickx.be',
|
||||||
Referer: 'https://www.pickx.be/'
|
Referer: 'https://www.pickx.be/'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parser({ channel, content }) {
|
parser({ channel, content }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
if (content) {
|
if (content) {
|
||||||
const items = JSON.parse(content)
|
const items = JSON.parse(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.program.title,
|
title: item.program.title,
|
||||||
sub_title: item.program.episodeTitle,
|
sub_title: item.program.episodeTitle,
|
||||||
description: item.program.description,
|
description: item.program.description,
|
||||||
category: item.program.translatedCategory?.[channel.lang]
|
category: item.program.translatedCategory?.[channel.lang]
|
||||||
? item.program.translatedCategory[channel.lang]
|
? item.program.translatedCategory[channel.lang]
|
||||||
: item.program.category.split('.')[1],
|
: item.program.category.split('.')[1],
|
||||||
image: item.program.posterFileName
|
image: item.program.posterFileName
|
||||||
? `https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/${item.program.posterFileName}`
|
? `https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/${item.program.posterFileName}`
|
||||||
: null,
|
: null,
|
||||||
season: item.program.seasonNumber,
|
season: item.program.seasonNumber,
|
||||||
episode: item.program.episodeNumber,
|
episode: item.program.episodeNumber,
|
||||||
actors: item.program.actors,
|
actors: item.program.actors,
|
||||||
director: item.program.director ? [item.program.director] : null,
|
director: item.program.director ? [item.program.director] : null,
|
||||||
start: dayjs.utc(item.programScheduleStart),
|
start: dayjs.utc(item.programScheduleStart),
|
||||||
stop: dayjs.utc(item.programScheduleEnd)
|
stop: dayjs.utc(item.programScheduleEnd)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ lang = '' }) {
|
async channels({ lang = '' }) {
|
||||||
const query = {
|
const query = {
|
||||||
operationName: 'getChannels',
|
operationName: 'getChannels',
|
||||||
variables: {
|
variables: {
|
||||||
language: lang,
|
language: lang,
|
||||||
queryParams: {},
|
queryParams: {},
|
||||||
id: '0',
|
id: '0',
|
||||||
params: {
|
params: {
|
||||||
shouldReadFromCache: true
|
shouldReadFromCache: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
query: `query getChannels($language: String!, $queryParams: ChannelQueryParams, $id: String, $params: ChannelParams) {
|
query: `query getChannels($language: String!, $queryParams: ChannelQueryParams, $id: String, $params: ChannelParams) {
|
||||||
channels(language: $language, queryParams: $queryParams, id: $id, params: $params) {
|
channels(language: $language, queryParams: $queryParams, id: $id, params: $params) {
|
||||||
id
|
id
|
||||||
channelReferenceNumber
|
channelReferenceNumber
|
||||||
name
|
name
|
||||||
callLetter
|
callLetter
|
||||||
number
|
number
|
||||||
logo {
|
logo {
|
||||||
key
|
key
|
||||||
url
|
url
|
||||||
__typename
|
__typename
|
||||||
}
|
}
|
||||||
language
|
language
|
||||||
hd
|
hd
|
||||||
radio
|
radio
|
||||||
replayable
|
replayable
|
||||||
ottReplayable
|
ottReplayable
|
||||||
playable
|
playable
|
||||||
ottPlayable
|
ottPlayable
|
||||||
recordable
|
recordable
|
||||||
subscribed
|
subscribed
|
||||||
cloudRecordable
|
cloudRecordable
|
||||||
catchUpWindowInHours
|
catchUpWindowInHours
|
||||||
isOttNPVREnabled
|
isOttNPVREnabled
|
||||||
ottNPVRStart
|
ottNPVRStart
|
||||||
subscription {
|
subscription {
|
||||||
channelRef
|
channelRef
|
||||||
subscribed
|
subscribed
|
||||||
upselling {
|
upselling {
|
||||||
upsellable
|
upsellable
|
||||||
packages
|
packages
|
||||||
__typename
|
__typename
|
||||||
}
|
}
|
||||||
__typename
|
__typename
|
||||||
}
|
}
|
||||||
packages
|
packages
|
||||||
__typename
|
__typename
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
const result = await axios
|
const result = await axios
|
||||||
.post('https://api.proximusmwc.be/tiams/v3/graphql', query)
|
.post('https://api.proximusmwc.be/tiams/v3/graphql', query)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
result?.data?.channels
|
result?.data?.channels
|
||||||
.filter(
|
.filter(
|
||||||
channel =>
|
channel =>
|
||||||
!channel.radio && (!lang || channel.language === (lang === 'de' ? 'ger' : lang))
|
!channel.radio && (!lang || channel.language === (lang === 'de' ? 'ger' : lang))
|
||||||
)
|
)
|
||||||
.map(channel => {
|
.map(channel => {
|
||||||
return {
|
return {
|
||||||
lang: channel.language === 'ger' ? 'de' : channel.language,
|
lang: channel.language === 'ger' ? 'de' : channel.language,
|
||||||
site_id: channel.id,
|
site_id: channel.id,
|
||||||
name: channel.name
|
name: channel.name
|
||||||
}
|
}
|
||||||
}) || []
|
}) || []
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function fetchApiVersion() {
|
async function fetchApiVersion() {
|
||||||
return new Promise(async (resolve, reject) => {
|
// you'll never find what happened here :)
|
||||||
try {
|
// load the pickx page and get the hash from the MWC configuration.
|
||||||
// you'll never find what happened here :)
|
// it's not the best way to get the version but it's the only way to get it.
|
||||||
// load the pickx page and get the hash from the MWC configuration.
|
const hashUrl = 'https://www.pickx.be/nl/televisie/tv-gids'
|
||||||
// it's not the best way to get the version but it's the only way to get it.
|
const hashData = await axios
|
||||||
|
.get(hashUrl)
|
||||||
const hashUrl = 'https://www.pickx.be/nl/televisie/tv-gids';
|
.then(r => {
|
||||||
|
const re = /"hashes":\["(.*)"\]/
|
||||||
const hashData = await axios.get(hashUrl)
|
const match = r.data.match(re)
|
||||||
.then(r => {
|
if (match && match[1]) {
|
||||||
const re = /"hashes":\["(.*)"\]/
|
return match[1]
|
||||||
const match = r.data.match(re)
|
} else {
|
||||||
if (match && match[1]) {
|
throw new Error('React app version hash not found')
|
||||||
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}`
|
||||||
.catch(console.error);
|
const response = await axios.get(versionUrl, {
|
||||||
|
headers: {
|
||||||
const versionUrl = `https://www.pickx.be/api/s-${hashData}`
|
Origin: 'https://www.pickx.be',
|
||||||
|
Referer: 'https://www.pickx.be/'
|
||||||
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
|
||||||
if (response.status === 200) {
|
resolve()
|
||||||
apiVersion = response.data.version
|
} else {
|
||||||
resolve()
|
console.error(`Failed to fetch API version. Status: ${response.status}`)
|
||||||
} else {
|
reject(`Failed to fetch API version. Status: ${response.status}`)
|
||||||
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)
|
||||||
} catch (error) {
|
reject(error)
|
||||||
console.error('Error during fetchApiVersion:', error)
|
}
|
||||||
reject(error)
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,76 +1,69 @@
|
||||||
jest.mock('./pickx.be.config.js', () => {
|
jest.mock('./pickx.be.config.js', () => {
|
||||||
const originalModule = jest.requireActual('./pickx.be.config.js')
|
const originalModule = jest.requireActual('./pickx.be.config.js')
|
||||||
return {
|
return {
|
||||||
...originalModule,
|
...originalModule,
|
||||||
fetchApiVersion: jest.fn(() => Promise.resolve())
|
fetchApiVersion: jest.fn(() => Promise.resolve())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const { parser, url, request, setApiVersion } = require('./pickx.be.config.js')
|
||||||
parser,
|
|
||||||
url,
|
const fs = require('fs')
|
||||||
request,
|
const path = require('path')
|
||||||
fetchApiVersion,
|
const dayjs = require('dayjs')
|
||||||
setApiVersion,
|
const utc = require('dayjs/plugin/utc')
|
||||||
getApiVersion
|
|
||||||
} = require('./pickx.be.config.js')
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const fs = require('fs')
|
const date = dayjs.utc('2023-12-13').startOf('d')
|
||||||
const path = require('path')
|
const channel = {
|
||||||
const dayjs = require('dayjs')
|
lang: 'fr',
|
||||||
const utc = require('dayjs/plugin/utc')
|
site_id: 'UID0118',
|
||||||
|
xmltv_id: 'Vedia.be'
|
||||||
dayjs.extend(utc)
|
}
|
||||||
|
|
||||||
const date = dayjs.utc('2023-12-13').startOf('d')
|
beforeEach(() => {
|
||||||
const channel = {
|
setApiVersion('mockedApiVersion')
|
||||||
lang: 'fr',
|
})
|
||||||
site_id: 'UID0118',
|
|
||||||
xmltv_id: 'Vedia.be'
|
it('can generate valid url', async () => {
|
||||||
}
|
const generatedUrl = await url({ channel, date })
|
||||||
|
expect(generatedUrl).toBe(
|
||||||
beforeEach(() => {
|
'https://px-epg.azureedge.net/airings/mockedApiVersion/2023-12-13/channel/UID0118?timezone=Europe%2FBrussels'
|
||||||
setApiVersion('mockedApiVersion')
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid url', async () => {
|
it('can generate valid request headers', () => {
|
||||||
const generatedUrl = await url({ channel, date })
|
expect(request.headers).toMatchObject({
|
||||||
expect(generatedUrl).toBe(
|
Origin: 'https://www.pickx.be',
|
||||||
`https://px-epg.azureedge.net/airings/mockedApiVersion/2023-12-13/channel/UID0118?timezone=Europe%2FBrussels`
|
Referer: 'https://www.pickx.be/'
|
||||||
)
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can generate valid request headers', () => {
|
it('can parse response', () => {
|
||||||
expect(request.headers).toMatchObject({
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
|
||||||
Origin: 'https://www.pickx.be',
|
const result = parser({ content, channel, date }).map(p => {
|
||||||
Referer: 'https://www.pickx.be/'
|
p.start = p.start.toJSON()
|
||||||
})
|
p.stop = p.stop.toJSON()
|
||||||
})
|
return p
|
||||||
|
})
|
||||||
it('can parse response', () => {
|
|
||||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
|
expect(result[0]).toMatchObject({
|
||||||
const result = parser({ content, channel, date }).map(p => {
|
start: '2023-12-12T23:55:00.000Z',
|
||||||
p.start = p.start.toJSON()
|
stop: '2023-12-13T00:15:00.000Z',
|
||||||
p.stop = p.stop.toJSON()
|
title: 'Le 22h30',
|
||||||
return p
|
description: 'Le journal de vivre ici.',
|
||||||
})
|
category: 'Info',
|
||||||
|
image:
|
||||||
expect(result[0]).toMatchObject({
|
'https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/250_250_4B990CC58066A7B2A660AFA0BDDE5C41.jpg'
|
||||||
start: '2023-12-12T23:55:00.000Z',
|
})
|
||||||
stop: '2023-12-13T00:15:00.000Z',
|
})
|
||||||
title: 'Le 22h30',
|
|
||||||
description: 'Le journal de vivre ici.',
|
it('can handle empty guide', () => {
|
||||||
category: 'Info',
|
const result = parser({
|
||||||
image:
|
date,
|
||||||
'https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/250_250_4B990CC58066A7B2A660AFA0BDDE5C41.jpg'
|
channel,
|
||||||
})
|
content: ''
|
||||||
})
|
})
|
||||||
|
expect(result).toMatchObject([])
|
||||||
it('can handle empty guide', () => {
|
})
|
||||||
const result = parser({
|
|
||||||
date,
|
|
||||||
channel,
|
|
||||||
content: ''
|
|
||||||
})
|
|
||||||
expect(result).toMatchObject([])
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,102 +1,104 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'player.ee.co.uk',
|
site: 'player.ee.co.uk',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ date, channel, hour = 0 }) {
|
url({ date, channel, hour = 0 }) {
|
||||||
return `https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=${
|
return `https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=${encodeURIComponent(
|
||||||
encodeURIComponent(channel.site_id)
|
channel.site_id
|
||||||
}&interval=${date.format('YYYY-MM-DD')}T${hour.toString().padStart(2,'0')}Z/PT12H`
|
)}&interval=${date.format('YYYY-MM-DD')}T${hour.toString().padStart(2, '0')}Z/PT12H`
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
headers: {
|
headers: {
|
||||||
Referer: 'https://player.ee.co.uk/'
|
Referer: 'https://player.ee.co.uk/'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async parser({ content, channel, date }) {
|
async parser({ content, channel, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
if (content) {
|
if (content) {
|
||||||
const schedule = JSON.parse(content)
|
const schedule = JSON.parse(content)
|
||||||
// fetch next 12 hours schedule
|
// fetch next 12 hours schedule
|
||||||
const { url, request } = module.exports
|
const { url, request } = module.exports
|
||||||
const nextSchedule = await axios
|
const nextSchedule = await axios
|
||||||
.get(url({ channel, date, hour: 12 }), { headers: request.headers })
|
.get(url({ channel, date, hour: 12 }), { headers: request.headers })
|
||||||
.then(response => response.data)
|
.then(response => response.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
if (schedule?.items) {
|
if (schedule?.items) {
|
||||||
// merge schedules
|
// merge schedules
|
||||||
if (nextSchedule?.items) {
|
if (nextSchedule?.items) {
|
||||||
schedule.items.push(...nextSchedule.items)
|
schedule.items.push(...nextSchedule.items)
|
||||||
}
|
}
|
||||||
schedule.items.forEach(item => {
|
schedule.items.forEach(item => {
|
||||||
let season, episode
|
let season, episode
|
||||||
const start = dayjs.utc(item.publishedStartTime)
|
const start = dayjs.utc(item.publishedStartTime)
|
||||||
const stop = start.add(item.publishedDuration, 's')
|
const stop = start.add(item.publishedDuration, 's')
|
||||||
const description = item.synopsis
|
const description = item.synopsis
|
||||||
if (description) {
|
if (description) {
|
||||||
const matches = description.trim().match(/\(?S(\d+)[\/\s]Ep(\d+)\)?/)
|
const matches = description.trim().match(/\(?S(\d+)[/\s]Ep(\d+)\)?/)
|
||||||
if (matches) {
|
if (matches) {
|
||||||
if (matches[1]) {
|
if (matches[1]) {
|
||||||
season = parseInt(matches[1])
|
season = parseInt(matches[1])
|
||||||
}
|
}
|
||||||
if (matches[2]) {
|
if (matches[2]) {
|
||||||
episode = parseInt(matches[2])
|
episode = parseInt(matches[2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.title,
|
title: item.title,
|
||||||
description,
|
description,
|
||||||
season,
|
season,
|
||||||
episode,
|
episode,
|
||||||
start,
|
start,
|
||||||
stop
|
stop
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels() {
|
async channels() {
|
||||||
const token =
|
const token =
|
||||||
'eyJkaXNjb3ZlcnlVc2VyR3JvdXBzIjpbIkFMTFVTRVJTIiwiYWxsIiwiaHR0cDovL3JlZmRhd' +
|
'eyJkaXNjb3ZlcnlVc2VyR3JvdXBzIjpbIkFMTFVTRVJTIiwiYWxsIiwiaHR0cDovL3JlZmRhd' +
|
||||||
'GEueW91dmlldy5jb20vbXBlZzdjcy9Zb3VWaWV3QXBwbGljYXRpb25QbGF5ZXJDUy8yMDIxLT' +
|
'GEueW91dmlldy5jb20vbXBlZzdjcy9Zb3VWaWV3QXBwbGljYXRpb25QbGF5ZXJDUy8yMDIxLT' +
|
||||||
'A5LTEwI2FuZHJvaWRfcnVudGltZS1wcm9maWxlMSIsInRhZzpidC5jb20sMjAxOC0wNy0xMTp' +
|
'A5LTEwI2FuZHJvaWRfcnVudGltZS1wcm9maWxlMSIsInRhZzpidC5jb20sMjAxOC0wNy0xMTp' +
|
||||||
'1c2VyZ3JvdXAjR0JSLWJ0X25vd1RWX211bHRpY2FzdCIsInRhZzpidC5jb20sMjAyMS0xMC0y' +
|
'1c2VyZ3JvdXAjR0JSLWJ0X25vd1RWX211bHRpY2FzdCIsInRhZzpidC5jb20sMjAyMS0xMC0y' +
|
||||||
'NTp1c2VyZ3JvdXAjR0JSLWJ0X2V1cm9zcG9ydCJdLCJyZWdpb25zIjpbIkFMTFJFR0lPTlMiL' +
|
'NTp1c2VyZ3JvdXAjR0JSLWJ0X2V1cm9zcG9ydCJdLCJyZWdpb25zIjpbIkFMTFJFR0lPTlMiL' +
|
||||||
'CJHQlIiLCJHQlItRU5HIiwiR0JSLUVORy1sb25kb24iLCJhbGwiXSwic3Vic2V0IjoiMy41Lj' +
|
'CJHQlIiLCJHQlItRU5HIiwiR0JSLUVORy1sb25kb24iLCJhbGwiXSwic3Vic2V0IjoiMy41Lj' +
|
||||||
'EvYW5kcm9pZF9ydW50aW1lLXByb2ZpbGUxL0JST0FEQ0FTVF9JUC9HQlItYnRfYnJvYWRiYW5' +
|
'EvYW5kcm9pZF9ydW50aW1lLXByb2ZpbGUxL0JST0FEQ0FTVF9JUC9HQlItYnRfYnJvYWRiYW5' +
|
||||||
'kIiwic3Vic2V0cyI6WyIvLy8iLCIvL0JST0FEQ0FTVF9JUC8iLCIzLjUvLy8iXX0='
|
'kIiwic3Vic2V0cyI6WyIvLy8iLCIvL0JST0FEQ0FTVF9JUC8iLCIzLjUvLy8iXX0='
|
||||||
const extensions = [
|
const extensions = [
|
||||||
'LinearCategoriesExtension',
|
'LinearCategoriesExtension',
|
||||||
'LogicalChannelNumberExtension',
|
'LogicalChannelNumberExtension',
|
||||||
'BTSubscriptionCodesExtension'
|
'BTSubscriptionCodesExtension'
|
||||||
]
|
]
|
||||||
const result = await axios
|
const result = await axios
|
||||||
.get(`https://api.youview.tv/metadata/linear/v2/linear-services`, {
|
.get('https://api.youview.tv/metadata/linear/v2/linear-services', {
|
||||||
params: {
|
params: {
|
||||||
contentTargetingToken: token,
|
contentTargetingToken: token,
|
||||||
extensions: extensions.join(',')
|
extensions: extensions.join(',')
|
||||||
},
|
},
|
||||||
headers: module.exports.request.headers
|
headers: module.exports.request.headers
|
||||||
})
|
})
|
||||||
.then(response => response.data)
|
.then(response => response.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
return result?.items
|
return (
|
||||||
.filter(channel => channel.contentTypes.indexOf('tv') >= 0)
|
result?.items
|
||||||
.map(channel => {
|
.filter(channel => channel.contentTypes.indexOf('tv') >= 0)
|
||||||
return {
|
.map(channel => {
|
||||||
lang: 'en',
|
return {
|
||||||
site_id: channel.serviceLocator,
|
lang: 'en',
|
||||||
name: channel.fullName
|
site_id: channel.serviceLocator,
|
||||||
}
|
name: channel.fullName
|
||||||
}) || []
|
}
|
||||||
}
|
}) || []
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,72 +1,74 @@
|
||||||
const { parser, url } = require('./player.ee.co.uk.config.js')
|
const { parser, url } = require('./player.ee.co.uk.config.js')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const date = dayjs.utc('2023-12-13').startOf('d')
|
const date = dayjs.utc('2023-12-13').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'dvb://233a..6d60',
|
site_id: 'dvb://233a..6d60',
|
||||||
xmltv_id: 'HGTV.uk'
|
xmltv_id: 'HGTV.uk'
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.get.mockImplementation((url, opts) => {
|
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') {
|
if (
|
||||||
return Promise.resolve({
|
url ===
|
||||||
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/data1.json')))
|
'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', () => {
|
return Promise.resolve({ data: '' })
|
||||||
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 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 => {
|
it('can parse response', async () => {
|
||||||
p.start = p.start.toJSON()
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
|
||||||
p.stop = p.stop.toJSON()
|
const result = (await parser({ content, channel, date })).map(p => {
|
||||||
return p
|
p.start = p.start.toJSON()
|
||||||
})
|
p.stop = p.stop.toJSON()
|
||||||
|
return p
|
||||||
expect(result).toMatchObject([
|
})
|
||||||
{
|
|
||||||
title: 'Bargain Mansions',
|
expect(result).toMatchObject([
|
||||||
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',
|
title: 'Bargain Mansions',
|
||||||
season: 4,
|
description:
|
||||||
episode: 1,
|
'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',
|
||||||
start: '2023-12-13T13:00:00.000Z',
|
season: 4,
|
||||||
stop: '2023-12-13T14:00:00.000Z'
|
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',
|
title: 'Flip Or Flop',
|
||||||
season: 2,
|
description:
|
||||||
episode: 2,
|
'Tarek and Christina are contacted by a cash strapped flipper who needs to unload a project house. S2/Ep2',
|
||||||
start: '2023-12-13T14:00:00.000Z',
|
season: 2,
|
||||||
stop: '2023-12-13T14:30:00.000Z'
|
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,
|
it('can handle empty guide', async () => {
|
||||||
date,
|
const result = await parser({
|
||||||
content: ''
|
channel,
|
||||||
})
|
date,
|
||||||
expect(result).toMatchObject([])
|
content: ''
|
||||||
})
|
})
|
||||||
|
expect(result).toMatchObject([])
|
||||||
|
})
|
||||||
|
|
|
@ -43,7 +43,7 @@ module.exports = {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.post(
|
.post(
|
||||||
`https://playtv.unifi.com.my:7053/VSP/V3/QueryAllChannel`,
|
'https://playtv.unifi.com.my:7053/VSP/V3/QueryAllChannel',
|
||||||
{ isReturnAllMedia: '0' },
|
{ isReturnAllMedia: '0' },
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
|
@ -74,7 +74,7 @@ function parseItems(content, channel) {
|
||||||
|
|
||||||
const channelData = data.find(i => i.id == channel.site_id)
|
const channelData = data.find(i => i.id == channel.site_id)
|
||||||
return channelData.items && Array.isArray(channelData.items) ? channelData.items : []
|
return channelData.items && Array.isArray(channelData.items) ? channelData.items : []
|
||||||
} catch (err) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,10 +46,10 @@ module.exports = {
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ country, lang }) {
|
async channels() {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const data = await 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)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ module.exports = {
|
||||||
$('.channelList-listItemsLink').each((i, el) => {
|
$('.channelList-listItemsLink').each((i, el) => {
|
||||||
const name = $(el).attr('title')
|
const name = $(el).attr('title')
|
||||||
const url = $(el).attr('href')
|
const url = $(el).attr('href')
|
||||||
const [, site_id] = url.match(/\/programme\-(.*)\.html$/i)
|
const [, site_id] = url.match(/\/programme-(.*)\.html$/i)
|
||||||
|
|
||||||
channels.push({
|
channels.push({
|
||||||
lang: 'fr',
|
lang: 'fr',
|
||||||
|
|
|
@ -51,7 +51,7 @@ module.exports = {
|
||||||
|
|
||||||
return data.programmes.map(item => {
|
return data.programmes.map(item => {
|
||||||
const site_id = item.url.replace('/', '')
|
const site_id = item.url.replace('/', '')
|
||||||
const name = site_id.replace(/\-/gi, ' ')
|
const name = site_id.replace(/-/gi, ' ')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lang: 'fr',
|
lang: 'fr',
|
||||||
|
|
|
@ -13,19 +13,17 @@ module.exports = {
|
||||||
site: 'programme.tvb.com',
|
site: 'programme.tvb.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel, date, time = null }) {
|
url({ channel, date, time = null }) {
|
||||||
return `https://programme.tvb.com/api/schedule?input_date=${
|
return `https://programme.tvb.com/api/schedule?input_date=${date.format(
|
||||||
date.format('YYYYMMDD')
|
'YYYYMMDD'
|
||||||
}&network_code=${channel.site_id}&_t=${time ? time : parseInt(Date.now() / 1000)}`
|
)}&network_code=${channel.site_id}&_t=${time ? time : parseInt(Date.now() / 1000)}`
|
||||||
},
|
},
|
||||||
parser({ content, channel, date }) {
|
parser({ content, channel, date }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
const data = content ? JSON.parse(content) : {}
|
const data = content ? JSON.parse(content) : {}
|
||||||
if (Array.isArray(data.data?.list)) {
|
if (Array.isArray(data.data?.list)) {
|
||||||
const dt = date.format('YYYY-MM-DD')
|
|
||||||
for (const d of data.data.list) {
|
for (const d of data.data.list) {
|
||||||
if (Array.isArray(d.schedules)) {
|
if (Array.isArray(d.schedules)) {
|
||||||
const schedules = d.schedules
|
const schedules = d.schedules.filter(s => s.network_code === channel.site_id)
|
||||||
.filter(s => s.network_code === channel.site_id)
|
|
||||||
schedules.forEach((s, i) => {
|
schedules.forEach((s, i) => {
|
||||||
const start = dayjs.tz(s.event_datetime, 'YYYY-MM-DD HH:mm:ss', tz)
|
const start = dayjs.tz(s.event_datetime, 'YYYY-MM-DD HH:mm:ss', tz)
|
||||||
let stop
|
let stop
|
||||||
|
@ -64,7 +62,7 @@ module.exports = {
|
||||||
if (assets) {
|
if (assets) {
|
||||||
queues.push(...assets.map(a => base + '/' + a))
|
queues.push(...assets.map(a => base + '/' + a))
|
||||||
} else {
|
} else {
|
||||||
const metadata = content.match(/e\=(\[(.*?)\])/)
|
const metadata = content.match(/e=(\[(.*?)\])/)
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
const infos = eval(metadata[1])
|
const infos = eval(metadata[1])
|
||||||
if (Array.isArray(infos)) {
|
if (Array.isArray(infos)) {
|
||||||
|
|
|
@ -34,7 +34,7 @@ it('can parse response (en)', () => {
|
||||||
expect(results[1]).toMatchObject({
|
expect(results[1]).toMatchObject({
|
||||||
start: '2024-12-06T15:55:00.000Z',
|
start: '2024-12-06T15:55:00.000Z',
|
||||||
stop: '2024-12-06T16: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',
|
stop: '2024-12-06T16:55:00.000Z',
|
||||||
title: '使徒行者3#16[粵][PG]',
|
title: '使徒行者3#16[粵][PG]',
|
||||||
description:
|
description:
|
||||||
'文鼎從淑梅手上救走大聖爺兒子,大聖爺還恩於歡喜,答允支持九指強。崇聯社定下選舉日子,恰巧是韋傑出獄之日,頭目們顧念舊日恩義,紛紛轉投浩洋。浩洋帶亞希逛傢俬店,憧憬二人未來。亞希向家強承認愛上浩洋,要求退出臥底任務。作榮與歡喜暗中會面,將國際犯罪組織「永恆幫」情報交給他。阿火遭家強出賣,到沐足店搶錢。家強逮住阿火,惟被合星誤會而受拘捕。家強把正植遺下的頸鏈和學生證交還,合星意識到家強已知悉正植身世。',
|
'文鼎從淑梅手上救走大聖爺兒子,大聖爺還恩於歡喜,答允支持九指強。崇聯社定下選舉日子,恰巧是韋傑出獄之日,頭目們顧念舊日恩義,紛紛轉投浩洋。浩洋帶亞希逛傢俬店,憧憬二人未來。亞希向家強承認愛上浩洋,要求退出臥底任務。作榮與歡喜暗中會面,將國際犯罪組織「永恆幫」情報交給他。阿火遭家強出賣,到沐足店搶錢。家強逮住阿火,惟被合星誤會而受拘捕。家強把正植遺下的頸鏈和學生證交還,合星意識到家強已知悉正植身世。'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ module.exports = {
|
||||||
$('ul.channelList a').each((i, el) => {
|
$('ul.channelList a').each((i, el) => {
|
||||||
const name = $(el).text()
|
const name = $(el).text()
|
||||||
const url = $(el).attr('href')
|
const url = $(el).attr('href')
|
||||||
const [, site_id] = url.match(/^\/program\-tv\/(.*)$/i)
|
const [, site_id] = url.match(/^\/program-tv\/(.*)$/i)
|
||||||
|
|
||||||
channels.push({
|
channels.push({
|
||||||
lang: 'pl',
|
lang: 'pl',
|
||||||
|
|
|
@ -58,7 +58,7 @@ function parseItems(content, channel) {
|
||||||
let data
|
let data
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(content)
|
data = JSON.parse(content)
|
||||||
} catch (error) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,7 @@ dayjs.extend(timezone)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
doFetch
|
doFetch.setCheckResult(false).setDebugger(debug)
|
||||||
.setCheckResult(false)
|
|
||||||
.setDebugger(debug)
|
|
||||||
|
|
||||||
const tz = 'Asia/Riyadh'
|
const tz = 'Asia/Riyadh'
|
||||||
const defaultHeaders = {
|
const defaultHeaders = {
|
||||||
|
@ -47,7 +45,7 @@ module.exports = {
|
||||||
headers: {
|
headers: {
|
||||||
...defaultHeaders,
|
...defaultHeaders,
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
cookie: cookies[channel.lang],
|
cookie: cookies[channel.lang]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queues.push({ i: item, url, params })
|
queues.push({ i: item, url, params })
|
||||||
|
@ -61,7 +59,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
async channels({ lang = 'en' }) {
|
async channels({ lang = 'en' }) {
|
||||||
const result = await axios
|
const result = await axios
|
||||||
.get(`https://rotana.net/api/channels`)
|
.get('https://rotana.net/api/channels')
|
||||||
.then(response => response.data)
|
.then(response => response.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
|
@ -88,34 +86,37 @@ function parseProgram(item, result) {
|
||||||
item.description = desc
|
item.description = desc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break
|
||||||
case 'Element':
|
case 'Element':
|
||||||
if (el.name === 'span') {
|
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) {
|
switch (k) {
|
||||||
case 'Category':
|
case 'Category':
|
||||||
case 'التصنيف':
|
case 'التصنيف':
|
||||||
item.category = v;
|
item.category = v
|
||||||
break;
|
break
|
||||||
case 'Country':
|
case 'Country':
|
||||||
case 'البلد':
|
case 'البلد':
|
||||||
item.country = v;
|
item.country = v
|
||||||
break;
|
break
|
||||||
case 'Director':
|
case 'Director':
|
||||||
case 'المخرج':
|
case 'المخرج':
|
||||||
item.director = v;
|
item.director = v
|
||||||
break;
|
break
|
||||||
case 'Language':
|
case 'Language':
|
||||||
case 'اللغة':
|
case 'اللغة':
|
||||||
item.language = v;
|
item.language = v
|
||||||
break;
|
break
|
||||||
case 'Release Year':
|
case 'Release Year':
|
||||||
case 'سنة الإصدار':
|
case 'سنة الإصدار':
|
||||||
item.date = v;
|
item.date = v
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,7 +143,9 @@ function parseItems(content, date) {
|
||||||
const heading = top.find('.iq-accordion-title .big-title')
|
const heading = top.find('.iq-accordion-title .big-title')
|
||||||
if (heading.length) {
|
if (heading.length) {
|
||||||
const progId = top.attr('id')
|
const progId = top.attr('id')
|
||||||
const title = heading.find('span:eq(1)').text()
|
const title = heading
|
||||||
|
.find('span:eq(1)')
|
||||||
|
.text()
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(a => a.trim())
|
.map(a => a.trim())
|
||||||
.join(' ')
|
.join(' ')
|
||||||
|
@ -151,7 +154,7 @@ function parseItems(content, date) {
|
||||||
items.push({
|
items.push({
|
||||||
program: progId.substr(progId.indexOf('-') + 1),
|
program: progId.substr(progId.indexOf('-') + 1),
|
||||||
title: title ? title.trim() : title,
|
title: title ? title.trim() : title,
|
||||||
start: `${y}-${m}-${d} ${time.trim()}`,
|
start: `${y}-${m}-${d} ${time.trim()}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ const channel = {
|
||||||
}
|
}
|
||||||
const channelAr = Object.assign({}, channel, { lang: 'ar' })
|
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') {
|
if (url === 'https://rotana.net/en/streams?channel=439&itemId=736970') {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html'))
|
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 () => {
|
it('can parse english response', async () => {
|
||||||
const result = (await parser({
|
const result = (
|
||||||
channel,
|
await parser({
|
||||||
date,
|
channel,
|
||||||
content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html'))
|
date,
|
||||||
})).map(a => {
|
content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html'))
|
||||||
|
})
|
||||||
|
).map(a => {
|
||||||
a.start = a.start.toJSON()
|
a.start = a.start.toJSON()
|
||||||
a.stop = a.stop.toJSON()
|
a.stop = a.stop.toJSON()
|
||||||
return a
|
return a
|
||||||
|
@ -69,17 +71,20 @@ it('can parse english response', async () => {
|
||||||
title: 'Khiyana Mashroua',
|
title: 'Khiyana Mashroua',
|
||||||
description:
|
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...',
|
'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'
|
category: 'Movie'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse arabic response', async () => {
|
it('can parse arabic response', async () => {
|
||||||
const result = (await parser({
|
const result = (
|
||||||
channel: channelAr,
|
await parser({
|
||||||
date,
|
channel: channelAr,
|
||||||
content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html'))
|
date,
|
||||||
})).map(a => {
|
content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html'))
|
||||||
|
})
|
||||||
|
).map(a => {
|
||||||
a.start = a.start.toJSON()
|
a.start = a.start.toJSON()
|
||||||
a.stop = a.stop.toJSON()
|
a.stop = a.stop.toJSON()
|
||||||
return a
|
return a
|
||||||
|
@ -92,7 +97,8 @@ it('can parse arabic response', async () => {
|
||||||
title: 'خيانة مشروعة',
|
title: 'خيانة مشروعة',
|
||||||
description:
|
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: 'فيلم'
|
category: 'فيلم'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -53,7 +53,7 @@ async function parseItems(buffer) {
|
||||||
let data
|
let data
|
||||||
try {
|
try {
|
||||||
data = await pdf(buffer)
|
data = await pdf(buffer)
|
||||||
} catch (err) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const axios = require('axios')
|
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const duration = require("dayjs/plugin/duration")
|
const duration = require('dayjs/plugin/duration')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
@ -10,72 +10,74 @@ dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(duration)
|
dayjs.extend(duration)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 's.mxtv.jp',
|
site: 's.mxtv.jp',
|
||||||
days: 1,
|
days: 1,
|
||||||
lang: 'ja',
|
lang: 'ja',
|
||||||
url: function ({ date, channel }) {
|
url: function ({ date, channel }) {
|
||||||
const id = `SV${channel.site_id}EPG${date.format('YYYYMMDD')}`
|
const id = `SV${channel.site_id}EPG${date.format('YYYYMMDD')}`
|
||||||
return `https://s.mxtv.jp/bangumi_file/json01/${id}.json`
|
return `https://s.mxtv.jp/bangumi_file/json01/${id}.json`
|
||||||
},
|
},
|
||||||
parser: function ({ content }) {
|
parser: function ({ content }) {
|
||||||
let programs = []
|
let programs = []
|
||||||
const items = parseItems(content)
|
const items = parseItems(content)
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
programs.push({
|
programs.push({
|
||||||
title: item.Event_name,
|
title: item.Event_name,
|
||||||
description: item.Event_text,
|
description: item.Event_text,
|
||||||
category: parseCategory(item),
|
category: parseCategory(item),
|
||||||
image: parseImage(item),
|
image: parseImage(item),
|
||||||
start: parseStart(item),
|
start: parseStart(item),
|
||||||
stop: parseStop(item)
|
stop: parseStop(item)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
channels() {
|
channels() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
lang: 'ja',
|
lang: 'ja',
|
||||||
site_id: '1',
|
site_id: '1',
|
||||||
name: 'Tokyo MX1',
|
name: 'Tokyo MX1',
|
||||||
xmltv_id: 'TokyoMX1.jp'
|
xmltv_id: 'TokyoMX1.jp'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lang: 'ja',
|
lang: 'ja',
|
||||||
site_id: '2',
|
site_id: '2',
|
||||||
name: 'Tokyo MX2',
|
name: 'Tokyo MX2',
|
||||||
xmltv_id: 'TokyoMX2.jp'
|
xmltv_id: 'TokyoMX2.jp'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseImage(item) {
|
function parseImage() {
|
||||||
// Should return a string if we can output an image URL
|
// Should return a string if we can output an image URL
|
||||||
// Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ?
|
// Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ?
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCategory(item) {
|
function parseCategory() {
|
||||||
// Should return a string if we can determine the category
|
// 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` ?
|
// Might be done with `https://s.mxtv.jp/index_set/csv/ranking_bangumi_allU.csv` ?
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart(item) {
|
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) {
|
function parseStop(item) {
|
||||||
// Add the duration to the start time
|
// Add the duration to the start time
|
||||||
const durationDate = dayjs(item.Duration, 'HH:mm:ss');
|
const durationDate = dayjs(item.Duration, 'HH:mm:ss')
|
||||||
return parseStart(item).add(dayjs.duration({
|
return parseStart(item).add(
|
||||||
hours: durationDate.hour(),
|
dayjs.duration({
|
||||||
minutes: durationDate.minute(),
|
hours: durationDate.hour(),
|
||||||
seconds: durationDate.second()
|
minutes: durationDate.minute(),
|
||||||
}))
|
seconds: durationDate.second()
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseItems(content) {
|
function parseItems(content) {
|
||||||
return JSON.parse(content) || []
|
return JSON.parse(content) || []
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ const channel = {
|
||||||
name: 'Tokyo MX2',
|
name: 'Tokyo MX2',
|
||||||
xmltv_id: 'TokyoMX2.jp'
|
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', () => {
|
it('can generate valid url', () => {
|
||||||
const result = url({ date, channel })
|
const result = url({ date, channel })
|
||||||
|
|
|
@ -114,7 +114,7 @@ module.exports = {
|
||||||
const $ = cheerio.load(data)
|
const $ = cheerio.load(data)
|
||||||
$('.main-container-channels-events > .container-channel-events').each((i, el) => {
|
$('.main-container-channels-events > .container-channel-events').each((i, el) => {
|
||||||
const name = $(el).find('.channel-title').text().trim()
|
const name = $(el).find('.channel-title').text().trim()
|
||||||
const channelId = name.replace(/\s\&\s/gi, ' & ')
|
const channelId = name.replace(/\s&\s/gi, ' & ')
|
||||||
|
|
||||||
if (!name) return
|
if (!name) return
|
||||||
|
|
||||||
|
|
|
@ -1,74 +1,79 @@
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'shahid.mbc.net',
|
site: 'shahid.mbc.net',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ channel, date}) {
|
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}`
|
return `https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${
|
||||||
},
|
channel.site_id
|
||||||
parser({ content, channel }) {
|
}&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format(
|
||||||
const programs = parseItems(content, channel)
|
'YYYY-MM-DD'
|
||||||
.map(item => {
|
)}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}`
|
||||||
return {
|
},
|
||||||
title: item.title,
|
parser({ content, channel }) {
|
||||||
description: item.description,
|
const programs = parseItems(content, channel).map(item => {
|
||||||
session: item.seasonNumber,
|
return {
|
||||||
episode: item.episodeNumber,
|
title: item.title,
|
||||||
start: dayjs.tz(item.actualFrom, 'Asia/Riyadh').toISOString(),
|
description: item.description,
|
||||||
stop: dayjs.tz(item.actualTo, 'Asia/Riyadh').toISOString()
|
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')
|
return programs
|
||||||
const items = []
|
},
|
||||||
let page = 0
|
async channels({ lang = 'en' }) {
|
||||||
while (true) {
|
const axios = require('axios')
|
||||||
const result = await axios
|
const items = []
|
||||||
.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}`)
|
let page = 0
|
||||||
.then(response => response.data)
|
while (true) {
|
||||||
.catch(console.error)
|
const result = await axios
|
||||||
if (result.productList) {
|
.get(
|
||||||
items.push(...result.productList.products)
|
`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}`
|
||||||
if (result.productList.hasMore) {
|
)
|
||||||
page++
|
.then(response => response.data)
|
||||||
continue
|
.catch(console.error)
|
||||||
}
|
if (result.productList) {
|
||||||
}
|
items.push(...result.productList.products)
|
||||||
break;
|
if (result.productList.hasMore) {
|
||||||
}
|
page++
|
||||||
const channels = items.map(channel => {
|
continue
|
||||||
return {
|
}
|
||||||
lang,
|
}
|
||||||
site_id: channel.id,
|
break
|
||||||
name: channel.title
|
}
|
||||||
}
|
const channels = items.map(channel => {
|
||||||
})
|
return {
|
||||||
|
lang,
|
||||||
return channels
|
site_id: channel.id,
|
||||||
}
|
name: channel.title
|
||||||
}
|
}
|
||||||
|
})
|
||||||
function parseItems(content, channel) {
|
|
||||||
const items = []
|
return channels
|
||||||
content = content ? JSON.parse(content) : []
|
}
|
||||||
if (content.items) {
|
}
|
||||||
content.items.forEach(schedules => {
|
|
||||||
if (schedules.channelId == channel.site_id) {
|
function parseItems(content, channel) {
|
||||||
items.push(...schedules.items)
|
const items = []
|
||||||
return true
|
content = content ? JSON.parse(content) : []
|
||||||
}
|
if (content.items) {
|
||||||
})
|
content.items.forEach(schedules => {
|
||||||
}
|
if (schedules.channelId == channel.site_id) {
|
||||||
|
items.push(...schedules.items)
|
||||||
return items
|
return true
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
|
@ -1,36 +1,40 @@
|
||||||
const { url, parser } = require('./shahid.mbc.net.config.js')
|
const { url, parser } = require('./shahid.mbc.net.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
const date = dayjs.utc('2023-11-11').startOf('d')
|
const date = dayjs.utc('2023-11-11').startOf('d')
|
||||||
const channel = { site_id: '996520', xmltv_id: 'AlAanTV.ae', lang: 'en' }
|
const channel = { site_id: '996520', xmltv_id: 'AlAanTV.ae', lang: 'en' }
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe(
|
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}`
|
`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'
|
||||||
it('can parse response', () => {
|
)}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}`
|
||||||
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 })
|
|
||||||
|
it('can parse response', () => {
|
||||||
expect(result).toMatchObject([
|
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"}]}]}'
|
||||||
start: '2023-11-10T21:00:00.000Z',
|
const result = parser({ content, channel, date })
|
||||||
stop: '2023-11-10T21:30:00.000Z',
|
|
||||||
title: 'Menassaatona Fi Osboo\'',
|
expect(result).toMatchObject([
|
||||||
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.'
|
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([])
|
|
||||||
})
|
it('can handle empty guide', () => {
|
||||||
|
const result = parser({ content: '' })
|
||||||
|
|
||||||
|
expect(result).toMatchObject([])
|
||||||
|
})
|
||||||
|
|
|
@ -40,7 +40,7 @@ module.exports = {
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
|
|
||||||
const data = await axios
|
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)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ function parseItems(content, channel) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(content)
|
const data = JSON.parse(content)
|
||||||
return data && data[channel.site_id] ? data[channel.site_id] : []
|
return data && data[channel.site_id] ? data[channel.site_id] : []
|
||||||
} catch (err) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ module.exports = {
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
|
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(`https://sjonvarp.is/`)
|
.get('https://sjonvarp.is/')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,7 @@ module.exports = {
|
||||||
site: 'sky.com',
|
site: 'sky.com',
|
||||||
days: 2,
|
days: 2,
|
||||||
url({ date, channel }) {
|
url({ date, channel }) {
|
||||||
return `https://awk.epgsky.com/hawk/linear/schedule/${
|
return `https://awk.epgsky.com/hawk/linear/schedule/${date.format('YYYYMMDD')}/${
|
||||||
date.format('YYYYMMDD')
|
|
||||||
}/${
|
|
||||||
channel.site_id
|
channel.site_id
|
||||||
}`
|
}`
|
||||||
},
|
},
|
||||||
|
@ -27,19 +25,18 @@ module.exports = {
|
||||||
.filter(schedule => schedule.sid === channel.site_id)
|
.filter(schedule => schedule.sid === channel.site_id)
|
||||||
.forEach(schedule => {
|
.forEach(schedule => {
|
||||||
if (Array.isArray(schedule.events)) {
|
if (Array.isArray(schedule.events)) {
|
||||||
schedule.events
|
schedule.events.forEach(event => {
|
||||||
.forEach(event => {
|
const start = dayjs.utc(event.st * 1000)
|
||||||
const start = dayjs.utc(event.st * 1000)
|
const stop = start.add(event.d, 's')
|
||||||
const stop = start.add(event.d, 's')
|
programs.push({
|
||||||
programs.push({
|
title: event.t,
|
||||||
title: event.t,
|
description: event.sy,
|
||||||
description: event.sy,
|
season: event.seasonnumber,
|
||||||
season: event.seasonnumber,
|
episode: event.episodenumber,
|
||||||
episode: event.episodenumber,
|
start,
|
||||||
start,
|
stop
|
||||||
stop
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -55,10 +52,12 @@ module.exports = {
|
||||||
if (queue.t === 'r') {
|
if (queue.t === 'r') {
|
||||||
const $ = cheerio.load(res)
|
const $ = cheerio.load(res)
|
||||||
const initialData = JSON.parse(decodeURIComponent($('#initialData').text()))
|
const initialData = JSON.parse(decodeURIComponent($('#initialData').text()))
|
||||||
initialData.state.epgData.regions
|
initialData.state.epgData.regions.forEach(region => {
|
||||||
.forEach(region => {
|
queues.push({
|
||||||
queues.push({ t: 'c', url: `https://awk.epgsky.com/hawk/linear/services/${region.bouquet}/${region.subBouquet}` })
|
t: 'c',
|
||||||
|
url: `https://awk.epgsky.com/hawk/linear/services/${region.bouquet}/${region.subBouquet}`
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// process channels
|
// process channels
|
||||||
if (queue.t === 'c') {
|
if (queue.t === 'c') {
|
||||||
|
|
|
@ -15,9 +15,7 @@ const channel = {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('can generate valid url', () => {
|
it('can generate valid url', () => {
|
||||||
expect(url({ channel, date })).toBe(
|
expect(url({ channel, date })).toBe('https://awk.epgsky.com/hawk/linear/schedule/20241214/4086')
|
||||||
'https://awk.epgsky.com/hawk/linear/schedule/20241214/4086'
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
|
@ -34,7 +32,7 @@ it('can parse response', () => {
|
||||||
stop: '2024-12-13T23:00:00.000Z',
|
stop: '2024-12-13T23:00:00.000Z',
|
||||||
title: 'The UnXplained With...',
|
title: 'The UnXplained With...',
|
||||||
description:
|
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,
|
season: 4,
|
||||||
episode: 14
|
episode: 14
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
|
1
sites/skyperfectv.co.jp/__data__/content.html
Normal file
1
sites/skyperfectv.co.jp/__data__/content.html
Normal file
File diff suppressed because one or more lines are too long
1
sites/skyperfectv.co.jp/__data__/empty.html
Normal file
1
sites/skyperfectv.co.jp/__data__/empty.html
Normal file
File diff suppressed because one or more lines are too long
|
@ -12,103 +12,110 @@ dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(duration)
|
dayjs.extend(duration)
|
||||||
|
|
||||||
const exported = {
|
const exported = {
|
||||||
site: 'skyperfectv.co.jp',
|
site: 'skyperfectv.co.jp',
|
||||||
days: 1,
|
days: 1,
|
||||||
lang: 'ja',
|
lang: 'ja',
|
||||||
url: function ({ date, channel }) {
|
url: function ({ date, channel }) {
|
||||||
let [type, ...code] = channel.site_id.split('_')
|
let [type, ...code] = channel.site_id.split('_')
|
||||||
code = code.join('_')
|
code = code.join('_')
|
||||||
return `https://www.skyperfectv.co.jp/program/schedule/${type}/channel:${code}/date:${date.format('YYMMDD')}`
|
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`
|
},
|
||||||
},
|
logo: function ({ channel }) {
|
||||||
// Specific function that permits to gather NSFW channels (needs confirmation)
|
return `https://www.skyperfectv.co.jp/library/common/img/channel/icon/basic/m_${channel.site_id.toLowerCase()}.gif`
|
||||||
async fetchSchedule({ date, channel }) {
|
},
|
||||||
const url = exported.url({ date, channel })
|
// Specific function that permits to gather NSFW channels (needs confirmation)
|
||||||
const response = await axios.get(url, {
|
async fetchSchedule({ date, channel }) {
|
||||||
headers: {
|
const url = exported.url({ date, channel })
|
||||||
'Cookie': 'adult_auth=true'
|
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 = [
|
return programs
|
||||||
{ id: 'js-am', addition: 0 },
|
},
|
||||||
{ id: 'js-pm', addition: 0 },
|
async channels() {
|
||||||
{ id: 'js-md', addition: 1 }
|
const pageParser = (content, type) => {
|
||||||
]
|
// type: "basic" | "premium"
|
||||||
|
// Returns an array of channel objects
|
||||||
|
|
||||||
sections.forEach(({ id, addition }) => {
|
const $ = cheerio.load(content)
|
||||||
$(`#${id} > td`).each((index, element) => {
|
const channels = []
|
||||||
// `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) => {
|
$('.p-channel').each((index, element) => {
|
||||||
// timeString is in the format "HH:mm"
|
const site_id = `${type}_${$(element).find('.p-channel__id').text()}`
|
||||||
// replace `today` with the time from timeString
|
const name = $(element).find('.p-channel__name').text()
|
||||||
const [hour, minute] = timeString.split(':').map(Number)
|
channels.push({ site_id, name, lang: 'ja' })
|
||||||
return today.hour(hour).minute(minute)
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const $element = $(element) // Wrap element with Cheerio
|
return channels
|
||||||
$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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@ -1,97 +1,86 @@
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const { DateTime } = require('luxon')
|
const dayjs = require('dayjs')
|
||||||
const dayjs = require('dayjs')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
|
||||||
|
dayjs.extend(utc)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(customParseFormat)
|
|
||||||
|
module.exports = {
|
||||||
const channel = [{ site_id: '1208', xmltv_id: 'AlAoula.ma', lang: 'ar' },
|
site: 'snrt.ma',
|
||||||
{ site_id: '4069', xmltv_id: 'Laayoune.ma', lang: 'ar' },
|
channels: 'snrt.ma.channels.xml',
|
||||||
{ site_id: '4070', xmltv_id: 'Arryadia.ma', lang: 'ar' },
|
days: 2,
|
||||||
{ site_id: '4071', xmltv_id: 'Athaqafia.ma', lang: 'ar' },
|
url: function ({ channel }) {
|
||||||
{ site_id: '4072', xmltv_id: 'AlMaghribia.ma', lang: 'ar' },
|
return `https://www.snrt.ma/ar/node/${channel.site_id}`
|
||||||
{ site_id: '4073', xmltv_id: 'Assadissa.ma', lang: 'ar' },
|
},
|
||||||
{ site_id: '4075', xmltv_id: 'Tamazight.ma', lang: 'ar' }]
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
module.exports = {
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
site: 'snrt.ma',
|
},
|
||||||
channels: 'snrt.ma.channels.xml',
|
data: function ({ date }) {
|
||||||
days: 2,
|
const params = new URLSearchParams()
|
||||||
url: function ({ channel }) {
|
params.append('_method', 'POST')
|
||||||
return `https://www.snrt.ma/ar/node/${channel.site_id}`
|
params.append('data-date', date.format('YYYYMMDD'))
|
||||||
},
|
params.append('current_date', date.format('YYYYMMDD'))
|
||||||
request: {
|
|
||||||
method: 'POST',
|
return params
|
||||||
headers: {
|
}
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
},
|
||||||
},
|
parser: function ({ content, date }) {
|
||||||
data: function ({ date }) {
|
const programs = []
|
||||||
const params = new URLSearchParams()
|
const items = parseItems(content)
|
||||||
params.append('_method', 'POST')
|
items.forEach(item => {
|
||||||
params.append('data-date', date.format('YYYYMMDD'))
|
const prev = programs[programs.length - 1]
|
||||||
params.append('current_date', date.format('YYYYMMDD'))
|
const $item = cheerio.load(item)
|
||||||
|
let start = parseStart($item, date)
|
||||||
return params
|
if (prev) {
|
||||||
}
|
if (start.isBefore(prev.start)) {
|
||||||
},
|
start = start.add(1, 'd')
|
||||||
parser: function ({ content, date }) {
|
date = date.add(1, 'd')
|
||||||
const programs = []
|
}
|
||||||
const items = parseItems(content)
|
prev.stop = start
|
||||||
items.forEach(item => {
|
}
|
||||||
const prev = programs[programs.length - 1]
|
const stop = start.add(30, 'm')
|
||||||
const $item = cheerio.load(item)
|
programs.push({
|
||||||
let start = parseStart($item, date)
|
title: parseTitle($item),
|
||||||
if (prev) {
|
description: parseDescription($item),
|
||||||
if (start.isBefore(prev.start)) {
|
category: parseCategory($item),
|
||||||
start = start.add(1, 'd')
|
start,
|
||||||
date = date.add(1, 'd')
|
stop
|
||||||
}
|
})
|
||||||
prev.stop = start
|
})
|
||||||
}
|
|
||||||
const stop = start.add(30, 'm')
|
return programs
|
||||||
programs.push({
|
}
|
||||||
title: parseTitle($item),
|
}
|
||||||
description: parseDescription($item),
|
|
||||||
category: parseCategory($item),
|
function parseStart($item, date) {
|
||||||
start,
|
const timeString = $item('.grille-time').text().trim()
|
||||||
stop
|
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 programs
|
|
||||||
}
|
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm:ss', 'Africa/Casablanca')
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseStart($item, date) {
|
function parseTitle($item) {
|
||||||
const timeString = $item('.grille-time').text().trim()
|
return $item('.program-title-sm').text().trim()
|
||||||
const [hours, minutes] = timeString.split('H').map(Number)
|
}
|
||||||
const formattedTime = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:00`
|
|
||||||
|
function parseDescription($item) {
|
||||||
const dateString = `${date.format('YYYY-MM-DD')} ${formattedTime}`
|
return $item('.program-description-sm').text().trim()
|
||||||
|
}
|
||||||
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm:ss', 'Africa/Casablanca')
|
|
||||||
}
|
function parseCategory($item) {
|
||||||
|
return $item('.genre-first').text().trim()
|
||||||
|
}
|
||||||
function parseTitle($item) {
|
|
||||||
return $item('.program-title-sm').text().trim()
|
function parseItems(content) {
|
||||||
}
|
const $ = cheerio.load(content)
|
||||||
|
|
||||||
function parseDescription($item) {
|
return $('.grille-line').toArray()
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
const { parser, url } = require('./snrt.ma.config.js')
|
const { parser, url } = require('./snrt.ma.config.js')
|
||||||
const cheerio = require('cheerio')
|
|
||||||
const { DateTime } = require('luxon')
|
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
|
@ -25,16 +23,14 @@ it('can parse response', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(results[0]).toMatchObject({
|
expect(results[0]).toMatchObject({
|
||||||
"category": "القرآن الكريم",
|
category: 'القرآن الكريم',
|
||||||
"description": "",
|
description: '',
|
||||||
"start": "2024-12-19T06:00:00.000Z",
|
start: '2024-12-19T06:00:00.000Z',
|
||||||
"stop": "2024-12-19T06:10:00.000Z",
|
stop: '2024-12-19T06:30:00.000Z',
|
||||||
"stop": "2024-12-19T06:30:00.000Z",
|
title: 'ﺍﻟﺴﻼﻡ ﺍﻟﻮﻃﻨﻲ + ﺍﻟﻘﺮﺁﻥ ﺍﻟﻜﺮﻳﻢ'
|
||||||
"title": "ﺍﻟﺴﻼﻡ ﺍﻟﻮﻃﻨﻲ + ﺍﻟﻘﺮﺁﻥ ﺍﻟﻜﺮﻳﻢ"
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
date,
|
date,
|
||||||
|
@ -42,4 +38,4 @@ it('can handle empty guide', () => {
|
||||||
content: '<!DOCTYPE html><html lang="ar" dir="rtl"><head></head><body></body></html>'
|
content: '<!DOCTYPE html><html lang="ar" dir="rtl"><head></head><body></body></html>'
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,15 +9,9 @@ module.exports = {
|
||||||
url({ date, channel }) {
|
url({ date, channel }) {
|
||||||
return `https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/schedules?locale=${
|
return `https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/schedules?locale=${
|
||||||
languages[channel.lang]
|
languages[channel.lang]
|
||||||
}&locale_default=${
|
}&locale_default=${languages[channel.lang]}&device=1&in_channel_id=${
|
||||||
languages[channel.lang]
|
|
||||||
}&device=1&in_channel_id=${
|
|
||||||
channel.site_id
|
channel.site_id
|
||||||
}>_end=${
|
}>_end=${date.unix()}<_start=${date.add(1, 'd').unix()}&limit=100&page=1`
|
||||||
date.unix()
|
|
||||||
}<_start=${
|
|
||||||
date.add(1, 'd').unix()
|
|
||||||
}&limit=100&page=1`
|
|
||||||
},
|
},
|
||||||
async parser({ content, date, channel }) {
|
async parser({ content, date, channel }) {
|
||||||
const programs = []
|
const programs = []
|
||||||
|
@ -29,7 +23,11 @@ module.exports = {
|
||||||
}
|
}
|
||||||
if (res.page && res.page.current < res.page.total) {
|
if (res.page && res.page.current < res.page.total) {
|
||||||
res = await axios
|
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)
|
.then(r => r.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
} else {
|
} else {
|
||||||
|
@ -39,7 +37,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
const season = s => {
|
const season = s => {
|
||||||
if (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) {
|
if (n) {
|
||||||
return parseInt(n)
|
return parseInt(n)
|
||||||
}
|
}
|
||||||
|
@ -66,11 +64,9 @@ module.exports = {
|
||||||
let page = 1
|
let page = 1
|
||||||
while (true) {
|
while (true) {
|
||||||
const items = await axios
|
const items = await axios
|
||||||
.get(`https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/channels?locale=${
|
.get(
|
||||||
languages[lang]
|
`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}`
|
||||||
}&locale_default=${
|
)
|
||||||
languages[lang]
|
|
||||||
}&device=1&limit=50&page=${page}`)
|
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
if (items.resources) {
|
if (items.resources) {
|
||||||
|
|
|
@ -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 dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
@ -36,9 +36,12 @@ it('can parse response', async () => {
|
||||||
title: 'Northern Rexposure',
|
title: 'Northern Rexposure',
|
||||||
subTitle: 'Hudson & Rex (Season 5)',
|
subTitle: 'Hudson & Rex (Season 5)',
|
||||||
description:
|
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'],
|
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,
|
season: 5,
|
||||||
episode: 15,
|
episode: 15,
|
||||||
rating: 'PG13'
|
rating: 'PG13'
|
||||||
|
|
|
@ -8,9 +8,7 @@ const debug = require('debug')('site:startimestv.com')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
doFetch
|
doFetch.setDebugger(debug).setMaxWorker(5)
|
||||||
.setDebugger(debug)
|
|
||||||
.setMaxWorker(5)
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'startimestv.com',
|
site: 'startimestv.com',
|
||||||
|
@ -24,7 +22,8 @@ module.exports = {
|
||||||
const programs = []
|
const programs = []
|
||||||
if (content) {
|
if (content) {
|
||||||
const $ = cheerio.load(content)
|
const $ = cheerio.load(content)
|
||||||
$('.box .mask').toArray()
|
$('.box .mask')
|
||||||
|
.toArray()
|
||||||
.forEach(el => {
|
.forEach(el => {
|
||||||
let title = parseText($(el).find('h4'))
|
let title = parseText($(el).find('h4'))
|
||||||
const [s, e] = title.substr(0, title.indexOf(' ')).split('-') || [null, null]
|
const [s, e] = title.substr(0, title.indexOf(' ')).split('-') || [null, null]
|
||||||
|
@ -53,7 +52,8 @@ module.exports = {
|
||||||
// process area-id
|
// process area-id
|
||||||
if (queue.t === 'a') {
|
if (queue.t === 'a') {
|
||||||
const $ = cheerio.load(res)
|
const $ = cheerio.load(res)
|
||||||
$('dd.update-areaID').toArray()
|
$('dd.update-areaID')
|
||||||
|
.toArray()
|
||||||
.forEach(el => {
|
.forEach(el => {
|
||||||
const dd = $(el)
|
const dd = $(el)
|
||||||
const areaId = dd.attr('area-id')
|
const areaId = dd.attr('area-id')
|
||||||
|
@ -72,7 +72,8 @@ module.exports = {
|
||||||
if (queue.t === 's') {
|
if (queue.t === 's') {
|
||||||
if (res) {
|
if (res) {
|
||||||
const $ = cheerio.load(res)
|
const $ = cheerio.load(res)
|
||||||
$(`.channl .c`).toArray()
|
$('.channl .c')
|
||||||
|
.toArray()
|
||||||
.forEach(el => {
|
.forEach(el => {
|
||||||
// only process channel with schedule only
|
// only process channel with schedule only
|
||||||
const clazz = $(el).attr('class')
|
const clazz = $(el).attr('class')
|
||||||
|
@ -98,13 +99,10 @@ module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseText($item) {
|
function parseText($item) {
|
||||||
let text = $item.text()
|
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
|
||||||
.replace(/\t/g, '')
|
|
||||||
.replace(/\n/g, ' ')
|
|
||||||
.trim()
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (text.match(/ /)) {
|
if (text.match(/\s\s/)) {
|
||||||
text = text.replace(/ /g, ' ')
|
text = text.replace(/\s\s/g, ' ')
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
@ -33,10 +33,10 @@ module.exports = {
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ country, lang }) {
|
async channels() {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(`https://streamingtvguides.com/Preferences`)
|
.get('https://streamingtvguides.com/Preferences')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
|
|
@ -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_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_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 = {
|
module.exports = {
|
||||||
site: 'telenet.tv',
|
site: 'telenet.tv',
|
||||||
|
@ -94,7 +94,7 @@ module.exports = {
|
||||||
|
|
||||||
async function loadProgramDetails(item, channel) {
|
async function loadProgramDetails(item, channel) {
|
||||||
if (!item.id) return {}
|
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
|
const data = await axios
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
|
@ -134,5 +134,5 @@ function parseEpisode(detail) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIcon(item) {
|
function parseIcon(item) {
|
||||||
return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile`;
|
return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile`
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,10 @@ module.exports = {
|
||||||
|
|
||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ lang }) {
|
async channels() {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const data = await axios
|
const data = await axios
|
||||||
.get(`https://telkussa.fi/API/Channels`)
|
.get('https://telkussa.fi/API/Channels')
|
||||||
.then(r => r.data)
|
.then(r => r.data)
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
|
|
|
@ -1,132 +1,134 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const cheerio = require('cheerio')
|
const cheerio = require('cheerio')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
const doFetch = require('@ntlab/sfetch')
|
const doFetch = require('@ntlab/sfetch')
|
||||||
const debug = require('debug')('site:tivie.id')
|
const debug = require('debug')('site:tivie.id')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
doFetch
|
doFetch.setDebugger(debug)
|
||||||
.setDebugger(debug)
|
|
||||||
|
const tz = 'Asia/Jakarta'
|
||||||
const tz = 'Asia/Jakarta'
|
|
||||||
|
module.exports = {
|
||||||
module.exports = {
|
site: 'tivie.id',
|
||||||
site: 'tivie.id',
|
days: 2,
|
||||||
days: 2,
|
url({ channel, date }) {
|
||||||
url({ channel, date }) {
|
return `https://tivie.id/channel/${channel.site_id}/${date.format('YYYYMMDD')}`
|
||||||
return `https://tivie.id/channel/${
|
},
|
||||||
channel.site_id
|
async parser({ content, date }) {
|
||||||
}/${
|
const programs = []
|
||||||
date.format('YYYYMMDD')
|
if (content) {
|
||||||
}`
|
const $ = cheerio.load(content)
|
||||||
},
|
const items = $('ul[x-data] > li[id*="event-"] > div.w-full')
|
||||||
async parser({ content, date }) {
|
.toArray()
|
||||||
const programs = []
|
.map(item => {
|
||||||
if (content) {
|
const $item = $(item)
|
||||||
const $ = cheerio.load(content)
|
const time = $item.find('div:nth-child(1) span:nth-child(1)')
|
||||||
const items = $('ul[x-data] > li[id*="event-"] > div.w-full').toArray()
|
const info = $item.find('div:nth-child(2) h5')
|
||||||
.map(item => {
|
const detail = info.find('a')
|
||||||
const $item = $(item)
|
const p = {
|
||||||
const time = $item.find('div:nth-child(1) span:nth-child(1)')
|
start: dayjs.tz(`${date.format('YYYY-MM-DD')} ${time.html()}`, 'YYYY-MM-DD HH:mm', tz)
|
||||||
const info = $item.find('div:nth-child(2) h5')
|
}
|
||||||
const detail = info.find('a')
|
if (detail.length) {
|
||||||
const p = {
|
const subtitle = detail.find('div')
|
||||||
start: dayjs.tz(`${date.format('YYYY-MM-DD')} ${time.html()}`, 'YYYY-MM-DD HH:mm', tz)
|
p.title = parseText(subtitle.length ? subtitle : detail)
|
||||||
}
|
p.url = detail.attr('href')
|
||||||
if (detail.length) {
|
} else {
|
||||||
const subtitle = detail.find('div')
|
p.title = parseText(info)
|
||||||
p.title = parseText(subtitle.length ? subtitle : detail)
|
}
|
||||||
p.url = detail.attr('href')
|
if (p.title) {
|
||||||
} else {
|
const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [
|
||||||
p.title = parseText(info)
|
null,
|
||||||
}
|
null,
|
||||||
if (p.title) {
|
null,
|
||||||
const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [null, null, null, null]
|
null
|
||||||
if (season) {
|
]
|
||||||
p.season = parseInt(season)
|
if (season) {
|
||||||
}
|
p.season = parseInt(season)
|
||||||
if (episode) {
|
}
|
||||||
p.episode = parseInt(episode)
|
if (episode) {
|
||||||
}
|
p.episode = parseInt(episode)
|
||||||
}
|
}
|
||||||
return p
|
}
|
||||||
})
|
return p
|
||||||
// fetch detailed guide if necessary
|
})
|
||||||
const queues = items
|
// fetch detailed guide if necessary
|
||||||
.filter(i => i.url)
|
const queues = items
|
||||||
.map(i => {
|
.filter(i => i.url)
|
||||||
const url = i.url
|
.map(i => {
|
||||||
delete i.url
|
const url = i.url
|
||||||
return {i, url}
|
delete i.url
|
||||||
})
|
return { i, url }
|
||||||
if (queues.length) {
|
})
|
||||||
await doFetch(queues, (queue, res) => {
|
if (queues.length) {
|
||||||
const $ = cheerio.load(res)
|
await doFetch(queues, (queue, res) => {
|
||||||
const img = $('#main-content > div > div:nth-child(1) img')
|
const $ = cheerio.load(res)
|
||||||
const info = $('#main-content > div > div:nth-child(2)')
|
const img = $('#main-content > div > div:nth-child(1) img')
|
||||||
const title = parseText(info.find('h2:nth-child(2)'))
|
const info = $('#main-content > div > div:nth-child(2)')
|
||||||
if (!queue.i.title.startsWith(title)) {
|
const title = parseText(info.find('h2:nth-child(2)'))
|
||||||
queue.i.subTitle = 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.description = parseText(info.find('div[class=""]:nth-child(4)'))
|
||||||
queue.i.image = img.length ? img.attr('src') : null
|
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++) {
|
// fill start-stop
|
||||||
if (i < items.length - 1) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
items[i].stop = items[i + 1].start
|
if (i < items.length - 1) {
|
||||||
} else {
|
items[i].stop = items[i + 1].start
|
||||||
items[i].stop = dayjs.tz(`${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`, 'YYYY-MM-DD HH:mm', tz)
|
} else {
|
||||||
}
|
items[i].stop = dayjs.tz(
|
||||||
}
|
`${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`,
|
||||||
// add programs
|
'YYYY-MM-DD HH:mm',
|
||||||
programs.push(...items)
|
tz
|
||||||
}
|
)
|
||||||
|
}
|
||||||
return programs
|
}
|
||||||
},
|
// add programs
|
||||||
async channels({ lang = 'id' }) {
|
programs.push(...items)
|
||||||
const result = await axios
|
}
|
||||||
.get('https://tivie.id/channel')
|
|
||||||
.then(response => response.data)
|
return programs
|
||||||
.catch(console.error)
|
},
|
||||||
|
async channels({ lang = 'id' }) {
|
||||||
const $ = cheerio.load(result)
|
const result = await axios
|
||||||
const items = $('ul[x-data] li[x-data] div header h2 a').toArray()
|
.get('https://tivie.id/channel')
|
||||||
const channels = items.map(item => {
|
.then(response => response.data)
|
||||||
const $item = $(item)
|
.catch(console.error)
|
||||||
const url = $item.attr('href')
|
|
||||||
return {
|
const $ = cheerio.load(result)
|
||||||
lang,
|
const items = $('ul[x-data] li[x-data] div header h2 a').toArray()
|
||||||
site_id: url.substr(url.lastIndexOf('/') + 1),
|
const channels = items.map(item => {
|
||||||
name: $item.find('strong').text()
|
const $item = $(item)
|
||||||
}
|
const url = $item.attr('href')
|
||||||
})
|
return {
|
||||||
|
lang,
|
||||||
return channels
|
site_id: url.substr(url.lastIndexOf('/') + 1),
|
||||||
}
|
name: $item.find('strong').text()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
function parseText($item) {
|
|
||||||
let text = $item.text()
|
return channels
|
||||||
.replace(/\t/g, '')
|
}
|
||||||
.replace(/\n/g, ' ')
|
}
|
||||||
.trim()
|
|
||||||
while (true) {
|
function parseText($item) {
|
||||||
if (text.match(/ /)) {
|
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
|
||||||
text = text.replace(/ /g, ' ')
|
while (true) {
|
||||||
continue
|
if (text.match(/\s\s/)) {
|
||||||
}
|
text = text.replace(/\s\s/g, ' ')
|
||||||
break
|
continue
|
||||||
}
|
}
|
||||||
|
break
|
||||||
return text
|
}
|
||||||
}
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
|
@ -1,77 +1,75 @@
|
||||||
const { parser, url } = require('./tivie.id.config')
|
const { parser, url } = require('./tivie.id.config')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
|
|
||||||
const date = dayjs.utc('2024-12-31').startOf('d')
|
const date = dayjs.utc('2024-12-31').startOf('d')
|
||||||
const channel = {
|
const channel = {
|
||||||
site_id: 'axn',
|
site_id: 'axn',
|
||||||
xmltv_id: 'AXN.id',
|
xmltv_id: 'AXN.id',
|
||||||
lang: 'id'
|
lang: 'id'
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.get.mockImplementation(url => {
|
axios.get.mockImplementation(url => {
|
||||||
const urls = {
|
const urls = {
|
||||||
'https://tivie.id/film/white-house-down-nwzDnwz9nAv6':
|
'https://tivie.id/film/white-house-down-nwzDnwz9nAv6': 'program01.html',
|
||||||
'program01.html',
|
'https://tivie.id/program/hudson-rex-s6-e14-nwzDnwvBmQr9': 'program02.html'
|
||||||
'https://tivie.id/program/hudson-rex-s6-e14-nwzDnwvBmQr9':
|
}
|
||||||
'program02.html',
|
let data = ''
|
||||||
}
|
if (urls[url] !== undefined) {
|
||||||
let data = ''
|
data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString()
|
||||||
if (urls[url] !== undefined) {
|
}
|
||||||
data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString()
|
return Promise.resolve({ data })
|
||||||
}
|
})
|
||||||
return Promise.resolve({ data })
|
|
||||||
})
|
it('can generate valid url', () => {
|
||||||
|
expect(url({ channel, date })).toBe('https://tivie.id/channel/axn/20241231')
|
||||||
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'))
|
||||||
it('can parse response', async () => {
|
const results = (await parser({ date, content, channel })).map(p => {
|
||||||
const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html'))
|
p.start = p.start.toJSON()
|
||||||
const results = (
|
p.stop = p.stop.toJSON()
|
||||||
await parser({ date, content, channel })
|
return p
|
||||||
).map(p => {
|
})
|
||||||
p.start = p.start.toJSON()
|
|
||||||
p.stop = p.stop.toJSON()
|
expect(results.length).toBe(27)
|
||||||
return p
|
expect(results[0]).toMatchObject({
|
||||||
})
|
start: '2024-12-30T17:00:00.000Z',
|
||||||
|
stop: '2024-12-30T17:05:00.000Z',
|
||||||
expect(results.length).toBe(27)
|
title: 'White House Down',
|
||||||
expect(results[0]).toMatchObject({
|
description:
|
||||||
start: '2024-12-30T17:00:00.000Z',
|
'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.',
|
||||||
stop: '2024-12-30T17:05:00.000Z',
|
image:
|
||||||
title: 'White House Down',
|
'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2023/09/65116c78791c2-1695640694.jpg?resize=480,270'
|
||||||
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.',
|
expect(results[2]).toMatchObject({
|
||||||
image: 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2023/09/65116c78791c2-1695640694.jpg?resize=480,270',
|
start: '2024-12-30T18:00:00.000Z',
|
||||||
})
|
stop: '2024-12-30T18:55:00.000Z',
|
||||||
expect(results[2]).toMatchObject({
|
title: 'Hudson & Rex S6, Ep. 14',
|
||||||
start: '2024-12-30T18:00:00.000Z',
|
description:
|
||||||
stop: '2024-12-30T18:55:00.000Z',
|
'Saat guru musik Jesse terbunuh di studio rekamannya, Charlie dan Rex menghubungkan kejahatan tersebut dengan pembunuhan yang tampaknya tak ada hubungannya.',
|
||||||
title: 'Hudson & Rex S6, Ep. 14',
|
image:
|
||||||
description:
|
'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2024/07/668b7ced47b25-1720417517.jpg?resize=480,270',
|
||||||
'Saat guru musik Jesse terbunuh di studio rekamannya, Charlie dan Rex menghubungkan kejahatan tersebut dengan pembunuhan yang tampaknya tak ada hubungannya.',
|
season: 6,
|
||||||
image: 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2024/07/668b7ced47b25-1720417517.jpg?resize=480,270',
|
episode: 14
|
||||||
season: 6,
|
})
|
||||||
episode: 14,
|
})
|
||||||
})
|
|
||||||
})
|
it('can handle empty guide', async () => {
|
||||||
|
const results = await parser({
|
||||||
it('can handle empty guide', async () => {
|
date,
|
||||||
const results = await parser({
|
channel,
|
||||||
date,
|
content: ''
|
||||||
channel,
|
})
|
||||||
content: '',
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
expect(results).toMatchObject([])
|
|
||||||
})
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ function parseItems(content, channel) {
|
||||||
let parsed
|
let parsed
|
||||||
try {
|
try {
|
||||||
parsed = JSON.parse(content)
|
parsed = JSON.parse(content)
|
||||||
} catch (error) {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
if (!parsed || !parsed.k) return []
|
if (!parsed || !parsed.k) return []
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue