mirror of
https://github.com/iptv-org/epg
synced 2026-09-06 03:49:49 -04:00
The grabber requested each day as a fixed UTC+2 window. The API's broadcast days start at 04:00 Europe/Helsinki, so during daylight saving time (UTC+3) the misaligned interval was answered with a "303 See Other" redirect to a widened two-day window. On top of that the API paginates 20 items per response by default, so each day was truncated to the first ~20 programmes. - Request the exact broadcast-day window in Europe/Helsinki time - Add limit=1000 so a full day fits in one response - Drop the local-timezone day filter: the requested interval already bounds the results, and the filter discarded late-night programmes depending on the runner's timezone - Throw on malformed or unrecognized responses instead of returning an empty list, so failures are reported as errors rather than a silently empty (but "successful") guide
112 lines
3.3 KiB
JavaScript
112 lines
3.3 KiB
JavaScript
const axios = require('axios')
|
|
const dayjs = require('dayjs')
|
|
const utc = require('dayjs/plugin/utc')
|
|
const timezone = require('dayjs/plugin/timezone')
|
|
|
|
dayjs.extend(utc)
|
|
dayjs.extend(timezone)
|
|
|
|
// The API groups programmes into broadcast days that start at 04:00
|
|
// Europe/Helsinki. Requesting exactly that window (instead of a fixed
|
|
// UTC+2 offset) keeps the interval aligned year-round; a misaligned
|
|
// interval gets answered with a "303 See Other" to a widened window.
|
|
// Without an explicit `limit` the API paginates 20 items per response,
|
|
// so only the first ~20 programmes of each day would be returned.
|
|
const dayStart = date => dayjs.tz(date.format('YYYY-MM-DD'), 'Europe/Helsinki').add(4, 'h')
|
|
|
|
module.exports = {
|
|
site: 'dna.fi',
|
|
days: 2,
|
|
url({ date, channel }) {
|
|
const beginTimestamp = dayStart(date).valueOf()
|
|
const endTimestamp = dayStart(date.add(1, 'd')).subtract(1, 's').valueOf()
|
|
|
|
return `https://mts-pro-envoy-vip.dna.fi/hbx/api/pub/xrtv/g/media?q=channel:${channel.site_id}&q=profile:pr&q=start-interval:${beginTimestamp}/${endTimestamp}&limit=1000`
|
|
},
|
|
parser({ content }) {
|
|
let programs = []
|
|
let items = parseItems(content)
|
|
items.forEach(item => {
|
|
const data = item?._embedded?.['xrtv:meta']?.data
|
|
if (!data?.start) return
|
|
|
|
programs.push({
|
|
title: data?.title,
|
|
subtitle: data?.episode_title,
|
|
description: data?.description,
|
|
season: data?.season_number,
|
|
episode: data?.episode_number,
|
|
date: data?.year,
|
|
categories: parseCategories(item),
|
|
rating: parseRating(data),
|
|
images: parseImages(item),
|
|
directors: parseCast(data, 'director'),
|
|
actors: parseCast(data, 'actors'),
|
|
start: dayjs(data.start),
|
|
stop: dayjs(data.end)
|
|
})
|
|
})
|
|
|
|
return programs
|
|
},
|
|
async channels() {
|
|
const data = await axios
|
|
.get('https://mts-pro-envoy-vip.dna.fi/hbx/api/pub/xrtv/g/media?q=profile:ch&limit=1000')
|
|
.then(r => r.data)
|
|
.catch(console.error)
|
|
|
|
return data._embedded['xrtv:media-item'].map(c => {
|
|
return {
|
|
lang: 'fi',
|
|
site_id: c.datalistTerm,
|
|
name: c.name
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
function parseCast(data, role) {
|
|
if (!data[role] || !data[role].value) return []
|
|
|
|
return data[role].value.split(', ').map(name => ({
|
|
lang: data[role].lang,
|
|
value: name
|
|
}))
|
|
}
|
|
|
|
function parseCategories(item) {
|
|
const categories = item?._embedded?.['xrtv:media-category']
|
|
|
|
return Array.isArray(categories) ? categories.map(category => category.name) : []
|
|
}
|
|
|
|
function parseRating(data) {
|
|
if (!data.age_rating) return null
|
|
|
|
return {
|
|
system: 'VET',
|
|
value: data.age_rating
|
|
}
|
|
}
|
|
|
|
function parseImages(item) {
|
|
const images = item?._embedded?.['xrtv:image']
|
|
|
|
return Array.isArray(images) ? images.map(image => image.src) : []
|
|
}
|
|
|
|
function parseItems(content) {
|
|
if (!content) return []
|
|
|
|
// Let malformed content (e.g. an HTML error page) throw, so the grab
|
|
// is reported as an error instead of silently producing no programmes.
|
|
const data = JSON.parse(content)
|
|
if (!data?.page) {
|
|
throw new Error('Unexpected response structure')
|
|
}
|
|
|
|
const items = data?._embedded?.['xrtv:media-item']
|
|
|
|
return Array.isArray(items) ? items : []
|
|
}
|