diff --git a/SITES.md b/SITES.md index c29cadf4..e90c6968 100644 --- a/SITES.md +++ b/SITES.md @@ -91,6 +91,7 @@ | [mytvsuper.com](sites/mytvsuper.com) | 🟢 | | | [nhk.or.jp](sites/nhk.or.jp) | 🟢 | | | [nhkworldpremium.com](sites/nhkworldpremium.com) | 🟢 | | +| [nhl.com](sites/nhl.com) | 🟢 | | | [nostv.pt](sites/nostv.pt) | 🟢 | | | [novacyprus.com](sites/novacyprus.com) | 🟢 | | | [novasports.gr](sites/novasports.gr) | 🟢 | | diff --git a/sites/nhl.com/__data__/content.json b/sites/nhl.com/__data__/content.json new file mode 100644 index 00000000..929f68c0 --- /dev/null +++ b/sites/nhl.com/__data__/content.json @@ -0,0 +1 @@ +{"date":"2024-11-22","startDate":"2024-11-08","endDate":"2024-12-05","broadcasts":[{"startTime":"2024-11-22T00:00:00","endTime":"2024-11-22T01:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124SOR","broadcastType":"HD","broadcastStatus":"LIVE","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T01:00:00","endTime":"2024-11-22T02:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T02:00:00","endTime":"2024-11-22T03:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T03:00:00","endTime":"2024-11-22T04:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T04:00:00","endTime":"2024-11-22T05:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T05:00:00","endTime":"2024-11-22T06:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T06:00:00","endTime":"2024-11-22T07:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T07:00:00","endTime":"2024-11-22T08:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T08:00:00","endTime":"2024-11-22T09:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T09:00:00","endTime":"2024-11-22T10:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T10:00:00","endTime":"2024-11-22T11:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T11:00:00","endTime":"2024-11-22T12:00:00","durationSeconds":3600,"title":"On The Fly","description":"On The Fly","houseNumber":"HOTF25R112124CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"onthefly.png"},{"startTime":"2024-11-22T12:00:00","endTime":"2024-11-22T14:00:00","durationSeconds":7200,"title":"NHL Game","description":"New York Rangers at Calgary Flames on 11/21/2024 From Scotiabank Saddledome","houseNumber":"H120NYRCGY11212024","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"nhl.png"},{"startTime":"2024-11-22T14:00:00","endTime":"2024-11-22T16:00:00","durationSeconds":7200,"title":"NHL Game","description":"Carolina Hurricanes at New Jersey Devils on 11/21/2024 From Prudential Center","houseNumber":"H120CARNJD11212024","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"nhl.png"},{"startTime":"2024-11-22T16:00:00","endTime":"2024-11-22T17:00:00","durationSeconds":3600,"title":"NHL Tonight: First Shift","description":"NHL Tonight: First Shift","houseNumber":"HNHLTFS25112224LV","broadcastType":"HD","broadcastStatus":"LIVE","broadcastImageUrl":""},{"startTime":"2024-11-22T17:00:00","endTime":"2024-11-22T19:00:00","durationSeconds":7200,"title":"NHL Now","description":"NHL Now","houseNumber":"HNOW25R112224LV","broadcastType":"HD","broadcastStatus":"LIVE","broadcastImageUrl":"nhlnow.png"},{"startTime":"2024-11-22T19:00:00","endTime":"2024-11-22T22:00:00","durationSeconds":10800,"title":"Regular Season Hockey on NHL Network","description":"Winnipeg Jets at Pittsburgh Penguins on 11/22/2024 From PPG Paints Arena","houseNumber":"H180WPGPIT11222024","broadcastType":"HD","broadcastStatus":"LIVE","broadcastImageUrl":"nhl.png"},{"startTime":"2024-11-22T22:00:00","endTime":"2024-11-22T22:30:00","durationSeconds":1800,"title":"NHL Tonight","description":"NHL Tonight","houseNumber":"HNHLT25RS112224LV","broadcastType":"HD","broadcastStatus":"LIVE","broadcastImageUrl":"nhltonight.png"},{"startTime":"2024-11-22T22:30:00","endTime":"2024-11-22T23:00:00","durationSeconds":1800,"title":"NHL Tonight","description":"NHL Tonight","houseNumber":"HNHLT25RS112224CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"nhltonight.png"},{"startTime":"2024-11-22T23:00:00","endTime":"2024-11-22T23:30:00","durationSeconds":1800,"title":"NHL Tonight","description":"NHL Tonight","houseNumber":"HNHLT25RS112224CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"nhltonight.png"},{"startTime":"2024-11-22T23:30:00","endTime":"2024-11-23T00:00:00","durationSeconds":1800,"title":"NHL Tonight","description":"NHL Tonight","houseNumber":"HNHLT25RS112224CC","broadcastType":"HD","broadcastStatus":"","broadcastImageUrl":"nhltonight.png"}]} \ No newline at end of file diff --git a/sites/nhl.com/nhl.com.channels.xml b/sites/nhl.com/nhl.com.channels.xml new file mode 100644 index 00000000..28f85d9f --- /dev/null +++ b/sites/nhl.com/nhl.com.channels.xml @@ -0,0 +1,10 @@ + + + + NHL Network + diff --git a/sites/nhl.com/nhl.com.config.js b/sites/nhl.com/nhl.com.config.js new file mode 100644 index 00000000..41354d78 --- /dev/null +++ b/sites/nhl.com/nhl.com.config.js @@ -0,0 +1,45 @@ +const dayjs = require('dayjs') + +module.exports = { + site: 'nhl.com', + // I'm not sure what `endDate` represents but they only return 1 day of + // results, with `endTime`s ocassionally in the following day. + days: 1, + url: ({ date }) => `https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split("T")[0]}`, + parser({ content }) { + const programs = [] + const items = parseItems(content) + for (const item of items) { + programs.push({ + title: item.title, + description: item.description === item.title ? undefined : item.description, + category: item.title === "NHL Game" ? "Sports event" : "Sports non-event", + // image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + } + + return programs + } +} + +// Unfortunately I couldn't determine how these are +// supposed to be formatted. Pointers appreciated! +// function parseImage(item) { +// const uri = item.broadcastImageUrl + +// return uri ? `https://???/${uri}` : null +// } + +function parseStart(item) { + return dayjs(item.startTime) +} + +function parseStop(item) { + return dayjs(item.endTime) +} + +function parseItems(content) { + return JSON.parse(content).broadcasts +} diff --git a/sites/nhl.com/nhl.com.test.js b/sites/nhl.com/nhl.com.test.js new file mode 100644 index 00000000..4a1b4732 --- /dev/null +++ b/sites/nhl.com/nhl.com.test.js @@ -0,0 +1,44 @@ +const { parser, url } = require('./nhl.com.config.js') +const fs = require('fs') +const path = require('path') +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('2024-11-21', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://api-web.nhle.com/v1/network/tv-schedule/2024-11-21' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2024-11-21T12:00:00.000Z', + stop: '2024-11-21T13:00:00.000Z', + title: 'On The Fly', + category: 'Sports non-event', + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: JSON.stringify({ + // extra props not necessary but they form a valid response + date: "2024-11-21", + startDate: "2024-11-07", + endDate: "2024-12-05", + broadcasts: [], + }) }) + expect(results).toMatchObject([]) +}) diff --git a/sites/nhl.com/readme.md b/sites/nhl.com/readme.md new file mode 100644 index 00000000..562599a0 --- /dev/null +++ b/sites/nhl.com/readme.md @@ -0,0 +1,15 @@ +# nhl.com + +https://www.nhl.com/nhl-network/programming-schedule + +### Download the guide + +```sh +npm run grab -- --site=nhl.com +``` + +### Test + +```sh +npm test -- nhl.com +```