Files
epg/sites/vrt.be/vrt.be.config.js
Michaël Arnauts 8ceebfc749 Add vrt.be
2026-03-24 08:34:48 +01:00

229 lines
5.2 KiB
JavaScript

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
}