feat: add sky uhd channels via atlantis

This commit is contained in:
knylbyte
2026-07-29 22:18:15 +02:00
parent ba706bb6c1
commit e1f61486d4
4 changed files with 489 additions and 324 deletions
+149 -50
View File
@@ -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 "<territory>#<sid>"'
@@ -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'
}
])
)
})