mirror of
https://github.com/iptv-org/epg
synced 2026-05-02 15:36:59 -04:00
Add vrt.be
This commit is contained in:
228
sites/vrt.be/vrt.be.config.js
Normal file
228
sites/vrt.be/vrt.be.config.js
Normal file
@@ -0,0 +1,228 @@
|
||||
const axios = require('axios')
|
||||
const dayjs = require('dayjs')
|
||||
const utc = require('dayjs/plugin/utc')
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
const EPG_QUERY = `
|
||||
query EpgPage($pageId: ID!, $lazyItemCount: Int = 100) {
|
||||
page(id: $pageId) {
|
||||
... on ElectronicProgramGuidePage {
|
||||
previous {
|
||||
...epgListFragment
|
||||
}
|
||||
next {
|
||||
...epgListFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment epgListFragment on PaginatedTileList {
|
||||
listId
|
||||
paginatedItems(first: $lazyItemCount) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
...epgTileFragment
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment epgTileFragment on Tile {
|
||||
... on ITile {
|
||||
title
|
||||
description
|
||||
primaryMeta {
|
||||
value
|
||||
shortValue
|
||||
}
|
||||
indexMeta {
|
||||
value
|
||||
}
|
||||
progress {
|
||||
durationInSeconds
|
||||
}
|
||||
status {
|
||||
accessibilityLabel
|
||||
text {
|
||||
small
|
||||
default
|
||||
}
|
||||
}
|
||||
image {
|
||||
templateUrl
|
||||
}
|
||||
action {
|
||||
... on LinkAction {
|
||||
link
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const CHANNELS_QUERY = `
|
||||
query ProgramGuidePage($pageId: ID!) {
|
||||
page(id: $pageId) {
|
||||
... on ElectronicProgramGuidePage {
|
||||
channelNavigation {
|
||||
items {
|
||||
... on ContentTile {
|
||||
title
|
||||
brandLogos {
|
||||
primary
|
||||
type
|
||||
}
|
||||
action {
|
||||
... on LinkAction {
|
||||
link
|
||||
linkTokens {
|
||||
placeholder
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const API_ENDPOINT = 'https://www.vrt.be/vrtnu-api/graphql/public/v1'
|
||||
const API_HEADERS = {
|
||||
'content-type': 'application/json',
|
||||
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0',
|
||||
'x-vrt-client-name': 'WEB'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
site: 'vrt.be',
|
||||
days: 2,
|
||||
url: API_ENDPOINT,
|
||||
request: {
|
||||
method: 'POST',
|
||||
headers: API_HEADERS,
|
||||
data({ channel, date }) {
|
||||
return {
|
||||
query: EPG_QUERY,
|
||||
variables: {
|
||||
pageId: `/vrtmax/tv-gids/${channel.site_id}/${date.format('YYYY-MM-DD')}/`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parser({ content }) {
|
||||
let data
|
||||
try {
|
||||
data = JSON.parse(content)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
if (!data.data?.page) return []
|
||||
|
||||
const page = data.data?.page
|
||||
const previousEdges = page.previous?.paginatedItems?.edges || []
|
||||
const nextEdges = page.next?.paginatedItems?.edges || []
|
||||
const edges = [...previousEdges, ...nextEdges]
|
||||
|
||||
const programs = []
|
||||
edges.forEach((edge, index) => {
|
||||
const node = edge.node
|
||||
if (!node || !node.title) return
|
||||
|
||||
const start = parseCursor(edge.cursor)
|
||||
if (!start) return
|
||||
|
||||
const nextEdge = edges[index + 1]
|
||||
const stop = nextEdge ? parseCursor(nextEdge.cursor) : parseFallbackStop(start, node)
|
||||
if (!stop || !stop.isAfter(start)) return
|
||||
|
||||
programs.push({
|
||||
title: node.title,
|
||||
description: node.description || null,
|
||||
season: parseSeason(node.primaryMeta),
|
||||
episode: parseEpisode(node.primaryMeta),
|
||||
image: node.image?.templateUrl || null,
|
||||
start,
|
||||
stop
|
||||
})
|
||||
})
|
||||
|
||||
return programs
|
||||
},
|
||||
async channels() {
|
||||
const data = await axios
|
||||
.post(
|
||||
API_ENDPOINT,
|
||||
{
|
||||
query: CHANNELS_QUERY,
|
||||
variables: { pageId: '/vrtmax/tv-gids/' }
|
||||
},
|
||||
{ headers: API_HEADERS }
|
||||
)
|
||||
.then(r => r.data)
|
||||
.catch(console.error)
|
||||
|
||||
if (!data) return []
|
||||
|
||||
const items = data.data?.page?.channelNavigation?.items || []
|
||||
return items
|
||||
.map(item => {
|
||||
const siteId = item.action?.linkTokens?.find(
|
||||
t => t.placeholder === ':livestreamName'
|
||||
)?.value
|
||||
if (!siteId) return null
|
||||
|
||||
const logo = item.brandLogos?.find(l => l.type === 'png')?.primary || null
|
||||
|
||||
return {
|
||||
lang: 'nl',
|
||||
site_id: siteId,
|
||||
name: item.title,
|
||||
logo
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
function parseSeason(primaryMeta) {
|
||||
if (!Array.isArray(primaryMeta)) return null
|
||||
const item = primaryMeta.find(m => /^S\d+$/.test(m.shortValue))
|
||||
return item ? parseInt(item.shortValue.slice(1), 10) : null
|
||||
}
|
||||
|
||||
function parseEpisode(primaryMeta) {
|
||||
if (!Array.isArray(primaryMeta)) return null
|
||||
const item = primaryMeta.find(m => /^Afl\.\d+$/.test(m.shortValue))
|
||||
return item ? parseInt(item.shortValue.replace('Afl.', ''), 10) : null
|
||||
}
|
||||
|
||||
function parseCursor(cursor) {
|
||||
if (!cursor) return null
|
||||
const iso = cursor.replace(/^epg#[^#]+#/, '')
|
||||
const d = dayjs.utc(iso)
|
||||
return d.isValid() ? d : null
|
||||
}
|
||||
|
||||
function parseFallbackStop(start, node) {
|
||||
// Try progress.durationInSeconds (radio)
|
||||
const durationS = node.progress?.durationInSeconds
|
||||
if (durationS) return start.add(durationS, 'second')
|
||||
|
||||
// Try status.text.small e.g. "16 min"
|
||||
const statusSmall = node.status?.text?.small
|
||||
if (statusSmall) {
|
||||
const match = statusSmall.match(/(\d+)\s*min/)
|
||||
if (match) return start.add(parseInt(match[1], 10), 'minute')
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user