From 30817105195cd171e223b5d187c0cb6e13b51930 Mon Sep 17 00:00:00 2001 From: fewieden Date: Mon, 2 Oct 2023 22:35:22 +0200 Subject: [PATCH 01/21] Update CHANGELOG.md --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20dbd129..7c980be0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # MMM-NHL Changelog +## [Unreleased] + +### Fixed + +### Added + +### Changed + +### Removed + ## [2.3.1] Thanks to @parnic @dannoh @timpeer for their contributions. From 46eb7337e0008a06b2038b1806dfee23eeefdf20 Mon Sep 17 00:00:00 2001 From: Parnic Date: Thu, 16 Nov 2023 20:54:56 -0600 Subject: [PATCH 02/21] Update for new NHL API This gets most pieces of the module back into working order with the new API. I haven't found any way of determining how much time is left in a period, so that still needs to be figured out. I'm also not sure what the CRIT gameState is, but it seems to be "game almost over". Periods are missing their ordinal suffix right now, so they're just numbers rather than "1st" or "2nd". I don't know what a postponed game state is, either, so that is still TBD. I have yet to find any details on endpoints for playoff series information, so while I can query last season's playoff games, I am not sure how to query for the full playoff series details. Fixes https://github.com/fewieden/MMM-NHL/issues/49 --- MMM-NHL.js | 6 +-- node_helper.js | 117 +++++++++++++++++++----------------------- templates/MMM-NHL.njk | 14 ++--- 3 files changed, 63 insertions(+), 74 deletions(-) diff --git a/MMM-NHL.js b/MMM-NHL.js index 79811320..d1937cc6 100644 --- a/MMM-NHL.js +++ b/MMM-NHL.js @@ -31,9 +31,9 @@ Module.register('MMM-NHL', { * @member {object.} modes - Maps mode short codes to names. */ modes: { - PR: 'Pre-season', - R: 'Regular season', - P: 'Playoffs', + 1: 'Pre-season', + 2: 'Regular season', + 3: 'Playoffs', }, /** diff --git a/node_helper.js b/node_helper.js index 515528b7..8bfd9123 100644 --- a/node_helper.js +++ b/node_helper.js @@ -15,12 +15,6 @@ */ const fetch = require('node-fetch'); -/** - * @external querystring - * @see https://nodejs.org/api/querystring.html - */ -const qs = require('querystring'); - /** * @external logger * @see https://github.com/MichMich/MagicMirror/blob/master/js/logger.js @@ -33,7 +27,6 @@ const Log = require('logger'); */ const NodeHelper = require('node_helper'); -const BASE_URL = 'https://statsapi.web.nhl.com/api/v1'; const BASE_PLAYOFF_URL = 'https://statsapi.web.nhl.com/api/v1/tournaments/playoffs?expand=round.series'; /** @@ -41,8 +34,7 @@ const BASE_PLAYOFF_URL = 'https://statsapi.web.nhl.com/api/v1/tournaments/playof * * @typedef {object} Team * @property {number} id - Team identifier. - * @property {string} name - Full team name. - * @property {string} short - 3 letter team name. + * @property {string} abbrev - 3 letter team name. * @property {number} score - Current score of the team. */ @@ -53,25 +45,25 @@ const BASE_PLAYOFF_URL = 'https://statsapi.web.nhl.com/api/v1/tournaments/playof * @property {number} id - Game identifier. * @property {string} timestamp - Start date of the game in UTC timezone. * @property {string} gameDay - Game day in format YYYY-MM-DD in north american timezone. - * @property {object} status - Contains information about the game status. - * @property {string} status.abstract - Abstract game status e.g. Preview, Live or Final. - * @property {string} status.detailed - More detailed version of the abstract game status. - * @property {object} teams - Contains information about both teams. - * @property {Team} teams.away - Contains information about the away team. - * @property {Team} teams.home - Contains information about the home team. - * @property {object} live - Contains information about the live state of the game. - * @property {string} live.period - Period of the game e.g. 1st, 2nd, 3rd, OT or SO. - * @property {string} live.timeRemaining - Remaining time of the current period in format mm:ss. + * @property {string} gameState - Contains information about the game status, e.g. OFF, LIVE, CRIT, FUT. + * @property {Team} away_team - Contains information about the away team. + * @property {Team} home_team - Contains information about the home team. + * @property {object} periodDescriptor - Contains information about the period of play of the game. Is present on all games, past, present, and future. + * @property {number} periodDescriptor.number - Period of the game e.g. 1, 2, 3, 4. + * @property {string} periodDescriptor.periodType - Abbreviated description of the period type, e.g. REG, OT. */ /** * Derived game details from API endpoint for easier usage. * * @typedef {object} Series - * @property {number} number - Game identifier. - * @property {number} round - Start date of the game in UTC timezone. - * @property {Team} teams.away - Contains information about the away team. - * @property {Team} teams.home - Contains information about the home team. + * @property {number} gameNumberOfSeries - Game identifier. + * @property {number} round - Playoff round number, e.g. 1, 2, 3, 4. + * @property {string} roundAbbrev - Abbreviation of round type, e.g. SCF + * @property {number} topSeedTeamId - Contains the ID of the top-seeded team. + * @property {number} topSeedWins - Contains the number of wins of the top-seeded team in this round. + * @property {number} bottomSeedTeamId - Contains the ID of the bottom-seeded team. + * @property {number} bottomSeedWins - Contains the number of wins of the bottom-seed team in this round. */ /** @@ -79,7 +71,7 @@ const BASE_PLAYOFF_URL = 'https://statsapi.web.nhl.com/api/v1/tournaments/playof * * @typedef {object} SeasonDetails * @property {string} year - Year of the season in format yy/yy e.g. 20/21. - * @property {string} mode - Mode of the season e.g. PR, R and P. + * @property {number} mode - Mode of the season e.g. 0, 1 and 2. */ /** @@ -130,7 +122,7 @@ module.exports = NodeHelper.create({ * @returns {void} */ async initTeams() { - const response = await fetch(`${BASE_URL}/teams`); + const response = await fetch(`https://api.nhle.com/stats/rest/en/team`); if (!response.ok) { Log.error(`Initializing NHL teams failed: ${response.status} ${response.statusText}`); @@ -138,10 +130,10 @@ module.exports = NodeHelper.create({ return; } - const { teams } = await response.json(); + const { data } = await response.json(); - this.teamMapping = teams.reduce((mapping, team) => { - mapping[team.id] = team.abbreviation; + this.teamMapping = data.reduce((mapping, team) => { + mapping[team.id] = {short: team.triCode, name: team.fullName}; return mapping; }, {}); @@ -160,12 +152,7 @@ module.exports = NodeHelper.create({ const startDate = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) .format(date); - date.setDate(date.getDate() + this.config.daysInPast + this.config.daysAhead); - const endDate = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) - .format(date); - - const query = qs.stringify({ startDate, endDate, expand: 'schedule.linescore' }); - const url = `${BASE_URL}/schedule?${query}`; + const url = `https://api-web.nhle.com/v1/schedule/${startDate}`; const response = await fetch(url); if (!response.ok) { @@ -173,9 +160,9 @@ module.exports = NodeHelper.create({ return; } - const { dates } = await response.json(); + const { gameWeek } = await response.json(); - return dates.map(({ date, games }) => games.map(game => ({ ...game, gameDay: date }))).flat(); + return gameWeek.map(({ date, games }) => games.map(game => ({ ...game, gameDay: date, gameDate: new Date(game.startTimeUTC) }))).flat(); }, /** @@ -186,6 +173,7 @@ module.exports = NodeHelper.create({ * @returns {object} Raw playoff data from API endpoint. */ async fetchPlayoffs() { + // todo: find playoff endpoints in new api const response = await fetch(BASE_PLAYOFF_URL); if (!response.ok) { @@ -212,8 +200,8 @@ module.exports = NodeHelper.create({ return true; } - const homeTeam = this.teamMapping[game.teams.home.team.id]; - const awayTeam = this.teamMapping[game.teams.away.team.id]; + const homeTeam = this.teamMapping[game.home_team.id].short; + const awayTeam = this.teamMapping[game.away_team.id].short; return focus.includes(homeTeam) || focus.includes(awayTeam); }, @@ -238,9 +226,9 @@ module.exports = NodeHelper.create({ const today = games.filter(game => game.gameDay === date); const tomorrow = games.filter(game => game.gameDay > date); - const ongoingStates = ['Final', 'Live']; + const ongoingStates = ['OFF', 'CRIT', 'LIVE']; - if (today.some(game => ongoingStates.includes(game.status.abstract))) { + if (today.some(game => ongoingStates.includes(game.gameState))) { return [...today, ...tomorrow]; } @@ -256,11 +244,11 @@ module.exports = NodeHelper.create({ * @returns {SeasonDetails} Current season details. */ computeSeasonDetails(schedule) { - const game = schedule.find(game => game.status.abstractGameState !== 'Final') || schedule[schedule.length - 1]; + const game = schedule.find(game => game.gameState !== 'OFF') || schedule[schedule.length - 1]; if (game) { return { - year: `${game.season.slice(2, 4)}/${game.season.slice(6, 8)}`, + year: `${game.season.toString().slice(2, 4)}/${game.season.toString().slice(6, 8)}`, mode: game.gameType }; } @@ -271,7 +259,7 @@ module.exports = NodeHelper.create({ return { year: `${currentYear}/${nextYear}`, - mode: 'PR' + mode: 1 }; }, @@ -308,17 +296,16 @@ module.exports = NodeHelper.create({ * * @returns {Team} Parsed team information. */ - parseTeam(teams = {}, type) { - const team = teams[type]; + parseTeam(team) { if (!team) { - Log.error({ NoTeamFound: teams, type }); + Log.error('no team given'); return {}; } return { - id: team.team.id, - name: team.team.name, - short: this.teamMapping[team.team.id], - score: team.score + id: team.id, + name: this.teamMapping[team.id].name, + short: team.short, + score: team.score ?? 0 }; }, @@ -332,9 +319,13 @@ module.exports = NodeHelper.create({ * * @returns {Game} Parsed game information. */ - parsePlayoffTeam(teamData = {}, index) { - const team = this.parseTeam(teamData, index); - team.score = teamData[index].seriesRecord.wins; + parsePlayoffTeam(rawTeam, game) { + const team = this.parseTeam(rawTeam); + if (game.seriesStatus.topSeedTeamId == team.id) { + team.score = game.seriesStatus.topSeedWins; + } else { + team.score = game.seriesStatus.bottomSeedWins; + } return team; }, @@ -348,20 +339,18 @@ module.exports = NodeHelper.create({ */ parseGame(game = {}) { return { - id: game.gamePk, + id: game.id, timestamp: game.gameDate, gameDay: game.gameDay, - status: { - abstract: game.status.abstractGameState, - detailed: game.status.detailedState - }, + gameState: game.gameState, teams: { - away: this.parseTeam(game.teams, 'away'), - home: this.parseTeam(game.teams, 'home') + away: this.parseTeam(game.awayTeam), + home: this.parseTeam(game.homeTeam) }, live: { - period: game.linescore.currentPeriodOrdinal, - timeRemaining: game.linescore.currentPeriodTimeRemaining + period: game.periodDescriptor.number, + periodType: game.periodDescriptor.periodType, + timeRemaining: '?' // todo: unavailable in new api? } }; }, @@ -397,8 +386,8 @@ module.exports = NodeHelper.create({ * @returns {void} */ setNextandLiveGames(games) { - this.nextGame = games.find(game => game.status.abstract === 'Preview'); - this.liveGames = games.filter(game => game.status.abstract === 'Live'); + this.nextGame = games.find(game => game.gameState === 'FUT'); + this.liveGames = games.filter(game => game.gameState === 'LIVE' || game.gameState === 'CRIT'); }, /** @@ -438,7 +427,7 @@ module.exports = NodeHelper.create({ this.setNextandLiveGames(rollOverGames); this.sendSocketNotification('SCHEDULE', { games: rollOverGames, season }); - if (season.mode === 'P' || games.length === 0) { + if (season.mode === 3 || games.length === 0) { const playoffData = await this.fetchPlayoffs(); const playoffSeries = this.computePlayoffDetails(playoffData).filter(s => s.round >= playoffData.defaultRound); diff --git a/templates/MMM-NHL.njk b/templates/MMM-NHL.njk index 0208f7fd..2cb2cfa4 100644 --- a/templates/MMM-NHL.njk +++ b/templates/MMM-NHL.njk @@ -19,26 +19,26 @@ {% for index in range(rotateIndex, maxGames) %} - {% if games[index].status.detailed === "Pre-Game" %} + {% if games[index].gameState === "PRE" %} {{ "PRE_GAME" | translate }} - {% elif games[index].status.detailed === "Postponed" %} + {% elif games[index].gameState === "Postponed - todo: find out what this state is in new api" %} {{ "POSTPONED" | translate }} - {% elif games[index].status.abstract === "Preview" %} + {% elif games[index].gameState === "FUT" %} {{ games[index] | formatStartDate }} - {% elif games[index].status.abstract === "Live" and games[index].live.period %} + {% elif (games[index].gameState === "LIVE" or games[index].gameState === "CRIT") and games[index].live.period %}
{{ games[index].live.period | translate }}
- {% if games[index].live.timeRemaining === "Final" %} + {% if games[index].gameState === "CRIT" %} {{ "FINAL" | translate }} {% else %} {{ "TIME_LEFT" | translate({TIME: games[index].live.timeRemaining}) }} {% endif %}
{% else %} - {% if games[index].live.period === '3rd' %} + {% if games[index].live.period === 3 %} {{ "FINAL" | translate }} {% else %} - {{ ("FINAL_" + games[index].live.period) | translate }} + {{ ("FINAL_" + games[index].live.periodType) | translate }} {% endif %} {% endif %} From c4158316199c90b96651f7f92d10142c57370722 Mon Sep 17 00:00:00 2001 From: Parnic Date: Thu, 16 Nov 2023 20:58:23 -0600 Subject: [PATCH 03/21] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c980be0..f4b8de46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Fixed +* Updated module to work with the new NHL API. + ### Added ### Changed From 1e9209a60c7c2231886a58d77022cd4b797e596c Mon Sep 17 00:00:00 2001 From: Parnic Date: Thu, 16 Nov 2023 21:01:46 -0600 Subject: [PATCH 04/21] Try to satisfy linter --- node_helper.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/node_helper.js b/node_helper.js index 8bfd9123..23276a50 100644 --- a/node_helper.js +++ b/node_helper.js @@ -133,7 +133,7 @@ module.exports = NodeHelper.create({ const { data } = await response.json(); this.teamMapping = data.reduce((mapping, team) => { - mapping[team.id] = {short: team.triCode, name: team.fullName}; + mapping[team.id] = { short: team.triCode, name: team.fullName }; return mapping; }, {}); @@ -291,8 +291,7 @@ module.exports = NodeHelper.create({ * @function parseTeam * @description Transforms raw team information for easier usage. * - * @param {object} teams - Both teams in raw format. - * @param {string} type - Type of team: away or home. + * @param {object} team - Both teams in raw format. * * @returns {Team} Parsed team information. */ @@ -313,15 +312,15 @@ module.exports = NodeHelper.create({ * @function parsePlayoffTeam * @description Transforms raw game information for easier usage. * - * @param {object} teamData - Raw game information. + * @param {object} rawTeam - Raw team information. * - * @param {number} index - Which index of teamData to operate on. + * @param {object} game - Raw game information. * * @returns {Game} Parsed game information. */ parsePlayoffTeam(rawTeam, game) { const team = this.parseTeam(rawTeam); - if (game.seriesStatus.topSeedTeamId == team.id) { + if (game.seriesStatus.topSeedTeamId === team.id) { team.score = game.seriesStatus.topSeedWins; } else { team.score = game.seriesStatus.bottomSeedWins; @@ -371,8 +370,8 @@ module.exports = NodeHelper.create({ number: series.number, round: series.round.number, teams: { - home: this.parsePlayoffTeam(series.matchupTeams, 0), - away: this.parsePlayoffTeam(series.matchupTeams, 1), + home: this.parsePlayoffTeam(series.matchupTeams, undefined), + away: this.parsePlayoffTeam(series.matchupTeams, undefined), } } }, From b140eec85a1b4a18fc06a09c70ef330a6d073c69 Mon Sep 17 00:00:00 2001 From: Parnic Date: Thu, 16 Nov 2023 21:18:19 -0600 Subject: [PATCH 05/21] Fix team names and logos I broke this after a late edit while preparing the change to be committed. --- node_helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node_helper.js b/node_helper.js index 23276a50..34f8276f 100644 --- a/node_helper.js +++ b/node_helper.js @@ -303,7 +303,7 @@ module.exports = NodeHelper.create({ return { id: team.id, name: this.teamMapping[team.id].name, - short: team.short, + short: team.abbrev, score: team.score ?? 0 }; }, From b3054ae109ec78641cffd6ce615078c4b80cd27d Mon Sep 17 00:00:00 2001 From: Parnic Date: Thu, 16 Nov 2023 21:20:02 -0600 Subject: [PATCH 06/21] Restore period ordinal suffix This is how the module is expecting to handle the period. --- node_helper.js | 16 +++++++++++++++- templates/MMM-NHL.njk | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/node_helper.js b/node_helper.js index 34f8276f..8b6b4457 100644 --- a/node_helper.js +++ b/node_helper.js @@ -347,13 +347,27 @@ module.exports = NodeHelper.create({ home: this.parseTeam(game.homeTeam) }, live: { - period: game.periodDescriptor.number, + period: this.getNumberWithOrdinal(game.periodDescriptor.number), periodType: game.periodDescriptor.periodType, timeRemaining: '?' // todo: unavailable in new api? } }; }, + /** + * @function getNumberWithOrdinal + * @description Converts a raw number into a number with appropriate English ordinal suffix. + * + * @param {number} n - The number to apply an ordinal suffix to. + * + * @returns {string} The given number with its ordinal suffix appended. + */ + getNumberWithOrdinal(n) { + let s = ["th", "st", "nd", "rd"]; + let v = n % 100; + return n + (s[(v - 20) % 10] || s[v] || s[0]); + }, + /** * @function parseSeries * @description Transforms raw series information for easier usage. diff --git a/templates/MMM-NHL.njk b/templates/MMM-NHL.njk index 2cb2cfa4..70e77223 100644 --- a/templates/MMM-NHL.njk +++ b/templates/MMM-NHL.njk @@ -35,7 +35,7 @@ {% endif %} {% else %} - {% if games[index].live.period === 3 %} + {% if games[index].live.period === '3rd' %} {{ "FINAL" | translate }} {% else %} {{ ("FINAL_" + games[index].live.periodType) | translate }} From 62dfe664e740c910e9bdf9ce2f8cd13a8c85979a Mon Sep 17 00:00:00 2001 From: Parnic Date: Thu, 16 Nov 2023 21:21:42 -0600 Subject: [PATCH 07/21] Placate linter --- node_helper.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/node_helper.js b/node_helper.js index 8b6b4457..c23807de 100644 --- a/node_helper.js +++ b/node_helper.js @@ -357,14 +357,14 @@ module.exports = NodeHelper.create({ /** * @function getNumberWithOrdinal * @description Converts a raw number into a number with appropriate English ordinal suffix. - * + * * @param {number} n - The number to apply an ordinal suffix to. - * + * * @returns {string} The given number with its ordinal suffix appended. */ getNumberWithOrdinal(n) { - let s = ["th", "st", "nd", "rd"]; - let v = n % 100; + const s = ['th', 'st', 'nd', 'rd']; + const v = n % 100; return n + (s[(v - 20) % 10] || s[v] || s[0]); }, From d443b7904114db79e6d685b5e7230ff587d64f81 Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 17 Nov 2023 08:21:46 -0600 Subject: [PATCH 08/21] Respect config.daysAhead again As much as we can, anyway. The API only gives us back about a week's worth of games. --- node_helper.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/node_helper.js b/node_helper.js index c23807de..62081c06 100644 --- a/node_helper.js +++ b/node_helper.js @@ -149,10 +149,13 @@ module.exports = NodeHelper.create({ async fetchSchedule() { const date = new Date(); date.setDate(date.getDate() - this.config.daysInPast); - const startDate = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) + const startDateStr = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) .format(date); - const url = `https://api-web.nhle.com/v1/schedule/${startDate}`; + date.setDate(date.getDate() + this.config.daysInPast + this.config.daysAhead); + const endDate = date; + + const url = `https://api-web.nhle.com/v1/schedule/${startDateStr}`; const response = await fetch(url); if (!response.ok) { @@ -162,7 +165,7 @@ module.exports = NodeHelper.create({ const { gameWeek } = await response.json(); - return gameWeek.map(({ date, games }) => games.map(game => ({ ...game, gameDay: date, gameDate: new Date(game.startTimeUTC) }))).flat(); + return gameWeek.map(({ date, games }) => games.filter(game => { return new Date(game.startTimeUTC) < endDate; }).map(game => ({ ...game, gameDay: date, gameDate: new Date(game.startTimeUTC) }))).flat(); }, /** From ad1d6e62c34b5d51940ea232ae20e53574d5be91 Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 17 Nov 2023 08:46:25 -0600 Subject: [PATCH 09/21] Make daysAhead check more consistent This will return all games that take place on the day that you define as daysAhead. My previous implementation would return all games happening at (now + days ahead) which meant it would take the current time into consideration. Now the time is dropped so if daysAhead were 1, you'd see all games happening any time today and any time tomorrow (plus whatever daysInPast is set to). --- node_helper.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/node_helper.js b/node_helper.js index 62081c06..d9f113eb 100644 --- a/node_helper.js +++ b/node_helper.js @@ -152,8 +152,11 @@ module.exports = NodeHelper.create({ const startDateStr = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) .format(date); - date.setDate(date.getDate() + this.config.daysInPast + this.config.daysAhead); + date.setDate(date.getDate() + this.config.daysInPast + this.config.daysAhead + 1); const endDate = date; + endDate.setHours(0); + endDate.setMinutes(0); + endDate.setSeconds(0); const url = `https://api-web.nhle.com/v1/schedule/${startDateStr}`; const response = await fetch(url); @@ -165,7 +168,9 @@ module.exports = NodeHelper.create({ const { gameWeek } = await response.json(); - return gameWeek.map(({ date, games }) => games.filter(game => { return new Date(game.startTimeUTC) < endDate; }).map(game => ({ ...game, gameDay: date, gameDate: new Date(game.startTimeUTC) }))).flat(); + const schedule = gameWeek.map(({ date, games }) => games.filter(game => new Date(game.startTimeUTC) < endDate).map(game => ({ ...game, gameDay: date, gameDate: new Date(game.startTimeUTC) }))).flat(); + + return schedule; }, /** From f231c2c05320418440bb4ef6a9c42d1ca0e896ae Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 17 Nov 2023 08:48:52 -0600 Subject: [PATCH 10/21] Add time remaining back for live games I think this will work, but I need to wait until a game is live before being certain. This fetches all current scores, which includes game clock information, and applies the clock info to games that are in progress. Furthermore, if we aren't able to retrieve the time remaining, we will just display the period number the same way that "Final" or date/time is already displayed. --- node_helper.js | 15 ++++++++++++++- templates/MMM-NHL.njk | 20 ++++++++++++-------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/node_helper.js b/node_helper.js index d9f113eb..4eec8c24 100644 --- a/node_helper.js +++ b/node_helper.js @@ -170,6 +170,19 @@ module.exports = NodeHelper.create({ const schedule = gameWeek.map(({ date, games }) => games.filter(game => new Date(game.startTimeUTC) < endDate).map(game => ({ ...game, gameDay: date, gameDate: new Date(game.startTimeUTC) }))).flat(); + const scoresUrl = `https://api-web.nhle.com/v1/score/${startDateStr}`; + const scoresResponse = await fetch(scoresUrl); + const { games } = await scoresResponse.json(); + + for (const game of schedule) { + if (game.gameState !== 'LIVE' && game.gameState !== 'CRIT') { + continue; + } + + const score = games.find(score => score.id === game.id); + game.timeRemaining = score?.clock?.timeRemaining; + } + return schedule; }, @@ -357,7 +370,7 @@ module.exports = NodeHelper.create({ live: { period: this.getNumberWithOrdinal(game.periodDescriptor.number), periodType: game.periodDescriptor.periodType, - timeRemaining: '?' // todo: unavailable in new api? + timeRemaining: game.timeRemaining } }; }, diff --git a/templates/MMM-NHL.njk b/templates/MMM-NHL.njk index 70e77223..ea8adbc0 100644 --- a/templates/MMM-NHL.njk +++ b/templates/MMM-NHL.njk @@ -26,14 +26,18 @@ {% elif games[index].gameState === "FUT" %} {{ games[index] | formatStartDate }} {% elif (games[index].gameState === "LIVE" or games[index].gameState === "CRIT") and games[index].live.period %} -
{{ games[index].live.period | translate }}
-
- {% if games[index].gameState === "CRIT" %} - {{ "FINAL" | translate }} - {% else %} - {{ "TIME_LEFT" | translate({TIME: games[index].live.timeRemaining}) }} - {% endif %} -
+ {% if games[index].live.timeRemaining or games[index].gameState === "CRIT" %} +
{{ games[index].live.period | translate }}
+
+ {% if games[index].gameState === "CRIT" %} + {{ "FINAL" | translate }} + {% else %} + {{ "TIME_LEFT" | translate({TIME: games[index].live.timeRemaining}) }} + {% endif %} +
+ {% else %} + {{ games[index].live.period | translate }} + {% endif %} {% else %} {% if games[index].live.period === '3rd' %} {{ "FINAL" | translate }} From 43b88f7d9fca7a8de89a9ba9253db649b5c5d8c0 Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 17 Nov 2023 09:05:50 -0600 Subject: [PATCH 11/21] Revert some internal data layout changes This allows the template to change minimally. Since we're already transforming the received data into a different object for presentation, it makes sense to keep things consistent where possible. --- node_helper.js | 11 +++++++---- templates/MMM-NHL.njk | 12 ++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/node_helper.js b/node_helper.js index 4eec8c24..fa9f3719 100644 --- a/node_helper.js +++ b/node_helper.js @@ -249,7 +249,7 @@ module.exports = NodeHelper.create({ const ongoingStates = ['OFF', 'CRIT', 'LIVE']; - if (today.some(game => ongoingStates.includes(game.gameState))) { + if (today.some(game => ongoingStates.includes(game.status.abstract))) { return [...today, ...tomorrow]; } @@ -362,7 +362,10 @@ module.exports = NodeHelper.create({ id: game.id, timestamp: game.gameDate, gameDay: game.gameDay, - gameState: game.gameState, + status: { + abstract: game.gameState, + detailed: game.gameState, + }, teams: { away: this.parseTeam(game.awayTeam), home: this.parseTeam(game.homeTeam) @@ -420,8 +423,8 @@ module.exports = NodeHelper.create({ * @returns {void} */ setNextandLiveGames(games) { - this.nextGame = games.find(game => game.gameState === 'FUT'); - this.liveGames = games.filter(game => game.gameState === 'LIVE' || game.gameState === 'CRIT'); + this.nextGame = games.find(game => game.status.abstract === 'FUT'); + this.liveGames = games.filter(game => game.status.abstract === 'LIVE' || game.status.abstract === 'CRIT'); }, /** diff --git a/templates/MMM-NHL.njk b/templates/MMM-NHL.njk index ea8adbc0..666bfe9b 100644 --- a/templates/MMM-NHL.njk +++ b/templates/MMM-NHL.njk @@ -19,17 +19,17 @@ {% for index in range(rotateIndex, maxGames) %} - {% if games[index].gameState === "PRE" %} + {% if games[index].status.detailed === "PRE" %} {{ "PRE_GAME" | translate }} - {% elif games[index].gameState === "Postponed - todo: find out what this state is in new api" %} + {% elif games[index].status.detailed === "Postponed - todo: find out what this state is in new api" %} {{ "POSTPONED" | translate }} - {% elif games[index].gameState === "FUT" %} + {% elif games[index].status.abstract === "FUT" %} {{ games[index] | formatStartDate }} - {% elif (games[index].gameState === "LIVE" or games[index].gameState === "CRIT") and games[index].live.period %} - {% if games[index].live.timeRemaining or games[index].gameState === "CRIT" %} + {% elif (games[index].status.abstract === "LIVE" or games[index].status.detailed === "CRIT") and games[index].live.period %} + {% if games[index].live.timeRemaining or games[index].status.detailed === "CRIT" %}
{{ games[index].live.period | translate }}
- {% if games[index].gameState === "CRIT" %} + {% if games[index].status.detailed === "CRIT" %} {{ "FINAL" | translate }} {% else %} {{ "TIME_LEFT" | translate({TIME: games[index].live.timeRemaining}) }} From 2bae21d4897a1ea2471072f019e2705ce9e10cf9 Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 17 Nov 2023 13:32:15 -0600 Subject: [PATCH 12/21] Don't grab team mapping if we don't need to This endpoint is being incredibly slow today, and once we've loaded it we probably don't need to load it again. --- node_helper.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/node_helper.js b/node_helper.js index fa9f3719..6a169610 100644 --- a/node_helper.js +++ b/node_helper.js @@ -122,6 +122,10 @@ module.exports = NodeHelper.create({ * @returns {void} */ async initTeams() { + if (this.teamMapping) { + return; + } + const response = await fetch(`https://api.nhle.com/stats/rest/en/team`); if (!response.ok) { From 656d97b3378540197cbcbfc75cba0b57f0efc93a Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 17 Nov 2023 13:32:47 -0600 Subject: [PATCH 13/21] Fix game time display The scores endpoint only returns games for the requested day, so we need to use today, not startDate. --- node_helper.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/node_helper.js b/node_helper.js index 6a169610..9b599a6b 100644 --- a/node_helper.js +++ b/node_helper.js @@ -152,6 +152,9 @@ module.exports = NodeHelper.create({ */ async fetchSchedule() { const date = new Date(); + const todayDateStr = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) + .format(date); + date.setDate(date.getDate() - this.config.daysInPast); const startDateStr = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) .format(date); @@ -174,7 +177,7 @@ module.exports = NodeHelper.create({ const schedule = gameWeek.map(({ date, games }) => games.filter(game => new Date(game.startTimeUTC) < endDate).map(game => ({ ...game, gameDay: date, gameDate: new Date(game.startTimeUTC) }))).flat(); - const scoresUrl = `https://api-web.nhle.com/v1/score/${startDateStr}`; + const scoresUrl = `https://api-web.nhle.com/v1/score/${todayDateStr}`; const scoresResponse = await fetch(scoresUrl); const { games } = await scoresResponse.json(); From f430b45a1c9a4705435115f49f7d3601c2b9ef74 Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 17 Nov 2023 13:58:32 -0600 Subject: [PATCH 14/21] Don't pass intermission clock as game clock --- node_helper.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node_helper.js b/node_helper.js index 9b599a6b..87a670e4 100644 --- a/node_helper.js +++ b/node_helper.js @@ -188,6 +188,7 @@ module.exports = NodeHelper.create({ const score = games.find(score => score.id === game.id); game.timeRemaining = score?.clock?.timeRemaining; + game.inIntermission = score?.clock?.inIntermission; } return schedule; @@ -380,7 +381,7 @@ module.exports = NodeHelper.create({ live: { period: this.getNumberWithOrdinal(game.periodDescriptor.number), periodType: game.periodDescriptor.periodType, - timeRemaining: game.timeRemaining + timeRemaining: game.inIntermission ? '00:00' : game.timeRemaining, } }; }, From e5107591cf6cda05d1604aebb2608f3b630d60c7 Mon Sep 17 00:00:00 2001 From: Parnic Date: Fri, 17 Nov 2023 15:57:52 -0600 Subject: [PATCH 15/21] CRIT is still an active game Don't show "Final" during crit, just show the score as normal. --- templates/MMM-NHL.njk | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/templates/MMM-NHL.njk b/templates/MMM-NHL.njk index 666bfe9b..4b8c4652 100644 --- a/templates/MMM-NHL.njk +++ b/templates/MMM-NHL.njk @@ -26,14 +26,10 @@ {% elif games[index].status.abstract === "FUT" %} {{ games[index] | formatStartDate }} {% elif (games[index].status.abstract === "LIVE" or games[index].status.detailed === "CRIT") and games[index].live.period %} - {% if games[index].live.timeRemaining or games[index].status.detailed === "CRIT" %} + {% if games[index].live.timeRemaining %}
{{ games[index].live.period | translate }}
- {% if games[index].status.detailed === "CRIT" %} - {{ "FINAL" | translate }} - {% else %} - {{ "TIME_LEFT" | translate({TIME: games[index].live.timeRemaining}) }} - {% endif %} + {{ "TIME_LEFT" | translate({TIME: games[index].live.timeRemaining}) }}
{% else %} {{ games[index].live.period | translate }} From 3dd2f48df3effe78f89e0ff6e69a1693946f60cd Mon Sep 17 00:00:00 2001 From: Parnic Date: Tue, 21 Nov 2023 17:24:43 -0600 Subject: [PATCH 16/21] Fix outdated doc --- node_helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node_helper.js b/node_helper.js index 87a670e4..bb2f40cb 100644 --- a/node_helper.js +++ b/node_helper.js @@ -320,7 +320,7 @@ module.exports = NodeHelper.create({ * @function parseTeam * @description Transforms raw team information for easier usage. * - * @param {object} team - Both teams in raw format. + * @param {object} team - Team in raw format. * * @returns {Team} Parsed team information. */ From f0b828c7377c2580c944007f6d5ad356a0614929 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 16 Dec 2023 09:01:12 +0100 Subject: [PATCH 17/21] Fixes config option focus_on for new API and safe guards playoff score parsing --- node_helper.js | 90 ++++++++++++++++++++++++------------------- templates/MMM-NHL.njk | 9 +++-- 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/node_helper.js b/node_helper.js index bb2f40cb..e10bc038 100644 --- a/node_helper.js +++ b/node_helper.js @@ -79,7 +79,6 @@ const BASE_PLAYOFF_URL = 'https://statsapi.web.nhl.com/api/v1/tournaments/playof * @description Backend for the module to query data from the API provider. * * @requires external:node-fetch - * @requires external:querystring * @requires external:logger * @requires external:node_helper */ @@ -152,33 +151,40 @@ module.exports = NodeHelper.create({ */ async fetchSchedule() { const date = new Date(); - const todayDateStr = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) - .format(date); - date.setDate(date.getDate() - this.config.daysInPast); const startDateStr = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) .format(date); - date.setDate(date.getDate() + this.config.daysInPast + this.config.daysAhead + 1); - const endDate = date; - endDate.setHours(0); - endDate.setMinutes(0); - endDate.setSeconds(0); + const scheduleUrl = `https://api-web.nhle.com/v1/schedule/${startDateStr}`; + const scheduleResponse = await fetch(scheduleUrl); - const url = `https://api-web.nhle.com/v1/schedule/${startDateStr}`; - const response = await fetch(url); - - if (!response.ok) { - Log.error(`Fetching NHL schedule failed: ${response.status} ${response.statusText}. Url: ${url}`); + if (!scheduleResponse.ok) { + Log.error(`Fetching NHL schedule failed: ${scheduleResponse.status} ${scheduleResponse.statusText}. Url: ${scheduleUrl}`); return; } - const { gameWeek } = await response.json(); + const { gameWeek } = await scheduleResponse.json(); - const schedule = gameWeek.map(({ date, games }) => games.filter(game => new Date(game.startTimeUTC) < endDate).map(game => ({ ...game, gameDay: date, gameDate: new Date(game.startTimeUTC) }))).flat(); + date.setDate(date.getDate() + this.config.daysInPast + this.config.daysAhead + 1); + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + const endDateUTC = date.toISOString(); + + const schedule = gameWeek.map(({ date, games }) => games.filter(game => game.startTimeUTC < endDateUTC).map(game => ({...game, gameDay: date}))).flat(); + + const todayDateStr = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) + .format(new Date()); const scoresUrl = `https://api-web.nhle.com/v1/score/${todayDateStr}`; const scoresResponse = await fetch(scoresUrl); + + if (!scoresResponse.ok) { + Log.error(`Fetching NHL scores failed: ${scoresResponse.status} ${scoresResponse.statusText}. Url: ${scoresUrl}`); + + return schedule; + } + const { games } = await scoresResponse.json(); for (const game of schedule) { @@ -187,8 +193,7 @@ module.exports = NodeHelper.create({ } const score = games.find(score => score.id === game.id); - game.timeRemaining = score?.clock?.timeRemaining; - game.inIntermission = score?.clock?.inIntermission; + game.timeRemaining = score?.clock?.inIntermission ? '00:00' : score?.clock?.timeRemaining; } return schedule; @@ -202,7 +207,7 @@ module.exports = NodeHelper.create({ * @returns {object} Raw playoff data from API endpoint. */ async fetchPlayoffs() { - // todo: find playoff endpoints in new api + // TODO: Find playoff endpoints in new API const response = await fetch(BASE_PLAYOFF_URL); if (!response.ok) { @@ -212,6 +217,7 @@ module.exports = NodeHelper.create({ const playoffs = await response.json(); playoffs.rounds.sort((a, b) => a.number <= b.number ? 1 : -1); + return playoffs; }, @@ -229,8 +235,8 @@ module.exports = NodeHelper.create({ return true; } - const homeTeam = this.teamMapping[game.home_team.id].short; - const awayTeam = this.teamMapping[game.away_team.id].short; + const homeTeam = this.teamMapping[game.homeTeam.id].short; + const awayTeam = this.teamMapping[game.awayTeam.id].short; return focus.includes(homeTeam) || focus.includes(awayTeam); }, @@ -257,7 +263,7 @@ module.exports = NodeHelper.create({ const ongoingStates = ['OFF', 'CRIT', 'LIVE']; - if (today.some(game => ongoingStates.includes(game.status.abstract))) { + if (today.some(game => ongoingStates.includes(game.status))) { return [...today, ...tomorrow]; } @@ -304,6 +310,7 @@ module.exports = NodeHelper.create({ if (!playoffData || !playoffData.rounds) { return []; } + const series = []; playoffData.rounds.forEach(r => { r.series.forEach(s => { @@ -313,6 +320,7 @@ module.exports = NodeHelper.create({ } }); }); + return series; }, @@ -329,10 +337,11 @@ module.exports = NodeHelper.create({ Log.error('no team given'); return {}; } + return { id: team.id, name: this.teamMapping[team.id].name, - short: team.abbrev, + short: this.teamMapping[team.id].short, score: team.score ?? 0 }; }, @@ -349,11 +358,13 @@ module.exports = NodeHelper.create({ */ parsePlayoffTeam(rawTeam, game) { const team = this.parseTeam(rawTeam); - if (game.seriesStatus.topSeedTeamId === team.id) { - team.score = game.seriesStatus.topSeedWins; + + if (game?.seriesStatus?.topSeedTeamId === team.id) { + team.score = game?.seriesStatus?.topSeedWins; } else { - team.score = game.seriesStatus.bottomSeedWins; + team.score = game?.seriesStatus?.bottomSeedWins; } + return team; }, @@ -368,12 +379,9 @@ module.exports = NodeHelper.create({ parseGame(game = {}) { return { id: game.id, - timestamp: game.gameDate, + timestamp: game.startTimeUTC, gameDay: game.gameDay, - status: { - abstract: game.gameState, - detailed: game.gameState, - }, + status: game.gameState, teams: { away: this.parseTeam(game.awayTeam), home: this.parseTeam(game.homeTeam) @@ -381,7 +389,7 @@ module.exports = NodeHelper.create({ live: { period: this.getNumberWithOrdinal(game.periodDescriptor.number), periodType: game.periodDescriptor.periodType, - timeRemaining: game.inIntermission ? '00:00' : game.timeRemaining, + timeRemaining: game.timeRemaining, } }; }, @@ -395,8 +403,10 @@ module.exports = NodeHelper.create({ * @returns {string} The given number with its ordinal suffix appended. */ getNumberWithOrdinal(n) { + // TODO: This function seems over complicated, don't we just have 1st 2nd and 3rd? const s = ['th', 'st', 'nd', 'rd']; const v = n % 100; + return n + (s[(v - 20) % 10] || s[v] || s[0]); }, @@ -412,12 +422,13 @@ module.exports = NodeHelper.create({ if (!series.matchupTeams || series.matchupTeams.length === 0) { return null; } + return { number: series.number, round: series.round.number, teams: { - home: this.parsePlayoffTeam(series.matchupTeams, undefined), - away: this.parsePlayoffTeam(series.matchupTeams, undefined), + home: this.parsePlayoffTeam(series.matchupTeams, undefined), // TODO: Don't pass undefined to retrieve the correct score + away: this.parsePlayoffTeam(series.matchupTeams, undefined), // TODO: Don't pass undefined to retrieve the correct score } } }, @@ -431,8 +442,8 @@ module.exports = NodeHelper.create({ * @returns {void} */ setNextandLiveGames(games) { - this.nextGame = games.find(game => game.status.abstract === 'FUT'); - this.liveGames = games.filter(game => game.status.abstract === 'LIVE' || game.status.abstract === 'CRIT'); + this.nextGame = games.find(game => game.status === 'FUT'); + this.liveGames = games.filter(game => ['LIVE', 'CRIT'].includes(game.status)); }, /** @@ -445,11 +456,11 @@ module.exports = NodeHelper.create({ * @returns {number} Should game be before or after in the list? */ sortGamesByTimestampAndID(game1, game2) { - if (game1.gameDate === game2.gameDate) { + if (game1.startTimeUTC === game2.startTimeUTC) { return game1.id > game2.id ? 1 : -1; } - return game1.gameDate > game2.gameDate ? 1 : -1; + return game1.startTimeUTC > game2.startTimeUTC ? 1 : -1; }, /** @@ -472,6 +483,7 @@ module.exports = NodeHelper.create({ this.setNextandLiveGames(rollOverGames); this.sendSocketNotification('SCHEDULE', { games: rollOverGames, season }); + if (season.mode === 3 || games.length === 0) { const playoffData = await this.fetchPlayoffs(); @@ -490,7 +502,7 @@ module.exports = NodeHelper.create({ */ fetchOnLiveState() { const hasLiveGames = this.liveGames.length > 0; - const gameAboutToStart = this.nextGame && new Date() > new Date(this.nextGame.timestamp); + const gameAboutToStart = this.nextGame && new Date().toISOString() > this.nextGame.timestamp; if (hasLiveGames || gameAboutToStart) { return this.updateSchedule(); diff --git a/templates/MMM-NHL.njk b/templates/MMM-NHL.njk index 4b8c4652..4f492bbe 100644 --- a/templates/MMM-NHL.njk +++ b/templates/MMM-NHL.njk @@ -19,13 +19,14 @@ {% for index in range(rotateIndex, maxGames) %} - {% if games[index].status.detailed === "PRE" %} + {% if games[index].status === "PRE" %} {{ "PRE_GAME" | translate }} - {% elif games[index].status.detailed === "Postponed - todo: find out what this state is in new api" %} + {# TODO: Find out what the state postponed state is in the new API #} + {% elif games[index].status === "Postponed" %} {{ "POSTPONED" | translate }} - {% elif games[index].status.abstract === "FUT" %} + {% elif games[index].status === "FUT" %} {{ games[index] | formatStartDate }} - {% elif (games[index].status.abstract === "LIVE" or games[index].status.detailed === "CRIT") and games[index].live.period %} + {% elif (games[index].status === "LIVE" or games[index].status === "CRIT") and games[index].live.period %} {% if games[index].live.timeRemaining %}
{{ games[index].live.period | translate }}
From a6175f87fdd11e953bcc7f5f8d12d18d7603731d Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 16 Dec 2023 09:03:55 +0100 Subject: [PATCH 18/21] Updates documentation --- node_helper.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node_helper.js b/node_helper.js index e10bc038..8ef488d9 100644 --- a/node_helper.js +++ b/node_helper.js @@ -46,8 +46,8 @@ const BASE_PLAYOFF_URL = 'https://statsapi.web.nhl.com/api/v1/tournaments/playof * @property {string} timestamp - Start date of the game in UTC timezone. * @property {string} gameDay - Game day in format YYYY-MM-DD in north american timezone. * @property {string} gameState - Contains information about the game status, e.g. OFF, LIVE, CRIT, FUT. - * @property {Team} away_team - Contains information about the away team. - * @property {Team} home_team - Contains information about the home team. + * @property {Team} awayTeam - Contains information about the away team. + * @property {Team} homeTeam - Contains information about the home team. * @property {object} periodDescriptor - Contains information about the period of play of the game. Is present on all games, past, present, and future. * @property {number} periodDescriptor.number - Period of the game e.g. 1, 2, 3, 4. * @property {string} periodDescriptor.periodType - Abbreviated description of the period type, e.g. REG, OT. From 155af64649bd7f4911206b61b2a2f2e1ddf26775 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 16 Dec 2023 09:05:06 +0100 Subject: [PATCH 19/21] Updates changelog --- CHANGELOG.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b8de46..aabfbc5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,13 @@ # MMM-NHL Changelog -## [Unreleased] +## [2.4.0] + +Thanks to @parnic @dannoh for their contributions. ### Fixed * Updated module to work with the new NHL API. -### Added - -### Changed - -### Removed - ## [2.3.1] Thanks to @parnic @dannoh @timpeer for their contributions. From 9ad0b20fd32f505e70749d528505cb03dab0a21f Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 16 Dec 2023 09:30:18 +0100 Subject: [PATCH 20/21] Extracts logic for code climate score --- node_helper.js | 85 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/node_helper.js b/node_helper.js index 8ef488d9..a398a5fb 100644 --- a/node_helper.js +++ b/node_helper.js @@ -143,42 +143,45 @@ module.exports = NodeHelper.create({ }, /** - * @function fetchSchedule - * @description Retrieves a list of games from the API with timespan based on config options. + * @function getDates + * @description Helper function to retrieve dates in the past and future based on config options. * @async * - * @returns {object[]} Raw games from API endpoint. + * @returns {object} Dates in the past and future. */ - async fetchSchedule() { - const date = new Date(); - date.setDate(date.getDate() - this.config.daysInPast); - const startDateStr = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) - .format(date); - - const scheduleUrl = `https://api-web.nhle.com/v1/schedule/${startDateStr}`; - const scheduleResponse = await fetch(scheduleUrl); - - if (!scheduleResponse.ok) { - Log.error(`Fetching NHL schedule failed: ${scheduleResponse.status} ${scheduleResponse.statusText}. Url: ${scheduleUrl}`); - return; - } + getScheduleDates() { + const start = new Date(); + start.setDate(start.getDate() - this.config.daysInPast); - const { gameWeek } = await scheduleResponse.json(); - - date.setDate(date.getDate() + this.config.daysInPast + this.config.daysAhead + 1); - date.setHours(0); - date.setMinutes(0); - date.setSeconds(0); + const end = new Date(); + end.setDate(end.getDate() + this.config.daysAhead + 1); + end.setHours(0); + end.setMinutes(0); + end.setSeconds(0); - const endDateUTC = date.toISOString(); + const today = new Date(); - const schedule = gameWeek.map(({ date, games }) => games.filter(game => game.startTimeUTC < endDateUTC).map(game => ({...game, gameDay: date}))).flat(); + return { + startUtc: start.toISOString(), + startFormatted: new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }).format(start), + endUtc: end.toISOString(), + endFormatted: new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }).format(end), + todayUtc: today.toISOString(), + todayFormatted: new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }).format(today) + }; + }, - const todayDateStr = new Intl.DateTimeFormat('fr-ca', { timeZone: 'America/Toronto' }) - .format(new Date()); - const scoresUrl = `https://api-web.nhle.com/v1/score/${todayDateStr}`; + /** + * @function hydrateRemainingTime + * @description Hydrates remaining time on the games in the schedule from the scores API endpoint. + * @async + * + * @returns {object[]} Raw games from API endpoint including remaining time. + */ + async hydrateRemainingTime(schedule) { + const { todayFormatted } = this.getScheduleDates(); + const scoresUrl = `https://api-web.nhle.com/v1/score/${todayFormatted}`; const scoresResponse = await fetch(scoresUrl); - if (!scoresResponse.ok) { Log.error(`Fetching NHL scores failed: ${scoresResponse.status} ${scoresResponse.statusText}. Url: ${scoresUrl}`); @@ -199,6 +202,32 @@ module.exports = NodeHelper.create({ return schedule; }, + /** + * @function fetchSchedule + * @description Retrieves a list of games from the API with timespan based on config options. + * @async + * + * @returns {object[]} Raw games from API endpoint. + */ + async fetchSchedule() { + const { startFormatted, endUtc } = this.getScheduleDates(); + + const scheduleUrl = `https://api-web.nhle.com/v1/schedule/${startFormatted}`; + const scheduleResponse = await fetch(scheduleUrl); + if (!scheduleResponse.ok) { + Log.error(`Fetching NHL schedule failed: ${scheduleResponse.status} ${scheduleResponse.statusText}. Url: ${scheduleUrl}`); + return; + } + + const { gameWeek } = await scheduleResponse.json(); + + const schedule = gameWeek.map(({ date, games }) => games.filter(game => game.startTimeUTC < endUtc).map(game => ({ ...game, gameDay: date }))).flat(); + + const scheduleWithRemainingTime = await this.hydrateRemainingTime(schedule); + + return scheduleWithRemainingTime; + }, + /** * @function fetchPlayoffs * @description Retrieves playoff data from the API. From 760250bd57bcb98c65c96c335c614d11c996c548 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 16 Dec 2023 09:41:56 +0100 Subject: [PATCH 21/21] Extracts logic for code climate score --- node_helper.js | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/node_helper.js b/node_helper.js index a398a5fb..1ad6a8af 100644 --- a/node_helper.js +++ b/node_helper.js @@ -89,6 +89,8 @@ module.exports = NodeHelper.create({ nextGame: null, /** @member {Game[]} liveGames - List of all ongoing games. */ liveGames: [], + /** @member {Game[]} liveStates - List of all live game states. */ + liveStates: ['LIVE', 'CRIT'], /** * @function socketNotificationReceived @@ -143,7 +145,7 @@ module.exports = NodeHelper.create({ }, /** - * @function getDates + * @function getScheduleDates * @description Helper function to retrieve dates in the past and future based on config options. * @async * @@ -171,6 +173,26 @@ module.exports = NodeHelper.create({ }; }, + /** + * @function getRemainingGameTime + * @description Helper function to retrieve remaining game time. + * @async + * + * @returns {string?} Remaining game time. + */ + getRemainingGameTime(game, scores) { + if (!this.liveStates.includes(game.gameState)) { + return; + } + + const score = scores.find(score => score.id === game.id); + if (!score) { + return; + } + + return score?.clock?.inIntermission ? '00:00' : score?.clock?.timeRemaining; + }, + /** * @function hydrateRemainingTime * @description Hydrates remaining time on the games in the schedule from the scores API endpoint. @@ -191,12 +213,7 @@ module.exports = NodeHelper.create({ const { games } = await scoresResponse.json(); for (const game of schedule) { - if (game.gameState !== 'LIVE' && game.gameState !== 'CRIT') { - continue; - } - - const score = games.find(score => score.id === game.id); - game.timeRemaining = score?.clock?.inIntermission ? '00:00' : score?.clock?.timeRemaining; + game.timeRemaining = this.getRemainingGameTime(game, games); } return schedule; @@ -472,7 +489,7 @@ module.exports = NodeHelper.create({ */ setNextandLiveGames(games) { this.nextGame = games.find(game => game.status === 'FUT'); - this.liveGames = games.filter(game => ['LIVE', 'CRIT'].includes(game.status)); + this.liveGames = games.filter(game => this.liveStates.includes(game.status)); }, /**