diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5956f32..41e0c23 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -3,6 +3,3 @@ Thanks for contributing to this module! Please create pull requests to the branch `develop`. - -To hold one code style and standard there are several linters and tools in this project set. Make sure you fullfill the requirements. -Also there will be automatically analysis performed once you created the pull request. diff --git a/.github/example.jpg b/.github/example.jpg deleted file mode 100644 index 3e4c81b..0000000 Binary files a/.github/example.jpg and /dev/null differ diff --git a/.github/example1.JPG b/.github/example1.JPG new file mode 100644 index 0000000..a42cacb Binary files /dev/null and b/.github/example1.JPG differ diff --git a/.github/example2.JPG b/.github/example2.JPG new file mode 100644 index 0000000..4985cd3 Binary files /dev/null and b/.github/example2.JPG differ diff --git a/.github/example_focused.png b/.github/example_focused.png deleted file mode 100644 index 3fcaaf2..0000000 Binary files a/.github/example_focused.png and /dev/null differ diff --git a/.github/example_full.png b/.github/example_full.png deleted file mode 100644 index 2d377fa..0000000 Binary files a/.github/example_full.png and /dev/null differ diff --git a/.gitignore b/.gitignore index 5148e52..f6a245a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ build/Release node_modules jspm_packages +package-lock.json + # Optional npm cache directory .npm diff --git a/CHANGELOG.md b/CHANGELOG.md index 602a0b6..83c2a11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,4 @@ -# MMM-soccer Changelog - -## [2.0.0] - -### Added - -* Club logo downloader -* New config option `logos`. -* Swedish translations -* Documentation -* [Doclets.io](https://doclets.io/fewieden/MMM-soccer/master) integration -* Contributing guidelines -* Issue template -* Pull request template -* Editor config - -### Changed - -* Switched from Api v1 to v2. -* Updated league ids. -* Switched rendering from js to nunjuck template. -* Updated travis-ci config. -* Disabled markdown lint rules `MD024` and `MD026` +# MMM-soccer v2 changelog ## [1.0.0] diff --git a/LICENSE b/LICENSE index 7eedfa7..050a484 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 fewieden +Copyright (c) 2020 lavolp3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MMM-soccer.css b/MMM-soccer.css index 5a7465f..8a2b349 100644 --- a/MMM-soccer.css +++ b/MMM-soccer.css @@ -1,8 +1,16 @@ +.MMM-soccer .soccerWrapper { + #display: flex; +} + .MMM-soccer .icon { width: 24px; height: 24px; } +.MMM-soccer .big-icon { + width: 48px; +} + .MMM-soccer .no-color { -webkit-filter: grayscale(100%); } @@ -11,8 +19,45 @@ text-align: left; } -.MMM-soccer .table { - border-spacing: 2px; +.MMM-soccer .matchDay { + text-align: center; + margin-bottom: 10px; +} + +.MMM-soccer .games { + margin-bottom: 20px; +} + +.MMM-soccer .games .status { + padding: 5px; + border: 4px solid black; + background-color: #99aacc; + color: black; + border-radius: 5px; + text-align: center; + font-size: 100%; + width: 55px; + min-width: 55px; +} + +.MMM-soccer .games .status.TIMED{ + font-size: 90%; +} + +.MMM-soccer .games .IN_PLAY { + color: #fdfd96; +} + +.MMM-soccer .games .status.IN_PLAY { + background-color: #fdfd96; + color: black; +} + +.MMM-soccer .logo { + max-width: 25px; + width: 25px; + padding: 0px 8px; + vertical-align: middle; } .MMM-soccer .centered-row { @@ -23,6 +68,19 @@ text-align: center; } +.MMM-soccer .focused { + font-weight: bold; +} + +.MMM-soccer .name .IN_PLAY { + color: yellow; +} + + +.MMM-soccer .table { + margin: 10px 0px; +} + .MMM-soccer-blur { -webkit-filter: blur(2px) brightness(50%); } @@ -41,3 +99,23 @@ .MMM-soccer .modal ul { margin: 0; } + +.MMM-soccer .homeTeam { + text-align: right; +} + +.MMM-soccer .matchName { + max-width: 50%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.MMM-soccer .matchName.winner { + font-weight: bold; + #text-decoration: underline; +} + +.MMM-soccer svg { + background-color: #000; +} diff --git a/MMM-soccer.js b/MMM-soccer.js index 87aa5b2..0139e05 100644 --- a/MMM-soccer.js +++ b/MMM-soccer.js @@ -1,73 +1,51 @@ /** * @file MMM-soccer.js * - * @author fewieden + * @author lavolp3/fewieden * @license MIT * - * @see https://github.com/fewieden/MMM-soccer + * @see https://github.com/lavolp3/MMM-soccer */ -/* global Module Log */ +/* jshint esversion: 6 */ -/** - * @external Module - * @see https://github.com/MichMich/MagicMirror/blob/master/js/module.js - */ - -/** - * @external Log - * @see https://github.com/MichMich/MagicMirror/blob/master/js/logger.js - */ +/* global Module Log */ -/** - * @module MMM-soccer - * @description Frontend of the MagicMirror² module. - * - * @requires external:Module - * @requires external:Log - */ Module.register('MMM-soccer', { - /** - * @member {Object} defaults - Defines the default config values. - * @property {boolean|string} api_key - API acces key for football-data.org. - * @property {boolean} colored - Flag to show logos in color or black/white. - * @property {string} show - Country name (uppercase) to be shown in module. - * @property {boolean|Object} focus_on - Hash of country name -> club name to determine highlighted team per league. - * @property {boolean|int} max_teams - Maximium amount of teams to be displayed. - * @property {boolean} logos - Flag to show club logos. - * @property {Object} leagues - Hash of country name -> league id. - */ + defaults: { api_key: false, colored: false, - show: 'GERMANY', + width: 400, + show: ['BL1', 'CL', 'PL'], + updateInterval: 30, + apiCallInterval: 10 * 60, focus_on: false, + fadeFocus: true, max_teams: false, - logos: false, + logos: true, + showTables: true, + showMatches: true, + showMatchDay: true, + matchType: 'league', //choose 'next', 'daily', or 'league' + numberOfNextMatches: 8, leagues: { GERMANY: 'BL1', FRANCE: 'FL1', ENGLAND: 'PL', SPAIN: 'PD', ITALY: 'SA' - } + }, + replace: 'default', //choose 'default', 'short' or '' for original names + daysOffset: 0, + debug: false, }, - /** - * @member {Object} modals - Stores the status of the module's modals. - * @property {boolean} standings - Full standings table. - * @property {boolean} help - List of voice commands of this module. - */ modals: { standings: false, help: false }, - /** - * @member {Object} voice - Defines the default mode and commands of this module. - * @property {string} mode - Voice mode of this module. - * @property {string[]} sentences - List of voice commands of this module. - */ voice: { mode: 'SOCCER', sentences: [ @@ -79,293 +57,349 @@ Module.register('MMM-soccer', { ] }, - /** - * @member {boolean} loading - Flag to indicate the loading state of the module. - */ loading: true, - /** - * @member {Object[]} standing - Stores the list of standing table entries of current selected league. - */ - standing: [], - /** - * @member {Object} competition - Details about the current selected league. - */ - competition: {}, - - /** - * @function start - * @description Adds nunjuck filters and requests for league data. - * @override - */ - start() { + tables: {}, + matches: {}, + teams: {}, + matchDay: "", + showTable: true, + leagues: [], + liveMode: false, + liveMatches: [], + liveLeagues: [], + replacements: { + default: {} + }, + competition: '', + + + start: function() { Log.info(`Starting module: ${this.name}`); this.addFilters(); - this.currentLeague = this.config.leagues[this.config.show]; - this.getData(); - setInterval(() => { - this.getData(); - }, this.config.api_key ? 300000 : 1800000); // with api_key every 5min without every 30min + this.leagues = this.config.show; + this.competition = this.leagues[0]; + this.showTable = this.config.showTables; + var self = this; + this.replacers = this.loadReplacements(response => { + self.replacements = JSON.parse(response); + //self.log(self.replacements); + }); + this.sendSocketNotification('GET_SOCCER_DATA', this.config); + this.scheduleDOMUpdates(); }, - /** - * @function start - * @description Sends request to the node_helper to fetch data for the current selected league. - */ - getData() { - this.sendSocketNotification('GET_DATA', { league: this.currentLeague, api_key: this.config.api_key }); + + loadReplacements: function(callback) { + this.log("Loading replacements file"); + var xobj = new XMLHttpRequest(); + var path = this.file('replacements.json'); + xobj.overrideMimeType("application/json"); + xobj.open("GET", path, true); + xobj.onreadystatechange = function() { + if (xobj.readyState === 4 && xobj.status === 200) { + callback(xobj.responseText); + } + }; + xobj.send(null); }, - /** - * @function socketNotificationReceived - * @description Handles incoming messages from node_helper. - * @override - * - * @param {string} notification - Notification name - * @param {*} payload - Detailed payload of the notification. - */ - socketNotificationReceived(notification, payload) { - if (notification === 'DATA') { - this.standing = payload.standings[0].table; - this.season = payload.season; - this.competition = payload.competition; + + scheduleDOMUpdates: function () { + var count = 0; + var self = this; + setInterval(() => { + const comps = self.leagues.length; + count = (count >= comps - 1) ? 0 : count + 1; + self.competition = self.leagues[count]; + self.log("Showing competition: " + self.competition); + self.log(self.tables[self.competition]); + self.standing = self.filterTables(self.tables[self.competition], self.config.focus_on[self.competition]); + self.updateDom(500); + }, this.config.updateInterval * 1000); + }, + + + socketNotificationReceived: function(notification, payload) { + this.log(`received a Socket Notification: ${notification}`); + if (notification === 'TABLES') { + this.log(payload); + this.tables = payload; + this.standing = this.filterTables(this.tables[this.competition], this.config.focus_on[this.competition]); + this.log("Current table: " + JSON.stringify(this.standing)); + } else if (notification === 'MATCHES') { + this.matches = payload; + this.log("Received matches: "+this.matches); + } else if (notification === 'TEAMS') { + this.teams = payload; + /*} else if (notification === 'LIVE_MATCHES') { + var matches = payload;*/ + } else if (notification === 'LIVE') { + this.liveMode = payload.live; + this.leagues = (payload.leagues.length > 0) ? payload.leagues : this.config.show; + this.liveMatches = payload.matches; + } + if (this.loading === true && this.tables.hasOwnProperty(this.competition) && this.matches.hasOwnProperty(this.competition)) { this.loading = false; this.updateDom(); } }, - /** - * @function notificationReceived - * @description Handles incoming broadcasts from other modules or the MagicMirror² core. - * @override - * - * @param {string} notification - Notification name - * @param {*} payload - Detailed payload of the notification. - * @param {Object} sender - Module that sent the notification or undefined for MagicMirror² core. - */ - notificationReceived(notification, payload, sender) { + notificationReceived: function(notification, payload, sender) { if (notification === 'ALL_MODULES_STARTED') { const voice = Object.assign({}, this.voice); voice.sentences.push(Object.keys(this.config.leagues).join(' ')); this.sendNotification('REGISTER_VOICE_MODULE', voice); } else if (notification === 'VOICE_SOCCER' && sender.name === 'MMM-voice') { this.checkCommands(payload); - } else if (notification === 'VOICE_MODE_CHANGED' && sender.name === 'MMM-voice' - && payload.old === this.voice.mode) { + } else if (notification === 'VOICE_MODE_CHANGED' && sender.name === 'MMM-voice' && payload.old === this.voice.mode) { this.closeAllModals(); - this.updateDom(300); + this.updateDom(500); } }, - /** - * @function getStyles - * @description Style dependencies for this module. - * @override - * - * @returns {string[]} List of the style dependency filepaths. - */ - getStyles() { - return ['font-awesome.css', 'MMM-soccer.css']; + getStyles: function() { + return ['MMM-soccer.css']; }, - /** - * @function getTranslations - * @description Translations for this module. - * @override - * - * @returns {Object.} Available translations for this module (key: language code, value: filepath). - */ - getTranslations() { + getTranslations: function() { return { en: 'translations/en.json', de: 'translations/de.json', id: 'translations/id.json', - sv: 'translations/sv.json' + sv: 'translations/sv.json', + fr: 'translations/fr.json' }; }, - /** - * @function getTemplate - * @description Nunjuck template. - * @override - * - * @returns {string} Path to nunjuck template. - */ - getTemplate() { + getTemplate: function() { return 'MMM-soccer.njk'; }, - /** - * @function getTemplateData - * @description Data that gets rendered in the nunjuck template. - * @override - * - * @returns {string} Data for the nunjuck template. - */ - getTemplateData() { + + getTemplateData: function() { return { - boundaries: this.calculateTeamDisplayBoundaries(), - competitionName: this.competition.name || this.name, + boundaries: (this.tables.hasOwnProperty(this.competition)) ? this.calculateTeamDisplayBoundaries(this.competition) : {}, + matchHeader: this.getMatchHeader(), config: this.config, isModalActive: this.isModalActive(), modals: this.modals, - season: this.season ? - `${this.translate('MATCHDAY')}: ${this.season.currentMatchday || 'N/A'}` : this.translate('LOADING'), - standing: this.standing, + table: this.standing, + comps: (Object.keys(this.matches).length > 0) ? this.prepareMatches(this.matches, this.config.focus_on[this.competition]) : "", + showTable: this.showTable, + teams: (Object.keys(this.tables).length > 0) ? this.teams : {}, + showMatchDay: this.config.showMatchDay, voice: this.voice }; }, - /** - * @function handleModals - * @description Hide/show modules based on voice commands. - */ - handleModals(data, modal, open, close) { - if (close.test(data) || (this.modals[modal] && !open.test(data))) { - this.closeAllModals(); - } else if (open.test(data) || (!this.modals[modal] && !close.test(data))) { - this.closeAllModals(); - this.modals[modal] = true; + getMatchHeader: function() { + if (this.config.matchType == "daily") { + return { + competition: this.translate('TODAYS_MATCHES'), + season: (Object.keys(this.tables).length > 0) ? "" : this.translate('LOADING'), + } + } else if (this.config.matchType == "next") { + return { + competition: this.translate('NEXT_MATCHES'), + season: (Object.keys(this.tables).length > 0) ? "" : this.translate('LOADING'), + } } + return { + competition: (Object.keys(this.tables).length > 0) ? this.tables[this.competition].competition.name : "", + season: (Object.keys(this.tables).length > 0) ? `${this.translate('MATCHDAY')}: ${this.translate(this.matchDay)}` : this.translate('LOADING'), + } + }, - const modules = document.querySelectorAll('.module'); - for (let i = 0; i < modules.length; i += 1) { - if (!modules[i].classList.contains('MMM-soccer')) { - if (this.isModalActive()) { - modules[i].classList.add('MMM-soccer-blur'); - } else { - modules[i].classList.remove('MMM-soccer-blur'); + + prepareMatches: function(allMatches, focusTeam) { + var returnedMatches = []; + if (this.config.matchType === 'league') { + var diff = 0; + var matches = allMatches[this.competition].matches; + var minDiff = Math.abs(moment().diff(matches[0].utcDate)); + for (var m = 0; m < matches.length; m++) { + if (!matches[m].matchday) { matches[m].matchday = matches[m].stage; } //for cup modes, copy stage to matchday property + diff = Math.abs(moment().diff(matches[m].utcDate)); + if (diff < minDiff) { + minDiff = diff; + this.matchDay = matches[m].matchday; } } + this.log("Current matchday: " + this.matchDay); + this.showTable = (!isNaN(this.matchDay)); + returnedMatches.push({ + competition: (Object.keys(this.tables).length > 0) ? this.tables[this.competition].competition.name : "", + emblem: (Object.keys(this.tables).length > 0) ? this.tables[this.competition].competition.emblem : "", + season: (Object.keys(this.tables).length > 0) ? `${this.translate('MATCHDAY')}: ${this.translate(this.matchDay)}` : this.translate('LOADING'), + matches: matches.filter(match => { + return match.matchday == this.matchDay; + }) + }); + } else if (this.config.matchType === 'next') { + var teams = []; + var nextMatches = []; + for (var comp in this.config.focus_on) { + teams.push(this.config.focus_on[comp]); + } + for (var league in allMatches) { + filteredMatches = allMatches[league].matches.filter(match => { + return (teams.includes(match.homeTeam.name) || teams.includes(match.awayTeam.name)); + }); + var index = filteredMatches.findIndex(match => { + return (parseInt(moment(match.utcDate).format("X")) > parseInt(moment().format("X"))); + }); + for (var i = index - 1; i < filteredMatches.length; i++) { + nextMatches.push(filteredMatches[i]); + } + } + nextMatches.sort(function (match1, match2) { + return (moment(match1.utcDate) - moment(match2.utcDate)); + }); + returnedMatches.push({ + competition: this.translate('NEXT_MATCHES'), + season: (Object.keys(this.tables).length > 0) ? "" : this.translate('LOADING'), + matches: nextMatches.slice(0, this.config.numberOfNextMatches) + }); + + } else if (this.config.matchType === 'daily') { + var today = moment().subtract(this.config.daysOffset, 'days'); + var todaysMatches = []; + for (var league in allMatches) { + var filteredMatches = allMatches[league].matches.filter(match => { + return ( moment(match.utcDate).isSame(today, 'day') ); + }); + this.log("Filtered macthes: "); + this.log(filteredMatches); + if (filteredMatches.length) { + returnedMatches.push({ + competition: (Object.keys(this.tables).length > 0) ? this.tables[league].competition.name : "", + season: (Object.keys(this.tables).length > 0) ? "" : this.translate('LOADING'), + matches: filteredMatches + }); + } + } + /*todaysMatches = todaysMatches.flat(); + todaysMatches.sort(function (match1, match2) { + return (match1.season.id - match2.season.id); + }); + returnedMatches.push({ + competition: this.translate('TODAYS_MATCHES'), + season: (Object.keys(this.tables).length > 0) ? "" : this.translate('LOADING'), + matches: todaysMatches + });*/ } + returnedMatches.forEach(matchset => { + matchset.matches.forEach(match => { + if (this.config.matchType == "league" || this.config.matchType == "daily") { + match.focused = (match.homeTeam.name === focusTeam) ? true : (match.awayTeam.name === focusTeam) ? true : false; + } + if (match.status == "TIMED" || match.status == "SCHEDULED" || match.status == "POSTPONED") { + match.state = (moment(match.utcDate).diff(moment(), 'days') > 7) ? moment(match.utcDate).format("D.MM.") : (moment(match.utcDate).startOf('day') > moment()) ? moment(match.utcDate).format("dd HH:mm") : moment(match.utcDate).format("LT"); + } else { + match.state = match.score.fullTime.home + " - " + match.score.fullTime.away; + if (match.score.winner == "HOME_TEAM") { + match.homeTeam["status"] = "winner" + } else if (match.score.winner == "AWAY_TEAM") { + match.awayTeam["status"] = "winner" + } + } + }); + }); + this.log("Returned matches:"); + this.log(returnedMatches); + return returnedMatches; }, - /** - * @function closeAllModals - * @description Close all modals of the module. - */ - closeAllModals() { - const modals = Object.keys(this.modals); - modals.forEach((modal) => { this.modals[modal] = false; }); - }, - - /** - * @function isModalActive - * @description Checks if at least one modal is active. - * - * @returns {boolean} Flag if there is an active modal. - */ - isModalActive() { - const modals = Object.keys(this.modals); - return modals.some(modal => this.modals[modal] === true); - }, - /** - * @function checkCommands - * @description Voice command handler. - */ - checkCommands(data) { - if (/(HELP)/g.test(data)) { - this.handleModals(data, 'help', /(OPEN)/g, /(CLOSE)/g); - } else if (/(VIEW)/g.test(data)) { - this.handleModals(data, 'standings', /(EXPAND)/g, /(COLLAPSE)/g); - } else if (/(STANDINGS)/g.test(data)) { - const countrys = Object.keys(this.config.leagues); - for (let i = 0; i < countrys.length; i += 1) { - const regexp = new RegExp(countrys[i], 'g'); - if (regexp.test(data)) { - this.closeAllModals(); - if (this.currentLeague !== this.config.leagues[countrys[i]]) { - this.currentLeague = this.config.leagues[countrys[i]]; - this.getData(); + filterTables: function(tables, focusTeam) { + //filtering out "home" and "away" tables + if (tables && !tables.standings) return ""; + tableArray = tables.standings.filter(table => { + return table.type === "TOTAL"; + }); + if (tableArray[0].group === "GROUP_A" && this.config.focus_on.hasOwnProperty(tables.competition.code)) { //cup mode + for (var t = 0; t < tableArray.length; t++) { + for (var n = 0; n < tableArray[t].table.length; n++) { + if (tableArray[t].table[n].team.name === focusTeam) { + table = tableArray[t].table; } - break; } } + } else { + table = tableArray[0].table; } - this.updateDom(300); + return table; }, - /** - * @function isMaxTeamsLessAll - * @description Are there more entries than the config option specifies. - * - * @returns {boolean} - */ - isMaxTeamsLessAll() { - return (this.config.max_teams && this.config.max_teams <= this.standing.length); - }, - /** - * @function findFocusTeam - * @description Helper function to find index of team in standings - * - * @returns {Object} Index of team, first and last team to display. - */ - findFocusTeam() { - let focusTeamIndex; - - for (let i = 0; i < this.standing.length; i += 1) { - if (this.standing[i].team.name === this.config.focus_on[this.config.show]) { + findFocusTeam: function() { + this.log("Finding focus team for table..."); + let focusTeamIndex = -1; + var table = this.standing; + for (let i = 0; i < table.length; i ++) { + if (table[i].team.name === this.config.focus_on[this.competition]) { focusTeamIndex = i; + this.log("Focus Team found: " + table[i].team.name); break; } } - const { firstTeam, lastTeam } = this.getFirstAndLastTeam(focusTeamIndex); - - return { focusTeamIndex, firstTeam, lastTeam }; + if (focusTeamIndex < 0) { + this.log("No Focus Team found! Please check your entry!"); + return { + focusTeamIndex: -1, + firstTeam: 0, + lastTeam: this.config.max_teams || this.standing.length + }; + } else { + const { firstTeam, lastTeam } = this.getFirstAndLastTeam(focusTeamIndex); + return { focusTeamIndex, firstTeam, lastTeam }; + } }, - /** - * @function getFirstAndLastTeam - * @description Helper function to get the boundaries of the teams that should be displayed. - * - * @returns {Object} Index of the first and the last team. - */ - getFirstAndLastTeam(index) { + + getFirstAndLastTeam: function(index) { let firstTeam; let lastTeam; if (this.config.max_teams) { const before = parseInt(this.config.max_teams / 2); - firstTeam = index - before >= 0 ? index - before : 0; + firstTeam = (index - before >= 0) ? (index - before) : 0; if (firstTeam + this.config.max_teams <= this.standing.length) { lastTeam = firstTeam + this.config.max_teams; } else { lastTeam = this.standing.length; - firstTeam = lastTeam - this.config.max_teams >= 0 ? - lastTeam - this.config.max_teams : 0; + /*firstTeam = lastTeam - this.config.max_teams >= 0 ? + lastTeam - this.config.max_teams : 0;*/ } } else { firstTeam = 0; lastTeam = this.standing.length; } - + this.log({firstTeam, lastTeam}); return { firstTeam, lastTeam }; }, - /** - * @function calculateTeamDisplayBoundaries - * @description Calculates the boundaries of teams based on the config. - * - * @returns {Object} Index of team, first and last team to display. - */ - calculateTeamDisplayBoundaries() { - if (this.config.focus_on && Object.prototype.hasOwnProperty.call(this.config.focus_on, this.config.show)) { - if (this.config.focus_on[this.config.show] === 'TOP') { + + calculateTeamDisplayBoundaries: function(competition) { + this.log("Calculating Team Display Boundaries"); + if (this.config.focus_on && this.config.focus_on.hasOwnProperty(competition)) { + if (this.config.focus_on[competition] === 'TOP') { + this.log("Focus on TOP"); return { focusTeamIndex: -1, firstTeam: 0, lastTeam: this.isMaxTeamsLessAll() ? this.config.max_teams : this.standing.length }; - } else if (this.config.focus_on[this.config.show] === 'BOTTOM') { + } else if (this.config.focus_on[this.leagues] === 'BOTTOM') { + this.log("Focus on BOTTOM"); return { focusTeamIndex: -1, firstTeam: this.isMaxTeamsLessAll() ? this.standing.length - this.config.max_teams : 0, lastTeam: this.standing.length }; } - + this.log("Focus on Team"); return this.findFocusTeam(); } @@ -376,20 +410,94 @@ Module.register('MMM-soccer', { }; }, - /** - * @function addFilters - * @description Adds the filter used by the nunjuck template. - */ - addFilters() { - this.nunjucksEnvironment().addFilter('fade', (index, focus) => { - if (this.config.max_teams && focus >= 0) { + + + isMaxTeamsLessAll: function() { + return (this.config.max_teams && this.config.max_teams <= this.standing.length); + }, + + + handleModals: function(data, modal, open, close) { + if (close.test(data) || (this.modals[modal] && !open.test(data))) { + this.closeAllModals(); + } else if (open.test(data) || (!this.modals[modal] && !close.test(data))) { + this.closeAllModals(); + this.modals[modal] = true; + } + + const modules = document.querySelectorAll('.module'); + for (let i = 0; i < modules.length; i += 1) { + if (!modules[i].classList.contains('MMM-soccer')) { + if (this.isModalActive()) { + modules[i].classList.add('MMM-soccer-blur'); + } else { + modules[i].classList.remove('MMM-soccer-blur'); + } + } + } + }, + + + closeAllModals: function() { + const modals = Object.keys(this.modals); + modals.forEach((modal) => { this.modals[modal] = false; }); + }, + + + isModalActive: function() { + const modals = Object.keys(this.modals); + return modals.some(modal => this.modals[modal] === true); + }, + + + checkCommands: function(data) { + if (/(HELP)/g.test(data)) { + this.handleModals(data, 'help', /(OPEN)/g, /(CLOSE)/g); + } else if (/(VIEW)/g.test(data)) { + this.handleModals(data, 'standings', /(EXPAND)/g, /(COLLAPSE)/g); + } else if (/(STANDINGS)/g.test(data)) { + const countrys = Object.keys(this.config.leagues); + for (let i = 0; i < countrys.length; i += 1) { + const regexp = new RegExp(countrys[i], 'g'); + if (regexp.test(data)) { + this.closeAllModals(); + if (this.currentLeague !== this.config.leagues[countrys[i]]) { + this.currentLeague = this.config.leagues[countrys[i]]; + this.getData(); + } + break; + } + } + } + this.updateDom(300); + }, + + + addFilters: function () { + njEnv = this.nunjucksEnvironment(); + njEnv.addFilter('fade', (index, focus) => { + if (this.config.max_teams && this.config.fadeFocus && focus >= 0) { if (index !== focus) { const currentStep = Math.abs(index - focus); return `opacity: ${1 - ((1 / this.config.max_teams) * currentStep)}`; } } - return ''; }); - } + + njEnv.addFilter('replace', (team) => { + var replace = this.config.replace; + if ((replace == 'default' || replace == 'short') && (this.replacements.default.hasOwnProperty(team))) { + return this.replacements[replace][team]; + } else { + return team; + } + }); + }, + + log: function (msg) { + if (this.config && this.config.debug) { + console.log(this.name + ":", JSON.stringify(msg)); + } + }, }); diff --git a/MMM-soccer.njk b/MMM-soccer.njk index 8a8c34e..28e61cd 100644 --- a/MMM-soccer.njk +++ b/MMM-soccer.njk @@ -1,77 +1,127 @@ -
-
- {% if loading %} - {{'LOADING' | translate}} - {% else %} -
{{competitionName}}
-
{{season}}
- - - - - {% if config.logos %} - - {% endif %} - - - +
+
MMM-Soccer
+ +{% if loading %} + {{'LOADING' | translate}} +{% else %} + {% if config.showMatches %} +
+ {% if comps.length %} + {% for i in range(0, comps.length) %} +
{{'TEAM' | translate}}
+ + + + - - - {% for index in range(boundaries.firstTeam, boundaries.lastTeam) %} - - - {% if config.logos %} - - {% endif %} - - - - - {% endfor %} - -
+ + {{ comps[i].competition }}
{{standing[index].position}}{{standing[index].team.name}}{{standing[index].points}}{{standing[index].goalDifference}}
- {% endif %} -
- {% if isModalActive %} - - {% endif %} + + + {% for m in range(0, comps[i].matches.length) %} + + + {% if config.logos %} + + {% endif %} + + {% if config.logos %} + + {% endif %} + + + {% endfor %} + +
{{comps[i].matches[m].homeTeam.name | replace}}{{ comps[i].matches[m].state }}{{comps[i].matches[m].awayTeam.name | replace}}
+ {% endfor %} + {% else %} + + {{ 'NO_MATCHES' | translate }} + + {% endif %} +
+ {% endif %} + + {% if showTable %} + + + + + {% if config.logos %} + + {% endif %} + + + + + + + + {% for i in range(boundaries.firstTeam, boundaries.lastTeam) %} + + + + {% if config.logos %} + + {% endif %} + + + + + + {% endfor %} + +
{{'TEAM' | translate}}
{{table[i].position}}{{table[i].team.name | replace}}{{table[i].playedGames}}{{table[i].points}}{% if table[i].goalDifference > 0 %}+{% endif %}{{table[i].goalDifference}}
+ {% endif %} +{% endif %} + + +{% if isModalActive %} + +{% endif %} diff --git a/README.md b/README.md index c770047..2368c88 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,116 @@ -# MMM-soccer [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://raw.githubusercontent.com/fewieden/MMM-soccer/master/LICENSE) [![Build Status](https://travis-ci.org/fewieden/MMM-soccer.svg?branch=master)](https://travis-ci.org/fewieden/MMM-soccer) [![Code Climate](https://codeclimate.com/github/fewieden/MMM-soccer/badges/gpa.svg?style=flat)](https://codeclimate.com/github/fewieden/MMM-soccer) [![Known Vulnerabilities](https://snyk.io/test/github/fewieden/mmm-soccer/badge.svg)](https://snyk.io/test/github/fewieden/mmm-soccer) [![API Doc](https://doclets.io/fewieden/MMM-soccer/master.svg)](https://doclets.io/fewieden/MMM-soccer/master) +# MMM-soccer [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://raw.githubusercontent.com/fewieden/MMM-soccer/master/LICENSE) -European Soccer Standings Module for MagicMirror² +A Soccer Standings Module for MagicMirror², based on @fewiedens [MMM-soccer](https://github.com/fewieden/MMM-soccer) -## Example +## Examples -![](.github/example_full.png) ![](.github/example_focused.png) -![](.github/example.jpg) +![](.github/example1.JPG) ![](.github/example2.JPG) ## Dependencies * An installation of [MagicMirror²](https://github.com/MichMich/MagicMirror) * OPTIONAL: [Voice Control](https://github.com/fewieden/MMM-voice) * npm -* [request](https://www.npmjs.com/package/request) +* [axios](https://www.npmjs.com/package/axios) ## Installation -1. Clone this repo into `~/MagicMirror/modules` directory. -1. Configure your `~/MagicMirror/config/config.js`: +1. Clone this repo into your `~/MagicMirror/modules` directory. +``` +git clone https://github.com/lavolp3/MMM-soccer +``` +2. Run command `npm install` in `~/MagicMirror/modules/MMM-soccer` directory. +3. Add the module to your `~/MagicMirror/config/config.js`: ``` { module: 'MMM-soccer', - position: 'bottom_right', + position: 'top_left', config: { - ... + api_key: '', + show: ['CL', 'BL1', 'PL'], + colored: true, + updateInterval: 60, + focus_on: { + null + }, + max_teams: 5, + matchType: 'league' } - } + }, ``` -1. Run command `npm i --production` in `~/MagicMirror/modules/MMM-soccer` directory. -1. Optional: Get a free api key [here](http://api.football-data.org/register) + +4. Optional: Get a free api key [here](http://api.football-data.org/register) (highly recommended) + ## Config Options | **Option** | **Default** | **Description** | | --- | --- | --- | -| `api_key` | false | Either false (limited to 50 requests a day) or an API Key obtained from (limited to 50 requests a minute) . | -| `colored` | false | Boolean to show club logos in color or not. | -| `show` | 'GERMANY' | Which league should be displayed 'GERMANY', 'FRANCE', 'ENGLAND', 'SPAIN' or 'ITALY' | -| `focus_on` | false | Which team should the standings focus on per league e.g. {"GERMANY": "FC Bayern München", "FRANCE": "Olympique Lyonnais"}. Omit this option or set to false to show the full league table. | -| `max_teams` | false | How many teams should be displayed. Omit this option or set to false to show the full league table. | -| `leagues` | `{"GERMANY": "BL1", "FRANCE": "FL1", "ENGLAND": "PL", "SPAIN": "PD", "ITALY": "SA"}` | A collection of leagues obtained from | -| `logos` | `false` | Boolean to show club logos or not. | +| `width` | `400` | Width of match and standings table. The module has a flexible design aligning matches and table vertically or horizontically as space allows. | +| `api_key` | false | Either false (limited to 50 requests a day) or an API Key obtained from (limited to 10 requests a minute) . | +| `colored` | true | Boolean to show club logos in color or not. | +| `show` | ['BL1', 'PL', 'CL'] | An array of league codes to be displayed. In normal mode, the leagues revolve using below update cycle. With activated touch mode (see below), you can choose one of the leagues via a button (planned) | +| `updateInterval` | 60 | The time frame for each league to be shown in seconds. | +| `apiCallInterval` | 10 | The time frame for API calls (in minutes) in normal mode. | +| `showMatches` | true | Show matches of current league | +| `showTables` | true | Show table of current league. **Note:** For cups like Champions League, this will be set to false in knockout rounds. | +| `focus_on` | null | Which team to focus on per league. This needs to be an object, e.g. {'BL1': 'FC Bayern München', 'CL': 'Liverpool FC'}. **See description below.** | +| `fadeFocus` | true | Includes fading the teams out if one is focused. | +| `max_teams` | false | How many teams should be displayed when focus is activated. Omit this option or set to false to show the full league table. | +| `replace` | 'default' | Choose between 'default' for a default replacement of original club names or 'short' for a 3-Letter-Code of the teams. Choose anything else (like '') for original team names from the API. **See below** for further information | +| `logos` | true | Boolean to show club logos. | +| `liveMode` | true | Activates live mode when games are in play. (see below) | +| `matchType` | 'league' | Choose between the following: `'league'` for showing the current matchday of selected leagues (in `show`), `'next'` for showing the next matches of all your focused clubs (in `focus_on`), `'daily'` for showing all of todays matches for selected leagues. | +| `numberOfNextMatches` | 8 | Defines number of next matches of all focused clubs for matchType `'next'` | +| `touchMode` | false | Activates touch mode with touch options (see below, not active yet) | +| `debug` | false | Debug mode: additional output on server side (console) and client side (browser) | + + +## Focus + +You can focus on one time per league/cup using the focus_on method. This variable needs to be an object. +An example is below: +``` +focus_on: { + 'BL1': 'FC Bayern München', + 'CL': 'Liverpool FC' +}, +``` +Please take care to include all quotation marks, separate with commata, and use the same league codes (find below) you have included in the 'show' array. +The team name needs to correspond to the original name of the team as provided by the API. +Have a look into the `replace` object in the config to see if the team name is replaced with a shorter one on the mirror. If that is the case, take the original one (the one on the left for each replace property). + +Omitting a league code from `'show'` in this array will show the full league table and not include any focus. +Any league included here need to be included in `'show'` as well to show the league on your mirror. + +## Replacements +There is a `replacements.json` file in the directory including all teams of the free plan. By default, the default replacement for the original team name will be used in the module. You can choose between 'default' mode or 'short' mode showing the 3-letter ID code for the team for a super slim module. + +## Live Mode + +The module calls all requested matches every X minutes (see config option `apiCallInterval`). Whenever one or more matches are scheduled in less than this interval, a Live Mode will activate. +All matches currently played will be included in an array and requested once every minute. +~Additional informations like game minute and scorers will be provided for these games.~ (another API is needed for this) +Also, only the leagues with current matches will be shown. +When no game is live, the module will return back to normal mode. + +Can be switched off in config. + -## Logos +## Touch mode (planned) -As the v2 api doesn't provide logos anymore, I developed a club logo downloader. It supports the five major leagues as above named. -To run the downloader you need to execute the following steps. +Touch mode will create buttons to choose between leagues. +It is also planned to include more detailed information like scorers per league and scorers per game. -1. Go to the module directory `cd ~/MagicMirror/modules/MMM-soccer`. -1. Execute `node scripts/downloader COUNTRYNAME`. -1. Run this command for all the leagues you want to display on the mirror. -1. Don't forget to activate the display of the logos in the config. +Can be switched off in config. -If there isn't every club logo, you can also place them manually in the public directory of the module, -the logos need to be in `svg` format and the name of the file has to match the displayed name. -## OPTIONAL: Voice Control +## OPTIONAL: Voice Control (may be bugged!) + +This module supports voice control by @fewiedens [MMM-voice](https://github.com/fewieden/MMM-voice). In order to use this feature, it's required to install the voice module. There are no extra config options for voice control needed. -This module supports voice control by -[MMM-voice](https://github.com/fewieden/MMM-voice). In order to use this -feature, it's required to install the voice module. There are no extra config -options for voice control needed. ### Mode @@ -78,3 +126,38 @@ The voice control mode for this module is `SOCCER` you have to edit the config) * EXPAND VIEW -> Expands the standings table and shows all teams. * COLLAPSE VIEW -> Collapse the expanded view. + + +## List of available leagues (for the free API): + +As per the [Football-data API Docs](https://www.football-data.org/documentation/api#league-codes): + + +| **League** | **Code** | +| --- | --- | +| (Europe) Champions League | 'CL' | +| (Europe) European Championship 2020 | 'EC' | +| (English) Premier League | 'PL' | +| (English) Championship | 'ELC' | +| (German) Bundesliga | 'BL1' | +| (Italian) Serie A | 'SA' | +| (French) Ligue 1 | 'FL1' | +| (Spain) La Liga | 'PD' | +| (Portugal) Primiera Liga | 'PPL' | +| (Netherlands) Eredivisie | 'DED' | +| (Brazil) Serie A | '' | + + +### TODOs + +- [ ] Current top scorer list per league +- [ ] Touch mode +- [ ] Tap additional API (presumably API-football) for further competitions (e.g. DFB cup) +- [ ] Option to show fixed table head with focus on. +- [x] Highlight currently playing teams in table. + + +Add team specific data, e.g. +- [ ] next matches +- [ ] ~current squad / line-up~ not available in free plan! +- [ ] ~Include option to show scorers for each match~ not available in free plan! diff --git a/node_helper.js b/node_helper.js index 32dc1c2..d2ce5e0 100644 --- a/node_helper.js +++ b/node_helper.js @@ -1,80 +1,228 @@ /** * @file node_helper.js * - * @author fewieden + * @author lavolp3 / fewieden (original module) * @license MIT * - * @see https://github.com/fewieden/MMM-soccer + * @see https://github.com/lavolp3/MMM-soccer */ -/* eslint-env node */ -/* eslint-disable no-console */ +/* jshint esversion: 6 */ -/** - * @external request - * @see https://www.npmjs.com/package/request - */ -const request = require('request'); - -/** - * @external node_helper - * @see https://github.com/MichMich/MagicMirror/blob/master/modules/node_modules/node_helper/index.js - */ +const axios = require('axios'); const NodeHelper = require('node_helper'); +const moment = require('moment'); -/** - * @module node_helper - * @description Backend for the module to query data from the API provider. - * - * @requires external:request - * @requires external:node_helper - */ module.exports = NodeHelper.create({ - /** - * @function start - * @description Logs a start message to the console. - * @override - */ - start() { + matches: {}, + tables: {}, + teams: {}, + teamList: {}, + liveMatches: [], + liveLeagues: [], + isRunning: false, + + start: function() { console.log(`Starting module: ${this.name}`); }, - /** - * @function socketNotificationReceived - * @description Receives socket notifications from the module. - * @override - * - * @param {string} notification - Notification name - * @param {*} payload - Detailed payload of the notification. - */ - socketNotificationReceived(notification, payload) { - if (notification === 'GET_DATA') { - const options = { - url: `http://api.football-data.org/v2/competitions/${payload.league}/standings` - }; - if (payload.api_key) { - options.headers = { 'X-Auth-Token': payload.api_key }; + + socketNotificationReceived: function(notification, payload) { + if (notification === 'GET_SOCCER_DATA') { + this.log("Socket notification received: " + notification + " Payload: " + JSON.stringify(payload)); + this.config = payload; + this.leagues = this.config.show; + this.headers = payload.api_key ? { 'X-Auth-Token': payload.api_key } : {}; + this.getTables(this.leagues); + this.getMatches(this.leagues); + if (!this.isRunning) { + this.log("Starting API call cycle"); + this.liveMode = false; + this.isRunning = true; + this.scheduleAPICalls(false); } - this.getData(options); } }, - /** - * @function getData - * @description Request data from the supplied URL and broadcast it to the MagicMirror module if it's received. - * - * @param {Object} options - request optionsthe notification. - */ - getData(options) { - console.log(`Get league table for url ${options.url}`); - request(options, (error, response, body) => { - if (response.statusCode === 200) { - this.sendSocketNotification('DATA', JSON.parse(body)); + scheduleAPICalls: function(live) { + var self = this; + //var updateInterval = (this.liveLeagues.length > 0) ? (60/(Math.floor(5/this.liveLeagues.length))) * 1000 : this.config.apiCallInterval * 1000; + var updateInterval = (this.liveLeagues.length > 0) ? 60 * 1000 : this.config.apiCallInterval * 1000; + this.callInterval = setInterval(() => { + self.getTables(self.leagues); + self.getMatches(self.leagues); + //self.getMatchDetails(self.liveMatches); + }, updateInterval); + }, + + getTables: function(leagues) { + var self = this; + this.log("Collecting league tables for leagues: "+leagues); + var urlArray = leagues.map(league => { return `http://api.football-data.org/v4/competitions/${league}/standings`; }); + Promise.all(urlArray.map(url => { + return axios.get(url, { headers: self.headers }) + .then(function (response) { + self.log("Requests available: " + response.headers["x-requests-available-minute"]); + var tableData = response.data; + var tables = { + competition: tableData.competition, + season: tableData.season, + standings: tableData.standings, + }; + self.log(tables); + return(tables); + }) + .catch(function (error) { + self.handleErrors(error, url); + return {}; + }); + })) + .then(function(tableArray) { + tableArray.forEach(tables => { + if (tables.hasOwnProperty('standings')) { + tables.standings.forEach(standing => { + standing.table.forEach(team => { + self.teams[team.team.id] = team.team; + self.teamList[team.team.name] = team.team.name; + }); + }); + self.tables[tables.competition.code] = tables; + } + }); + //self.log("Collected Tables: " + self.tables); + self.log("Collected Teams: " + JSON.stringify(self.teams)); + self.sendSocketNotification("TABLES", self.tables); + self.sendSocketNotification("TEAMS", self.teams); + }) + /*.catch(function(error) { + console.error("[MMM-soccer] ERROR occured while fetching tables: " + error); + });*/ + }, + + getMatches:function(leagues) { + var now = moment().subtract(60*13, "minutes"); //subtract minutes or hours to test live mode + this.log("Collecting matches for leagues: " + leagues); + var urlArray = leagues.map(league => { return `http://api.football-data.org/v4/competitions/${league}/matches`; }); + this.liveLeagues = []; + var self = this; + Promise.all(urlArray.map(url => { + return axios.get(url, { headers: self.headers }) + .then(function (response) { + //self.log("Requests available: " + response.headers["x-requests-available-minute"]); + var matchesData = response.data; + var currentLeague = matchesData.competition.code; + matchesData.matches.forEach(match => { + delete match.referees; + + //check for live matches + if (match.status == "IN_PLAY" || Math.abs(moment(match.utcDate).diff(now, 'seconds')) < self.config.apiCallInterval * 2) { + if (self.liveMatches.indexOf(match.id) === -1) { + self.log(`Live match detected starting at ${moment(match.utcDate).format("HH:mm")}, Home Team: ${match.homeTeam.name}`); + self.log(`Live match ${match.id} added at ${moment().format("HH:mm")}`); + self.liveMatches.push(match.id); + } + if (self.liveLeagues.indexOf(currentLeague) === -1) { + self.log(`Live league ${currentLeague} added at ${moment().format("HH:mm")}`); + self.liveLeagues.push(currentLeague); + } + } else { + if (self.liveMatches.indexOf(match.id)!= -1) { + self.log("Live match finished!"); + self.liveMatches.splice(self.liveMatches.indexOf(match.id), 1); + } + } + }); + return(matchesData); + self.log(matchesData); + }) + .catch(function (error) { + self.handleErrors(error, url); + return {}; + }); + })) + .then(function (matchesArray) { + matchesArray.forEach(comp => { + if (comp.hasOwnProperty('competition')) { + self.matches[comp.competition.code] = comp; + } + }); + //self.log(self.matches); + self.log("Live matches: "+JSON.stringify(self.liveMatches)); + self.log("Live leagues: "+JSON.stringify(self.liveLeagues)); + self.sendSocketNotification("MATCHES", self.matches); + self.toggleLiveMode(self.liveMatches.length > 0); + }) + .catch(function(error) { + console.error("[MMM-soccer] ERROR occured while fetching matches: " + error); + }); + }, + + + getMatchDetails: function (matches) { + var self = this; + this.log("Getting match details for matches: " + matches); + var urlArray = matches.map(match => { return `http://api.football-data.org/v2/matches/${match}`; }); + Promise.all(urlArray.map(url => { + return axios.get(url, { headers: self.headers }) + .then(function (response) { + self.log("Requests available: " + response.headers["x-requests-available-minute"]); + var matchData = response.data; + self.log(matchData); + if (matchData.match.status != "IN_PLAY" && self.liveMatches.indexOf(matchData.match.id)!= -1) { + self.log("Live match finished"); + self.liveMatches.splice(self.liveMatches.indexOf(comp.matches[m].id), 1); + self.log("Live matches: "+self.liveMatches); + } + return(matchData); + }) + .catch(function (error) { + self.handleErrors(error, url); + return {}; + }); + })) + .then(function (liveMatchesArray) { + /*LiveMatchesArray.forEach(match => { + liveMatches[match.match.competition.id] = match; + });*/ + self.sendSocketNotification("LIVE_MATCHES", liveMatchesArray); + }); + }, + + handleErrors: function(error, url) { + console.log("An error occured while requesting the API for Data: "+error); + console.log("URL: "+url); + if (error.response && error.response.status === 429) { + console.log(error.response.status + ": API Request Quota of 10 calls per minute exceeded. Try selecting less leagues."); + } else if (error.request) { + //console.log(error.request); + } else { + // Something happened in setting up the request that triggered an Error + console.log('Error: ', error.message); + } + }, + + toggleLiveMode: function (isLive) { + if (isLive != this.liveMode) { + clearInterval(this.callInterval); + if (isLive) { + this.log("Live Mode activated!"); + //this.leagues = this.liveLeagues; + this.sendSocketNotification("LIVE", { live: true, matches: this.liveMatches, leagues: this.liveLeagues }); + this.scheduleAPICalls(true); } else { - this.sendSocketNotification('DATA'); - console.log(`Error getting league table ${response.statusCode}`); + this.log("Usual mode active!"); + //this.leagues = this.config.show; + this.sendSocketNotification("LIVE", { live: false, matches: this.liveMatches, leagues: this.liveLeagues }); + this.scheduleAPICalls(false); } - }); - } + this.liveMode = isLive; + } + }, + + log: function (msg) { + if (this.config && this.config.debug) { + console.log(this.name + ":", JSON.stringify(msg)); + } + }, }); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..eef4e56 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,58 @@ +{ + "name": "mmm-soccer", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "mmm-soccer", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "axios": "^0.21.1" + } + }, + "node_modules/axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "dependencies": { + "follow-redirects": "^1.10.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + } + }, + "dependencies": { + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "follow-redirects": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" + } + } +} diff --git a/package.json b/package.json index 3353552..16cf2fa 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,25 @@ { "name": "mmm-soccer", - "version": "1.0.1", + "version": "1.0.0", "description": "European Soccer Standings Module for MagicMirror2", "repository": { "type": "git", - "url": "git+https://github.com/fewieden/MMM-soccer.git" + "url": "git+https://github.com/lavolp3/MMM-soccer.git" }, "keywords": [ "MagicMirror", - "soccer standings" + "soccer standings", + "soccer", + "tables", + "football" ], - "author": "fewieden", + "author": "lavolp3", "license": "MIT", "bugs": { - "url": "https://github.com/fewieden/MMM-soccer/issues" - }, - "homepage": "https://github.com/fewieden/MMM-soccer#readme", - "scripts": { - "lint": "./node_modules/.bin/eslint . && ./node_modules/.bin/stylelint .", - "docs": "./node_modules/.bin/jsdoc -c jsdoc.json ." + "url": "https://github.com/lavolp3/MMM-soccer/issues" }, + "homepage": "https://github.com/lavolp3/MMM-soccer#readme", "dependencies": { - "request": "^2.74.0" - }, - "devDependencies": { - "eslint": "^3.14.1", - "eslint-config-airbnb-base": "^11.0.1", - "eslint-plugin-import": "^2.2.0", - "stylelint": "^7.8.0", - "stylelint-config-standard": "^16.0.0" + "axios": "^0.21.1" } } diff --git a/replacements.json b/replacements.json new file mode 100644 index 0000000..565f672 --- /dev/null +++ b/replacements.json @@ -0,0 +1,345 @@ +{ + "default": { + "Paris Saint-Germain FC":"Paris Saint-Germain", + "Real Madrid CF":"Real Madrid", + "Club Brugge KV":"Club Brugge", + "Galatasaray SK":"Galatasaray", + "FC Bayern München":"Bayern München", + "Tottenham Hotspur FC":"Tottenham Hotspur", + "PAE Olympiakos SFP":"Olympiakos Piräus", + "FK Crvena Zvezda":"Roter Stern Belgrad", + "Manchester City FC":"Manchester City", + "Atalanta BC":"Atalanta BC", + "FK Shakhtar Donetsk":"Shakhtar Donetsk", + "GNK Dinamo Zagreb":"Dinamo Zagreb", + "Juventus FC":"Juventus FC", + "Club Atlético de Madrid":"Athletico Madrid", + "Bayer 04 Leverkusen":"Bayer Leverkusen", + "FK Lokomotiv Moskva":"Lokomotive Moskva", + "Liverpool FC":"Liverpool", + "SSC Napoli":"SSC Napoli", + "FC Red Bull Salzburg":"Red Bull Salzburg", + "KRC Genk":"KRC Genk", + "FC Barcelona":"Barcelona", + "BV Borussia 09 Dortmund":"Borussia Dortmund", + "FC Internazionale Milano":"Inter Mailand", + "SK Slavia Praha":"Slavia Prag", + "RB Leipzig":"RB Leipzig", + "Olympique Lyonnais":"Olympique Lyon", + "Sport Lisboa e Benfica":"Benfica Lissabon", + "FK Zenit Sankt-Petersburg":"Zenit Sankt-Petersburg", + "Valencia CF":"Valencia", + "Chelsea FC":"Chelsea", + "AFC Ajax":"Ajax Amsterdam", + "Lille OSC":"Lille OSC", + "Borussia Mönchengladbach":"Bor Mönchengladbach", + "FC Schalke 04":"FC Schalke 04", + "VfL Wolfsburg":"VfL Wolfsburg", + "TSG 1899 Hoffenheim":"1899 Hoffenheim", + "SC Freiburg":"SC Freiburg", + "1. FC Union Berlin":"Union Berlin", + "Eintracht Frankfurt":"Eintracht Frankfurt", + "FC Augsburg":"FC Augsburg", + "1. FC Köln":"1. FC Köln", + "Hertha BSC":"Hertha BSC", + "1. FSV Mainz 05":"FSV Mainz", + "TSV Fortuna 95 Düsseldorf":"Fortuna Düsseldorf", + "DSC Arminia Bielefeld": "Arminia Bielefeld", + "SV Werder Bremen":"Werder Bremen", + "SC Paderborn 07":"SC Paderborn", + "Leicester City FC":"Leicester City", + "Manchester United FC":"Manchester United", + "Sheffield United FC":"Sheffield United", + "Wolverhampton Wanderers FC":"Wolverhampton Wanderers", + "Arsenal FC":"Arsenal London", + "Burnley FC":"Burnley", + "Everton FC":"Everton", + "Southampton FC":"Southampton", + "Crystal Palace FC":"Crystal Palace", + "Newcastle United FC":"Newcastle United", + "Brighton & Hove Albion FC":"Brighton & Hove Albion", + "AFC Bournemouth":"Bournemouth", + "Aston Villa FC":"Aston Villa", + "West Ham United FC":"West Ham United", + "Watford FC":"Watford", + "Norwich City FC":"Norwich City", + "West Bromwich Albion FC":"West Bromwich Albion", + "Leeds United AFC":"Leeds United", + "Nottingham Forest FC":"Nottingham Forest", + "Fulham FC":"Fulham", + "Brentford FC":"Brentford", + "Preston North End FC":"Preston North End", + "Bristol City FC":"Bristol City", + "Blackburn Rovers FC":"Blackburn Rovers", + "Swansea City AFC":"Swansea City", + "Cardiff City FC":"Cardiff City", + "Millwall FC":"Millwall", + "Queens Park Rangers FC":"Queens Park Rangers", + "Sheffield Wednesday FC":"Sheffield Wednesday", + "Derby County FC":"Derby County", + "Birmingham City FC":"Birmingham City", + "Reading FC":"Reading", + "Hull City AFC":"Hull City", + "Charlton Athletic FC":"Charlton Athletic", + "Huddersfield Town AFC":"Huddersfield Town", + "Stoke City FC":"Stoke City", + "Middlesbrough FC":"Middlesbrough", + "Wigan Athletic FC":"Wigan Athletic", + "Luton Town FC":"Luton Town", + "Barnsley FC":"Barnsley FC", + "SS Lazio":"Lazio Rom", + "AS Roma":"AS Rom", + "AC Milan":"AC Mailand", + "Hellas Verona FC":"Hellas Verona", + "Parma Calcio 1913":"Parma Calcio", + "Bologna FC 1909":"Bologna FC", + "Cagliari Calcio":"Cagliari Calcio", + "ACF Fiorentina":"ACF Fiorentina", + "US Sassuolo Calcio":"Sassuolo Calcio", + "Udinese Calcio":"Udinese Calcio", + "Torino FC":"FC Turin", + "US Lecce":"US Lecce", + "UC Sampdoria":"UC Sampdoria", + "Genoa CFC":"Genoa CFC", + "Brescia Calcio":"Brescia Calcio", + "SPAL 2013":"SPAL 2013", + "Olympique de Marseille":"Olympique Marseille", + "Stade Rennais FC 1901":"Stade Rennais", + "AS Monaco FC":"AS Monaco", + "RC Strasbourg Alsace":"Strasbourg Alsace", + "Stade de Reims":"Stade de Reims", + "Montpellier HSC":"Montpellier HSC", + "OGC Nice":"OGC Nice", + "FC Nantes":"FC Nantes", + "FC Girondins de Bordeaux":"Girondins de Bordeaux", + "Stade Brestois 29":"Stade Brestois", + "Angers SCO":"Angers SCO", + "AS Saint-Étienne":"Saint-Étienne", + "FC Metz":"FC Metz", + "Dijon Football Côte d'Or":"FCO Dijon", + "Nîmes Olympique":"Olympique Nîmes", + "Amiens SC":"Amiens SC", + "Toulouse FC":"Toulouse FC", + "Sevilla FC":"Sevilla FC", + "Getafe CF":"Getafe CF", + "Real Sociedad de Fútbol":"Real Sociedad", + "Villarreal CF":"Villarreal CF", + "Granada CF":"Granada CF", + "Levante UD":"Levante UD", + "Athletic Club":"Athletico Bilbao", + "CA Osasuna":"CA Osasuna", + "Real Betis Balompié":"Betis Sevilla", + "Deportivo Alavés":"Deportivo Alavés", + "Real Valladolid CF":"Real Valladolid", + "SD Eibar":"SD Eibar", + "RC Celta de Vigo":"Celta de Vigo", + "RCD Mallorca":"RCD Mallorca", + "CD Leganés":"CD Leganés", + "RCD Espanyol de Barcelona":"Espanyol de Barcelona", + "FC Porto":"FC Porto", + "Sporting Clube de Braga":"Sporting Braga", + "Sporting Clube de Portugal":"Sporting Lissabon", + "Rio Ave FC":"Rio Ave FC", + "FC Famalicão":"FC Famalicão", + "Vitória SC":"Vitória SC", + "CD Santa Clara":"CD Santa Clara", + "Boavista FC":"Boavista FC", + "Moreirense FC":"Moreirense FC", + "Gil Vicente FC":"Gil Vicente FC", + "Vitória FC":"Vitória FC", + "Os Belenenses Futebol":"Os Belenenses Futebol", + "CD Tondela":"CD Tondela", + "CS Marítimo":"CS Marítimo", + "FC Paços de Ferreira":"FC Paços de Ferreira", + "Portimonense SC":"Portimonense SC", + "CD Aves":"CD Aves", + "AZ":"AZ Alkmaar", + "Feyenoord Rotterdam":"Feyenoord Rotterdam", + "PSV":"PSV Eindhoven", + "Willem II Tilburg":"Willem II Tilburg", + "FC Utrecht":"FC Utrecht", + "SBV Vitesse":"Vitesse Arnhem", + "FC Groningen":"FC Groningen", + "Heracles Almelo":"Heracles Almelo", + "SC Heerenveen":"SC Heerenveen", + "Sparta Rotterdam":"Sparta Rotterdam", + "FC Emmen":"FC Emmen", + "FC Twente '65":"FC Twente", + "VVV Venlo":"VVV Venlo", + "Fortuna Sittard":"Fortuna Sittard", + "PEC Zwolle":"PEC Zwolle", + "ADO Den Haag":"ADO Den Haag", + "RKC Waalwijk":"RKC Waalwijk" + }, + "short": { + "Paris Saint-Germain FC":"PSG", + "Real Madrid CF":"MAD", + "Club Brugge KV":"BRU", + "Galatasaray SK":"GAL", + "FC Bayern München":"BAY", + "Tottenham Hotspur FC":"TOT", + "PAE Olympiakos SFP":"OLY", + "FK Crvena Zvezda":"RSB", + "Manchester City FC":"MCI", + "Atalanta BC":"ATT", + "FK Shakhtar Donetsk":"SHK", + "GNK Dinamo Zagreb":"DZA", + "Juventus FC":"JUV", + "Club Atlético de Madrid":"ATM", + "Bayer 04 Leverkusen":"B04", + "FK Lokomotiv Moskva":"LMO", + "Liverpool FC":"LIV", + "SSC Napoli":"NAP", + "FC Red Bull Salzburg":"RBS", + "KRC Genk":"KRC Genk", + "FC Barcelona":"FCB", + "BV Borussia 09 Dortmund":"BVB", + "FC Internazionale Milano":"INT", + "SK Slavia Praha":"SLP", + "RB Leipzig":"RBL", + "Olympique Lyonnais":"LYO", + "Sport Lisboa e Benfica":"SLB", + "FK Zenit Sankt-Petersburg":"ZSP", + "Valencia CF":"VAL", + "Chelsea FC":"CHE", + "AFC Ajax":"AJA", + "Lille OSC":"LIL", + "Borussia Mönchengladbach":"BMG", + "FC Schalke 04":"S04", + "VfL Wolfsburg":"WOB", + "TSG 1899 Hoffenheim":"TSG", + "SC Freiburg":"SCF", + "1. FC Union Berlin":"UNB", + "Eintracht Frankfurt":"SGE", + "FC Augsburg":"AUG", + "1. FC Köln":"CGN", + "Hertha BSC":"BSC", + "1. FSV Mainz 05":"MAI", + "TSV Fortuna 95 Düsseldorf":"DUS", + "SV Werder Bremen":"SVW", + "SC Paderborn 07":"SCP", + "Leicester City FC":"LEI", + "Manchester United FC":"MUN", + "Sheffield United FC":"SHU", + "Wolverhampton Wanderers FC":"WLV", + "Arsenal FC":"ARS", + "Burnley FC":"BUR", + "Everton FC":"EVE", + "Southampton FC":"SOU", + "Crystal Palace FC":"CRY", + "Newcastle United FC":"NEW", + "Brighton & Hove Albion FC":"BRH", + "AFC Bournemouth":"BOU", + "Aston Villa FC":"AVA", + "West Ham United FC":"WHU", + "Watford FC":"WAT", + "Norwich City FC":"NOR", + "West Bromwich Albion FC":"WBA", + "Leeds United AFC":"LEE", + "Nottingham Forest FC":"NTG", + "Fulham FC":"FUL", + "Brentford FC":"BRE", + "Preston North End FC":"PNE", + "Bristol City FC":"BSC", + "Blackburn Rovers FC":"BBR", + "Swansea City AFC":"SWA", + "Cardiff City FC":"CDF", + "Millwall FC":"MLW", + "Queens Park Rangers FC":"QPR", + "Sheffield Wednesday FC":"SHF", + "Derby County FC":"DRB", + "Birmingham City FC":"BRC", + "Reading FC":"RDG", + "Hull City AFC":"HUL", + "Charlton Athletic FC":"CHA", + "Huddersfield Town AFC":"HDD", + "Stoke City FC":"STK", + "Middlesbrough FC":"MID", + "Wigan Athletic FC":"WIG", + "Luton Town FC":"LUT", + "Barnsley FC":"BRS", + "SS Lazio":"LAZ", + "AS Roma":"ROM", + "AC Milan":"MIL", + "Hellas Verona FC":"HEL", + "Parma Calcio 1913":"PRM", + "Bologna FC 1909":"BGN", + "Cagliari Calcio":"CAG", + "ACF Fiorentina":"FIO", + "US Sassuolo Calcio":"SAS", + "Udinese Calcio":"UDI", + "Torino FC":"TOR", + "US Lecce":"LEC", + "UC Sampdoria":"SAM", + "Genoa CFC":"GEN", + "Brescia Calcio":"BCA", + "SPAL 2013":"SPA", + "Olympique de Marseille":"OLM", + "Stade Rennais FC 1901":"REN", + "AS Monaco FC":"AMO", + "RC Strasbourg Alsace":"STR", + "Stade de Reims":"REI", + "Montpellier HSC":"MPL", + "OGC Nice":"NCE", + "FC Nantes":"NAN", + "FC Girondins de Bordeaux":"BOR", + "Stade Brestois 29":"B29", + "Angers SCO":"ANG", + "AS Saint-Étienne":"STE", + "FC Metz":"MTZ", + "Dijon Football Côte d'Or":"DIJ", + "Nîmes Olympique":"NIM", + "Amiens SC":"AMI", + "Toulouse FC":"TOU", + "Sevilla FC":"SEV", + "Getafe CF":"GET", + "Real Sociedad de Fútbol":"SOC", + "Villarreal CF":"VIL", + "Granada CF":"GCF", + "Levante UD":"LVT", + "Athletic Club":"ATB", + "CA Osasuna":"OSA", + "Real Betis Balompié":"SEV", + "Deportivo Alavés":"ALV", + "Real Valladolid CF":"REV", + "SD Eibar":"EIB", + "RC Celta de Vigo":"CLV", + "RCD Mallorca":"RCD", + "CD Leganés":"LEG", + "RCD Espanyol de Barcelona":"ESY", + "FC Porto":"POR", + "Sporting Clube de Braga":"SBR", + "Sporting Clube de Portugal":"SLI", + "Rio Ave FC":"RIO", + "FC Famalicão":"FAM", + "Vitória SC":"VDG", + "CD Santa Clara":"SCL", + "Boavista FC":"BOA", + "Moreirense FC":"MOE", + "Gil Vicente FC":"GVI", + "Vitória FC":"SET", + "Os Belenenses Futebol":"BLN", + "CD Tondela":"TND", + "CS Marítimo":"MTI", + "FC Paços de Ferreira":"PAC", + "Portimonense SC":"PRT", + "CD Aves":"AVE", + "AZ":"AZA", + "Feyenoord Rotterdam":"FEY", + "PSV":"PSV", + "Willem II Tilburg":"WIL", + "FC Utrecht":"UTR", + "SBV Vitesse":"VIT", + "FC Groningen":"GRO", + "Heracles Almelo":"HEA", + "SC Heerenveen":"HEE", + "Sparta Rotterdam":"SPR", + "FC Emmen":"EMM", + "FC Twente '65":"TWE", + "VVV Venlo":"VVV", + "Fortuna Sittard":"FST", + "PEC Zwolle":"PEC", + "ADO Den Haag":"ADO", + "RKC Waalwijk":"WAA" + } +} diff --git a/scripts/downloader.js b/scripts/downloader.js deleted file mode 100644 index 1363480..0000000 --- a/scripts/downloader.js +++ /dev/null @@ -1,137 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const request = require('request'); - -const leagues = { - GERMANY: 452, - FRANCE: 450, - ENGLAND: 445, - SPAIN: 455, - ITALY: 456 -}; - -if (process.argv.length <= 2) { - throw new Error('You need to specify a country name!'); -} - -const country = process.argv[2].toUpperCase(); - -if (!Object.prototype.hasOwnProperty.call(leagues, country)) { - throw new Error('Selected country is not supported!'); -} - -const options = { - url: `http://api.football-data.org/v1/competitions/${leagues[country]}/leagueTable` -}; - -const missingTeams = { - GERMANY: ['1. FC Nürnberg', 'TSV Fortuna 95 Düsseldorf'], - FRANCE: ['En Avant Guingamp', 'Nîmes Olympique', 'Stade de Reims'], - ENGLAND: ['Cardiff City FC', 'Fulham FC', 'Wolverhampton Wanderers FC'], - SPAIN: ['Rayo Vallecano de Madrid', 'Real Valladolid CF', 'SD Huesca'], - ITALY: ['Empoli FC', 'Frosinone Calcio', 'Parma Calcio 1913'] -}; - -function extendTeams(teams) { - missingTeams[country].forEach((name) => { - teams.push({ teamName: name }); - }); -} - -const iconFixes = { - // Germany - '1. FC Nürnberg': 'https://upload.wikimedia.org/wikipedia/commons/5/56/FC_N%C3%BCrnberg.svg', - 'Bor. Mönchengladbach': 'https://upload.wikimedia.org/wikipedia/commons/8/81/Borussia_M%C3%B6nchengladbach_logo.svg', - 'FC Bayern München': 'https://upload.wikimedia.org/wikipedia/commons/1/1f/Logo_FC_Bayern_M%C3%BCnchen_%282002%E2%80%932017%29.svg', - 'TSV Fortuna 95 Düsseldorf': 'https://upload.wikimedia.org/wikipedia/commons/9/94/Fortuna_D%C3%BCsseldorf.svg', - // France - 'En Avant Guingamp': 'https://vignette.wikia.nocookie.net/logopedia/images/9/99/En_Avant_de_Guingamp_logo.svg', - 'Nîmes Olympique': 'https://upload.wikimedia.org/wikipedia/fr/f/f0/N%C3%AEmes_Olympique_logo_2018.svg', - 'OGC Nice': 'https://upload.wikimedia.org/wikipedia/de/5/58/OGC_Nizza_Logo.svg', - 'OSC Lille': 'https://vignette.wikia.nocookie.net/logopedia/images/a/ab/Lille_OSC_logo.svg', - 'SM Caen': 'https://upload.wikimedia.org/wikipedia/commons/6/64/SM_Caen.svg', - 'Stade de Reims': 'https://upload.wikimedia.org/wikipedia/de/9/9e/Stade_Reims_Logo.svg', - // England - 'Burnley FC': 'https://upload.wikimedia.org/wikipedia/de/4/49/FC_Burnley.svg', - 'Cardiff City FC': 'https://upload.wikimedia.org/wikipedia/de/1/18/Cardiff_City_AFC.svg', - 'Crystal Palace FC': 'https://upload.wikimedia.org/wikipedia/de/f/fc/Crystal_Palace_FC.svg', - 'Fulham FC': 'https://upload.wikimedia.org/wikipedia/de/a/a8/Fulham_fc.svg', - 'Leicester City FC': 'https://upload.wikimedia.org/wikipedia/de/b/b6/Leicester_City.svg', - 'Wolverhampton Wanderers FC': 'https://upload.wikimedia.org/wikipedia/de/1/1d/Wolverhampton_wanderers.svg', - // Spain - 'CD Leganes': 'https://svgur.com/i/890.svg', - 'Málaga CF': 'https://upload.wikimedia.org/wikipedia/de/e/e8/FC_M%C3%A1laga.svg', - 'Rayo Vallecano de Madrid': 'https://upload.wikimedia.org/wikipedia/de/1/12/Rayo_vallecano_madrid.svg', - 'RC Deportivo La Coruna': 'https://upload.wikimedia.org/wikipedia/de/b/b9/Deportivo_La_Coruna.svg', - 'Real Sociedad de Fútbol': 'https://upload.wikimedia.org/wikipedia/de/5/55/Real_Sociedad_San_Sebasti%C3%A1n.svg', - 'Real Valladolid CF': 'https://upload.wikimedia.org/wikipedia/de/6/6e/Real_Valladolid_Logo.svg', - 'SD Huesca': 'https://upload.wikimedia.org/wikipedia/de/7/71/SD_Huesca.svg', - 'Sevilla FC': 'https://upload.wikimedia.org/wikipedia/de/c/c0/FC_Sevilla.svg', - // Italy - 'Benevento Calcio': 'https://upload.wikimedia.org/wikipedia/de/4/48/Benevento_Calcio_Logo.svg', - 'Empoli FC': 'https://upload.wikimedia.org/wikipedia/de/4/42/Logo_FC_Empoli.svg', - 'FC Internazionale Milano': 'https://upload.wikimedia.org/wikipedia/commons/4/41/Inter_Mailand.svg', - 'Frosinone Calcio': 'https://upload.wikimedia.org/wikipedia/de/2/2b/Frosinone_Calcio.svg', - 'Parma Calcio 1913': 'https://upload.wikimedia.org/wikipedia/de/e/e2/FC_Parma.svg', - 'SPAL Ferrara': 'https://upload.wikimedia.org/wikipedia/de/e/e7/SPAL_Ferrara.svg', - 'SS Lazio': 'https://upload.wikimedia.org/wikipedia/sco/e/e4/SS_Lazio.svg', - 'UC Sampdoria': 'https://upload.wikimedia.org/wikipedia/ro/c/c4/UC_Sampdoria.svg' -}; - -const renamedTeams = { - // Germany - 'Bayer Leverkusen': 'Bayer 04 Leverkusen', - 'Borussia Dortmund': 'BV Borussia 09 Dortmund', - 'Bor. Mönchengladbach': 'Borussia Mönchengladbach', - 'Red Bull Leipzig': 'RB Leipzig', - 'Werder Bremen': 'SV Werder Bremen', - // France - 'Dijon FCO': 'Dijon Football Côte d\'Or', - 'Montpellier Hérault SC': 'Montpellier HSC', - 'OGC Nice': 'OGC de Nice Côte d\'Azur', - 'OSC Lille': 'Lille OSC', - 'Paris Saint-Germain': 'Paris Saint-Germain FC', - 'Stade Rennais FC': 'Stade Rennais FC 1901', - // England - 'Brighton & Hove Albion': 'Brighton & Hove Albion FC', - 'Huddersfield Town': 'Huddersfield Town AFC', - // Spain, - 'RCD Espanyol': 'RCD Espanyol de Barcelona', - 'Real Betis': 'Real Betis Balompié', - // Italy - 'Bologna FC': 'Bologna FC 1909', - 'Juventus Turin': 'Juventus FC', - 'SPAL Ferrara': 'SPAL 2013' -}; - -function download(uri, team, callback) { - const filename = path.join(__dirname, '..', 'public', `${Object.prototype.hasOwnProperty.call(renamedTeams, team) ? renamedTeams[team] : team}.svg`); - request(Object.prototype.hasOwnProperty.call(iconFixes, team) ? iconFixes[team] : uri) - .pipe(fs.createWriteStream(filename)) - .on('close', () => callback(team)); -} - -let iconsCount = 0; - -function counter(team) { - iconsCount -= 1; - console.log(`Downloaded: ${team}`); // eslint-disable-line no-console - - if (iconsCount <= 0) { - console.log('All icons downloaded'); // eslint-disable-line no-console - process.exit(1); - } -} - -request(options, (error, response, body) => { - if (response.statusCode === 200) { - const parsedBody = JSON.parse(body); - extendTeams(parsedBody.standing); - iconsCount = parsedBody.standing.length; - - parsedBody.standing.forEach(({ teamName, crestURI }) => { - const uri = Object.prototype.hasOwnProperty.call(iconFixes, teamName) ? iconFixes[teamName] : crestURI; - download(uri, teamName, counter); - }); - } -}); diff --git a/translations/de.json b/translations/de.json index b431072..3ac7dfd 100644 --- a/translations/de.json +++ b/translations/de.json @@ -1,9 +1,17 @@ { "LOADING": "Lade...", "NO_DATA_AVAILABLE": "Keine Daten verfügbar!", + "NO_MATCHES": "Keine Spiele", + "TODAYS_MATCHES": "Heute", + "NEXT_MATCHES": "Nächste Spiele", "MATCHDAY": "Spieltag", "TEAM": "Mannschaft", "COMMAND_LIST": "Liste aller Sprachbefehle", "MODE": "Modus", - "VOICE_COMMANDS": "Sprachbefehle" -} \ No newline at end of file + "VOICE_COMMANDS": "Sprachbefehle", + "PLAY_OFF_ROUND": "Play-Off-Runde", + "ROUND_OF_16": "Achtelfinale", + "QUARTER_FINALS": "Viertelfinale", + "SEMI_FINALS": "Halbfinale", + "FINAL": "Finale" +} diff --git a/translations/en.json b/translations/en.json index 3935324..1d42126 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1,9 +1,17 @@ { - "LOADING": "loading...", + "LOADING": "Loading...", "NO_DATA_AVAILABLE": "No data available!", + "NO_MATCHES": "No matches", + "TODAYS_MATCHES": "Today", + "NEXT_MATCHES": "Next Matches", "MATCHDAY": "Matchday", "TEAM": "Team", "COMMAND_LIST": "List of all Voice Commands", "MODE": "Mode", - "VOICE_COMMANDS": "Voice Commands" -} \ No newline at end of file + "VOICE_COMMANDS": "Voice Commands", + "PLAY_OFF_ROUND": "Play-off round", + "ROUND_OF_16": "Round of 16", + "ROUND_OF_8": "Round of 8", + "ROUND_OF_4": "Semi Finals", + "FINAL": "Finals" +} diff --git a/translations/fr.json b/translations/fr.json new file mode 100644 index 0000000..7860e3e --- /dev/null +++ b/translations/fr.json @@ -0,0 +1,17 @@ +{ + "LOADING": "Chargement...", + "NO_DATA_AVAILABLE": "Aucune donnée disponible !", + "NO_MATCHES": "Aucune correspondance", + "TODAYS_MATCHES": "Aujourd'hui", + "NEXT_MATCHES": "Prochains matchs", + "MATCHDAY": "Jour de match", + "TEAM": "Équipe", + "COMMAND_LIST": "Liste de toutes les commandes vocales", + "MODE": "Mode", + "VOICE_COMMANDS": "Commandes vocales", + "PLAY_OFF_ROUND": "Tour de barrages", + "ROUND_OF_16": "Huitièmes de finale", + "ROUND_OF_8": "Quarts de finale", + "ROUND_OF_4": "Demi-finales", + "FINAL": "Finales" +}