From 2843bccced858bda5992ec929a6abed13974cd94 Mon Sep 17 00:00:00 2001 From: David ROBIN Date: Fri, 20 Mar 2026 22:57:38 +0100 Subject: [PATCH] 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