From e1f61486d45737b78a8802560b6957c7364cec96 Mon Sep 17 00:00:00 2001
From: knylbyte <40831653+knylbyte@users.noreply.github.com>
Date: Wed, 29 Jul 2026 22:18:15 +0200
Subject: [PATCH] feat: add sky uhd channels via atlantis
---
SITES.md | 2 +-
sites/sky.com/sky.com.channels.xml | 525 +++++++++++++++--------------
sites/sky.com/sky.com.config.js | 87 +++--
sites/sky.com/sky.com.test.js | 199 ++++++++---
4 files changed, 489 insertions(+), 324 deletions(-)
diff --git a/SITES.md b/SITES.md
index fd282a9f4..d5e502019 100644
--- a/SITES.md
+++ b/SITES.md
@@ -158,7 +158,7 @@
| singtel.com | 137 | 🟢 | |
| sjonvarp.is | 13 | 🟢 | |
| sky.co.nz | 91 | 🟢 | |
- | sky.com | 1116 | 🟢 | |
+ | sky.com | 1515 | 🟢 | |
| skylife.co.kr | 257 | 🟢 | |
| skyperfectv.co.jp | 196 | 🟢 | |
| snrt.ma | 11 | 🟢 | |
diff --git a/sites/sky.com/sky.com.channels.xml b/sites/sky.com/sky.com.channels.xml
index cdbe68f38..d9137bb45 100644
--- a/sites/sky.com/sky.com.channels.xml
+++ b/sites/sky.com/sky.com.channels.xml
@@ -1,5 +1,6 @@
+ UHD1 by ASTRA / HD+
Sky Cinema 1
Dyn Sport Mix
RTL Sport 1
@@ -109,17 +110,21 @@
Just Fishing
AXN White HD
Regio TV
+ RTL UHD
Prime Sportsbar
LT1-OOE
+ QVC UHD
Sportdigital EDGE
Spirit TV Germany
Franken Plus HD
+ Pro7Sat.1 UHD
Dlf Kultur
Dlf
DRadio DokDeb
Dlf Nova
HD Test1
HD Test 2
+ UHD Test1
Placeholder 1
Placeholder 2
3sat HD
@@ -402,6 +407,10 @@
Sky Sport 10 HD
Sky Sport 10 HD
Sky Sport 10 HD
+ Sky Sport UHD
+ Sky Sport UHD
+ Sky Sport UHD
+ Sky Sport UHD
Sky Sport Austria 1 HD
Sky Sport Austria 1 HD
Sky Sport Austria 1 HD
@@ -448,6 +457,8 @@
Sky Sport Bundesliga 10 HD
Sky Sport Bundesliga HD
Sky Sport Bundesliga HD
+ Sky Sport Bundesliga UHD
+ Sky Sport Bundesliga UHD
Sky Sport F1 HD
Sky Sport F1 HD
Sky Sport Golf HD
@@ -590,13 +601,21 @@
WRN Europe
50662
50663
+ SkySpF1 UHD
+ SkySpF1 UHD
+ Ultra HD
+ Ultra HD
+ Ultra HD
+ Ultra HD
My Sky
My Account
Program Your Remote
Help and Support
Reset your PIN
Get Sky Products
+ Ultra HD
H1Test
+ Ultra HD
___4109
(sub b +1000)
4seven
@@ -976,6 +995,8 @@
SkySpMainEvHD
SkySpMainEvHD
SkySpMainEv
+ SkySpME UHD
+ SkySpME UHD
SkySp Mix HD
SkySp Mix HD
SkySp Mix
@@ -1068,6 +1089,8 @@
TNTSBoxOff2HD
TNTSBoxOffHD
TNTSBoxOffHD
+ TNTUltimateUHD
+ TNTUltimateUHD
Together
Travelxp
TRT World HD
@@ -1116,95 +1139,14 @@
Zee Punjabi
Zee TV HD
Zee TV
- Sky Sport Uno
- Sky Sport Arena
- Sky Cinema Comedy
- Sky Sport24
- Sky Sport24
- Nick Jr. +1
- Sky Sport24
- Sky Cinema Suspense
- History +1
- Sky Arte
- Sky Arte
- Fox News
- Sky News
- Discovery HD
Prime Video Sportsbar
- cielo
- cielo
- Sky Crime
Sky Primafila
- Eurosport 2
- Sky Classica
- TV2000 HD
Radio Vaticana Europa
- Sky Cinema Uno
- Sky Cinema Uno
Western Collection
- Sky Cinema Action
- Sky Sport Calcio
- CNN Intl.
- Sky Cinema Uno +24
- Sky Atlantic
- NOVE HD
- Gambero Rosso HD
- Gambero Rosso HD
- Sky Sport Calcio
- Sky Cinema Romance
- Sky Sport
- Sky Sport
Sky Serie Maratone
Calcio Overlay HD
- CNBC HD
- Sky Sport Max
- Sky Crime
- Classica HD
- Sky Arte +1
- Sky Cinema Family
- Cartoon Network
- TV8 HD
- Milan TV
- Sky Uno +1
- TV8 HD
- Deejay TV
Sky Sport Info
- DeAJunior
- Nickelodeon
- Sky Meteo24
- Discovery +1
- TG24PrimoPiano
- Boomerang +1
- CACCIA e Pesca
- Caccia e PESCA
- Cartoon Network +1
- Boomerang
- Comedy Central
- LA7 HD
- -frisbee-
- Super!
- Nick Jr
- MTV Music
- MTV Music
Sky Collection
- Sky Uno +
- Class CNBC
- ACI SPORT TV
- DeAKids
- K2
- Sky Uno
- Sky Sport F1
- Sky Sport MotoGP
- Inter TV
- Real Time HD
- History
- Sky TG24
- Sky Atlantic +1
- Sky TG24
- History
- Comedy +1
- DAZN 2
- Fox Business
Radio DeeJay
M2O
Radio 105
@@ -1216,67 +1158,21 @@
Radio 24
RDS
Radio Montecarlo
- Sky Sport Tennis
Sky Sport Business 1
Sky Sport Business 2
Sky Cinema Stories
Sky Sport Business 3
- DAZN 1
- Sky Sport Legend
- Sky Sport Mix
Disney Jr.
- Sky Sport
Sky Sport
Sky Sport
Sky Selection
Gestione Collegam. Telefonico
Connessione internet Primafila
- Sky Serie
- Sky Investigation
- Sky Nature
- Sky Documentaries
- Sky Documentaries
- Sky Nature
- Food Network HD
- MTV HD
- Sky Sport Basket
- Sky Sport Golf
- Sky Cinema Drama
- Sky Sport
- Sky Sport
- Sky Cinema Due +24
- Euronews
- Rai Storia
- Rai Scuola
- RAI Sport
- Class Tv Moda
- RTL 102.5 HD
- Vatican Media Europa
- Uninettuno
- Senato Italiano
- Camera dei Deputati
- Canale Italia 83
People TV-Rete7
- TelePadrePio HD
Napoli Mia
- RAI Gulp
- Rai News 24
- Rai 3 HD
- Rai 2 HD
- Rai 1 HD
- Sky Sport
- Sky Sport
- Sky Sport
Dimensione Suono Soft
- Sky Documentaries +1
Sky Investigation Maratone
- Sky Documentaries +1
Sky Selection
- Sky Sport
- Sky Adventure
- Sky Adventure
- Kiss Kiss Tv HD
- TVA Vicenza
InBlu
Documentari HD
Bambini HD
@@ -1290,7 +1186,6 @@
Radio Maria
Radio Padre Pio
Radio Radicale
- Orler TV
PUSH REF SD 3000
PUSH REF HD 3001
REF 3010
@@ -1300,134 +1195,37 @@
REF 3050
REF 3055
REF 3060
- Eurosport 1
- Sky TG24 HD
- GIALLO HD
Discovery Turbo HD
Sky Cinema Restart
RFI Francais
Rai Radio Tutta Italiana
- HGTV HD
- Canale Italia 84
- Al Jazeera Intl. HD
- Al Jazeera HD
- France 24 Francais HD
- CGTN
- Kurdistan 24 HD
- Dubai TV HD
- Al Arabiya
- Dubai Sports 1 HD
- Abu Dhabi TV HD
- Aljazeera Mubasher
- Al Alam TV HD
- Jordan TV
- Iraqia News HD
- KTV 1 HD
- ALMAGHRIBIA
- TVP Polonia
- TVR Intl.
- BBC Arabic
- ESC - Al Masriyah
Tunis 7
- Deutsche Welle HD
- Daystar Tv HD
- Sender Neu Jerusalem
- SuperTennis HD
- Bloomberg
Rai Radio 1
Rai Radio 2
Rai Radio 3
- EWTN
- The Word Network
- Horse TV HD
- France 24 English HD
- NHK World TV HD
- Arirang TV
- San Marino RTV
TeleItalia
- TgCom24 HD
- DMAX HD
- Radio Monte Carlo
- RTL 102.5 Caliente
- Italian Fishing Tv
- Canale Italia
Radio San Marino
- TRM h24
- Sky News Arabia HD
- Virgin Radio
- San Marino RTV HD
- Parole di Vita
- LaC Sat
- RADIONORBA TV
- SonLife Broadcasting Network
- Euronews English HD
- Canale 5 HD
- Iris HD
- La 5 HD
Radio Italia Live
- EQUtv
- Mediaset Extra HD
- Mediaset Italia2 HD
- TOPcrime HD
- Focus HD
Rai Radio 2 Indie
- 27Twentyseven HD
- Radio Italia TV HD
- RADIO ZETA HD
- 20Mediaset HD
- Italia 1 HD
- ArteIn
- Rete 4 HD
- RADIOFRECCIA HD
Rai Radio Kids
Rai Radio Live
Rai Radio Techete
TELERADIOPACE
Rai Radio 1 Sport
- RDS Social TV
- Cine34 HD
GO-TV CHANNEL
- Videolina
- BYOBLU
- Telecupole
INBLU2000
- Well Tv
Radio Gamma Stereo
Radio Sintony
Radio Super Sound
- MARCOPOLO
- LA7CINEMA
Giornale Radio
Canzoni Napoletane
Med.TV
- JUWELO
wedotv Movies
wedotv Big Stories
- TELESARDEGNA
Urania TV
- Discovery HD
PRIMASET
- VALUE 24 TV
- BeJoy.kids
Canale Italia 2
- LA7 HD
- TV8 HD
- NOVE HD
- Real Time HD
- QVC
- RTL 102.5 HD
- K2
- -frisbee-
- Super!
- TV8 HD
- SuperTennis HD
- DMAX HD
- GIALLO HD
- Discovery HD
Discovery Turbo HD
- Sky Sport Uno
- HGTV HD
- 27 Twentyseven
Radio Italia
Sky Primafila Active
BBC English (Europe)
@@ -1452,36 +1250,269 @@
Rsi Rete 2
Rsi Rete 3
RADIOFRECCIA
- Canale 5
- La 5
- Iris
- LA7CINEMA
- Mediaset Extra
- Rete 4 HD
- Canale 5 HD
- Italia 1 HD
- Mediaset Italia2
- TOPcrime
- Rete 4
- Italia 1
- 20Mediaset HD
- Focus
- TgCom24
- Food Network HD
- RADIOFRECCIA HD
- RADIO ZETA
Radio DeeJay
Radio Capital
M2O
Radio Italia
RTL 102.5
- Cine34
- VH1
Radio Montecarlo
Radio 105
Virgin Radio
R 101
- DAZN 1
- ZONA DAZN 2
+ 20Mediaset HD
+ 20Mediaset HD
+ Abu Dhabi TV HD
+ ACI SPORT TV
+ Al Alam TV HD
+ Al Arabiya
+ Iraqia News HD
+ Al Jazeera HD
+ Al Jazeera Intl. HD
+ Aljazeera Mubasher
+ ALMAGHRIBIA
+ ESC - Al Masriyah
+ Arirang TV
+ ArteIn
+ BBC Arabic
+ BeJoy.kids
+ Bloomberg
+ Boomerang +1
+ Boomerang
+ BYOBLU
+ CACCIA e Pesca
+ Camera dei Deputati
+ Canale 5 HD
+ Canale 5
+ Canale 5 HD
+ Canale Italia 83
+ Canale Italia 84
+ Canale Italia
+ Cartoon Network
+ Cartoon Network +1
+ CGTN
+ cielo
+ cielo
+ cielo
+ Cine34 HD
+ Cine34
+ Class CNBC
+ Class Tv Moda
+ CNBC HD
+ CNN Intl.
+ Comedy +1
+ Comedy Central
+ Daystar Tv HD
+ DeAJunior
+ DeAKids
+ Deejay TV
+ Discovery HD
+ Discovery HD
+ Discovery HD
+ Discovery +1
+ DMAX HD
+ DMAX HD
+ Dubai Sports 1 HD
+ Dubai TV HD
+ Deutsche Welle HD
+ EQUtv
+ Euronews English HD
+ Euronews
+ Eurosport 1
+ Eurosport 2
+ EWTN
+ Focus HD
+ Focus
+ Food Network HD
+ Food Network HD
+ Fox Business
+ Fox News
+ France 24 English HD
+ France 24 Francais HD
+ -frisbee-
+ -frisbee-
+ Gambero Rosso HD
+ Gambero Rosso HD
+ GIALLO HD
+ GIALLO HD
+ HGTV HD
+ HGTV HD
+ History
+ History
+ History +1
+ Horse TV HD
+ Inter TV
+ Iris HD
+ Iris
+ Italia 1 HD
+ Italia 1 HD
+ Italia 1
+ Mediaset Italia2 HD
+ Mediaset Italia2
+ Italian Fishing Tv
+ Jordan TV
+ JUWELO
+ K2
+ K2
+ Kiss Kiss Tv HD
+ KTV 1 HD
+ Kurdistan 24 HD
+ La 5 HD
+ La 5
+ LA7 HD
+ LA7 HD
+ LA7CINEMA
+ LA7CINEMA
+ LaC Sat
+ MARCOPOLO
+ Mediaset Extra HD
+ Mediaset Extra
+ Milan TV
+ MTV HD
+ MTV Music
+ MTV Music
+ NHK World TV HD
+ Nickelodeon
+ Nick Jr. +1
+ Nick Jr
+ NOVE HD
+ NOVE HD
+ Orler TV
+ TelePadrePio HD
+ Parole di Vita
+ Caccia e PESCA
+ QVC
+ RADIOFRECCIA HD
+ RADIOFRECCIA HD
+ Radio Italia TV HD
Radio Italia TV
+ Radio Monte Carlo
+ RADIONORBA TV
+ RADIO ZETA HD
+ RADIO ZETA
+ Rai 1 HD
+ Rai 2 HD
+ Rai 3 HD
+ RAI 4
+ RAI 5
+ RAI Gulp
+ RAI MOVIE
+ Rai News 24
+ RAI PREMIUM
+ Rai Scuola
+ RAI Sport
+ Rai Storia
+ RAI YOYO
+ RDS Social TV
+ Real Time HD
+ Real Time HD
+ Rete 4 HD
+ Rete 4 HD
+ Rete 4
+ RTL 102.5 Caliente
+ RTL 102.5 HD
+ RTL 102.5 HD
+ San Marino RTV
+ San Marino RTV HD
+ SonLife Broadcasting Network
+ Senato Italiano
+ Sender Neu Jerusalem
+ Sky Adventure
+ Sky Adventure
+ Sky Arte
+ Sky Arte
+ Sky Arte +1
+ Sky Atlantic
+ Sky Atlantic +1
+ Sky Cinema Action
+ Sky Cinema Comedy
+ Sky Cinema Drama
+ Sky Cinema Due +24
+ Sky Cinema Family
+ Sky Cinema Romance
+ Sky Cinema Suspense
+ Sky Cinema Uno
+ Sky Cinema Uno
+ Sky Cinema Uno +24
+ Sky Classica
+ Classica HD
+ Sky Crime
+ Sky Crime
+ Sky Documentaries +1
+ Sky Documentaries +1
+ Sky Documentaries
+ Sky Documentaries
+ Sky Investigation
+ Sky Meteo24
+ Sky Nature
+ Sky Nature
+ Sky News
+ Sky News Arabia HD
+ Sky Serie
+ Sky Sport 4k
+ Sky Sport24
+ Sky Sport24
+ Sky Sport24
+ Sky Sport
+ Sky Sport
+ Sky Sport
+ Sky Sport
+ Sky Sport
+ Sky Sport
+ Sky Sport
+ Sky Sport
+ Sky Sport
+ Sky Sport Arena
+ Sky Sport Basket
+ Sky Sport Calcio
+ Sky Sport Calcio
+ Sky Sport F1
+ Sky Sport Golf
+ Sky Sport Legend
+ Sky Sport Max
+ Sky Sport Mix
+ Sky Sport MotoGP
+ Sky Sport Tennis
+ Sky Sport Uno
+ Sky Sport Uno
+ Sky TG24
+ Sky TG24
+ Sky TG24 HD
+ Sky TG24
+ TG24PrimoPiano
+ Sky Uno
+ Sky Uno +1
+ Sky Uno +
+ Super!
+ Super!
+ SuperTennis HD
+ SuperTennis HD
+ Telecupole
+ TELESARDEGNA
+ TgCom24 HD
+ TgCom24
+ The Word Network
+ TOPcrime HD
+ TOPcrime
+ TRM h24
+ TV8 HD
+ TV8 HD
+ TV8 HD
+ TV8 HD
+ TV2000 HD
+ TVA Vicenza
+ TVP Polonia
+ TVR Intl.
+ 27Twentyseven HD
+ 27 Twentyseven
+ Uninettuno
+ VALUE 24 TV
+ Vatican Media Europa
+ VH1
+ Videolina
+ Virgin Radio
+ Well Tv
+ DAZN 2
+ ZONA DAZN 2
+ DAZN 1
+ DAZN 1
diff --git a/sites/sky.com/sky.com.config.js b/sites/sky.com/sky.com.config.js
index 817495d41..dd02a9c03 100644
--- a/sites/sky.com/sky.com.config.js
+++ b/sites/sky.com/sky.com.config.js
@@ -10,17 +10,16 @@ dayjs.extend(utc)
doFetch.setCheckResult(false).setDebugger(debug)
-const API_ENDPOINT = 'https://awk.epgsky.com/hawk/linear'
+const ATLANTIS_API_ENDPOINT = 'https://atlantis.epgsky.com/as'
const CHANNELS_FILE = path.join(__dirname, 'sky.com.channels.xml')
+const HAWK_API_ENDPOINT = 'https://awk.epgsky.com/hawk/linear'
const LANG_BY_TERRITORY = {
DE: 'de',
GB: 'en',
IT: 'it'
}
const MAX_SIDS_PER_REQUEST = 20
-const GRAB_START_DATE = dayjs
- .utc(process.env.CURR_DATE || new Date().toISOString())
- .startOf('d')
+const GRAB_START_DATE = dayjs.utc(process.env.CURR_DATE || new Date().toISOString()).startOf('d')
const eventIds = new Set()
const scheduleRequests = new Map()
let scheduleBatches
@@ -30,7 +29,9 @@ module.exports = {
days: 2,
request: {
headers({ channel }) {
- return getHeaders(parseSiteId(channel.site_id).territory)
+ const { territory } = parseSiteId(channel.site_id)
+
+ return isUhdChannel(channel) ? getAtlantisHeaders(territory) : getHeaders(territory)
},
cache: {
ttl: 24 * 60 * 60 * 1000, // 1 day
@@ -39,9 +40,13 @@ module.exports = {
},
url({ date, channel }) {
const { sid } = parseSiteId(channel.site_id)
+ if (isUhdChannel(channel)) {
+ return `${ATLANTIS_API_ENDPOINT}/schedule/${date.format('YYYYMMDD')}/${sid}`
+ }
+
const sids = getScheduleBatch(channel.site_id) || [sid]
- return `${API_ENDPOINT}/schedule/${date.format('YYYYMMDD')}/${sids.join(',')}`
+ return `${HAWK_API_ENDPOINT}/schedule/${date.format('YYYYMMDD')}/${sids.join(',')}`
},
async parser({ content, channel, date, config }) {
const programs = []
@@ -95,7 +100,7 @@ module.exports = {
type: 'regions',
lang,
territory,
- url: `${API_ENDPOINT}/regions`,
+ url: `${HAWK_API_ENDPOINT}/regions`,
params: {
headers: getHeaders(territory)
}
@@ -135,16 +140,32 @@ module.exports = {
if (regions.has(key)) continue
regions.add(key)
- state.serviceRequests++
- queue.push({
- type: 'services',
- lang: request.lang,
- territory: request.territory,
- url: `${API_ENDPOINT}/services/${regionId}`,
- params: {
+ const serviceRequests = [
+ {
+ source: 'hawk',
+ url: `${HAWK_API_ENDPOINT}/services/${regionId}`,
headers: getHeaders(request.territory)
+ },
+ {
+ source: 'atlantis',
+ url: `${ATLANTIS_API_ENDPOINT}/services/${regionId}`,
+ headers: getAtlantisHeaders(request.territory)
}
- })
+ ]
+
+ state.serviceRequests += serviceRequests.length
+ for (const serviceRequest of serviceRequests) {
+ queue.push({
+ type: 'services',
+ lang: request.lang,
+ source: serviceRequest.source,
+ territory: request.territory,
+ url: serviceRequest.url,
+ params: {
+ headers: serviceRequest.headers
+ }
+ })
+ }
}
return
}
@@ -162,15 +183,17 @@ module.exports = {
}
if (!service.t) continue
+ if (request.source === 'atlantis' && service.schedule !== true) continue
const siteId = `${request.territory}#${service.sid}`
- if (!channels.has(siteId)) {
+ if (!channels.has(siteId) || request.source === 'hawk') {
+ const isNew = !channels.has(siteId)
channels.set(siteId, {
lang: request.lang,
site_id: siteId,
name: service.t
})
- state.channels++
+ if (isNew) state.channels++
}
}
})
@@ -187,9 +210,7 @@ module.exports = {
if (incomplete.length || failures.length) {
const reasons = [...new Set([...failures, ...incomplete])].join(', ')
- throw new Error(
- `Unable to load complete Sky channel list: ${reasons || 'no channels found'}`
- )
+ throw new Error(`Unable to load complete Sky channel list: ${reasons || 'no channels found'}`)
}
return [...channels.values()]
@@ -202,15 +223,22 @@ function getHeaders(territory) {
}
}
+function getAtlantisHeaders(territory) {
+ return {
+ 'X-SkyOTT-Proposition': 'SKYQ',
+ 'X-SkyOTT-Provider': 'SKY',
+ ...getHeaders(territory)
+ }
+}
+
function getEventId(channel, event) {
return `${channel.site_id}:${event.eid}`
}
function getGrabRange(config) {
const configuredDays = Number(config?.days)
- const days = Number.isInteger(configuredDays) && configuredDays > 0
- ? configuredDays
- : module.exports.days
+ const days =
+ Number.isInteger(configuredDays) && configuredDays > 0 ? configuredDays : module.exports.days
const stop = GRAB_START_DATE.add(days, 'd')
return {
@@ -228,7 +256,7 @@ function getSchedule(channel, date) {
if (!scheduleRequests.has(key)) {
const request = axios
.get(url, {
- headers: getHeaders(territory)
+ headers: module.exports.request.headers({ channel })
})
.then(response => response.data)
.catch(error => {
@@ -254,8 +282,11 @@ function loadScheduleBatches() {
Object.keys(LANG_BY_TERRITORY).map(territory => [territory, new Set()])
)
- for (const match of channelsXml.matchAll(/\bsite_id="([^"]+)"/g)) {
- const { sid, territory } = parseSiteId(match[1])
+ for (const match of channelsXml.matchAll(/]*)>([^<]*)<\/channel>/g)) {
+ const siteId = match[1].match(/\bsite_id="([^"]+)"/)?.[1]
+ if (!siteId || isUhdChannel({ name: match[2] })) continue
+
+ const { sid, territory } = parseSiteId(siteId)
sidsByTerritory.get(territory).add(sid)
}
@@ -289,6 +320,10 @@ function collectEvents(content, channel, events) {
}
}
+function isUhdChannel(channel) {
+ return /uhd|ultra\s*hd|4k/i.test(`${channel?.name || ''} ${channel?.xmltv_id || ''}`)
+}
+
function parseSiteId(siteId) {
const [territory, sid, ...extra] = String(siteId || '').split('#')
if (!LANG_BY_TERRITORY[territory] || !sid || extra.length) {
diff --git a/sites/sky.com/sky.com.test.js b/sites/sky.com/sky.com.test.js
index 706926267..bc51309bd 100644
--- a/sites/sky.com/sky.com.test.js
+++ b/sites/sky.com/sky.com.test.js
@@ -16,14 +16,21 @@ jest.mock('axios')
const date = dayjs.utc('2026-06-08').startOf('d')
const channel = {
lang: 'en',
+ name: 'Sky History HD',
site_id: 'GB#4086',
xmltv_id: 'SkyHistory.uk@HD'
}
+const uhdChannel = {
+ lang: 'en',
+ name: 'TNTUltimateUHD',
+ site_id: 'GB#1336',
+ xmltv_id: 'TNTSportsUltimate.uk@SD'
+}
function mockScheduleRequest(url) {
const urls = {
- '20260608': 'content1.json',
- '20260609': 'content2.json'
+ 20260608: 'content1.json',
+ 20260609: 'content2.json'
}
let data = ''
const match = url.match(/\/schedule\/(\d{8})\/([^/]+)$/)
@@ -60,6 +67,27 @@ it('falls back to a single SID for a channel outside the repository XML', () =>
)
})
+it.each([
+ ['TNTUltimateUHD', 'GB#1336', '1336'],
+ ['Ultra HD', 'GB#7233', '7233'],
+ ['Sky Sport 4k', 'IT#628', '628']
+])('uses Atlantis for %s', (name, siteId, sid) => {
+ const currentChannel = {
+ ...channel,
+ name,
+ site_id: siteId
+ }
+
+ expect(url({ channel: currentChannel, date })).toBe(
+ `https://atlantis.epgsky.com/as/schedule/20260608/${sid}`
+ )
+ expect(request.headers({ channel: currentChannel })).toEqual({
+ 'X-SkyOTT-Proposition': 'SKYQ',
+ 'X-SkyOTT-Provider': 'SKY',
+ 'X-SkyOTT-Territory': siteId.slice(0, 2)
+ })
+})
+
it('rejects an unsupported territory in the site ID', () => {
expect(() => url({ channel: { ...channel, site_id: 'XX#4086' }, date })).toThrow(
'Expected "#"'
@@ -76,17 +104,21 @@ it('can generate territory headers from the site ID', () => {
expect(request.headers({ channel: { ...channel, lang: 'it', site_id: 'IT#4086' } })).toEqual({
'X-SkyOTT-Territory': 'IT'
})
+ expect(request.headers({ channel: uhdChannel })).toEqual({
+ 'X-SkyOTT-Proposition': 'SKYQ',
+ 'X-SkyOTT-Provider': 'SKY',
+ 'X-SkyOTT-Territory': 'GB'
+ })
expect(request.cache.vary).toEqual(['X-SkyOTT-Territory'])
})
it('can parse response', async () => {
const content = fs.readFileSync(path.join(__dirname, '__data__', 'content1.json'))
- const result = (await parser({ config: { days: 1 }, content, channel, date }))
- .map(p => {
- p.start = p.start.toJSON()
- p.stop = p.stop.toJSON()
- return p
- })
+ const result = (await parser({ config: { days: 1 }, content, channel, date })).map(p => {
+ p.start = p.start.toJSON()
+ p.stop = p.stop.toJSON()
+ return p
+ })
expect(result.length).toBe(30)
expect(result[0]).toMatchObject({
@@ -94,7 +126,7 @@ it('can parse response', async () => {
stop: '2026-06-08T00:45:00.000Z',
title: 'The UnBelievable With Dan Aykroyd',
description:
- 'Bizarre Innovations: Discover bizarre innovations like a fully functioning car that hovers in midair. Or clothing made out of everyone\'s favorite source of calcium. (S2, ep 6)',
+ "Bizarre Innovations: Discover bizarre innovations like a fully functioning car that hovers in midair. Or clothing made out of everyone's favorite source of calcium. (S2, ep 6)",
season: 2,
episode: 6,
icon: 'https://images.metadata.sky.com/pd-image/007ade72-3239-47d7-a452-3070eb8e591d/16-9/640',
@@ -105,20 +137,17 @@ it('can parse response', async () => {
stop: '2026-06-09T00:00:00.000Z',
title: 'Digging For Britain',
description:
- 'The Tudors: Dr Alice Roberts pays tribute to the Bard, visiting Shakespeare\'s first theatre in London\'s Shoreditch and sifting through the poet\'s rubbish! (S1, ep 4)',
+ "The Tudors: Dr Alice Roberts pays tribute to the Bard, visiting Shakespeare's first theatre in London's Shoreditch and sifting through the poet's rubbish! (S1, ep 4)",
season: 1,
episode: 4,
icon: 'https://images.metadata.sky.com/pd-image/68152ae7-97d6-44c8-8a54-e78710b94a76/16-9/640',
image: 'https://images.metadata.sky.com/pd-image/68152ae7-97d6-44c8-8a54-e78710b94a76/16-9/640'
})
- expect(axios.get).toHaveBeenCalledWith(
- url({ channel, date: date.add(1, 'd') }),
- {
- headers: {
- 'X-SkyOTT-Territory': 'GB'
- }
+ expect(axios.get).toHaveBeenCalledWith(url({ channel, date: date.add(1, 'd') }), {
+ headers: {
+ 'X-SkyOTT-Territory': 'GB'
}
- )
+ })
})
it('only loads a follow-up batch for the final requested date', async () => {
@@ -137,14 +166,11 @@ it('only loads a follow-up batch for the final requested date', async () => {
date: date.add(1, 'd')
})
expect(axios.get).toHaveBeenCalledTimes(1)
- expect(axios.get).toHaveBeenCalledWith(
- url({ channel, date: date.add(2, 'd') }),
- {
- headers: {
- 'X-SkyOTT-Territory': 'GB'
- }
+ expect(axios.get).toHaveBeenCalledWith(url({ channel, date: date.add(2, 'd') }), {
+ headers: {
+ 'X-SkyOTT-Territory': 'GB'
}
- )
+ })
})
it('loads a shared follow-up batch only once', async () => {
@@ -161,14 +187,31 @@ it('loads a shared follow-up batch only once', async () => {
])
expect(axios.get).toHaveBeenCalledTimes(1)
- expect(axios.get).toHaveBeenCalledWith(
- url({ channel, date: finalDate.add(1, 'd') }),
- {
- headers: {
- 'X-SkyOTT-Territory': 'GB'
- }
+ expect(axios.get).toHaveBeenCalledWith(url({ channel, date: finalDate.add(1, 'd') }), {
+ headers: {
+ 'X-SkyOTT-Territory': 'GB'
}
- )
+ })
+})
+
+it('loads an UHD follow-up schedule from Atlantis', async () => {
+ const finalDate = date.add(1, 'd')
+
+ await parser({
+ config: { days: 2 },
+ content: '',
+ channel: uhdChannel,
+ date: finalDate
+ })
+
+ expect(axios.get).toHaveBeenCalledTimes(1)
+ expect(axios.get).toHaveBeenCalledWith('https://atlantis.epgsky.com/as/schedule/20260610/1336', {
+ headers: {
+ 'X-SkyOTT-Proposition': 'SKYQ',
+ 'X-SkyOTT-Provider': 'SKY',
+ 'X-SkyOTT-Territory': 'GB'
+ }
+ })
})
it('can handle empty guide', async () => {
@@ -227,6 +270,20 @@ it('can load and deduplicate all territories by language and site ID', async ()
})
}
+ if (url.includes('atlantis.epgsky.com')) {
+ return Promise.resolve({
+ data: {
+ services: [
+ { schedule: true, sid: '123', t: `${titles[territory]} Atlantis` },
+ { schedule: true, sid: '456', t: `${territory} UHD` },
+ { schedule: false, sid: '789', t: `${territory} Internal` }
+ ]
+ },
+ headers: {},
+ request: {}
+ })
+ }
+
return Promise.resolve({
data: {
services: [
@@ -240,29 +297,71 @@ it('can load and deduplicate all territories by language and site ID', async ()
})
})
- await expect(channels()).resolves.toEqual([
- {
- lang: 'de',
- site_id: 'DE#123',
- name: 'Sky One Deutschland HD'
- },
- {
- lang: 'en',
- site_id: 'GB#123',
- name: 'Sky One HD'
- },
- {
- lang: 'it',
- site_id: 'IT#123',
- name: 'Sky Uno HD'
- }
- ])
- expect(axios.get).toHaveBeenCalledTimes(6)
+ const result = await channels()
+
+ expect(result).toHaveLength(6)
+ expect(result).toEqual(
+ expect.arrayContaining([
+ {
+ lang: 'de',
+ site_id: 'DE#123',
+ name: 'Sky One Deutschland HD'
+ },
+ {
+ lang: 'de',
+ site_id: 'DE#456',
+ name: 'DE UHD'
+ },
+ {
+ lang: 'en',
+ site_id: 'GB#123',
+ name: 'Sky One HD'
+ },
+ {
+ lang: 'en',
+ site_id: 'GB#456',
+ name: 'GB UHD'
+ },
+ {
+ lang: 'it',
+ site_id: 'IT#123',
+ name: 'Sky Uno HD'
+ },
+ {
+ lang: 'it',
+ site_id: 'IT#456',
+ name: 'IT UHD'
+ }
+ ])
+ )
+ expect(result).not.toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ site_id: expect.stringMatching(/#789$/)
+ })
+ ])
+ )
+ expect(axios.get).toHaveBeenCalledTimes(9)
expect(axios.get.mock.calls.map(([, options]) => options.headers)).toEqual(
expect.arrayContaining([
{ 'X-SkyOTT-Territory': 'GB' },
{ 'X-SkyOTT-Territory': 'DE' },
- { 'X-SkyOTT-Territory': 'IT' }
+ { 'X-SkyOTT-Territory': 'IT' },
+ {
+ 'X-SkyOTT-Proposition': 'SKYQ',
+ 'X-SkyOTT-Provider': 'SKY',
+ 'X-SkyOTT-Territory': 'GB'
+ },
+ {
+ 'X-SkyOTT-Proposition': 'SKYQ',
+ 'X-SkyOTT-Provider': 'SKY',
+ 'X-SkyOTT-Territory': 'DE'
+ },
+ {
+ 'X-SkyOTT-Proposition': 'SKYQ',
+ 'X-SkyOTT-Provider': 'SKY',
+ 'X-SkyOTT-Territory': 'IT'
+ }
])
)
})