diff --git a/.github/example_nhl_4.png b/.github/example_nhl_4.png new file mode 100644 index 00000000..ef722e65 Binary files /dev/null and b/.github/example_nhl_4.png differ diff --git a/.github/example_nhl_5.png b/.github/example_nhl_5.png new file mode 100644 index 00000000..f0e38145 Binary files /dev/null and b/.github/example_nhl_5.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 63897082..41ae1399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # MMM-NHL Changelog +## [2.2.1] + +### Fixed + +* Changed Logo Urls to support all teams (specifically all-star teams) +* Added support for teams with no short name when showNames is true + +## [2.2.0] + +### Added + +* Added new config option `showPlayoffSeries` to display playoff series information + ## [2.1.0] ### Fixed diff --git a/MMM-NHL.js b/MMM-NHL.js index 5b0c8899..c9c50a34 100644 --- a/MMM-NHL.js +++ b/MMM-NHL.js @@ -45,9 +45,11 @@ Module.register('MMM-NHL', { '3rd': '3RD_PERIOD', OT: 'OVER_TIME', SO: 'SHOOTOUT', + SHOOTOUT: 'SHOOTOUT', FINAL: 'FINAL', 'FINAL OT': 'FINAL_OVERTIME', - 'FINAL SO': 'FINAL_SHOOTOUT' + 'FINAL SO': 'FINAL_SHOOTOUT', + PPD: 'PPD' }, /** @@ -58,6 +60,12 @@ Module.register('MMM-NHL', { * @member {Game[]} games - List of all games matching focus and timespan config options. */ games: [], + + /** + * @member {Series[]} playoffSeries - List of all current playoff series. + */ + playoffSeries: [], + /** * @member {SeasonDetails} season - Current season details e.g. year and mode. */ @@ -83,6 +91,7 @@ Module.register('MMM-NHL', { * @property {number} daysAhead - Amount of days a match should be displayed before it starts. * @property {boolean} showNames - Flag to show team names. * @property {boolean} showLogos - Flag to show club logos. + * @property {boolean} showPlayoffSeries - Flag to show playoff series status during playoffs. * @property {boolean} rollOverGames - Flag to show today's games and previous/next day based on game status. */ defaults: { @@ -96,6 +105,7 @@ Module.register('MMM-NHL', { daysAhead: 7, showNames: true, showLogos: true, + showPlayoffSeries: true, rollOver: false }, @@ -149,6 +159,7 @@ Module.register('MMM-NHL', { modes: this.modes, season: this.season, games: this.games, + playoffSeries: this.playoffSeries, rotateIndex: this.rotateIndex, maxGames: Math.min(this.games.length, this.rotateIndex + this.config.matches), config: this.config @@ -188,6 +199,9 @@ Module.register('MMM-NHL', { this.games = payload.games; this.season = payload.season; this.setRotateInterval(); + } else if (notification === 'PLAYOFFS') { + this.playoffSeries = payload; + this.updateDom(300); } }, diff --git a/README.md b/README.md index b9b6155d..169ee053 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ National Hockey League Module for MagicMirror2 ## Examples -![](.github/example_nhl.png) ![](.github/example_nhl_2.png) ![](.github/example_nhl_3.png) +![](.github/example_nhl.png) ![](.github/example_nhl_2.png) ![](.github/example_nhl_3.png) ![](.github/example_nhl_4.png) ![](.github/example_nhl_5.png) ## Dependencies @@ -43,6 +43,7 @@ National Hockey League Module for MagicMirror2 | `liveReloadInterval` | `60000 (1 min)` | How often should the data be fetched during a live game. | | `showNames` | `true` | Should team names be displayed? | | `showLogos` | `true` | Should team logos be displayed? | +| `showPlayoffSeries` | `true` | Should playoff series be displayed (if in playoffs)? | | `rollOver` | `false` | Displays today's games and based on game status also yesterdays games or tomorrows games. Automatically overrides `daysInPast` and `daysAhead` to 1. | ## Global config diff --git a/node_helper.js b/node_helper.js index 13d275ba..c2ca9c5a 100644 --- a/node_helper.js +++ b/node_helper.js @@ -28,6 +28,7 @@ const qs = require('querystring'); 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'; /** * Derived team details of a game from API endpoint for easier usage. @@ -57,6 +58,16 @@ const BASE_URL = 'https://statsapi.web.nhl.com/api/v1'; * @property {string} live.timeRemaining - Remaining time of the current period in format mm:ss. */ +/** + * 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. + */ + /** * Derived season details from API endpoint for easier usage. * @@ -158,6 +169,26 @@ module.exports = NodeHelper.create({ return dates.map(({date, games}) => games.map(game => ({...game, gameDay: date}))).flat(); }, + /** + * @function fetchPlayoffs + * @description Retrieves playoff data from the API. + * @async + * + * @returns {object} Raw playoff data from API endpoint. + */ + async fetchPlayoffs() { + const response = await fetch(BASE_PLAYOFF_URL); + + if (!response.ok) { + console.error(`Fetching NHL playoffs failed: ${response.status} ${response.statusText}.`); + return; + } + + const playoffs = await response.json(); + playoffs.rounds.sort((a, b) => a.number <= b.number ? 1 : -1); + return playoffs; + }, + /** * @function filterGameByFocus * @description Helper function to filter games based on config option. @@ -235,6 +266,30 @@ module.exports = NodeHelper.create({ }; }, + /** + * @function computePlayoffDetails + * @description Computes current playoff details from list of series. + * + * @param {object} playoffData - List of raw series from API endpoint. + * + * @returns {Series[]} Current season details. + */ + computePlayoffDetails(playoffData) { + if (!playoffData || !playoffData.rounds) { + return []; + } + const series = []; + playoffData.rounds.forEach(r => { + r.series.forEach(s => { + const parsed = this.parseSeries(s); + if (parsed) { + series.push(parsed); + } + }); + }); + return series; + }, + /** * @function parseTeam * @description Transforms raw team information for easier usage. @@ -245,14 +300,35 @@ module.exports = NodeHelper.create({ * @returns {Team} Parsed team information. */ parseTeam(teams = {}, type) { + const team = teams[type]; + if (!team) { + console.error({NoTeamFound: teams, type}); + return {}; + } return { - id: teams[type].team.id, - name: teams[type].team.name, - short: this.teamMapping[teams[type].team.id], - score: teams[type].score + id: team.team.id, + name: team.team.name, + short: this.teamMapping[team.team.id], + score: team.score }; }, + /** + * @function parsePlayoffTeam + * @description Transforms raw game information for easier usage. + * + * @param {object} teamData - Raw game information. + * + * @param {number} index - Which index of teamData to operate on. + * + * @returns {Game} Parsed game information. + */ + parsePlayoffTeam(teamData = {}, index) { + const team = this.parseTeam(teamData, index); + team.score = teamData[index].seriesRecord.wins; + return team; + }, + /** * @function parseGame * @description Transforms raw game information for easier usage. @@ -281,6 +357,28 @@ module.exports = NodeHelper.create({ }; }, + /** + * @function parseSeries + * @description Transforms raw series information for easier usage. + * + * @param {object} series - Raw series information. + * + * @returns {Series} Parsed series information. + */ + parseSeries(series = {}) { + if (!series.matchupTeams || series.matchupTeams.length === 0) { + return null; + } + return { + number: series.number, + round: series.round.number, + teams: { + home: this.parsePlayoffTeam(series.matchupTeams, 0), + away: this.parsePlayoffTeam(series.matchupTeams, 1), + } + } + }, + /** * @function setNextandLiveGames * @description Sets the next scheduled and live games from a list of games. @@ -331,6 +429,13 @@ module.exports = NodeHelper.create({ this.setNextandLiveGames(rollOverGames); this.sendSocketNotification('SCHEDULE', {games: rollOverGames, season}); + if (season.mode === 'P' || games.length === 0) { + + const playoffData = await this.fetchPlayoffs(); + const playoffSeries = this.computePlayoffDetails(playoffData).filter(s => s.round >= playoffData.defaultRound); + + this.sendSocketNotification('PLAYOFFS', playoffSeries); + } }, /** diff --git a/package-lock.json b/package-lock.json index cb30104c..80e619b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "mmm-nhl", - "version": "2.0.0", + "version": "2.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index cc33f4b3..d37b268e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mmm-nhl", - "version": "2.1.0", + "version": "2.2.1", "description": "National Hockey League Module for MagicMirror2", "scripts": { "docs": "jsdoc -c jsdoc.json .", diff --git a/templates/MMM-NHL.njk b/templates/MMM-NHL.njk index a05864f2..c7245353 100644 --- a/templates/MMM-NHL.njk +++ b/templates/MMM-NHL.njk @@ -44,13 +44,13 @@ {% if config.showNames %} - {{ games[index].teams.home.short }} + {{ games[index].teams.home.short if games[index].teams.home.short else games[index].teams.home.name }} {% endif %} {% if config.showLogos %} + src="https://www-league.nhlstatic.com/images/logos/teams-20212022-dark/{{games[index].teams.home.id}}.svg"/> {% endif %} {{ games[index].teams.home.score }} @@ -59,16 +59,52 @@ {% if config.showLogos %} + src="https://www-league.nhlstatic.com/images/logos/teams-20212022-dark/{{games[index].teams.away.id}}.svg"/> {% endif %} {% if config.showNames %} - {{ games[index].teams.away.short }} + {{ games[index].teams.away.short if games[index].teams.away.short else games[index].teams.away.name }} {% endif %} {% endfor %} + {% if config.showPlayoffSeries and playoffSeries and playoffSeries.length > 0 %} +
{{ "SERIES" | translate }}
+ + + {% for series in playoffSeries %} + + {% if config.showNames %} + + {% endif %} + {% if config.showLogos %} + + {% endif %} + {{ series.teams.home.score }} + + + {% if config.showLogos %} + + {% endif %} + {% if config.showNames %} + + {% endif %} + + {% endfor %} + +
+ {{ series.teams.home.short }} + + + :{{ series.teams.away.score }} + + + {{ series.teams.away.short }} +
+ {% endif %} {% endif %} diff --git a/translations/de.json b/translations/de.json index 806191af..523df312 100644 --- a/translations/de.json +++ b/translations/de.json @@ -11,5 +11,6 @@ "FINAL_OT": "Beendet (OT)", "FINAL_SO": "Beendet (SO)", "TIME_LEFT": "{TIME} übrig", - "POSTPONED": "Verlegt" + "POSTPONED": "Verlegt", + "SERIES": "Serie" } diff --git a/translations/en.json b/translations/en.json index c940ebeb..0afd821a 100644 --- a/translations/en.json +++ b/translations/en.json @@ -11,5 +11,6 @@ "FINAL_OT": "Final (OT)", "FINAL_SO": "Final (SO)", "TIME_LEFT": "{TIME} left", - "POSTPONED": "PPD" + "POSTPONED": "PPD", + "SERIES": "Series" } diff --git a/translations/fr.json b/translations/fr.json index 73a54297..987c9364 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -11,5 +11,6 @@ "FINAL_OT": "Final (OT)", "FINAL_SO": "Final (SO)", "TIME_LEFT": "{TIME} restant", - "POSTPONED": "REM" + "POSTPONED": "REM", + "SERIES": "Séries" }