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.com137🟢 sjonvarp.is13🟢 sky.co.nz91🟢 - sky.com1116🟢 + sky.com1515🟢 skylife.co.kr257🟢 skyperfectv.co.jp196🟢 snrt.ma11🟢 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' + } ]) ) })