mirror of
https://github.com/iptv-org/epg
synced 2026-03-22 03:41:02 -04:00
Merge pull request #2991 from CyberPoison/master
Fix `meo.pt` and `nostv.pt` grabbers
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -3,65 +3,55 @@ const { DateTime } = require('luxon')
|
||||
module.exports = {
|
||||
site: 'meo.pt',
|
||||
days: 2,
|
||||
url: 'https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getProgramsFromChannels',
|
||||
url: function ({ channel, date }) {
|
||||
return `https://meogouser.apps.meo.pt/Services/GridTv/GridTv.svc/GetLiveChannelProgramsByDate?callLetter=${channel.site_id}&date=${date.format('YYYY-MM-DD')}&userAgent=IPTV_OFR_GTV`
|
||||
},
|
||||
request: {
|
||||
method: 'POST',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Origin: 'https://www.meo.pt',
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; en-US Trident/4.0)'
|
||||
},
|
||||
data: function ({ channel, date }) {
|
||||
return {
|
||||
service: 'channelsguide',
|
||||
channels: [channel.site_id],
|
||||
dateStart: date.format('YYYY-MM-DDT00:00:00-00:00'),
|
||||
dateEnd: date.add(1, 'd').format('YYYY-MM-DDT00:00:00-00:00'),
|
||||
accountID: ''
|
||||
}
|
||||
'accept': '*/*',
|
||||
'accept-language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7,pt;q=0.6,cs;q=0.5',
|
||||
'cache-control': 'no-cache',
|
||||
'origin': 'https://www.meo.pt',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': 'https://www.meo.pt/',
|
||||
'sec-ch-ua': '"Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"',
|
||||
'sec-ch-ua-mobile': '?1',
|
||||
'sec-ch-ua-platform': '"Android"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36'
|
||||
}
|
||||
},
|
||||
async parser({ content }) {
|
||||
const axios = require('axios')
|
||||
async parser({ content, channel }) {
|
||||
let programs = []
|
||||
const items = parseItems(content)
|
||||
if (!items.length) return programs
|
||||
|
||||
// simple per-run in-memory cache
|
||||
const detailsCache = new Map()
|
||||
|
||||
for (const item of items) {
|
||||
const start = parseStart(item)
|
||||
let stop = parseStop(item)
|
||||
if (stop < start) {
|
||||
stop = stop.plus({ days: 1 })
|
||||
}
|
||||
|
||||
let description = ''
|
||||
let image = ''
|
||||
|
||||
const programID = item.uniqueId || null
|
||||
if (programID) {
|
||||
let details = detailsCache.get(programID)
|
||||
if (!details) {
|
||||
details = await fetchProgramDetails(programID, axios).catch(() => null)
|
||||
if (details) detailsCache.set(programID, details)
|
||||
}
|
||||
if (details) {
|
||||
description = details.description || description
|
||||
image = details.image || image
|
||||
}
|
||||
}
|
||||
const start = DateTime.fromISO(item.StartDate, { zone: 'Europe/Lisbon' }).toUTC()
|
||||
const stop = DateTime.fromISO(item.EndDate, { zone: 'Europe/Lisbon' }).toUTC()
|
||||
|
||||
const prog = {
|
||||
title: item.name || 'Sem título',
|
||||
title: item.Title || 'Sem título',
|
||||
start,
|
||||
stop
|
||||
}
|
||||
if (description) prog.description = description
|
||||
if (image) {
|
||||
|
||||
if (item.Synopsis) {
|
||||
prog.description = item.Synopsis
|
||||
}
|
||||
|
||||
// Construct image URL using the same logic as before if possible
|
||||
if (item.Title && channel.site_id) {
|
||||
const encodedTitle = encodeURIComponent(item.Title)
|
||||
const image = `https://proxycache.online.meo.pt/eemstb/ImageHandler.ashx?evTitle=${encodedTitle}&chCallLetter=${channel.site_id}&profile=16_9&width=600`
|
||||
prog.icon = { src: image }
|
||||
prog.image = image
|
||||
}
|
||||
|
||||
programs.push(prog)
|
||||
}
|
||||
|
||||
@@ -70,84 +60,45 @@ module.exports = {
|
||||
async channels() {
|
||||
const axios = require('axios')
|
||||
const data = await axios
|
||||
.post('https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getGridAnon', null, {
|
||||
.get('https://meogouser.apps.meo.pt/Services/GridTv/GridTv.svc/GetContentsForChannels?userAgent=IPTV_OFR_GTV', {
|
||||
headers: {
|
||||
Origin: 'https://www.meo.pt',
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; en-US Trident/4.0)'
|
||||
'accept': '*/*',
|
||||
'accept-language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7,pt;q=0.6,cs;q=0.5',
|
||||
'cache-control': 'no-cache',
|
||||
'origin': 'https://www.meo.pt',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': 'https://www.meo.pt/',
|
||||
'sec-ch-ua': '"Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"',
|
||||
'sec-ch-ua-mobile': '?1',
|
||||
'sec-ch-ua-platform': '"Android"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36'
|
||||
}
|
||||
})
|
||||
.then(r => r.data)
|
||||
.catch(console.log)
|
||||
|
||||
// channel logo at data.d.channels.logo
|
||||
|
||||
return data.d.channels
|
||||
return data.Result
|
||||
.map(item => {
|
||||
return {
|
||||
lang: 'pt',
|
||||
site_id: item.sigla,
|
||||
name: item.name
|
||||
site_id: item.CallLetter,
|
||||
name: item.Title
|
||||
}
|
||||
})
|
||||
.filter(channel => channel.site_id)
|
||||
}
|
||||
}
|
||||
|
||||
function parseStart(item) {
|
||||
return DateTime.fromFormat(`${item.date} ${item.timeIni}`, 'd-M-yyyy HH:mm', {
|
||||
zone: 'Europe/Lisbon'
|
||||
}).toUTC()
|
||||
}
|
||||
|
||||
function parseStop(item) {
|
||||
return DateTime.fromFormat(`${item.date} ${item.timeEnd}`, 'd-M-yyyy HH:mm', {
|
||||
zone: 'Europe/Lisbon'
|
||||
}).toUTC()
|
||||
}
|
||||
|
||||
function parseItems(content) {
|
||||
if (!content) return []
|
||||
const data = JSON.parse(content)
|
||||
const programs = data?.d?.channels?.[0]?.programs
|
||||
|
||||
return Array.isArray(programs) ? programs : []
|
||||
}
|
||||
|
||||
async function fetchProgramDetails(programID, axiosInstance) {
|
||||
try {
|
||||
const response = await axiosInstance.post(
|
||||
'https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getProgramDetails',
|
||||
{
|
||||
service: 'programdetail',
|
||||
programID: String(programID),
|
||||
accountID: ''
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Origin: 'https://www.meo.pt',
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; en-US Trident/4.0)'
|
||||
},
|
||||
timeout: 10000
|
||||
}
|
||||
)
|
||||
|
||||
const data = response.data
|
||||
// Response structure has program data directly in data.d
|
||||
const program = data?.d
|
||||
if (!program || typeof program !== 'object') return null
|
||||
|
||||
// Build image URL using MEO's image handler
|
||||
let image = null
|
||||
if (program.progName && program.channelSigla) {
|
||||
const encodedTitle = encodeURIComponent(program.progName)
|
||||
image = `https://proxycache.online.meo.pt/eemstb/ImageHandler.ashx?evTitle=${encodedTitle}&chCallLetter=${program.channelSigla}&profile=16_9&width=600`
|
||||
}
|
||||
|
||||
const description = program.description || null
|
||||
|
||||
return { description, image }
|
||||
const data = typeof content === 'string' ? JSON.parse(content) : content
|
||||
return Array.isArray(data.Result) ? data.Result : []
|
||||
} catch {
|
||||
// Silent fail returning null so parser continues
|
||||
return null
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,37 +18,28 @@ const channel = {
|
||||
}
|
||||
|
||||
it('can generate valid url', () => {
|
||||
expect(url).toBe(
|
||||
'https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getProgramsFromChannels'
|
||||
expect(url({ channel, date })).toBe(
|
||||
'https://meogouser.apps.meo.pt/Services/GridTv/GridTv.svc/GetLiveChannelProgramsByDate?callLetter=RTPM&date=2022-12-02&userAgent=IPTV_OFR_GTV'
|
||||
)
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.method).toBe('POST')
|
||||
expect(request.method).toBe('GET')
|
||||
})
|
||||
|
||||
it('can generate valid request headers', () => {
|
||||
expect(request.headers).toMatchObject({
|
||||
Origin: 'https://www.meo.pt'
|
||||
})
|
||||
})
|
||||
|
||||
it('can generate valid request method', () => {
|
||||
expect(request.data({ channel, date })).toMatchObject({
|
||||
service: 'channelsguide',
|
||||
channels: ['RTPM'],
|
||||
dateStart: '2022-12-02T00:00:00-00:00',
|
||||
dateEnd: '2022-12-03T00:00:00-00:00',
|
||||
accountID: ''
|
||||
'origin': 'https://www.meo.pt',
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36'
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse response', async () => {
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf-8')
|
||||
|
||||
axios.post.mockResolvedValue({ data: {} })
|
||||
axios.get.mockResolvedValue({ data: {} })
|
||||
|
||||
let results = await parser({ content })
|
||||
let results = await parser({ content, channel })
|
||||
results = results.map(p => {
|
||||
p.start = p.start.toJSON()
|
||||
p.stop = p.stop.toJSON()
|
||||
@@ -56,9 +47,13 @@ it('can parse response', async () => {
|
||||
})
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2022-12-01T23:35:00.000Z',
|
||||
stop: '2022-12-02T00:17:00.000Z',
|
||||
title: 'Walker, O Ranger Do Texas T6 - Ep. 14'
|
||||
start: '2026-01-22T23:40:00.000Z',
|
||||
stop: '2026-01-23T00:04:00.000Z',
|
||||
title: 'Barman - Ep. 4',
|
||||
description: "'Barman' é uma série de comédia dramática sobre um jovem comediante que começa a trabalhar como Barman porque precisa de arranjar dinheiro depressa, pelo caminho é obrigado a lidar com a vida noturna e conciliar duas realidades diferentes.",
|
||||
icon: {
|
||||
src: 'https://proxycache.online.meo.pt/eemstb/ImageHandler.ashx?evTitle=Barman%20-%20Ep.%204&chCallLetter=RTPM&profile=16_9&width=600'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,24 +5,33 @@ const utc = require('dayjs/plugin/utc')
|
||||
dayjs.extend(utc)
|
||||
|
||||
const headers = {
|
||||
'X-Apikey': 'xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI',
|
||||
'X-Core-Appversion': '2.20.0.3',
|
||||
'X-Core-Contentratinglimit': '0',
|
||||
'X-Core-Deviceid': '',
|
||||
'X-Core-Devicetype': 'web',
|
||||
Origin: 'https://nostv.pt',
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
||||
'accept': 'application/json, text/plain, */*',
|
||||
'accept-language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7,pt;q=0.6,cs;q=0.5',
|
||||
'cache-control': 'no-cache',
|
||||
'origin': 'https://nostv.pt',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': 'https://nostv.pt/',
|
||||
'sec-ch-ua': '"Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"',
|
||||
'sec-ch-ua-mobile': '?1',
|
||||
'sec-ch-ua-platform': '"Android"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'cross-site',
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36',
|
||||
'x-apikey': 'xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI',
|
||||
'x-core-appversion': '2.20.2.2',
|
||||
'x-core-contentratinglimit': '0',
|
||||
'x-core-deviceid': '',
|
||||
'x-core-devicetype': 'web',
|
||||
'x-core-timezoneoffset': '3600000'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
site: 'nostv.pt',
|
||||
days: 2,
|
||||
url({ channel, date }) {
|
||||
return `https://api.clg.nos.pt/nostv/ott/schedule/range/contents/guest?channels=${channel.site_id
|
||||
}&minDate=${date.format('YYYY-MM-DD')}T00:00:00Z&maxDate=${date.format(
|
||||
'YYYY-MM-DD'
|
||||
)}T23:59:59Z&isDateInclusive=true&client_id=${headers['X-Apikey']}`
|
||||
return `https://api.clg.nos.pt/nostv/ott/schedule/range/contents/guest?channels=${channel.site_id}&minDate=${date.format('YYYY-MM-DD')}T00:00:00Z&maxDate=${date.format('YYYY-MM-DD')}T23:59:59Z&isDateInclusive=true&client_id=${headers['x-apikey']}`
|
||||
},
|
||||
request: { headers },
|
||||
parser({ content }) {
|
||||
@@ -31,7 +40,7 @@ module.exports = {
|
||||
const items = Array.isArray(content) ? content : JSON.parse(content)
|
||||
items.forEach(item => {
|
||||
const image = item.Images
|
||||
? `https://mage.stream.nos.pt/mage/v1/Images?sourceUri=${item.Images[0].Url}&profile=ott_1_452x340&client_id=${headers['X-Apikey']}`
|
||||
? `https://mage.stream.nos.pt/mage/v1/Images?sourceUri=${item.Images[0].Url}&profile=ott_1_452x340&client_id=${headers['x-apikey']}`
|
||||
: null
|
||||
programs.push({
|
||||
title: item.Metadata?.Title,
|
||||
@@ -54,7 +63,7 @@ module.exports = {
|
||||
async channels() {
|
||||
const result = await axios
|
||||
.get(
|
||||
`https://api.clg.nos.pt/nostv/ott/channels/guest?client_id=${headers['X-Apikey']}`,
|
||||
`https://api.clg.nos.pt/nostv/ott/channels/guest?client_id=${headers['x-apikey']}`,
|
||||
{ headers }
|
||||
)
|
||||
.then(r => r.data)
|
||||
|
||||
@@ -28,20 +28,17 @@ it('can parse response', () => {
|
||||
return p
|
||||
})
|
||||
|
||||
const image = 'https://mage.stream.nos.pt/mage/v1/Images?sourceUri=http://vip.pam.local.internal/PAM.Images/Store/8329ed1aec5d4c0faa2056972256ff9f&profile=ott_1_452x340&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI'
|
||||
|
||||
expect(results[0]).toMatchObject({
|
||||
start: '2023-12-11T16:30:00.000Z',
|
||||
stop: '2023-12-11T17:00:00.000Z',
|
||||
title: 'Village Vets',
|
||||
description:
|
||||
'A história de dois melhores amigos veterinários e o seu extraordinário trabalho na Austrália.',
|
||||
season: 1,
|
||||
episode: 12,
|
||||
start: '2026-01-23T22:39:00.000Z',
|
||||
stop: '2026-01-24T00:23:00.000Z',
|
||||
title: 'Em Casa d\'Amália',
|
||||
description: 'Que mais poderíamos pedir para o regresso deste programa, do que receber um dos poetas de Amália? Manuel Alegre, autor do icónico "Trova do Vento que Passa", estará connosco. A este raro momento, juntam-se também ilustres convidados: Paulo de Carvalho e o seu filho Agir, Rita Guerra e André Amaro...',
|
||||
season: 9,
|
||||
episode: 15,
|
||||
icon: {
|
||||
src: image
|
||||
src: 'https://mage.stream.nos.pt/mage/v1/Images?sourceUri=http://vip.pam.local.internal/PAM.Images/Store/901d96a8f1534749b076212c296d821e&profile=ott_1_452x340&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI'
|
||||
},
|
||||
image
|
||||
image: 'https://mage.stream.nos.pt/mage/v1/Images?sourceUri=http://vip.pam.local.internal/PAM.Images/Store/901d96a8f1534749b076212c296d821e&profile=ott_1_452x340&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI'
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user