From 2843bccced858bda5992ec929a6abed13974cd94 Mon Sep 17 00:00:00 2001 From: David ROBIN Date: Fri, 20 Mar 2026 22:57:38 +0100 Subject: [PATCH 1/2] Add rts.ch --- sites/rts.ch/readme.md | 21 ++++++++ sites/rts.ch/rts.ch.channels.xml | 6 +++ sites/rts.ch/rts.ch.config.js | 44 +++++++++++++++++ sites/rts.ch/rts.ch.test.js | 82 ++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 sites/rts.ch/readme.md create mode 100644 sites/rts.ch/rts.ch.channels.xml create mode 100644 sites/rts.ch/rts.ch.config.js create mode 100644 sites/rts.ch/rts.ch.test.js diff --git a/sites/rts.ch/readme.md b/sites/rts.ch/readme.md new file mode 100644 index 00000000..3c7a740d --- /dev/null +++ b/sites/rts.ch/readme.md @@ -0,0 +1,21 @@ +# rts.ch + +https://rts.ch + +### Download the guide + +```sh +npm run grab --- --site=rts.ch +``` + +### Update channel list + +```sh +npm run channels:parse --- --config=./sites/rts.ch/rts.ch.config.js --output=./sites/rts.ch/rts.ch.channels.xml +``` + +### Test + +```sh +npm test --- rts.ch +``` diff --git a/sites/rts.ch/rts.ch.channels.xml b/sites/rts.ch/rts.ch.channels.xml new file mode 100644 index 00000000..08f1452a --- /dev/null +++ b/sites/rts.ch/rts.ch.channels.xml @@ -0,0 +1,6 @@ + + + RTS 1 + RTS 2 + RTS Info + diff --git a/sites/rts.ch/rts.ch.config.js b/sites/rts.ch/rts.ch.config.js new file mode 100644 index 00000000..175a1ef2 --- /dev/null +++ b/sites/rts.ch/rts.ch.config.js @@ -0,0 +1,44 @@ +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'rts.ch', + days: 2, + + url({ date }) { + return `https://www.rts.ch/play/v3/api/rts/production/tv-program-guide?date=${date.format('YYYY-MM-DD')}` + }, + + parser({ content, channel }) { + try { + const { data } = JSON.parse(content) + + const channelData = data.find(entry => entry.channel.id === channel.site_id) + if (!channelData || !Array.isArray(channelData.programList)) return [] + + return channelData.programList.map(program => ({ + title: program.title || '', + subTitle: program.subtitle || undefined, + description: program.description || program.show?.description || undefined, + start: new Date(program.startTime).toISOString(), + stop: new Date(program.endTime).toISOString(), + icon: program.imageUrl ? { src: program.imageUrl } : undefined, + category: program.genre || undefined, + })) + } catch { + return [] + } + }, + + async channels() { + const today = dayjs().format('YYYY-MM-DD') + const { data: body } = await axios.get( + `https://www.rts.ch/play/v3/api/rts/production/tv-program-guide?date=${today}` + ) + return body.data.map(entry => ({ + site_id: entry.channel.id, + name: entry.channel.title, + lang: 'fr', + })) + } +} \ No newline at end of file diff --git a/sites/rts.ch/rts.ch.test.js b/sites/rts.ch/rts.ch.test.js new file mode 100644 index 00000000..0a4e8f0e --- /dev/null +++ b/sites/rts.ch/rts.ch.test.js @@ -0,0 +1,82 @@ +const { parser, url } = require('./rts.ch.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2026-03-20', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '143932a79bb5a123a646b68b1d1188d7ae493e5b', name: 'RTS 1', lang: 'fr' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.rts.ch/play/v3/api/rts/production/tv-program-guide?date=2026-03-20' + ) +}) + +it('can parse response', () => { + const content = JSON.stringify({ + data: [ + { + channel: { id: '143932a79bb5a123a646b68b1d1188d7ae493e5b', title: 'RTS 1' }, + programList: [ + { + title: '19h30', + startTime: '2026-03-20T19:30:00+01:00', + endTime: '2026-03-20T20:01:00+01:00', + imageUrl: 'https://kingfisher.rts.ch/res/img/cdns3/sherlock/urn:orphea-image:1035156', + genre: 'Actualité', + description: 'Le journal du soir.', + }, + { + title: 'Météo', + startTime: '2026-03-20T20:00:00+01:00', + endTime: '2026-03-20T20:03:00+01:00', + imageUrl: 'https://kingfisher.rts.ch/res/img/cdns3/sherlock/urn:orphea-image:400670', + genre: 'Actualité', + }, + ], + }, + ], + }) + + const results = parser({ content, channel }) + + expect(results[0]).toMatchObject({ + title: '19h30', + start: '2026-03-20T18:30:00.000Z', + stop: '2026-03-20T19:01:00.000Z', + description: 'Le journal du soir.', + category: 'Actualité', + icon: { src: 'https://kingfisher.rts.ch/res/img/cdns3/sherlock/urn:orphea-image:1035156' }, + }) + expect(results[1]).toMatchObject({ + title: 'Météo', + start: '2026-03-20T19:00:00.000Z', + stop: '2026-03-20T19:03:00.000Z', + }) +}) + +it('can handle channel not found in response', () => { + const content = JSON.stringify({ + data: [ + { + channel: { id: 'some-other-channel-id', title: 'Other' }, + programList: [{ title: 'Show', startTime: '2026-03-20T10:00:00+01:00', endTime: '2026-03-20T11:00:00+01:00' }], + }, + ], + }) + + const results = parser({ content, channel }) + expect(results).toMatchObject([]) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '', channel }) + expect(results).toMatchObject([]) +}) + +it('can handle malformed JSON', () => { + const results = parser({ content: 'not-json', channel }) + expect(results).toMatchObject([]) +}) \ No newline at end of file From 5bbf38255d25c2596f95f5446272363f60c45b01 Mon Sep 17 00:00:00 2001 From: David ROBIN Date: Sat, 21 Mar 2026 09:10:12 +0100 Subject: [PATCH 2/2] EPG endpoint updated --- sites/rts.ch/rts.ch.config.js | 18 +++++------ sites/rts.ch/rts.ch.test.js | 61 +++++++++++++++++------------------ 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/sites/rts.ch/rts.ch.config.js b/sites/rts.ch/rts.ch.config.js index 175a1ef2..134de601 100644 --- a/sites/rts.ch/rts.ch.config.js +++ b/sites/rts.ch/rts.ch.config.js @@ -5,18 +5,16 @@ module.exports = { site: 'rts.ch', days: 2, - url({ date }) { - return `https://www.rts.ch/play/v3/api/rts/production/tv-program-guide?date=${date.format('YYYY-MM-DD')}` + url({ channel, date }) { + return `https://il.srgssr.ch/integrationlayer/2.0/rts/programGuide/tv/byDate/${date.format('YYYY-MM-DD')}?reduced=false&channelId=${channel.site_id}` }, - parser({ content, channel }) { + parser({ content }) { try { - const { data } = JSON.parse(content) + const { programGuide } = JSON.parse(content) + if (!programGuide?.[0]?.programList) return [] - const channelData = data.find(entry => entry.channel.id === channel.site_id) - if (!channelData || !Array.isArray(channelData.programList)) return [] - - return channelData.programList.map(program => ({ + return programGuide[0].programList.map(program => ({ title: program.title || '', subTitle: program.subtitle || undefined, description: program.description || program.show?.description || undefined, @@ -32,10 +30,10 @@ module.exports = { async channels() { const today = dayjs().format('YYYY-MM-DD') - const { data: body } = await axios.get( + const { data } = await axios.get( `https://www.rts.ch/play/v3/api/rts/production/tv-program-guide?date=${today}` ) - return body.data.map(entry => ({ + return data.data.map(entry => ({ site_id: entry.channel.id, name: entry.channel.title, lang: 'fr', diff --git a/sites/rts.ch/rts.ch.test.js b/sites/rts.ch/rts.ch.test.js index 0a4e8f0e..d761b7eb 100644 --- a/sites/rts.ch/rts.ch.test.js +++ b/sites/rts.ch/rts.ch.test.js @@ -5,78 +5,75 @@ const customParseFormat = require('dayjs/plugin/customParseFormat') dayjs.extend(customParseFormat) dayjs.extend(utc) -const date = dayjs.utc('2026-03-20', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '143932a79bb5a123a646b68b1d1188d7ae493e5b', name: 'RTS 1', lang: 'fr' } +const date = dayjs.utc('2026-03-21', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '5d332a26e06d08eec8ad385d566187df72955623', name: 'RTS Info', lang: 'fr' } it('can generate valid url', () => { expect(url({ channel, date })).toBe( - 'https://www.rts.ch/play/v3/api/rts/production/tv-program-guide?date=2026-03-20' + 'https://il.srgssr.ch/integrationlayer/2.0/rts/programGuide/tv/byDate/2026-03-21?reduced=false&channelId=5d332a26e06d08eec8ad385d566187df72955623' ) }) it('can parse response', () => { const content = JSON.stringify({ - data: [ + programGuide: [ { - channel: { id: '143932a79bb5a123a646b68b1d1188d7ae493e5b', title: 'RTS 1' }, + channel: { id: '5d332a26e06d08eec8ad385d566187df72955623', title: 'RTS Info' }, programList: [ { - title: '19h30', - startTime: '2026-03-20T19:30:00+01:00', - endTime: '2026-03-20T20:01:00+01:00', - imageUrl: 'https://kingfisher.rts.ch/res/img/cdns3/sherlock/urn:orphea-image:1035156', + title: "L'essentiel de l'actualité", + startTime: '2026-03-21T07:00:00+01:00', + endTime: '2026-03-21T19:00:00+01:00', + imageUrl: 'https://kingfisher.rts.ch/res/img/cdns3/sherlock/urn:orphea-image:1043433', genre: 'Actualité', - description: 'Le journal du soir.', }, { - title: 'Météo', - startTime: '2026-03-20T20:00:00+01:00', - endTime: '2026-03-20T20:03:00+01:00', - imageUrl: 'https://kingfisher.rts.ch/res/img/cdns3/sherlock/urn:orphea-image:400670', + title: 'Forum', + startTime: '2026-03-21T19:00:00+01:00', + endTime: '2026-03-21T20:00:00+01:00', + imageUrl: 'https://kingfisher.rts.ch/res/img/cdns3/sherlock/urn:orphea-image:1831387', genre: 'Actualité', + description: 'Le magazine du soir.', }, ], }, ], }) - const results = parser({ content, channel }) + const results = parser({ content }) expect(results[0]).toMatchObject({ - title: '19h30', - start: '2026-03-20T18:30:00.000Z', - stop: '2026-03-20T19:01:00.000Z', - description: 'Le journal du soir.', + title: "L'essentiel de l'actualité", + start: '2026-03-21T06:00:00.000Z', + stop: '2026-03-21T18:00:00.000Z', category: 'Actualité', - icon: { src: 'https://kingfisher.rts.ch/res/img/cdns3/sherlock/urn:orphea-image:1035156' }, + icon: { src: 'https://kingfisher.rts.ch/res/img/cdns3/sherlock/urn:orphea-image:1043433' }, }) expect(results[1]).toMatchObject({ - title: 'Météo', - start: '2026-03-20T19:00:00.000Z', - stop: '2026-03-20T19:03:00.000Z', + title: 'Forum', + start: '2026-03-21T18:00:00.000Z', + stop: '2026-03-21T19:00:00.000Z', + description: 'Le magazine du soir.', }) }) -it('can handle channel not found in response', () => { +it('can handle empty programList', () => { const content = JSON.stringify({ - data: [ - { - channel: { id: 'some-other-channel-id', title: 'Other' }, - programList: [{ title: 'Show', startTime: '2026-03-20T10:00:00+01:00', endTime: '2026-03-20T11:00:00+01:00' }], - }, + programGuide: [ + { channel: { id: '5d332a26e06d08eec8ad385d566187df72955623', title: 'RTS Info' }, programList: [] }, ], }) - const results = parser({ content, channel }) + const results = parser({ content }) expect(results).toMatchObject([]) }) it('can handle empty guide', () => { - const results = parser({ content: '', channel }) + const results = parser({ content: '' }) expect(results).toMatchObject([]) }) it('can handle malformed JSON', () => { - const results = parser({ content: 'not-json', channel }) + const results = parser({ content: 'not-json' }) expect(results).toMatchObject([]) }) \ No newline at end of file