commit bafd1aa9326005147b226f8d7773d80f4a7effae Author: Chris Pickett Date: Sat Apr 28 17:03:38 2018 -0500 Initial commit Still a lot to do here, such as better error handling and handling multiple teams matching the name typed in, but it's a start. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c245cd3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# package lock +package-lock.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b442934 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MMM-ShiftStats.js b/MMM-ShiftStats.js new file mode 100644 index 0000000..a6b3d12 --- /dev/null +++ b/MMM-ShiftStats.js @@ -0,0 +1,166 @@ +shiftStats = {}; + +Module.register("MMM-ShiftStats",{ + defaults: { + apiKey: null, + type: 'Regular Season', + teamName: null, + sport: null, + mode: 'standings', + maxGames: 6, + teamNameClass: "light", + updateInterval: 12 * 60 * 60 * 1000 + }, +// search for team name, look for newest team, pick playoffs if there are games, otherwise reg season, otherwise exhibition? +// teamSearch response comes with references.season which should get you the info you need. season has multiple season.stats +// which has .Exhibition and etc. which contains games_played. + start: function() { + this.sendSocketNotification('SHIFTSTATS_CONFIG', this.config); + this.sendSocketNotification('SHIFTSTATS_UPDATE'); + }, + + getStyles: function() { + return ["shiftstats.css"]; + }, + + getDom: function() { + if (!shiftStats.standings || !shiftStats.games) { + let wrapper = document.createElement("div"); + wrapper.innerHTML = 'Loading...'; + wrapper.className += "dimmed light small"; + + return wrapper; + } else { + let table = document.createElement('table'); + table.className = "small"; + if (this.config.colored) { + table.className += " colored"; + } + + if (this.config.mode == 'games') { + table = showGames(table, this.config) + } else { + table = showStandings(table) + } + + return table; + } + }, + + socketNotificationReceived: function(notification, payload) { + if (notification === 'SHIFTSTATS_STANDINGS') { + shiftStats = payload; + this.updateDom(); + } + } +}); + +function showGames(table, config) { + let now = new Date() + let mostRecent = [] + shiftStats.games.games.forEach((game) => { + if (new Date(game.datetime) < now) { + mostRecent.push({ + game: game, + home_team: shiftStats.games.references.team.find(team => team.id == game.home_team_id), + away_team: shiftStats.games.references.team.find(team => team.id == game.away_team_id), + }) + if (mostRecent.length > config.maxGames) { + mostRecent.shift() + } + } + }) + + let row + let cell + mostRecent.forEach((game) => { + row = document.createElement('tr') + + cell = document.createElement('td') + cell.innerHTML = game.home_team.name + cell.className = config.teamNameClass + row.appendChild(cell) + + cell = document.createElement('td') + cell.innerHTML = game.game.stats.home_score + cell.className = 'center' + row.appendChild(cell) + + cell = document.createElement('td') + cell.innerHTML = game.game.stats.away_score + cell.className = 'center' + row.appendChild(cell) + + cell = document.createElement('td') + cell.innerHTML = game.away_team.name + cell.className = `left ${config.teamNameClass}` + row.appendChild(cell) + + table.appendChild(row) + }) + + return table +} + +function showStandings(table) { + let row = document.createElement('tr') + + let cell = document.createElement('th') + cell.innerHTML = 'Team' + row.appendChild(cell) + + cell = document.createElement('th') + cell.className = 'center' + cell.innerHTML = 'W' + row.appendChild(cell) + + cell = document.createElement('th') + cell.className = 'center' + cell.innerHTML = 'L' + row.appendChild(cell) + + cell = document.createElement('th') + cell.className = 'center' + cell.innerHTML = 'OTW' + row.appendChild(cell) + + cell = document.createElement('th') + cell.className = 'center' + cell.innerHTML = 'OTL' + row.appendChild(cell) + + table.appendChild(row) + + shiftStats.standings.teams.forEach((team) => { + row = document.createElement('tr') + + cell = document.createElement('td') + cell.innerHTML = team.name + cell.className = 'light' + row.appendChild(cell) + + cell = document.createElement('td') + cell.innerHTML = team.stats['Regular Season'].wins + cell.className = 'center' + row.appendChild(cell) + + cell = document.createElement('td') + cell.innerHTML = team.stats['Regular Season'].losses + cell.className = 'center' + row.appendChild(cell) + + cell = document.createElement('td') + cell.innerHTML = team.stats['Regular Season'].otw + cell.className = 'center' + row.appendChild(cell) + + cell = document.createElement('td') + cell.innerHTML = team.stats['Regular Season'].otl + cell.className = 'center' + row.appendChild(cell) + + table.appendChild(row) + }) + + return table +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..2f638f1 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# MMM-ShiftStats +A [MagicMirror²](https://github.com/MichMich/MagicMirror) module used to display stats from any DigitalShift site ([HockeyShift](https://hockeyshift.com), [SoccerShift](https://soccershift.com), [LacrosseShift](https://lacrosseshift.com), [FootballShift](http://footballshift.com), [BasketballShift](https://basketballshift.com), and [BaseballShift](http://baseballshift.com)). + +## Installation +1. Navigate into your MagicMirror's `modules` folder and execute `git clone https://github.com/parnic/MMM-ShiftStats.git`. +2. `cd MMM-ShiftStats` +3. Execute `npm install` to install the node dependencies. +4. Add the module inside `config.js` placing it where you prefer. + +## Config +|Option|Type|Description|Default| +|---|---|---|---| +|apiKey|`string`|Your API key. If not supplied, the HockeyShift Android app's key is used by default.|| +|teamName|`string`|(REQUIRED) The name of the team you want to track.|| +|sport|`string`|(REQUIRED) The name of the sport you want to track (e.g. `'hockey'`, `'soccer'`).|| +|mode|`string`|What mode the module should run in. Valid values: `'standings'`, `'games'`|`'standings'`| +|maxGames|`number`|When in `games` mode, how many games should be shown (it will show this many most recent games).|`6`| +|teamNameClass|`string`|CSS class to apply to displayed team names.|`'light'`| +|updateInterval|`number`|How frequently, in milliseconds, to update the info.|`12 * 60 * 60 * 1000` (every 12 hours)| + +Here is an example of an entry in config.js +``` +{ + module: 'MMM-ShiftStats', + header: 'Standings', + position: 'top_left', + config: { + teamName: 'Bears', + sport: 'Hockey' + } +}, +``` + +## Screenshot +![Screenshot](/screenshot.png?raw=true "screenshot") + +## Notes +Pull requests are very welcome! If you'd like to see any additional functionality, don't hesitate to let me know. + +## Dependencies +This uses a Node.JS library I created for interfacing with DigitalShift sites: [node-shiftstats](https://github.com/parnic/node-shiftstats), so feel free to check that out for more information. diff --git a/node_helper.js b/node_helper.js new file mode 100644 index 0000000..31cdd65 --- /dev/null +++ b/node_helper.js @@ -0,0 +1,59 @@ +let NodeHelper = require('node_helper') +let ShiftStats = require('node-shiftstats') + +module.exports = NodeHelper.create({ + start: function() { + this.setTimer(12 * 60 * 60 * 1000) + }, + + doUpdate: async function() { + console.log("doupdate") + if (!this.config) { + return + } + + const s = new ShiftStats(this.config.apiKey) + await s.login() + let teams = await s.teamSearch(this.config.sport, this.config.teamName) +// standings will be sorted in the order of the type we requested (regular season, playoffs, ...) but also includes +// stats for other types. consider sorting by the type we want first. see property division_rank + let standings = await s.divisionStandings(teams.references.division[0].id, this.config.type) + let games = sortGames(await s.divisionGamesList(teams.references.division[0].id), this.config.type) + this.sendSocketNotification('SHIFTSTATS_STANDINGS', {standings: standings, games: games}) + }, + + setTimer: function(updateInterval) { + var update = true + update = typeof this.updateInterval === 'undefined' || this.updateInterval != updateInterval + this.updateInterval = updateInterval + + if (update) { + if (typeof this.timer !== 'undefined') { + clearInterval(this.timer) + } + + this.timer = setInterval(() => { + this.doUpdate() + }, this.updateInterval) + } + }, + + socketNotificationReceived: function(notification, payload) { + if (notification === 'SHIFTSTATS_CONFIG') { + this.config = payload + this.setTimer(this.config.updateInterval) + } + if (notification === 'SHIFTSTATS_UPDATE') { + this.doUpdate() + } + } +}) + +function sortGames(games, type) { + games.games = games.games.filter(game => game.type == type) + games.games.sort((a, b) => { + return new Date(a.datetime) - new Date(b.datetime) + }) + + return games +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..93bc9f3 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "magic-mirror-module-shiftstate", + "version": "1.0.0", + "description": "Show data from DigitalShift sites", + "main": "MMM-ShiftStats.js", + "author": "Chris Pickett", + "repository": "https://github.com/parnic/MMM-ShiftStats.git", + "license": "MIT", + "dependencies": { + "node-shiftstats": "^1.0.0" + } +} diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..35a7332 Binary files /dev/null and b/screenshot.png differ diff --git a/shiftstats.css b/shiftstats.css new file mode 100644 index 0000000..d5e9d0d --- /dev/null +++ b/shiftstats.css @@ -0,0 +1,11 @@ +.MMM-ShiftStats td { + padding: 2px 5px 2px 5px; +} + +.MMM-ShiftStats td.center, th.center { + text-align: center; +} + +.MMM-ShiftStats td.left { + text-align: left; +}