This commit is contained in:
MystaraTheGreat 2021-03-07 20:14:36 +00:00
commit b72556b9a9
138 changed files with 4912 additions and 4824 deletions

View File

@ -6,6 +6,8 @@ If you're not sure if it's a real bug or if it's just you, please open a topic o
Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting) Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting)
A common problem is that your config file could be invalid. Please run in your MagicMirror directory: `npm run config:check` and see if it reports an error.
## I found a bug in the MagicMirror installer ## I found a bug in the MagicMirror installer
If you are facing an issue or found a bug while trying to install MagicMirror via the installer please report it in the respective GitHub repository: If you are facing an issue or found a bug while trying to install MagicMirror via the installer please report it in the respective GitHub repository:
@ -23,9 +25,9 @@ If you are facing an issue or found a bug while running MagicMirror inside a Doc
Please make sure to only submit reproducible issues. You can safely remove everything above the dividing line. Please make sure to only submit reproducible issues. You can safely remove everything above the dividing line.
When submitting a new issue, please supply the following information: When submitting a new issue, please supply the following information:
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX). **Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX).
**Node Version**: Make sure it's version 8 or later. **Node Version**: Make sure it's version 10 or later.
**MagicMirror Version**: Please let us now which version of MagicMirror you are running. It can be found in the `package.log` file. **MagicMirror Version**: Please let us now which version of MagicMirror you are running. It can be found in the `package.log` file.

View File

@ -0,0 +1,24 @@
# This workflow runs the automated test and uploads the coverage results to codecov.io
name: "Run Codecov Tests"
on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master, develop ]
jobs:
run-and-upload-coverage-report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: |
Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99
npm ci
npm run test:coverage
- uses: codecov/codecov-action@v1
with:
file: ./coverage/lcov.info
fail_ci_if_error: true

View File

@ -1,10 +1,12 @@
# This workflow enforces the update of a changelog file on every pull request
name: "Enforce Changelog" name: "Enforce Changelog"
on: on:
pull_request: pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
jobs: jobs:
# Enforces the update of a changelog file on every pull request
check: check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

@ -1,7 +1,7 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Automated Tests name: "Run Automated Tests"
on: on:
push: push:
@ -11,13 +11,10 @@ on:
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [10.x, 12.x, 14.x] node-version: [10.x, 12.x, 14.x]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}

View File

@ -5,11 +5,54 @@ This project adheres to [Semantic Versioning](https://semver.org/).
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror² ❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
## [2.14.1] - 2021-03-07 ## [2.15.0] - Unreleased (Develop Branch)
_This release is scheduled to be released on 2021-04-01._
### Added ### Added
- @MystaraTheGreat added hiddenOnStartup flag to module config (#2475) - Added GitHub workflows for automated testing and changelog enforcement.
- Added CodeCov badge to Readme.
- Added CURRENTWEATHER_TYPE notification to currentweather and weather module, use it in compliments module.
- Added `start:dev` command to the npm scripts for starting electron with devTools open.
- Added logging when using deprecated modules weatherforecast or currentweather.
- Portuguese translations for "MODULE_CONFIG_CHANGED" and PRECIP.
- Respect parameter ColoredSymbolOnly also for custom events
- Added a new parameter to hide time portion on relative times
- `module.show` has now the option for a callback on error.
- Added locale to sample config file
- Added support for self-signed certificates for the default calendar module (#466)
- Added hiddenOnStartup flag to module config (#2475)
### Updated
- Updated markdown files.
- Cleaned up old code on server side.
- Convert `-0` to `0` when displaying temperature.
- Code cleanup for FEELS like and added {DEGREE} placeholder for FEELSLIKE for each language
- Converted newsfeed module to use templates.
- Update documentation and help screen about invalid config files.
- Moving weather provider specific code and configuration into each provider and making hourly part of the interface.
- Bump electron to v11 and enable contextIsolation.
- Dont update the DOM when a module is not displayed.
- Cleaned up jsdoc and tests.
- Exposed logger as node module for easier access for 3rd party modules
### Removed
- Removed danger.js library.
### Fixed
- Added default log levels to stop calendar log spamming.
- Fix socket.io cors errors, see [breaking change since socket.io v3](https://socket.io/docs/v3/handling-cors/)
- Fix Issue with weather forecast icons due to fixed day start and end time (#2221)
- Fix empty directory for each module's main javascript file in the inspector
- Fix Issue with weather forecast icons unit tests with different timezones (#2221)
- Fix issue with unencoded characters in translated strings when using nunjuck template (`Loading …` as an example)
- Fix socket.io backward compatibility with socket v2 clients
- 3rd party module language loading if language is English
- Fix e2e tests after spectron update
## [2.14.0] - 2021-01-01 ## [2.14.0] - 2021-01-01
@ -31,7 +74,6 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
- Calendar: new options "limitDays" and "coloredEvents". - Calendar: new options "limitDays" and "coloredEvents".
- Added new option "limitDays" - limit the number of discreet days displayed. - Added new option "limitDays" - limit the number of discreet days displayed.
- Added new option "customEvents" - use custom symbol/color based on keyword in event title. - Added new option "customEvents" - use custom symbol/color based on keyword in event title.
- Added GitHub workflows for automated testing and changelog enforcement.
### Updated ### Updated

View File

@ -1,6 +1,6 @@
# The MIT License (MIT) # The MIT License (MIT)
Copyright © 2016-2020 Michael Teeuw Copyright © 2016-2021 Michael Teeuw
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation obtaining a copy of this software and associated documentation

View File

@ -1,9 +1,10 @@
![MagicMirror²: The open source modular smart mirror platform. ](.github/header.png) ![MagicMirror²: The open source modular smart mirror platform. ](.github/header.png)
<p align="center"> <p style="text-align: center">
<a href="https://david-dm.org/MichMich/MagicMirror"><img src="https://david-dm.org/MichMich/MagicMirror.svg" alt="Dependency Status"></a> <a href="https://david-dm.org/MichMich/MagicMirror"><img src="https://david-dm.org/MichMich/MagicMirror.svg" alt="Dependency Status"></a>
<a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a> <a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge"></a> <a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge" alt="CLI Best Practices"></a>
<a href="https://codecov.io/gh/MichMich/MagicMirror"><img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg?token=LEG1KitZR6"/></a>
<a href="https://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a> <a href="https://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
<a href="https://github.com/MichMich/MagicMirror/actions?query=workflow%3A%22Automated+Tests%22"><img src="https://github.com/MichMich/MagicMirror/workflows/Automated%20Tests/badge.svg" alt="Tests"></a> <a href="https://github.com/MichMich/MagicMirror/actions?query=workflow%3A%22Automated+Tests%22"><img src="https://github.com/MichMich/MagicMirror/workflows/Automated%20Tests/badge.svg" alt="Tests"></a>
</p> </p>
@ -38,7 +39,6 @@ If we receive enough donations we might even be able to free up some working hou
To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link. To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link.
<p align="center"> <p style="text-align: center">
<br>
<a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a> <a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a>
</p> </p>

View File

@ -14,7 +14,6 @@
* *
* @param {string} key key to look for at the command line * @param {string} key key to look for at the command line
* @param {string} defaultValue value if no key is given at the command line * @param {string} defaultValue value if no key is given at the command line
*
* @returns {string} the value of the parameter * @returns {string} the value of the parameter
*/ */
function getCommandLineParameter(key, defaultValue = undefined) { function getCommandLineParameter(key, defaultValue = undefined) {
@ -36,7 +35,6 @@
* Gets the config from the specified server url * Gets the config from the specified server url
* *
* @param {string} url location where the server is running. * @param {string} url location where the server is running.
*
* @returns {Promise} the config * @returns {Promise} the config
*/ */
function getServerConfig(url) { function getServerConfig(url) {
@ -66,7 +64,7 @@
/** /**
* Print a message to the console in case of errors * Print a message to the console in case of errors
* *
* @param {string} [message] error message to print * @param {string} message error message to print
* @param {number} code error code for the exit call * @param {number} code error code for the exit call
*/ */
function fail(message, code = 1) { function fail(message, code = 1) {

View File

@ -28,6 +28,7 @@ var config = {
httpsCertificate: "", // HTTPS Certificate path, only require when useHttps is true httpsCertificate: "", // HTTPS Certificate path, only require when useHttps is true
language: "en", language: "en",
locale: "en-US",
logLevel: ["INFO", "LOG", "WARN", "ERROR"], // Add "DEBUG" for even more logging logLevel: ["INFO", "LOG", "WARN", "ERROR"], // Add "DEBUG" for even more logging
timeFormat: 24, timeFormat: 24,
units: "metric", units: "metric",
@ -66,22 +67,26 @@ var config = {
position: "lower_third" position: "lower_third"
}, },
{ {
module: "currentweather", module: "weather",
position: "top_right", position: "top_right",
config: { config: {
weatherProvider: "openweathermap",
type: "current",
location: "New York", location: "New York",
locationID: "5128581", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city locationID: "5128581", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
appid: "YOUR_OPENWEATHER_API_KEY" apiKey: "YOUR_OPENWEATHER_API_KEY"
} }
}, },
{ {
module: "weatherforecast", module: "weather",
position: "top_right", position: "top_right",
header: "Weather Forecast", header: "Weather Forecast",
config: { config: {
weatherProvider: "openweathermap",
type: "forecast",
location: "New York", location: "New York",
locationID: "5128581", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city locationID: "5128581", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
appid: "YOUR_OPENWEATHER_API_KEY" apiKey: "YOUR_OPENWEATHER_API_KEY"
} }
}, },
{ {

View File

@ -1,17 +0,0 @@
import { danger, fail, warn } from "danger";
// Check if the CHANGELOG.md file has been edited
// Fail the build and post a comment reminding submitters to do so if it wasn't changed
if (!danger.git.modified_files.includes("CHANGELOG.md")) {
warn("Please include an updated `CHANGELOG.md` file.<br>This way we can keep track of all the contributions.");
}
// Check if the PR request is send to the master branch.
// This should only be done by MichMich.
if (danger.github.pr.base.ref === "master" && danger.github.pr.user.login !== "MichMich") {
// Check if the PR body or title includes the text: #accepted.
// If not, the PR will fail.
if ((danger.github.pr.body + danger.github.pr.title).includes("#accepted")) {
fail("Please send all your pull requests to the `develop` branch.<br>Pull requests on the `master` branch will not be accepted.");
}
}

130
js/app.js
View File

@ -4,22 +4,23 @@
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var fs = require("fs");
var path = require("path");
var Log = require(__dirname + "/logger.js");
var Server = require(__dirname + "/server.js");
var Utils = require(__dirname + "/utils.js");
var defaultModules = require(__dirname + "/../modules/default/defaultmodules.js");
// Alias modules mentioned in package.js under _moduleAliases. // Alias modules mentioned in package.js under _moduleAliases.
require("module-alias/register"); require("module-alias/register");
const fs = require("fs");
const path = require("path");
const Log = require("logger");
const Server = require(`${__dirname}/server`);
const Utils = require(`${__dirname}/utils`);
const defaultModules = require(`${__dirname}/../modules/default/defaultmodules`);
// Get version number. // Get version number.
global.version = JSON.parse(fs.readFileSync("package.json", "utf8")).version; global.version = require(`${__dirname}/../package.json`).version;
Log.log("Starting MagicMirror: v" + global.version); Log.log("Starting MagicMirror: v" + global.version);
// global absolute root path // global absolute root path
global.root_path = path.resolve(__dirname + "/../"); global.root_path = path.resolve(`${__dirname}/../`);
if (process.env.MM_CONFIG_FILE) { if (process.env.MM_CONFIG_FILE) {
global.configuration_file = process.env.MM_CONFIG_FILE; global.configuration_file = process.env.MM_CONFIG_FILE;
@ -45,8 +46,8 @@ process.on("uncaughtException", function (err) {
* *
* @class * @class
*/ */
var App = function () { function App() {
var nodeHelpers = []; let nodeHelpers = [];
/** /**
* Loads the config file. Combines it with the defaults, and runs the * Loads the config file. Combines it with the defaults, and runs the
@ -54,34 +55,31 @@ var App = function () {
* *
* @param {Function} callback Function to be called after loading the config * @param {Function} callback Function to be called after loading the config
*/ */
var loadConfig = function (callback) { function loadConfig(callback) {
Log.log("Loading config ..."); Log.log("Loading config ...");
var defaults = require(__dirname + "/defaults.js"); const defaults = require(`${__dirname}/defaults`);
// For this check proposed to TestSuite // For this check proposed to TestSuite
// https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8 // https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8
var configFilename = path.resolve(global.root_path + "/config/config.js"); const configFilename = path.resolve(global.configuration_file || `${global.root_path}/config/config.js`);
if (typeof global.configuration_file !== "undefined") {
configFilename = path.resolve(global.configuration_file);
}
try { try {
fs.accessSync(configFilename, fs.F_OK); fs.accessSync(configFilename, fs.F_OK);
var c = require(configFilename); const c = require(configFilename);
checkDeprecatedOptions(c); checkDeprecatedOptions(c);
var config = Object.assign(defaults, c); const config = Object.assign(defaults, c);
callback(config); callback(config);
} catch (e) { } catch (e) {
if (e.code === "ENOENT") { if (e.code === "ENOENT") {
Log.error(Utils.colors.error("WARNING! Could not find config file. Please create one. Starting with default configuration.")); Log.error(Utils.colors.error("WARNING! Could not find config file. Please create one. Starting with default configuration."));
} else if (e instanceof ReferenceError || e instanceof SyntaxError) { } else if (e instanceof ReferenceError || e instanceof SyntaxError) {
Log.error(Utils.colors.error("WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: " + e.stack)); Log.error(Utils.colors.error(`WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: ${e.stack}`));
} else { } else {
Log.error(Utils.colors.error("WARNING! Could not load config file. Starting with default configuration. Error found: " + e)); Log.error(Utils.colors.error(`WARNING! Could not load config file. Starting with default configuration. Error found: ${e}`));
} }
callback(defaults); callback(defaults);
} }
}; }
/** /**
* Checks the config for deprecated options and throws a warning in the logs * Checks the config for deprecated options and throws a warning in the logs
@ -89,21 +87,15 @@ var App = function () {
* *
* @param {object} userConfig The user config * @param {object} userConfig The user config
*/ */
var checkDeprecatedOptions = function (userConfig) { function checkDeprecatedOptions(userConfig) {
var deprecated = require(global.root_path + "/js/deprecated.js"); const deprecated = require(`${global.root_path}/js/deprecated`);
var deprecatedOptions = deprecated.configs; const deprecatedOptions = deprecated.configs;
var usedDeprecated = []; const usedDeprecated = deprecatedOptions.filter((option) => userConfig.hasOwnProperty(option));
deprecatedOptions.forEach(function (option) {
if (userConfig.hasOwnProperty(option)) {
usedDeprecated.push(option);
}
});
if (usedDeprecated.length > 0) { if (usedDeprecated.length > 0) {
Log.warn(Utils.colors.warn("WARNING! Your config is using deprecated options: " + usedDeprecated.join(", ") + ". Check README and CHANGELOG for more up-to-date ways of getting the same functionality.")); Log.warn(Utils.colors.warn(`WARNING! Your config is using deprecated options: ${usedDeprecated.join(", ")}. Check README and CHANGELOG for more up-to-date ways of getting the same functionality.`));
}
} }
};
/** /**
* Loads a specific module. * Loads a specific module.
@ -111,35 +103,35 @@ var App = function () {
* @param {string} module The name of the module (including subpath). * @param {string} module The name of the module (including subpath).
* @param {Function} callback Function to be called after loading * @param {Function} callback Function to be called after loading
*/ */
var loadModule = function (module, callback) { function loadModule(module, callback) {
var elements = module.split("/"); const elements = module.split("/");
var moduleName = elements[elements.length - 1]; const moduleName = elements[elements.length - 1];
var moduleFolder = __dirname + "/../modules/" + module; let moduleFolder = `${__dirname}/../modules/${module}`;
if (defaultModules.indexOf(moduleName) !== -1) { if (defaultModules.includes(moduleName)) {
moduleFolder = __dirname + "/../modules/default/" + module; moduleFolder = `${__dirname}/../modules/default/${module}`;
} }
var helperPath = moduleFolder + "/node_helper.js"; const helperPath = `${moduleFolder}/node_helper.js`;
var loadModule = true; let loadHelper = true;
try { try {
fs.accessSync(helperPath, fs.R_OK); fs.accessSync(helperPath, fs.R_OK);
} catch (e) { } catch (e) {
loadModule = false; loadHelper = false;
Log.log("No helper found for module: " + moduleName + "."); Log.log(`No helper found for module: ${moduleName}.`);
} }
if (loadModule) { if (loadHelper) {
var Module = require(helperPath); const Module = require(helperPath);
var m = new Module(); let m = new Module();
if (m.requiresVersion) { if (m.requiresVersion) {
Log.log("Check MagicMirror version for node helper '" + moduleName + "' - Minimum version: " + m.requiresVersion + " - Current version: " + global.version); Log.log(`Check MagicMirror version for node helper '${moduleName}' - Minimum version: ${m.requiresVersion} - Current version: ${global.version}`);
if (cmpVersions(global.version, m.requiresVersion) >= 0) { if (cmpVersions(global.version, m.requiresVersion) >= 0) {
Log.log("Version is ok!"); Log.log("Version is ok!");
} else { } else {
Log.log("Version is incorrect. Skip module: '" + moduleName + "'"); Log.warn(`Version is incorrect. Skip module: '${moduleName}'`);
return; return;
} }
} }
@ -152,7 +144,7 @@ var App = function () {
} else { } else {
callback(); callback();
} }
}; }
/** /**
* Loads all modules. * Loads all modules.
@ -160,12 +152,15 @@ var App = function () {
* @param {Module[]} modules All modules to be loaded * @param {Module[]} modules All modules to be loaded
* @param {Function} callback Function to be called after loading * @param {Function} callback Function to be called after loading
*/ */
var loadModules = function (modules, callback) { function loadModules(modules, callback) {
Log.log("Loading module helpers ..."); Log.log("Loading module helpers ...");
var loadNextModule = function () { /**
*
*/
function loadNextModule() {
if (modules.length > 0) { if (modules.length > 0) {
var nextModule = modules[0]; const nextModule = modules[0];
loadModule(nextModule, function () { loadModule(nextModule, function () {
modules = modules.slice(1); modules = modules.slice(1);
loadNextModule(); loadNextModule();
@ -175,10 +170,10 @@ var App = function () {
Log.log("All module helpers loaded."); Log.log("All module helpers loaded.");
callback(); callback();
} }
}; }
loadNextModule(); loadNextModule();
}; }
/** /**
* Compare two semantic version numbers and return the difference. * Compare two semantic version numbers and return the difference.
@ -190,11 +185,11 @@ var App = function () {
* number if a is smaller and 0 if they are the same * number if a is smaller and 0 if they are the same
*/ */
function cmpVersions(a, b) { function cmpVersions(a, b) {
var i, diff; let i, diff;
var regExStrip0 = /(\.0+)+$/; const regExStrip0 = /(\.0+)+$/;
var segmentsA = a.replace(regExStrip0, "").split("."); const segmentsA = a.replace(regExStrip0, "").split(".");
var segmentsB = b.replace(regExStrip0, "").split("."); const segmentsB = b.replace(regExStrip0, "").split(".");
var l = Math.min(segmentsA.length, segmentsB.length); const l = Math.min(segmentsA.length, segmentsB.length);
for (i = 0; i < l; i++) { for (i = 0; i < l; i++) {
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10); diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
@ -219,21 +214,19 @@ var App = function () {
Log.setLogLevel(config.logLevel); Log.setLogLevel(config.logLevel);
var modules = []; let modules = [];
for (var m in config.modules) { for (const module of config.modules) {
var module = config.modules[m]; if (!modules.includes(module.module) && !module.disabled) {
if (modules.indexOf(module.module) === -1 && !module.disabled) {
modules.push(module.module); modules.push(module.module);
} }
} }
loadModules(modules, function () { loadModules(modules, function () {
var server = new Server(config, function (app, io) { const server = new Server(config, function (app, io) {
Log.log("Server started ..."); Log.log("Server started ...");
for (var h in nodeHelpers) { for (let nodeHelper of nodeHelpers) {
var nodeHelper = nodeHelpers[h];
nodeHelper.setExpressApp(app); nodeHelper.setExpressApp(app);
nodeHelper.setSocketIO(io); nodeHelper.setSocketIO(io);
nodeHelper.start(); nodeHelper.start();
@ -256,8 +249,7 @@ var App = function () {
* Added to fix #1056 * Added to fix #1056
*/ */
this.stop = function () { this.stop = function () {
for (var h in nodeHelpers) { for (const nodeHelper of nodeHelpers) {
var nodeHelper = nodeHelpers[h];
if (typeof nodeHelper.stop === "function") { if (typeof nodeHelper.stop === "function") {
nodeHelper.stop(); nodeHelper.stop();
} }
@ -292,6 +284,6 @@ var App = function () {
this.stop(); this.stop();
process.exit(0); process.exit(0);
}); });
}; }
module.exports = new App(); module.exports = new App();

View File

@ -11,9 +11,9 @@ const linter = new Linter();
const path = require("path"); const path = require("path");
const fs = require("fs"); const fs = require("fs");
const rootPath = path.resolve(__dirname + "/../"); const rootPath = path.resolve(`${__dirname}/../`);
const Log = require(rootPath + "/js/logger.js"); const Log = require(`${rootPath}/js/logger.js`);
const Utils = require(rootPath + "/js/utils.js"); const Utils = require(`${rootPath}/js/utils.js`);
/** /**
* Returns a string with path of configuration file. * Returns a string with path of configuration file.
@ -23,11 +23,7 @@ const Utils = require(rootPath + "/js/utils.js");
*/ */
function getConfigFile() { function getConfigFile() {
// FIXME: This function should be in core. Do you want refactor me ;) ?, be good! // FIXME: This function should be in core. Do you want refactor me ;) ?, be good!
let configFileName = path.resolve(rootPath + "/config/config.js"); return path.resolve(process.env.MM_CONFIG_FILE || `${rootPath}/config/config.js`);
if (process.env.MM_CONFIG_FILE) {
configFileName = path.resolve(process.env.MM_CONFIG_FILE);
}
return configFileName;
} }
/** /**
@ -54,21 +50,18 @@ function checkConfigFile() {
Log.info(Utils.colors.info("Checking file... "), configFileName); Log.info(Utils.colors.info("Checking file... "), configFileName);
// I'm not sure if all ever is utf-8 // I'm not sure if all ever is utf-8
fs.readFile(configFileName, "utf-8", function (err, data) { const configFile = fs.readFileSync(configFileName, "utf-8");
if (err) {
throw err; const errors = linter.verify(configFile);
} if (errors.length === 0) {
const messages = linter.verify(data);
if (messages.length === 0) {
Log.info(Utils.colors.pass("Your configuration file doesn't contain syntax errors :)")); Log.info(Utils.colors.pass("Your configuration file doesn't contain syntax errors :)"));
} else { } else {
Log.error(Utils.colors.error("Your configuration file contains syntax errors :(")); Log.error(Utils.colors.error("Your configuration file contains syntax errors :("));
// In case the there errors show messages and return
messages.forEach((error) => { for (const error of errors) {
Log.error("Line", error.line, "col", error.column, error.message); Log.error(`Line ${error.line} column ${error.column}: ${error.message}`);
}); }
} }
});
} }
checkConfigFile(); checkConfigFile();

View File

@ -20,6 +20,7 @@ var defaults = {
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en", language: "en",
logLevel: ["INFO", "LOG", "WARN", "ERROR"],
timeFormat: 24, timeFormat: 24,
units: "metric", units: "metric",
zoom: 1, zoom: 1,
@ -42,7 +43,7 @@ var defaults = {
module: "helloworld", module: "helloworld",
position: "middle_center", position: "middle_center",
config: { config: {
text: "Please create a config file." text: "Please create a config file or check the existing one for errors."
} }
}, },
{ {
@ -58,7 +59,7 @@ var defaults = {
position: "middle_center", position: "middle_center",
classes: "xsmall", classes: "xsmall",
config: { config: {
text: "If you get this message while your config file is already<br>created, your config file probably contains an error.<br>Use a JavaScript linter to validate your file." text: "If you get this message while your config file is already created,<br>" + "it probably contains an error. To validate your config file run in your MagicMirror directory<br>" + "<pre>npm run config:check</pre>"
} }
}, },
{ {

View File

@ -6,11 +6,6 @@
* Olex S. original idea this deprecated option * Olex S. original idea this deprecated option
*/ */
var deprecated = { module.exports = {
configs: ["kioskmode"] configs: ["kioskmode"]
}; };
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = deprecated;
}

View File

@ -2,10 +2,10 @@
const electron = require("electron"); const electron = require("electron");
const core = require("./app.js"); const core = require("./app.js");
const Log = require("./logger.js"); const Log = require("logger");
// Config // Config
var config = process.env.config ? JSON.parse(process.env.config) : {}; let config = process.env.config ? JSON.parse(process.env.config) : {};
// Module to control application life. // Module to control application life.
const app = electron.app; const app = electron.app;
// Module to create native browser window. // Module to create native browser window.
@ -20,13 +20,14 @@ let mainWindow;
*/ */
function createWindow() { function createWindow() {
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
var electronOptionsDefaults = { let electronOptionsDefaults = {
width: 800, width: 800,
height: 600, height: 600,
x: 0, x: 0,
y: 0, y: 0,
darkTheme: true, darkTheme: true,
webPreferences: { webPreferences: {
contextIsolation: true,
nodeIntegration: false, nodeIntegration: false,
zoomFactor: config.zoom zoomFactor: config.zoom
}, },
@ -42,7 +43,7 @@ function createWindow() {
electronOptionsDefaults.autoHideMenuBar = true; electronOptionsDefaults.autoHideMenuBar = true;
} }
var electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions); const electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions);
// Create the browser window. // Create the browser window.
mainWindow = new BrowserWindow(electronOptions); mainWindow = new BrowserWindow(electronOptions);
@ -50,14 +51,14 @@ function createWindow() {
// and load the index.html of the app. // and load the index.html of the app.
// If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost // If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost
var prefix; let prefix;
if (config["tls"] !== null && config["tls"]) { if (config["tls"] !== null && config["tls"]) {
prefix = "https://"; prefix = "https://";
} else { } else {
prefix = "http://"; prefix = "http://";
} }
var address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address; let address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address;
mainWindow.loadURL(`${prefix}${address}:${config.port}`); mainWindow.loadURL(`${prefix}${address}:${config.port}`);
// Open the DevTools if run with "npm start dev" // Open the DevTools if run with "npm start dev"
@ -125,7 +126,7 @@ app.on("before-quit", (event) => {
// Start the core application if server is run on localhost // Start the core application if server is run on localhost
// This starts all node helpers and starts the webserver. // This starts all node helpers and starts the webserver.
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) > -1) { if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].includes(config.address)) {
core.start(function (c) { core.start(function (c) {
config = c; config = c;
}); });

View File

@ -125,7 +125,7 @@ var Loader = (function () {
* @param {Function} callback Function called when done. * @param {Function} callback Function called when done.
*/ */
var loadModule = function (module, callback) { var loadModule = function (module, callback) {
var url = module.path + "/" + module.file; var url = module.path + module.file;
var afterLoad = function () { var afterLoad = function () {
var moduleObject = Module.create(module.name); var moduleObject = Module.create(module.name);

View File

@ -295,6 +295,9 @@ var MM = (function () {
// Otherwise cancel show action. // Otherwise cancel show action.
if (module.lockStrings.length !== 0 && options.force !== true) { if (module.lockStrings.length !== 0 && options.force !== true) {
Log.log("Will not show " + module.name + ". LockStrings active: " + module.lockStrings.join(",")); Log.log("Will not show " + module.name + ". LockStrings active: " + module.lockStrings.join(","));
if (typeof options.onError === "function") {
options.onError(new Error("LOCK_STRING_ACTIVE"));
}
return; return;
} }
@ -440,7 +443,6 @@ var MM = (function () {
* Removes a module instance from the collection. * Removes a module instance from the collection.
* *
* @param {object} module The module instance to remove from the collection. * @param {object} module The module instance to remove from the collection.
*
* @returns {Module[]} Filtered collection of modules. * @returns {Module[]} Filtered collection of modules.
*/ */
var exceptModule = function (module) { var exceptModule = function (module) {
@ -547,6 +549,11 @@ var MM = (function () {
return; return;
} }
if (!module.data.position) {
Log.warn("module tries to update the DOM without being displayed.");
return;
}
// Further implementation is done in the private method. // Further implementation is done in the private method.
updateDom(module, speed); updateDom(module, speed);
}, },

View File

@ -176,7 +176,7 @@ var Module = Class.extend({
}); });
this._nunjucksEnvironment.addFilter("translate", function (str, variables) { this._nunjucksEnvironment.addFilter("translate", function (str, variables) {
return self.translate(str, variables); return nunjucks.runtime.markSafe(self.translate(str, variables));
}); });
return this._nunjucksEnvironment; return this._nunjucksEnvironment;
@ -311,33 +311,33 @@ var Module = Class.extend({
* *
* @param {Function} callback Function called when done. * @param {Function} callback Function called when done.
*/ */
loadTranslations: function (callback) { loadTranslations(callback) {
var self = this; const translations = this.getTranslations() || {};
var translations = this.getTranslations(); const language = config.language.toLowerCase();
var lang = config.language.toLowerCase();
// The variable `first` will contain the first const languages = Object.keys(translations);
// defined translation after the following line. const fallbackLanguage = languages[0];
for (var first in translations) {
break; if (languages.length === 0) {
callback();
return;
} }
if (translations) { const translationFile = translations[language];
var translationFile = translations[lang] || undefined; const translationsFallbackFile = translations[fallbackLanguage];
var translationsFallbackFile = translations[first];
// If a translation file is set, load it and then also load the fallback translation file. if (!translationFile) {
// Otherwise only load the fallback translation file. Translator.load(this, translationsFallbackFile, true, callback);
if (translationFile !== undefined && translationFile !== translationsFallbackFile) { return;
Translator.load(self, translationFile, false, function () {
Translator.load(self, translationsFallbackFile, true, callback);
});
} else {
Translator.load(self, translationsFallbackFile, true, callback);
} }
Translator.load(this, translationFile, false, () => {
if (translationFile !== translationsFallbackFile) {
Translator.load(this, translationsFallbackFile, true, callback);
} else { } else {
callback(); callback();
} }
});
}, },
/** /**
@ -428,12 +428,11 @@ var Module = Class.extend({
callback = callback || function () {}; callback = callback || function () {};
options = options || {}; options = options || {};
var self = this;
MM.showModule( MM.showModule(
this, this,
speed, speed,
function () { () => {
self.resume(); this.resume();
callback(); callback();
}, },
options options

View File

@ -5,21 +5,21 @@
* MIT Licensed. * MIT Licensed.
*/ */
const Class = require("./class.js"); const Class = require("./class.js");
const Log = require("./logger.js"); const Log = require("logger");
const express = require("express"); const express = require("express");
var NodeHelper = Class.extend({ const NodeHelper = Class.extend({
init: function () { init() {
Log.log("Initializing new module helper ..."); Log.log("Initializing new module helper ...");
}, },
loaded: function (callback) { loaded(callback) {
Log.log("Module helper loaded: " + this.name); Log.log(`Module helper loaded: ${this.name}`);
callback(); callback();
}, },
start: function () { start() {
Log.log("Starting module helper: " + this.name); Log.log(`Starting module helper: ${this.name}`);
}, },
/* stop() /* stop()
@ -28,8 +28,8 @@ var NodeHelper = Class.extend({
* gracefully exit the module. * gracefully exit the module.
* *
*/ */
stop: function () { stop() {
Log.log("Stopping module helper: " + this.name); Log.log(`Stopping module helper: ${this.name}`);
}, },
/* socketNotificationReceived(notification, payload) /* socketNotificationReceived(notification, payload)
@ -38,8 +38,8 @@ var NodeHelper = Class.extend({
* argument notification string - The identifier of the notification. * argument notification string - The identifier of the notification.
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
*/ */
socketNotificationReceived: function (notification, payload) { socketNotificationReceived(notification, payload) {
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload); Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`);
}, },
/* setName(name) /* setName(name)
@ -47,7 +47,7 @@ var NodeHelper = Class.extend({
* *
* argument name string - Module name. * argument name string - Module name.
*/ */
setName: function (name) { setName(name) {
this.name = name; this.name = name;
}, },
@ -56,7 +56,7 @@ var NodeHelper = Class.extend({
* *
* argument path string - Module path. * argument path string - Module path.
*/ */
setPath: function (path) { setPath(path) {
this.path = path; this.path = path;
}, },
@ -66,7 +66,7 @@ var NodeHelper = Class.extend({
* argument notification string - The identifier of the notification. * argument notification string - The identifier of the notification.
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
*/ */
sendSocketNotification: function (notification, payload) { sendSocketNotification(notification, payload) {
this.io.of(this.name).emit(notification, payload); this.io.of(this.name).emit(notification, payload);
}, },
@ -76,11 +76,10 @@ var NodeHelper = Class.extend({
* *
* argument app Express app - The Express app object. * argument app Express app - The Express app object.
*/ */
setExpressApp: function (app) { setExpressApp(app) {
this.expressApp = app; this.expressApp = app;
var publicPath = this.path + "/public"; app.use(`/${this.name}`, express.static(`${this.path}/public`));
app.use("/" + this.name, express.static(publicPath));
}, },
/* setSocketIO(io) /* setSocketIO(io)
@ -89,27 +88,25 @@ var NodeHelper = Class.extend({
* *
* argument io Socket.io - The Socket io object. * argument io Socket.io - The Socket io object.
*/ */
setSocketIO: function (io) { setSocketIO(io) {
var self = this; this.io = io;
self.io = io;
Log.log("Connecting socket for: " + this.name); Log.log(`Connecting socket for: ${this.name}`);
var namespace = this.name;
io.of(namespace).on("connection", function (socket) { io.of(this.name).on("connection", (socket) => {
// add a catch all event. // add a catch all event.
var onevent = socket.onevent; const onevent = socket.onevent;
socket.onevent = function (packet) { socket.onevent = function (packet) {
var args = packet.data || []; const args = packet.data || [];
onevent.call(this, packet); // original call onevent.call(this, packet); // original call
packet.data = ["*"].concat(args); packet.data = ["*"].concat(args);
onevent.call(this, packet); // additional call to catch-all onevent.call(this, packet); // additional call to catch-all
}; };
// register catch all. // register catch all.
socket.on("*", function (notification, payload) { socket.on("*", (notification, payload) => {
if (notification !== "*") { if (notification !== "*") {
//Log.log('received message in namespace: ' + namespace); this.socketNotificationReceived(notification, payload);
self.socketNotificationReceived(notification, payload);
} }
}); });
}); });
@ -120,7 +117,4 @@ NodeHelper.create = function (moduleDefinition) {
return NodeHelper.extend(moduleDefinition); return NodeHelper.extend(moduleDefinition);
}; };
/*************** DO NOT EDIT THE LINE BELOW ***************/ module.exports = NodeHelper;
if (typeof module !== "undefined") {
module.exports = NodeHelper;
}

View File

@ -4,25 +4,29 @@
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var express = require("express"); const express = require("express");
var app = require("express")(); const app = require("express")();
var path = require("path"); const path = require("path");
var ipfilter = require("express-ipfilter").IpFilter; const ipfilter = require("express-ipfilter").IpFilter;
var fs = require("fs"); const fs = require("fs");
var helmet = require("helmet"); const helmet = require("helmet");
var Log = require("./logger.js"); const Log = require("logger");
var Utils = require("./utils.js"); const Utils = require("./utils.js");
var Server = function (config, callback) { /**
var port = config.port; * Server
if (process.env.MM_PORT) { *
port = process.env.MM_PORT; * @param {object} config The MM config
} * @param {Function} callback Function called when done.
* @class
*/
function Server(config, callback) {
const port = process.env.MM_PORT || config.port;
var server = null; let server = null;
if (config.useHttps) { if (config.useHttps) {
var options = { const options = {
key: fs.readFileSync(config.httpsPrivateKey), key: fs.readFileSync(config.httpsPrivateKey),
cert: fs.readFileSync(config.httpsCertificate) cert: fs.readFileSync(config.httpsCertificate)
}; };
@ -30,18 +34,24 @@ var Server = function (config, callback) {
} else { } else {
server = require("http").Server(app); server = require("http").Server(app);
} }
var io = require("socket.io")(server); const io = require("socket.io")(server, {
cors: {
origin: /.*$/,
credentials: true
},
allowEIO3: true
});
Log.log("Starting server on port " + port + " ... "); Log.log(`Starting server on port ${port} ... `);
server.listen(port, config.address ? config.address : "localhost"); server.listen(port, config.address || "localhost");
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) { if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
Log.warn(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs")); Log.warn(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
} }
app.use(function (req, res, next) { app.use(function (req, res, next) {
var result = ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) { ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
if (err === undefined) { if (err === undefined) {
return next(); return next();
} }
@ -52,10 +62,9 @@ var Server = function (config, callback) {
app.use(helmet({ contentSecurityPolicy: false })); app.use(helmet({ contentSecurityPolicy: false }));
app.use("/js", express.static(__dirname)); app.use("/js", express.static(__dirname));
var directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs"];
var directory; const directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs"];
for (var i in directories) { for (const directory of directories) {
directory = directories[i];
app.use(directory, express.static(path.resolve(global.root_path + directory))); app.use(directory, express.static(path.resolve(global.root_path + directory)));
} }
@ -68,10 +77,10 @@ var Server = function (config, callback) {
}); });
app.get("/", function (req, res) { app.get("/", function (req, res) {
var html = fs.readFileSync(path.resolve(global.root_path + "/index.html"), { encoding: "utf8" }); let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" });
html = html.replace("#VERSION#", global.version); html = html.replace("#VERSION#", global.version);
var configFile = "config/config.js"; let configFile = "config/config.js";
if (typeof global.configuration_file !== "undefined") { if (typeof global.configuration_file !== "undefined") {
configFile = global.configuration_file; configFile = global.configuration_file;
} }
@ -83,6 +92,6 @@ var Server = function (config, callback) {
if (typeof callback === "function") { if (typeof callback === "function") {
callback(app, io); callback(app, io);
} }
}; }
module.exports = Server; module.exports = Server;

View File

@ -103,26 +103,19 @@ var Translator = (function () {
* @param {boolean} isFallback Flag to indicate fallback translations. * @param {boolean} isFallback Flag to indicate fallback translations.
* @param {Function} callback Function called when done. * @param {Function} callback Function called when done.
*/ */
load: function (module, file, isFallback, callback) { load(module, file, isFallback, callback) {
if (!isFallback) { Log.log(`${module.name} - Load translation${isFallback && " fallback"}: ${file}`);
Log.log(module.name + " - Load translation: " + file);
} else { if (this.translationsFallback[module.name]) {
Log.log(module.name + " - Load translation fallback: " + file); callback();
return;
} }
var self = this; loadJSON(module.file(file), (json) => {
if (!this.translationsFallback[module.name]) { const property = isFallback ? "translationsFallback" : "translations";
loadJSON(module.file(file), function (json) { this[property][module.name] = json;
if (!isFallback) {
self.translations[module.name] = json;
} else {
self.translationsFallback[module.name] = json;
}
callback(); callback();
}); });
} else {
callback();
}
}, },
/** /**

View File

@ -4,9 +4,9 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
var colors = require("colors/safe"); const colors = require("colors/safe");
var Utils = { module.exports = {
colors: { colors: {
warn: colors.yellow, warn: colors.yellow,
error: colors.red, error: colors.red,
@ -14,7 +14,3 @@ var Utils = {
pass: colors.green pass: colors.green
} }
}; };
if (typeof module !== "undefined") {
module.exports = Utils;
}

View File

@ -36,6 +36,7 @@ Module.register("calendar", {
fadePoint: 0.25, // Start on 1/4th of the list. fadePoint: 0.25, // Start on 1/4th of the list.
hidePrivate: false, hidePrivate: false,
hideOngoing: false, hideOngoing: false,
hideTime: false,
colored: false, colored: false,
coloredSymbolOnly: false, coloredSymbolOnly: false,
customEvents: [], // Array of {keyword: "", symbol: "", color: ""} where Keyword is a regexp and symbol/color are to be applied for matched customEvents: [], // Array of {keyword: "", symbol: "", color: ""} where Keyword is a regexp and symbol/color are to be applied for matched
@ -57,7 +58,8 @@ Module.register("calendar", {
excludedEvents: [], excludedEvents: [],
sliceMultiDayEvents: false, sliceMultiDayEvents: false,
broadcastPastEvents: false, broadcastPastEvents: false,
nextDaysRelative: false nextDaysRelative: false,
selfSignedCert: false
}, },
requiresVersion: "2.1.0", requiresVersion: "2.1.0",
@ -100,7 +102,8 @@ Module.register("calendar", {
var calendarConfig = { var calendarConfig = {
maximumEntries: calendar.maximumEntries, maximumEntries: calendar.maximumEntries,
maximumNumberOfDays: calendar.maximumNumberOfDays, maximumNumberOfDays: calendar.maximumNumberOfDays,
broadcastPastEvents: calendar.broadcastPastEvents broadcastPastEvents: calendar.broadcastPastEvents,
selfSignedCert: calendar.selfSignedCert
}; };
if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) { if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
calendarConfig.symbolClass = ""; calendarConfig.symbolClass = "";
@ -277,8 +280,11 @@ Module.register("calendar", {
if (typeof this.config.customEvents[ev].color !== "undefined" && this.config.customEvents[ev].color !== "") { if (typeof this.config.customEvents[ev].color !== "undefined" && this.config.customEvents[ev].color !== "") {
needle = new RegExp(this.config.customEvents[ev].keyword, "gi"); needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
if (needle.test(event.title)) { if (needle.test(event.title)) {
// Respect parameter ColoredSymbolOnly also for custom events
if (!this.config.coloredSymbolOnly) {
eventWrapper.style.cssText = "color:" + this.config.customEvents[ev].color; eventWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
titleWrapper.style.cssText = "color:" + this.config.customEvents[ev].color; titleWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
}
if (this.config.displaySymbol) { if (this.config.displaySymbol) {
symbolWrapper.style.cssText = "color:" + this.config.customEvents[ev].color; symbolWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
} }
@ -363,7 +369,17 @@ Module.register("calendar", {
// Show relative times // Show relative times
if (event.startDate >= now) { if (event.startDate >= now) {
// Use relative time // Use relative time
if (!this.config.hideTime) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar()); timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar());
} else {
timeWrapper.innerHTML = this.capFirst(
moment(event.startDate, "x").calendar(null, {
sameDay: "[" + this.translate("TODAY") + "]",
nextDay: "[" + this.translate("TOMORROW") + "]",
nextWeek: "dddd"
})
);
}
if (event.startDate - now < this.config.getRelative * oneHour) { if (event.startDate - now < this.config.getRelative * oneHour) {
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow() // If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow()); timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
@ -592,7 +608,8 @@ Module.register("calendar", {
titleClass: calendarConfig.titleClass, titleClass: calendarConfig.titleClass,
timeClass: calendarConfig.timeClass, timeClass: calendarConfig.timeClass,
auth: auth, auth: auth,
broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents,
selfSignedCert: calendarConfig.selfSignedCert || this.config.selfSignedCert
}); });
}, },

View File

@ -4,7 +4,7 @@
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
const Log = require("../../../js/logger.js"); const Log = require("logger");
const ical = require("node-ical"); const ical = require("node-ical");
const request = require("request"); const request = require("request");
@ -25,9 +25,10 @@ const moment = require("moment");
* @param {number} maximumNumberOfDays The maximum number of days an event should be in the future. * @param {number} maximumNumberOfDays The maximum number of days an event should be in the future.
* @param {object} auth The object containing options for authentication against the calendar. * @param {object} auth The object containing options for authentication against the calendar.
* @param {boolean} includePastEvents If true events from the past maximumNumberOfDays will be fetched too * @param {boolean} includePastEvents If true events from the past maximumNumberOfDays will be fetched too
* @param {boolean} selfSignedCert If true, the server certificate is not verified against the list of supplied CAs.
* @class * @class
*/ */
const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) { const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents, selfSignedCert) {
const self = this; const self = this;
let reloadTimer = null; let reloadTimer = null;
@ -51,6 +52,13 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
gzip: true gzip: true
}; };
if (selfSignedCert) {
var agentOptions = {
rejectUnauthorized: false
};
opts.agentOptions = agentOptions;
}
if (auth) { if (auth) {
if (auth.method === "bearer") { if (auth.method === "bearer") {
opts.auth = { opts.auth = {

View File

@ -7,7 +7,7 @@
const NodeHelper = require("node_helper"); const NodeHelper = require("node_helper");
const validUrl = require("valid-url"); const validUrl = require("valid-url");
const CalendarFetcher = require("./calendarfetcher.js"); const CalendarFetcher = require("./calendarfetcher.js");
const Log = require("../../../js/logger"); const Log = require("logger");
module.exports = NodeHelper.create({ module.exports = NodeHelper.create({
// Override start method. // Override start method.
@ -19,7 +19,7 @@ module.exports = NodeHelper.create({
// Override socketNotificationReceived method. // Override socketNotificationReceived method.
socketNotificationReceived: function (notification, payload) { socketNotificationReceived: function (notification, payload) {
if (notification === "ADD_CALENDAR") { if (notification === "ADD_CALENDAR") {
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.id); this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.selfSignedCert, payload.id);
} }
}, },
@ -34,9 +34,10 @@ module.exports = NodeHelper.create({
* @param {number} maximumNumberOfDays The maximum number of days an event should be in the future. * @param {number} maximumNumberOfDays The maximum number of days an event should be in the future.
* @param {object} auth The object containing options for authentication against the calendar. * @param {object} auth The object containing options for authentication against the calendar.
* @param {boolean} broadcastPastEvents If true events from the past maximumNumberOfDays will be included in event broadcasts * @param {boolean} broadcastPastEvents If true events from the past maximumNumberOfDays will be included in event broadcasts
* @param {boolean} selfSignedCert If true, the server certificate is not verified against the list of supplied CAs.
* @param {string} identifier ID of the module * @param {string} identifier ID of the module
*/ */
createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, identifier) { createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert, identifier) {
var self = this; var self = this;
if (!validUrl.isUri(url)) { if (!validUrl.isUri(url)) {
@ -47,7 +48,7 @@ module.exports = NodeHelper.create({
var fetcher; var fetcher;
if (typeof self.fetchers[identifier + url] === "undefined") { if (typeof self.fetchers[identifier + url] === "undefined") {
Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval); Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents); fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert);
fetcher.onReceive(function (fetcher) { fetcher.onReceive(function (fetcher) {
self.sendSocketNotification("CALENDAR_EVENTS", { self.sendSocketNotification("CALENDAR_EVENTS", {

View File

@ -182,34 +182,14 @@ Module.register("compliments", {
}, },
// From data currentweather set weather type // From data currentweather set weather type
setCurrentWeatherType: function (data) { setCurrentWeatherType: function (type) {
var weatherIconTable = { this.currentWeatherType = type;
"01d": "day_sunny",
"02d": "day_cloudy",
"03d": "cloudy",
"04d": "cloudy_windy",
"09d": "showers",
"10d": "rain",
"11d": "thunderstorm",
"13d": "snow",
"50d": "fog",
"01n": "night_clear",
"02n": "night_cloudy",
"03n": "night_cloudy",
"04n": "night_cloudy",
"09n": "night_showers",
"10n": "night_rain",
"11n": "night_thunderstorm",
"13n": "night_snow",
"50n": "night_alt_cloudy_windy"
};
this.currentWeatherType = weatherIconTable[data.weather[0].icon];
}, },
// Override notification handler. // Override notification handler.
notificationReceived: function (notification, payload, sender) { notificationReceived: function (notification, payload, sender) {
if (notification === "CURRENTWEATHER_DATA") { if (notification === "CURRENTWEATHER_TYPE") {
this.setCurrentWeatherType(payload.data); this.setCurrentWeatherType(payload.type);
} }
} }
}); });

View File

@ -1,5 +1,7 @@
# Module: Current Weather # Module: Current Weather
> :warning: **This module is deprecated in favor of the [weather](https://docs.magicmirror.builders/modules/weather.html) module.**
The `currentweather` module is one of the default modules of the MagicMirror. The `currentweather` module is one of the default modules of the MagicMirror.
This module displays the current weather, including the windspeed, the sunset or sunrise time, the temperature and an icon to display the current conditions. This module displays the current weather, including the windspeed, the sunset or sunrise time, the temperature and an icon to display the current conditions.

View File

@ -3,6 +3,8 @@
* *
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*
* This module is deprecated. Any additional feature will no longer be merged.
*/ */
Module.register("currentweather", { Module.register("currentweather", {
// Default module config. // Default module config.
@ -47,24 +49,24 @@ Module.register("currentweather", {
roundTemp: false, roundTemp: false,
iconTable: { iconTable: {
"01d": "wi-day-sunny", "01d": "day-sunny",
"02d": "wi-day-cloudy", "02d": "day-cloudy",
"03d": "wi-cloudy", "03d": "cloudy",
"04d": "wi-cloudy-windy", "04d": "cloudy-windy",
"09d": "wi-showers", "09d": "showers",
"10d": "wi-rain", "10d": "rain",
"11d": "wi-thunderstorm", "11d": "thunderstorm",
"13d": "wi-snow", "13d": "snow",
"50d": "wi-fog", "50d": "fog",
"01n": "wi-night-clear", "01n": "night-clear",
"02n": "wi-night-cloudy", "02n": "night-cloudy",
"03n": "wi-night-cloudy", "03n": "night-cloudy",
"04n": "wi-night-cloudy", "04n": "night-cloudy",
"09n": "wi-night-showers", "09n": "night-showers",
"10n": "wi-night-rain", "10n": "night-rain",
"11n": "wi-night-thunderstorm", "11n": "night-thunderstorm",
"13n": "wi-night-snow", "13n": "night-snow",
"50n": "wi-night-alt-cloudy-windy" "50n": "night-alt-cloudy-windy"
} }
}, },
@ -219,7 +221,7 @@ Module.register("currentweather", {
if (this.config.hideTemp === false) { if (this.config.hideTemp === false) {
var weatherIcon = document.createElement("span"); var weatherIcon = document.createElement("span");
weatherIcon.className = "wi weathericon " + this.weatherType; weatherIcon.className = "wi weathericon wi-" + this.weatherType;
large.appendChild(weatherIcon); large.appendChild(weatherIcon);
var temperature = document.createElement("span"); var temperature = document.createElement("span");
@ -258,13 +260,9 @@ Module.register("currentweather", {
var feelsLike = document.createElement("span"); var feelsLike = document.createElement("span");
feelsLike.className = "dimmed"; feelsLike.className = "dimmed";
var feelsLikeHtml = this.translate("FEELS"); feelsLike.innerHTML = this.translate("FEELS", {
if (feelsLikeHtml.indexOf("{DEGREE}") > -1) {
feelsLikeHtml = this.translate("FEELS", {
DEGREE: this.feelsLike + degreeLabel DEGREE: this.feelsLike + degreeLabel
}); });
} else feelsLikeHtml += " " + this.feelsLike + degreeLabel;
feelsLike.innerHTML = feelsLikeHtml;
small.appendChild(feelsLike); small.appendChild(feelsLike);
wrapper.appendChild(small); wrapper.appendChild(small);
@ -506,6 +504,7 @@ Module.register("currentweather", {
this.loaded = true; this.loaded = true;
this.updateDom(this.config.animationSpeed); this.updateDom(this.config.animationSpeed);
this.sendNotification("CURRENTWEATHER_DATA", { data: data }); this.sendNotification("CURRENTWEATHER_DATA", { data: data });
this.sendNotification("CURRENTWEATHER_TYPE", { type: this.config.iconTable[data.weather[0].icon].replace("-", "_") });
}, },
/* scheduleUpdate() /* scheduleUpdate()
@ -593,6 +592,7 @@ Module.register("currentweather", {
*/ */
roundValue: function (temperature) { roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1; var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals); var roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
} }
}); });

View File

@ -0,0 +1,9 @@
const NodeHelper = require("node_helper");
const Log = require("logger");
module.exports = NodeHelper.create({
// Override start method.
start: function () {
Log.warn(`The module '${this.name}' is deprecated in favor of the 'weather'-module, please refer to the documentation for a migration path`);
}
});

View File

@ -0,0 +1,3 @@
<div>
<iframe class="newsfeed-fullarticle" src="{{ url }}"></iframe>
</div>

View File

@ -0,0 +1,14 @@
iframe.newsfeed-fullarticle {
width: 100vw;
/* very large height value to allow scrolling */
height: 3000px;
top: 0;
left: 0;
border: none;
z-index: 1;
}
.region.bottom.bar.newsfeed-fullarticle {
bottom: inherit;
top: -90px;
}

View File

@ -44,6 +44,11 @@ Module.register("newsfeed", {
return ["moment.js"]; return ["moment.js"];
}, },
//Define required styles.
getStyles: function () {
return ["newsfeed.css"];
},
// Define required translations. // Define required translations.
getTranslations: function () { getTranslations: function () {
// The translations for the default modules are defined in the core translation files. // The translations for the default modules are defined in the core translation files.
@ -75,6 +80,9 @@ Module.register("newsfeed", {
this.generateFeed(payload); this.generateFeed(payload);
if (!this.loaded) { if (!this.loaded) {
if (this.config.hideLoading) {
this.show();
}
this.scheduleUpdateInterval(); this.scheduleUpdateInterval();
} }
@ -82,123 +90,43 @@ Module.register("newsfeed", {
} }
}, },
// Override dom generator. //Override fetching of template name
getDom: function () { getTemplate: function () {
const wrapper = document.createElement("div");
if (this.config.feedUrl) { if (this.config.feedUrl) {
wrapper.className = "small bright"; return "oldconfig.njk";
wrapper.innerHTML = this.translate("MODULE_CONFIG_CHANGED", { MODULE_NAME: "Newsfeed" }); } else if (this.config.showFullArticle) {
return wrapper; return "fullarticle.njk";
}
return "newsfeed.njk";
},
//Override template data and return whats used for the current template
getTemplateData: function () {
// this.config.showFullArticle is a run-time configuration, triggered by optional notifications
if (this.config.showFullArticle) {
return {
url: this.getActiveItemURL()
};
}
if (this.newsItems.length === 0) {
return {
loaded: false
};
} }
if (this.activeItem >= this.newsItems.length) { if (this.activeItem >= this.newsItems.length) {
this.activeItem = 0; this.activeItem = 0;
} }
const item = this.newsItems[this.activeItem];
if (this.newsItems.length > 0) { return {
// this.config.showFullArticle is a run-time configuration, triggered by optional notifications loaded: true,
if (!this.config.showFullArticle && (this.config.showSourceTitle || this.config.showPublishDate)) { config: this.config,
const sourceAndTimestamp = document.createElement("div"); sourceTitle: item.sourceTitle,
sourceAndTimestamp.className = "newsfeed-source light small dimmed"; publishDate: moment(new Date(item.pubdate)).fromNow(),
title: item.title,
if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") { description: item.description
sourceAndTimestamp.innerHTML = this.newsItems[this.activeItem].sourceTitle; };
}
if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "" && this.config.showPublishDate) {
sourceAndTimestamp.innerHTML += ", ";
}
if (this.config.showPublishDate) {
sourceAndTimestamp.innerHTML += moment(new Date(this.newsItems[this.activeItem].pubdate)).fromNow();
}
if ((this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") || this.config.showPublishDate) {
sourceAndTimestamp.innerHTML += ":";
}
wrapper.appendChild(sourceAndTimestamp);
}
//Remove selected tags from the beginning of rss feed items (title or description)
if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") {
for (let f = 0; f < this.config.startTags.length; f++) {
if (this.newsItems[this.activeItem].title.slice(0, this.config.startTags[f].length) === this.config.startTags[f]) {
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(this.config.startTags[f].length, this.newsItems[this.activeItem].title.length);
}
}
}
if (this.config.removeStartTags === "description" || this.config.removeStartTags === "both") {
if (this.isShowingDescription) {
for (let f = 0; f < this.config.startTags.length; f++) {
if (this.newsItems[this.activeItem].description.slice(0, this.config.startTags[f].length) === this.config.startTags[f]) {
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(this.config.startTags[f].length, this.newsItems[this.activeItem].description.length);
}
}
}
}
//Remove selected tags from the end of rss feed items (title or description)
if (this.config.removeEndTags) {
for (let f = 0; f < this.config.endTags.length; f++) {
if (this.newsItems[this.activeItem].title.slice(-this.config.endTags[f].length) === this.config.endTags[f]) {
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(0, -this.config.endTags[f].length);
}
}
if (this.isShowingDescription) {
for (let f = 0; f < this.config.endTags.length; f++) {
if (this.newsItems[this.activeItem].description.slice(-this.config.endTags[f].length) === this.config.endTags[f]) {
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(0, -this.config.endTags[f].length);
}
}
}
}
if (!this.config.showFullArticle) {
const title = document.createElement("div");
title.className = "newsfeed-title bright medium light" + (!this.config.wrapTitle ? " no-wrap" : "");
title.innerHTML = this.newsItems[this.activeItem].title;
wrapper.appendChild(title);
}
if (this.isShowingDescription) {
const description = document.createElement("div");
description.className = "newsfeed-desc small light" + (!this.config.wrapDescription ? " no-wrap" : "");
const txtDesc = this.newsItems[this.activeItem].description;
description.innerHTML = this.config.truncDescription ? (txtDesc.length > this.config.lengthDescription ? txtDesc.substring(0, this.config.lengthDescription) + "..." : txtDesc) : txtDesc;
wrapper.appendChild(description);
}
if (this.config.showFullArticle) {
const fullArticle = document.createElement("iframe");
fullArticle.className = "";
fullArticle.style.width = "100vw";
// very large height value to allow scrolling
fullArticle.height = "3000";
fullArticle.style.height = "3000";
fullArticle.style.top = "0";
fullArticle.style.left = "0";
fullArticle.style.border = "none";
fullArticle.src = this.getActiveItemURL();
fullArticle.style.zIndex = 1;
wrapper.appendChild(fullArticle);
}
if (this.config.hideLoading) {
this.show();
}
} else {
if (this.config.hideLoading) {
this.hide();
} else {
wrapper.innerHTML = this.translate("LOADING");
wrapper.className = "small dimmed";
}
}
return wrapper;
}, },
getActiveItemURL: function () { getActiveItemURL: function () {
@ -257,6 +185,45 @@ Module.register("newsfeed", {
}, this); }, this);
} }
newsItems.forEach((item) => {
//Remove selected tags from the beginning of rss feed items (title or description)
if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") {
for (let f = 0; f < this.config.startTags.length; f++) {
if (item.title.slice(0, this.config.startTags[f].length) === this.config.startTags[f]) {
item.title = item.title.slice(this.config.startTags[f].length, item.title.length);
}
}
}
if (this.config.removeStartTags === "description" || this.config.removeStartTags === "both") {
if (this.isShowingDescription) {
for (let f = 0; f < this.config.startTags.length; f++) {
if (item.description.slice(0, this.config.startTags[f].length) === this.config.startTags[f]) {
item.description = item.description.slice(this.config.startTags[f].length, item.description.length);
}
}
}
}
//Remove selected tags from the end of rss feed items (title or description)
if (this.config.removeEndTags) {
for (let f = 0; f < this.config.endTags.length; f++) {
if (item.title.slice(-this.config.endTags[f].length) === this.config.endTags[f]) {
item.title = item.title.slice(0, -this.config.endTags[f].length);
}
}
if (this.isShowingDescription) {
for (let f = 0; f < this.config.endTags.length; f++) {
if (item.description.slice(-this.config.endTags[f].length) === this.config.endTags[f]) {
item.description = item.description.slice(0, -this.config.endTags[f].length);
}
}
}
}
});
// get updated news items and broadcast them // get updated news items and broadcast them
var updatedItems = []; var updatedItems = [];
newsItems.forEach((value) => { newsItems.forEach((value) => {
@ -335,8 +302,7 @@ Module.register("newsfeed", {
this.config.showFullArticle = false; this.config.showFullArticle = false;
this.scrollPosition = 0; this.scrollPosition = 0;
// reset bottom bar alignment // reset bottom bar alignment
document.getElementsByClassName("region bottom bar")[0].style.bottom = "0"; document.getElementsByClassName("region bottom bar")[0].classList.remove("newsfeed-fullarticle");
document.getElementsByClassName("region bottom bar")[0].style.top = "inherit";
if (!this.timer) { if (!this.timer) {
this.scheduleUpdateInterval(); this.scheduleUpdateInterval();
} }
@ -344,7 +310,9 @@ Module.register("newsfeed", {
notificationReceived: function (notification, payload, sender) { notificationReceived: function (notification, payload, sender) {
const before = this.activeItem; const before = this.activeItem;
if (notification === "ARTICLE_NEXT") { if (notification === "MODULE_DOM_CREATED" && this.config.hideLoading) {
this.hide();
} else if (notification === "ARTICLE_NEXT") {
this.activeItem++; this.activeItem++;
if (this.activeItem >= this.newsItems.length) { if (this.activeItem >= this.newsItems.length) {
this.activeItem = 0; this.activeItem = 0;
@ -406,8 +374,7 @@ Module.register("newsfeed", {
this.config.showFullArticle = !this.isShowingDescription; this.config.showFullArticle = !this.isShowingDescription;
// make bottom bar align to top to allow scrolling // make bottom bar align to top to allow scrolling
if (this.config.showFullArticle === true) { if (this.config.showFullArticle === true) {
document.getElementsByClassName("region bottom bar")[0].style.bottom = "inherit"; document.getElementsByClassName("region bottom bar")[0].classList.add("newsfeed-fullarticle");
document.getElementsByClassName("region bottom bar")[0].style.top = "-90px";
} }
clearInterval(this.timer); clearInterval(this.timer);
this.timer = null; this.timer = null;

View File

@ -0,0 +1,28 @@
{% if loaded %}
<div>
{% if (config.showSourceTitle and sourceTitle) or config.showPublishDate %}
<div class="newsfeed-source light small dimmed">
{% if sourceTitle and config.showSourceTitle %}
{{ sourceTitle }}{% if config.showPublishDate %}, {% else %}: {% endif %}
{% endif %}
{% if config.showPublishDate %}
{{ publishDate }}:
{% endif %}
</div>
{% endif %}
<div class="newsfeed-title bright medium light{{ ' no-wrap' if config.wrapTitle }}">
{{ title }}
</div>
<div class="newsfeed-desc small light{{ ' no-wrap' if config.wrapDescription }}">
{% if config.truncDescription %}
{{ description | truncate(config.lengthDescription) }}
{% else %}
{{ description }}
{% endif %}
</div>
</div>
{% else %}
<div class="small dimmed">
{{ "LOADING" | translate | safe }}
</div>
{% endif %}

View File

@ -4,7 +4,7 @@
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
const Log = require("../../../js/logger.js"); const Log = require("logger");
const FeedMe = require("feedme"); const FeedMe = require("feedme");
const request = require("request"); const request = require("request");
const iconv = require("iconv-lite"); const iconv = require("iconv-lite");

View File

@ -8,7 +8,7 @@
const NodeHelper = require("node_helper"); const NodeHelper = require("node_helper");
const validUrl = require("valid-url"); const validUrl = require("valid-url");
const NewsfeedFetcher = require("./newsfeedfetcher.js"); const NewsfeedFetcher = require("./newsfeedfetcher.js");
const Log = require("../../../js/logger"); const Log = require("logger");
module.exports = NodeHelper.create({ module.exports = NodeHelper.create({
// Override start method. // Override start method.

View File

@ -0,0 +1,3 @@
<div class="small bright">
{{ "MODULE_CONFIG_CHANGED" | translate({MODULE_NAME: "Newsfeed"}) | safe }}
</div>

View File

@ -3,7 +3,7 @@ const simpleGits = [];
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const defaultModules = require(__dirname + "/../defaultmodules.js"); const defaultModules = require(__dirname + "/../defaultmodules.js");
const Log = require(__dirname + "/../../../js/logger.js"); const Log = require("logger");
const NodeHelper = require("node_helper"); const NodeHelper = require("node_helper");
module.exports = NodeHelper.create({ module.exports = NodeHelper.create({

View File

@ -1,7 +1,4 @@
{% if current or weatherData %} {% if current %}
{% if weatherData %}
{% set current = weatherData.current %}
{% endif %}
{% if not config.onlyTemp %} {% if not config.onlyTemp %}
<div class="normal medium"> <div class="normal medium">
<span class="wi wi-strong-wind dimmed"></span> <span class="wi wi-strong-wind dimmed"></span>
@ -66,13 +63,10 @@
{% endif %} {% endif %}
</div> </div>
{% if (config.showFeelsLike or config.showPrecipitationAmount) and not config.onlyTemp %} {% if (config.showFeelsLike or config.showPrecipitationAmount) and not config.onlyTemp %}
<div class="normal medium"> <div class="normal medium feelslike">
{% if config.showFeelsLike %} {% if config.showFeelsLike %}
<span class="dimmed"> <span class="dimmed">
{{ "FEELS" | translate({DEGREE: current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }) }} {{ "FEELS" | translate({DEGREE: current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }) }}
{% if not config.feelsLikeWithDegree %}
{{ current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }}
{% endif %}
</span> </span>
{% endif %} {% endif %}
{% if config.showPrecipitationAmount %} {% if config.showPrecipitationAmount %}
@ -84,7 +78,7 @@
{% endif %} {% endif %}
{% else %} {% else %}
<div class="dimmed light small"> <div class="dimmed light small">
{{ "LOADING" | translate | safe }} {{ "LOADING" | translate }}
</div> </div>
{% endif %} {% endif %}

View File

@ -1,10 +1,5 @@
{% if forecast or weatherData %} {% if forecast %}
{% if weatherData %}
{% set forecast = weatherData.days %}
{% set numSteps = forecast | calcNumEntries %}
{% else %}
{% set numSteps = forecast | calcNumSteps %} {% set numSteps = forecast | calcNumSteps %}
{% endif %}
{% set currentStep = 0 %} {% set currentStep = 0 %}
<table class="{{ config.tableClass }}"> <table class="{{ config.tableClass }}">
{% set forecast = forecast.slice(0, numSteps) %} {% set forecast = forecast.slice(0, numSteps) %}
@ -35,7 +30,7 @@
</table> </table>
{% else %} {% else %}
<div class="dimmed light small"> <div class="dimmed light small">
{{ "LOADING" | translate | safe }} {{ "LOADING" | translate }}
</div> </div>
{% endif %} {% endif %}

View File

@ -1,7 +1,4 @@
{% if hourly or weatherData %} {% if hourly %}
{% if weatherData %}
{% set hourly = weatherData.hours %}
{% endif %}
{% set numSteps = hourly | calcNumEntries %} {% set numSteps = hourly | calcNumEntries %}
{% set currentStep = 0 %} {% set currentStep = 0 %}
<table class="{{ config.tableClass }}"> <table class="{{ config.tableClass }}">
@ -24,9 +21,9 @@
</table> </table>
{% else %} {% else %}
<div class="dimmed light small"> <div class="dimmed light small">
{{ "LOADING" | translate | safe }} {{ "LOADING" | translate }}
</div> </div>
{% endif %} {% endif %}
<!-- Uncomment the line below to see the contents of the `hourly` object. --> <!-- Uncomment the line below to see the contents of the `hourly` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{weatherData | dump}}</div> --> <!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{hourly | dump}}</div> -->

View File

@ -29,16 +29,23 @@ WeatherProvider.register("yourprovider", {
#### `fetchCurrentWeather()` #### `fetchCurrentWeather()`
This method is called when the weather module tries to fetch the current weather of your provider. The implementation of this method is required. This method is called when the weather module tries to fetch the current weather of your provider. The implementation of this method is required for current weather support.
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise. The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
After the response is processed, the current weather information (as a [WeatherObject](#weatherobject)) needs to be set with `this.setCurrentWeather(currentWeather);`. After the response is processed, the current weather information (as a [WeatherObject](#weatherobject)) needs to be set with `this.setCurrentWeather(currentWeather);`.
It will then automatically refresh the module DOM with the new data. It will then automatically refresh the module DOM with the new data.
#### `fetchWeatherForecast()` #### `fetchWeatherForecast()`
This method is called when the weather module tries to fetch the weather of your provider. The implementation of this method is required. This method is called when the weather module tries to fetch the weather of your provider. The implementation of this method is required for forecast support.
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise. The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
After the response is processed, the weather forecast information (as an array of [WeatherObject](#weatherobject)s) needs to be set with `this.setCurrentWeather(forecast);`. After the response is processed, the weather forecast information (as an array of [WeatherObject](#weatherobject)s) needs to be set with `this.setWeatherForecast(forecast);`.
It will then automatically refresh the module DOM with the new data.
#### `fetchWeatherHourly()`
This method is called when the weather module tries to fetch the weather of your provider. The implementation of this method is required for hourly support.
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
After the response is processed, the hourly weather forecast information (as an array of [WeatherObject](#weatherobject)s) needs to be set with `this.setWeatherHourly(forecast);`.
It will then automatically refresh the module DOM with the new data. It will then automatically refresh the module DOM with the new data.
### Weather Provider instance methods ### Weather Provider instance methods
@ -63,6 +70,10 @@ This returns a WeatherDay object for the current weather.
This returns an array of WeatherDay objects for the weather forecast. This returns an array of WeatherDay objects for the weather forecast.
#### `weatherHourly()`
This returns an array of WeatherDay objects for the hourly weather forecast.
#### `fetchedLocation()` #### `fetchedLocation()`
This returns the name of the fetched location or an empty string. This returns the name of the fetched location or an empty string.
@ -75,6 +86,10 @@ Set the currentWeather and notify the delegate that new information is available
Set the weatherForecastArray and notify the delegate that new information is available. Set the weatherForecastArray and notify the delegate that new information is available.
#### `setWeatherHourly(weatherHourlyArray)`
Set the weatherHourlyArray and notify the delegate that new information is available.
#### `setFetchedLocation(name)` #### `setFetchedLocation(name)`
Set the fetched location name. Set the fetched location name.

View File

@ -15,6 +15,15 @@ WeatherProvider.register("darksky", {
// Not strictly required, but helps for debugging. // Not strictly required, but helps for debugging.
providerName: "Dark Sky", providerName: "Dark Sky",
// Set the default config properties that is specific to this provider
defaults: {
apiBase: "https://cors-anywhere.herokuapp.com/https://api.darksky.net",
weatherEndpoint: "/forecast",
apiKey: "",
lat: 0,
lon: 0
},
units: { units: {
imperial: "us", imperial: "us",
metric: "si" metric: "si"

View File

@ -14,6 +14,18 @@ WeatherProvider.register("openweathermap", {
// But for debugging (and future alerts) it would be nice to have the real name. // But for debugging (and future alerts) it would be nice to have the real name.
providerName: "OpenWeatherMap", providerName: "OpenWeatherMap",
// Set the default config properties that is specific to this provider
defaults: {
apiVersion: "2.5",
apiBase: "https://api.openweathermap.org/data/",
weatherEndpoint: "",
locationID: false,
location: false,
lat: 0,
lon: 0,
apiKey: ""
},
// Overwrite the fetchCurrentWeather method. // Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() { fetchCurrentWeather() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
@ -56,8 +68,8 @@ WeatherProvider.register("openweathermap", {
.finally(() => this.updateAvailable()); .finally(() => this.updateAvailable());
}, },
// Overwrite the fetchWeatherData method. // Overwrite the fetchWeatherHourly method.
fetchWeatherData() { fetchWeatherHourly() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
.then((data) => { .then((data) => {
if (!data) { if (!data) {
@ -69,7 +81,7 @@ WeatherProvider.register("openweathermap", {
this.setFetchedLocation(`(${data.lat},${data.lon})`); this.setFetchedLocation(`(${data.lat},${data.lon})`);
const weatherData = this.generateWeatherObjectsFromOnecall(data); const weatherData = this.generateWeatherObjectsFromOnecall(data);
this.setWeatherData(weatherData); this.setWeatherHourly(weatherData.hours);
}) })
.catch(function (request) { .catch(function (request) {
Log.error("Could not load data ... ", request); Log.error("Could not load data ... ", request);
@ -77,6 +89,31 @@ WeatherProvider.register("openweathermap", {
.finally(() => this.updateAvailable()); .finally(() => this.updateAvailable());
}, },
/**
* Overrides method for setting config to check if endpoint is correct for hourly
*
* @param config
*/
setConfig(config) {
this.config = config;
if (!this.config.weatherEndpoint) {
switch (this.config.type) {
case "hourly":
this.config.weatherEndpoint = "/onecall";
break;
case "daily":
case "forecast":
this.config.weatherEndpoint = "/forecast";
break;
case "current":
this.config.weatherEndpoint = "/weather";
break;
default:
Log.error("weatherEndpoint not configured and could not resolve it based on type");
}
}
},
/** OpenWeatherMap Specific Methods - These are not part of the default provider methods */ /** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
/* /*
* Gets the complete url for the request * Gets the complete url for the request

View File

@ -14,6 +14,13 @@
WeatherProvider.register("smhi", { WeatherProvider.register("smhi", {
providerName: "SMHI", providerName: "SMHI",
// Set the default config properties that is specific to this provider
defaults: {
lat: 0,
lon: 0,
precipitationValue: "pmedian"
},
/** /**
* Implements method in interface for fetching current weather * Implements method in interface for fetching current weather
*/ */
@ -55,7 +62,7 @@ WeatherProvider.register("smhi", {
this.config = config; this.config = config;
if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) == -1) { if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) == -1) {
console.log("invalid or not set: " + config.precipitationValue); console.log("invalid or not set: " + config.precipitationValue);
config.precipitationValue = "pmedian"; config.precipitationValue = this.defaults.precipitationValue;
} }
}, },

View File

@ -14,6 +14,13 @@ WeatherProvider.register("ukmetoffice", {
// But for debugging (and future alerts) it would be nice to have the real name. // But for debugging (and future alerts) it would be nice to have the real name.
providerName: "UK Met Office", providerName: "UK Met Office",
// Set the default config properties that is specific to this provider
defaults: {
apiBase: "http://datapoint.metoffice.gov.uk/public/data/val/wxfcs/all/json/",
locationID: false,
apiKey: ""
},
units: { units: {
imperial: "us", imperial: "us",
metric: "si" metric: "si"

View File

@ -44,6 +44,16 @@ WeatherProvider.register("ukmetofficedatahub", {
// Set the name of the provider. // Set the name of the provider.
providerName: "UK Met Office (DataHub)", providerName: "UK Met Office (DataHub)",
// Set the default config properties that is specific to this provider
defaults: {
apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
apiKey: "",
apiSecret: "",
lat: 0,
lon: 0,
windUnits: "mph"
},
// Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api) // Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
getUrl(forecastType) { getUrl(forecastType) {
let queryStrings = "?"; let queryStrings = "?";

View File

@ -14,6 +14,15 @@ WeatherProvider.register("weatherbit", {
// Not strictly required, but helps for debugging. // Not strictly required, but helps for debugging.
providerName: "Weatherbit", providerName: "Weatherbit",
// Set the default config properties that is specific to this provider
defaults: {
apiBase: "https://api.weatherbit.io/v2.0",
weatherEndpoint: "/current",
apiKey: "",
lat: 0,
lon: 0
},
units: { units: {
imperial: "I", imperial: "I",
metric: "M" metric: "M"

View File

@ -19,6 +19,14 @@ WeatherProvider.register("weathergov", {
// But for debugging (and future alerts) it would be nice to have the real name. // But for debugging (and future alerts) it would be nice to have the real name.
providerName: "Weather.gov", providerName: "Weather.gov",
// Set the default config properties that is specific to this provider
defaults: {
apiBase: "https://api.weatherbit.io/v2.0",
weatherEndpoint: "/forecast",
lat: 0,
lon: 0
},
// Flag all needed URLs availability // Flag all needed URLs availability
configURLs: false, configURLs: false,

View File

@ -12,10 +12,6 @@ Module.register("weather", {
weatherProvider: "openweathermap", weatherProvider: "openweathermap",
roundTemp: false, roundTemp: false,
type: "current", // current, forecast, daily (equivalent to forecast), hourly (only with OpenWeatherMap /onecall endpoint) type: "current", // current, forecast, daily (equivalent to forecast), hourly (only with OpenWeatherMap /onecall endpoint)
lat: 0,
lon: 0,
location: false,
locationID: false,
units: config.units, units: config.units,
useKmh: false, useKmh: false,
tempUnits: config.units, tempUnits: config.units,
@ -40,20 +36,13 @@ Module.register("weather", {
fade: true, fade: true,
fadePoint: 0.25, // Start on 1/4th of the list. fadePoint: 0.25, // Start on 1/4th of the list.
initialLoadDelay: 0, // 0 seconds delay initialLoadDelay: 0, // 0 seconds delay
retryDelay: 2500,
apiKey: "",
apiSecret: "",
apiVersion: "2.5",
apiBase: "https://api.openweathermap.org/data/", // TODO: this should not be part of the weather.js file, but should be contained in the openweatherprovider
weatherEndpoint: "/weather",
appendLocationNameToHeader: true, appendLocationNameToHeader: true,
calendarClass: "calendar", calendarClass: "calendar",
tableClass: "small", tableClass: "small",
onlyTemp: false, onlyTemp: false,
showPrecipitationAmount: false, showPrecipitationAmount: false,
colored: false, colored: false,
showFeelsLike: true, showFeelsLike: true
feelsLikeWithDegree: false
}, },
// Module properties. // Module properties.
@ -89,8 +78,6 @@ Module.register("weather", {
// Let the weather provider know we are starting. // Let the weather provider know we are starting.
this.weatherProvider.start(); this.weatherProvider.start();
this.config.feelsLikeWithDegree = this.translate("FEELS").indexOf("{DEGREE}") > -1;
// Add custom filters // Add custom filters
this.addFilters(); this.addFilters();
@ -133,8 +120,9 @@ Module.register("weather", {
case "daily": case "daily":
case "forecast": case "forecast":
return `forecast.njk`; return `forecast.njk`;
//Make the invalid values use the "Loading..." from forecast
default: default:
return `${this.config.type.toLowerCase()}.njk`; return `forecast.njk`;
} }
}, },
@ -144,7 +132,7 @@ Module.register("weather", {
config: this.config, config: this.config,
current: this.weatherProvider.currentWeather(), current: this.weatherProvider.currentWeather(),
forecast: this.weatherProvider.weatherForecast(), forecast: this.weatherProvider.weatherForecast(),
weatherData: this.weatherProvider.weatherData(), hourly: this.weatherProvider.weatherHourly(),
indoor: { indoor: {
humidity: this.indoorHumidity, humidity: this.indoorHumidity,
temperature: this.indoorTemperature temperature: this.indoorTemperature
@ -157,6 +145,10 @@ Module.register("weather", {
Log.log("New weather information available."); Log.log("New weather information available.");
this.updateDom(0); this.updateDom(0);
this.scheduleUpdate(); this.scheduleUpdate();
if (this.weatherProvider.currentWeather()) {
this.sendNotification("CURRENTWEATHER_TYPE", { type: this.weatherProvider.currentWeather().weatherType.replace("-", "_") });
}
}, },
scheduleUpdate: function (delay = null) { scheduleUpdate: function (delay = null) {
@ -166,19 +158,27 @@ Module.register("weather", {
} }
setTimeout(() => { setTimeout(() => {
if (this.config.weatherEndpoint === "/onecall") { switch (this.config.type.toLowerCase()) {
this.weatherProvider.fetchWeatherData(); case "current":
} else if (this.config.type === "forecast") {
this.weatherProvider.fetchWeatherForecast();
} else {
this.weatherProvider.fetchCurrentWeather(); this.weatherProvider.fetchCurrentWeather();
break;
case "hourly":
this.weatherProvider.fetchWeatherHourly();
break;
case "daily":
case "forecast":
this.weatherProvider.fetchWeatherForecast();
break;
default:
Log.error(`Invalid type ${this.config.type} configured (must be one of 'current', 'hourly', 'daily' or 'forecast')`);
} }
}, nextLoad); }, nextLoad);
}, },
roundValue: function (temperature) { roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1; var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals); var roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
}, },
addFilters() { addFilters() {

View File

@ -11,12 +11,13 @@
var WeatherProvider = Class.extend({ var WeatherProvider = Class.extend({
// Weather Provider Properties // Weather Provider Properties
providerName: null, providerName: null,
defaults: {},
// The following properties have accessor methods. // The following properties have accessor methods.
// Try to not access them directly. // Try to not access them directly.
currentWeatherObject: null, currentWeatherObject: null,
weatherForecastArray: null, weatherForecastArray: null,
weatherDataObject: null, weatherHourlyArray: null,
fetchedLocationName: null, fetchedLocationName: null,
// The following properties will be set automatically. // The following properties will be set automatically.
@ -57,10 +58,10 @@ var WeatherProvider = Class.extend({
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`); Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`);
}, },
// This method should start the API request to fetch the weather forecast. // This method should start the API request to fetch the weather hourly.
// This method should definitely be overwritten in the provider. // This method should definitely be overwritten in the provider.
fetchWeatherData: function () { fetchWeatherHourly: function () {
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherData method.`); Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherHourly method.`);
}, },
// This returns a WeatherDay object for the current weather. // This returns a WeatherDay object for the current weather.
@ -74,8 +75,8 @@ var WeatherProvider = Class.extend({
}, },
// This returns an object containing WeatherDay object(s) depending on the type of call. // This returns an object containing WeatherDay object(s) depending on the type of call.
weatherData: function () { weatherHourly: function () {
return this.weatherDataObject; return this.weatherHourlyArray;
}, },
// This returns the name of the fetched location or an empty string. // This returns the name of the fetched location or an empty string.
@ -95,9 +96,9 @@ var WeatherProvider = Class.extend({
this.weatherForecastArray = weatherForecastArray; this.weatherForecastArray = weatherForecastArray;
}, },
// Set the weatherDataObject and notify the delegate that new information is available. // Set the weatherHourlyArray and notify the delegate that new information is available.
setWeatherData: function (weatherDataObject) { setWeatherHourly: function (weatherHourlyArray) {
this.weatherDataObject = weatherDataObject; this.weatherHourlyArray = weatherHourlyArray;
}, },
// Set the fetched location name. // Set the fetched location name.
@ -154,10 +155,11 @@ WeatherProvider.register = function (providerIdentifier, providerDetails) {
WeatherProvider.initialize = function (providerIdentifier, delegate) { WeatherProvider.initialize = function (providerIdentifier, delegate) {
providerIdentifier = providerIdentifier.toLowerCase(); providerIdentifier = providerIdentifier.toLowerCase();
var provider = new WeatherProvider.providers[providerIdentifier](); const provider = new WeatherProvider.providers[providerIdentifier]();
const config = Object.assign({}, provider.defaults, delegate.config);
provider.delegate = delegate; provider.delegate = delegate;
provider.setConfig(delegate.config); provider.setConfig(config);
provider.providerIdentifier = providerIdentifier; provider.providerIdentifier = providerIdentifier;
if (!provider.providerName) { if (!provider.providerName) {

View File

@ -1,5 +1,7 @@
# Module: Weather Forecast # Module: Weather Forecast
> :warning: **This module is deprecated in favor of the [weather](https://docs.magicmirror.builders/modules/weather.html) module.**
The `weatherforecast` module is one of the default modules of the MagicMirror. The `weatherforecast` module is one of the default modules of the MagicMirror.
This module displays the weather forecast for the coming week, including an an icon to display the current conditions, the minimum temperature and the maximum temperature. This module displays the weather forecast for the coming week, including an an icon to display the current conditions, the minimum temperature and the maximum temperature.

View File

@ -0,0 +1,9 @@
const NodeHelper = require("node_helper");
const Log = require("logger");
module.exports = NodeHelper.create({
// Override start method.
start: function () {
Log.warn(`The module '${this.name}' is deprecated in favor of the 'weather'-module, please refer to the documentation for a migration path`);
}
});

View File

@ -3,6 +3,8 @@
* *
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*
* This module is deprecated. Any additional feature will no longer be merged.
*/ */
Module.register("weatherforecast", { Module.register("weatherforecast", {
// Default module config. // Default module config.
@ -351,6 +353,13 @@ Module.register("weatherforecast", {
this.forecast = []; this.forecast = [];
var lastDay = null; var lastDay = null;
var forecastData = {}; var forecastData = {};
var dayStarts = 8;
var dayEnds = 17;
if (data.city && data.city.sunrise && data.city.sunset) {
dayStarts = new Date(moment.unix(data.city.sunrise).locale("en").format("YYYY/MM/DD HH:mm:ss")).getHours();
dayEnds = new Date(moment.unix(data.city.sunset).locale("en").format("YYYY/MM/DD HH:mm:ss")).getHours();
}
// Handle different structs between forecast16 and onecall endpoints // Handle different structs between forecast16 and onecall endpoints
var forecastList = null; var forecastList = null;
@ -371,10 +380,10 @@ Module.register("weatherforecast", {
var hour; var hour;
if (forecast.dt_txt) { if (forecast.dt_txt) {
day = moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("ddd"); day = moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("ddd");
hour = moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").toDate().getHours(); hour = new Date(moment(forecast.dt_txt).locale("en").format("YYYY-MM-DD HH:mm:ss")).getHours();
} else { } else {
day = moment(forecast.dt, "X").format("ddd"); day = moment(forecast.dt, "X").format("ddd");
hour = moment(forecast.dt, "X").toDate().getHours(); hour = new Date(moment(forecast.dt, "X")).getHours();
} }
if (day !== lastDay) { if (day !== lastDay) {
@ -400,7 +409,7 @@ Module.register("weatherforecast", {
// Since we don't want an icon from the start of the day (in the middle of the night) // Since we don't want an icon from the start of the day (in the middle of the night)
// we update the icon as long as it's somewhere during the day. // we update the icon as long as it's somewhere during the day.
if (hour >= 8 && hour <= 17) { if (hour > dayStarts && hour < dayEnds) {
forecastData.icon = this.config.iconTable[forecast.weather[0].icon]; forecastData.icon = this.config.iconTable[forecast.weather[0].icon];
} }
} }
@ -462,7 +471,8 @@ Module.register("weatherforecast", {
*/ */
roundValue: function (temperature) { roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1; var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals); var roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
}, },
/* processRain(forecast, allForecasts) /* processRain(forecast, allForecasts)

5709
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,26 @@
{ {
"name": "magicmirror", "name": "magicmirror",
"version": "2.14.0", "version": "2.15.0-develop",
"description": "The open source modular smart mirror platform.", "description": "The open source modular smart mirror platform.",
"main": "js/electron.js", "main": "js/electron.js",
"scripts": { "scripts": {
"start": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js", "start": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js",
"start:dev": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js dev",
"server": "node ./serveronly", "server": "node ./serveronly",
"install": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error", "install": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error",
"install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error", "install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error",
"postinstall": "npm run install-fonts && echo \"MagicMirror installation finished successfully! \n\"", "postinstall": "npm run install-fonts && echo \"MagicMirror installation finished successfully! \n\"",
"test": "NODE_ENV=test mocha tests --recursive", "test": "NODE_ENV=test mocha tests --recursive",
"test:coverage": "NODE_ENV=test nyc mocha tests --recursive --timeout=3000", "test:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text mocha tests --recursive --timeout=3000",
"test:e2e": "NODE_ENV=test mocha tests/e2e --recursive", "test:e2e": "NODE_ENV=test mocha tests/e2e --recursive",
"test:unit": "NODE_ENV=test mocha tests/unit --recursive", "test:unit": "NODE_ENV=test mocha tests/unit --recursive",
"test:prettier": "prettier --check **/*.{js,css,json,md,yml}", "test:prettier": "prettier --check **/*.{js,css,json,md,yml}",
"test:js": "eslint *.js js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --quiet", "test:js": "eslint js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --quiet",
"test:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json", "test:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json",
"test:calendar": "node ./modules/default/calendar/debug.js", "test:calendar": "node ./modules/default/calendar/debug.js",
"config:check": "node js/check_config.js", "config:check": "node js/check_config.js",
"lint:prettier": "prettier --write **/*.{js,css,json,md,yml}", "lint:prettier": "prettier --write **/*.{js,css,json,md,yml}",
"lint:js": "eslint *.js js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --fix", "lint:js": "eslint js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --fix",
"lint:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json --fix" "lint:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json --fix"
}, },
"repository": { "repository": {
@ -42,53 +43,57 @@
}, },
"homepage": "https://magicmirror.builders", "homepage": "https://magicmirror.builders",
"devDependencies": { "devDependencies": {
"chai": "^4.2.0", "chai": "^4.3.0",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"danger": "^10.5.4", "eslint-config-prettier": "^8.1.0",
"eslint-config-prettier": "^7.0.0", "eslint-plugin-jsdoc": "^32.2.0",
"eslint-plugin-jsdoc": "^30.7.8", "eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-prettier": "^3.2.0",
"express-basic-auth": "^1.2.0", "express-basic-auth": "^1.2.0",
"husky": "^4.3.5", "husky": "^4.3.8",
"jsdom": "^16.4.0", "jsdom": "^16.4.0",
"lodash": "^4.17.20", "lodash": "^4.17.21",
"mocha": "^8.2.1", "mocha": "^8.3.0",
"mocha-each": "^2.0.1", "mocha-each": "^2.0.1",
"mocha-logger": "^1.0.7", "mocha-logger": "^1.0.7",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"pretty-quick": "^3.1.0", "pretty-quick": "^3.1.0",
"spectron": "^10.0.1", "sinon": "^9.2.4",
"stylelint": "^13.8.0", "spectron": "^13.0.0",
"stylelint": "^13.11.0",
"stylelint-config-prettier": "^8.0.2", "stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^20.0.0", "stylelint-config-standard": "^20.0.0",
"stylelint-prettier": "^1.1.2" "stylelint-prettier": "^1.1.2"
}, },
"optionalDependencies": { "optionalDependencies": {
"electron": "^8.5.3" "electron": "^11.3.0"
}, },
"dependencies": { "dependencies": {
"colors": "^1.4.0", "colors": "^1.4.0",
"console-stamp": "^3.0.0-rc4.2", "console-stamp": "^3.0.0-rc4.2",
"eslint": "^7.15.0", "eslint": "^7.20.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-ipfilter": "^1.1.2", "express-ipfilter": "^1.1.2",
"feedme": "^2.0.2", "feedme": "^2.0.2",
"helmet": "^4.2.0", "helmet": "^4.4.1",
"ical": "^0.8.0", "ical": "^0.8.0",
"iconv-lite": "^0.6.2", "iconv-lite": "^0.6.2",
"module-alias": "^2.2.2", "module-alias": "^2.2.2",
"moment": "^2.29.1", "moment": "^2.29.1",
"node-ical": "^0.12.7", "node-ical": "^0.12.8",
"request": "^2.88.2", "request": "^2.88.2",
"rrule": "^2.6.6", "rrule": "^2.6.8",
"rrule-alt": "^2.2.8", "rrule-alt": "^2.2.8",
"simple-git": "^2.31.0", "simple-git": "^2.35.2",
"socket.io": "^3.0.4", "socket.io": "^3.1.2",
"valid-url": "^1.0.9" "valid-url": "^1.0.9"
}, },
"_moduleAliases": { "_moduleAliases": {
"node_helper": "js/node_helper.js" "node_helper": "js/node_helper.js",
"logger": "js/logger.js"
},
"engines": {
"node": ">=10"
}, },
"husky": { "husky": {
"hooks": { "hooks": {

View File

@ -1,5 +1,5 @@
const app = require("../js/app.js"); const app = require("../js/app.js");
const Log = require("../js/logger.js"); const Log = require("logger");
app.start(function (config) { app.start(function (config) {
var bindAddress = config.address ? config.address : "localhost"; var bindAddress = config.address ? config.address : "localhost";

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },
@ -25,7 +26,7 @@ var config = {
calendars: [ calendars: [
{ {
maximumNumberOfDays: 10000, maximumNumberOfDays: 10000,
url: "http://localhost:8011/tests/configs/data/calendar_test.ics", url: "http://localhost:8080/tests/configs/data/calendar_test.ics",
auth: { auth: {
user: "MagicMirror", user: "MagicMirror",
pass: "CallMeADog" pass: "CallMeADog"

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },
@ -25,7 +26,7 @@ var config = {
calendars: [ calendars: [
{ {
maximumNumberOfDays: 10000, maximumNumberOfDays: 10000,
url: "http://localhost:8010/tests/configs/data/calendar_test.ics", url: "http://localhost:8080/tests/configs/data/calendar_test.ics",
auth: { auth: {
user: "MagicMirror", user: "MagicMirror",
pass: "CallMeADog", pass: "CallMeADog",

View File

@ -0,0 +1,44 @@
/* Magic Mirror Test config default calendar with auth by default
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
calendars: [
{
maximumNumberOfDays: 10000,
url: "http://localhost:8010/tests/configs/data/calendar_test.ics",
auth: {
user: "MagicMirror",
pass: "CallMeADog"
}
}
]
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}

View File

@ -11,7 +11,8 @@ let config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -15,7 +15,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },
@ -25,7 +26,7 @@ var config = {
calendars: [ calendars: [
{ {
maximumNumberOfDays: 10000, maximumNumberOfDays: 10000,
url: "http://localhost:8012/tests/configs/data/calendar_test.ics", url: "http://localhost:8080/tests/configs/data/calendar_test.ics",
user: "MagicMirror", user: "MagicMirror",
pass: "CallMeADog" pass: "CallMeADog"
} }

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -11,7 +11,8 @@ let config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -16,7 +16,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -14,7 +14,8 @@ let config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -14,7 +14,8 @@ var config = {
width: 800, width: 800,
height: 600, height: 600,
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -15,7 +15,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -0,0 +1,49 @@
/* Magic Mirror Test config current weather compliments
*
* By rejas https://github.com/rejas
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
fullscreen: false,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [
{
module: "compliments",
position: "top_bar",
config: {
compliments: {
snow: ["snow"]
},
updateInterval: 4000
}
},
{
module: "weather",
position: "bottom_bar",
config: {
location: "Munich",
apiKey: "fake key",
initialLoadDelay: 3000
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}

View File

@ -14,7 +14,8 @@ let config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -14,7 +14,8 @@ let config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -14,7 +14,8 @@ let config = {
units: "imperial", units: "imperial",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -14,7 +14,8 @@ let config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -14,7 +14,8 @@ let config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
}, },

View File

@ -13,7 +13,8 @@ var config = {
units: "metric", units: "metric",
electronOptions: { electronOptions: {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true,
enableRemoteModule: true
} }
} }
}; };

View File

@ -31,26 +31,18 @@ describe("Electron app environment", function () {
return helpers.stopApplication(app); return helpers.stopApplication(app);
}); });
it("should open a browserwindow", function () { it("should open a browserwindow", async function () {
return ( await app.client.waitUntilWindowLoaded();
app.client app.browserWindow.focus();
.waitUntilWindowLoaded() expect(await app.client.getWindowCount()).to.equal(1);
// .browserWindow.focus() expect(await app.browserWindow.isMinimized()).to.be.false;
.getWindowCount() expect(await app.browserWindow.isDevToolsOpened()).to.be.false;
.should.eventually.equal(1) expect(await app.browserWindow.isVisible()).to.be.true;
.browserWindow.isMinimized() expect(await app.browserWindow.isFocused()).to.be.true;
.should.eventually.be.false.browserWindow.isDevToolsOpened() const bounds = await app.browserWindow.getBounds();
.should.eventually.be.false.browserWindow.isVisible() expect(bounds.width).to.be.above(0);
.should.eventually.be.true.browserWindow.isFocused() expect(bounds.height).to.be.above(0);
.should.eventually.be.true.browserWindow.getBounds() expect(await app.browserWindow.getTitle()).to.equal("MagicMirror²");
.should.eventually.have.property("width")
.and.be.above(0)
.browserWindow.getBounds()
.should.eventually.have.property("height")
.and.be.above(0)
.browserWindow.getTitle()
.should.eventually.equal("MagicMirror²")
);
}); });
it("get request from http://localhost:8080 should return 200", function (done) { it("get request from http://localhost:8080 should return 200", function (done) {

View File

@ -20,7 +20,7 @@ global.before(function () {
}); });
exports.getElectronPath = function () { exports.getElectronPath = function () {
var electronPath = path.join(__dirname, "..", "..", "node_modules", ".bin", "electron"); let electronPath = path.join(__dirname, "..", "..", "node_modules", ".bin", "electron");
if (process.platform === "win32") { if (process.platform === "win32") {
electronPath += ".cmd"; electronPath += ".cmd";
} }
@ -42,9 +42,9 @@ exports.startApplication = function (options) {
options.startTimeout = 30000; options.startTimeout = 30000;
} }
var app = new Application(options); const app = new Application(options);
return app.start().then(function () { return app.start().then(function () {
assert.equal(app.isRunning(), true); assert.strictEqual(app.isRunning(), true);
chaiAsPromised.transferPromiseness = app.transferPromiseness; chaiAsPromised.transferPromiseness = app.transferPromiseness;
return app; return app;
}); });
@ -56,6 +56,6 @@ exports.stopApplication = function (app) {
} }
return app.stop().then(function () { return app.stop().then(function () {
assert.equal(app.isRunning(), false); assert.strictEqual(app.isRunning(), false);
}); });
}; };

View File

@ -76,11 +76,11 @@ describe("Calendar module", function () {
}); });
}); });
describe("Basic auth", function () { describe("Changed port", function () {
before(function () { before(function () {
serverBasicAuth.listen(8010); serverBasicAuth.listen(8010);
// Set config sample for use in test // Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/basic-auth.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/changed-port.js";
}); });
after(function (done) { after(function (done) {
@ -92,17 +92,23 @@ describe("Calendar module", function () {
}); });
}); });
describe("Basic auth", function () {
before(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/basic-auth.js";
});
it("should return TestEvents", function () {
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
});
});
describe("Basic auth by default", function () { describe("Basic auth by default", function () {
before(function () { before(function () {
serverBasicAuth.listen(8011);
// Set config sample for use in test // Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/auth-default.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/auth-default.js";
}); });
after(function (done) {
serverBasicAuth.close(done());
});
it("should return TestEvents", function () { it("should return TestEvents", function () {
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
}); });
@ -110,15 +116,10 @@ describe("Calendar module", function () {
describe("Basic auth backward compatibility configuration: DEPRECATED", function () { describe("Basic auth backward compatibility configuration: DEPRECATED", function () {
before(function () { before(function () {
serverBasicAuth.listen(8012);
// Set config sample for use in test // Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/old-basic-auth.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/old-basic-auth.js";
}); });
after(function (done) {
serverBasicAuth.close(done());
});
it("should return TestEvents", function () { it("should return TestEvents", function () {
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
}); });

View File

@ -30,14 +30,16 @@ describe("Clock set to spanish language module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_24hr.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_24hr.js";
}); });
it("shows date with correct format", function () { it("shows date with correct format", async function () {
const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/; const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/;
return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex); const elem = await app.client.$(".clock .date");
return elem.getText(".clock .date").should.eventually.match(dateRegex);
}); });
it("shows time in 24hr format", function () { it("shows time in 24hr format", async function () {
const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/; const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); const elem = await app.client.$(".clock .time");
return elem.getText(".clock .time").should.eventually.match(timeRegex);
}); });
}); });
@ -47,14 +49,16 @@ describe("Clock set to spanish language module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_12hr.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_12hr.js";
}); });
it("shows date with correct format", function () { it("shows date with correct format", async function () {
const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/; const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/;
return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex); const elem = await app.client.$(".clock .date");
return elem.getText(".clock .date").should.eventually.match(dateRegex);
}); });
it("shows time in 12hr format", function () { it("shows time in 12hr format", async function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); const elem = await app.client.$(".clock .time");
return elem.getText(".clock .time").should.eventually.match(timeRegex);
}); });
}); });
@ -64,9 +68,10 @@ describe("Clock set to spanish language module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_showPeriodUpper.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_showPeriodUpper.js";
}); });
it("shows 12hr time with upper case AM/PM", function () { it("shows 12hr time with upper case AM/PM", async function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); const elem = await app.client.$(".clock .time");
return elem.getText(".clock .time").should.eventually.match(timeRegex);
}); });
}); });
@ -76,9 +81,10 @@ describe("Clock set to spanish language module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_showWeek.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_showWeek.js";
}); });
it("shows week with correct format", function () { it("shows week with correct format", async function () {
const weekRegex = /^Semana [0-9]{1,2}$/; const weekRegex = /^Semana [0-9]{1,2}$/;
return app.client.waitUntilWindowLoaded().getText(".clock .week").should.eventually.match(weekRegex); const elem = await app.client.$(".clock .week");
elem.getText(".clock .week").should.eventually.match(weekRegex);
}); });
}); });
}); });

View File

@ -32,14 +32,16 @@ describe("Clock module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_24hr.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_24hr.js";
}); });
it("should show the date in the correct format", function () { it("should show the date in the correct format", async function () {
const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/; const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex); const elem = await app.client.$(".clock .date");
return elem.getText(".clock .date").should.eventually.match(dateRegex);
}); });
it("should show the time in 24hr format", function () { it("should show the time in 24hr format", async function () {
const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/; const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); const elem = await app.client.$(".clock .time");
return elem.getText(".clock .time").should.eventually.match(timeRegex);
}); });
}); });
@ -49,14 +51,16 @@ describe("Clock module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_12hr.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_12hr.js";
}); });
it("should show the date in the correct format", function () { it("should show the date in the correct format", async function () {
const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/; const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex); const elem = await app.client.$(".clock .date");
return elem.getText(".clock .date").should.eventually.match(dateRegex);
}); });
it("should show the time in 12hr format", function () { it("should show the time in 12hr format", async function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); const elem = await app.client.$(".clock .time");
return elem.getText(".clock .time").should.eventually.match(timeRegex);
}); });
}); });
@ -66,9 +70,10 @@ describe("Clock module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showPeriodUpper.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showPeriodUpper.js";
}); });
it("should show 12hr time with upper case AM/PM", function () { it("should show 12hr time with upper case AM/PM", async function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); const elem = await app.client.$(".clock .time");
return elem.getText(".clock .time").should.eventually.match(timeRegex);
}); });
}); });
@ -78,9 +83,10 @@ describe("Clock module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_displaySeconds_false.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_displaySeconds_false.js";
}); });
it("should show 12hr time without seconds am/pm", function () { it("should show 12hr time without seconds am/pm", async function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); const elem = await app.client.$(".clock .time");
return elem.getText(".clock .time").should.eventually.match(timeRegex);
}); });
}); });
@ -90,15 +96,17 @@ describe("Clock module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showWeek.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showWeek.js";
}); });
it("should show the week in the correct format", function () { it("should show the week in the correct format", async function () {
const weekRegex = /^Week [0-9]{1,2}$/; const weekRegex = /^Week [0-9]{1,2}$/;
return app.client.waitUntilWindowLoaded().getText(".clock .week").should.eventually.match(weekRegex); const elem = await app.client.$(".clock .week");
return elem.getText(".clock .week").should.eventually.match(weekRegex);
}); });
it("should show the week with the correct number of week of year", function () { it("should show the week with the correct number of week of year", async function () {
const currentWeekNumber = moment().week(); const currentWeekNumber = moment().week();
const weekToShow = "Week " + currentWeekNumber; const weekToShow = "Week " + currentWeekNumber;
return app.client.waitUntilWindowLoaded().getText(".clock .week").should.eventually.equal(weekToShow); const elem = await app.client.$(".clock .week");
return elem.getText(".clock .week").should.eventually.equal(weekToShow);
}); });
}); });

View File

@ -31,40 +31,34 @@ describe("Compliments module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_parts_day.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_parts_day.js";
}); });
it("if Morning compliments for that part of day", function () { it("if Morning compliments for that part of day", async function () {
var hour = new Date().getHours(); var hour = new Date().getHours();
if (hour >= 3 && hour < 12) { if (hour >= 3 && hour < 12) {
// if morning check // if morning check
return app.client const elem = await app.client.$(".compliments");
.waitUntilWindowLoaded() return elem.getText(".compliments").then(function (text) {
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Hi", "Good Morning", "Morning test"]); expect(text).to.be.oneOf(["Hi", "Good Morning", "Morning test"]);
}); });
} }
}); });
it("if Afternoon show Compliments for that part of day", function () { it("if Afternoon show Compliments for that part of day", async function () {
var hour = new Date().getHours(); var hour = new Date().getHours();
if (hour >= 12 && hour < 17) { if (hour >= 12 && hour < 17) {
// if morning check // if morning check
return app.client const elem = await app.client.$(".compliments");
.waitUntilWindowLoaded() return elem.getText(".compliments").then(function (text) {
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Hello", "Good Afternoon", "Afternoon test"]); expect(text).to.be.oneOf(["Hello", "Good Afternoon", "Afternoon test"]);
}); });
} }
}); });
it("if Evening show Compliments for that part of day", function () { it("if Evening show Compliments for that part of day", async function () {
var hour = new Date().getHours(); var hour = new Date().getHours();
if (!(hour >= 3 && hour < 12) && !(hour >= 12 && hour < 17)) { if (!(hour >= 3 && hour < 12) && !(hour >= 12 && hour < 17)) {
// if evening check // if evening check
return app.client const elem = await app.client.$(".compliments");
.waitUntilWindowLoaded() return elem.getText(".compliments").then(function (text) {
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Hello There", "Good Evening", "Evening test"]); expect(text).to.be.oneOf(["Hello There", "Good Evening", "Evening test"]);
}); });
} }
@ -78,11 +72,9 @@ describe("Compliments module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_anytime.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_anytime.js";
}); });
it("Show anytime because if configure empty parts of day compliments and set anytime compliments", function () { it("Show anytime because if configure empty parts of day compliments and set anytime compliments", async function () {
return app.client const elem = await app.client.$(".compliments");
.waitUntilWindowLoaded() return elem.getText(".compliments").then(function (text) {
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Anytime here"]); expect(text).to.be.oneOf(["Anytime here"]);
}); });
}); });
@ -94,11 +86,9 @@ describe("Compliments module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_only_anytime.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_only_anytime.js";
}); });
it("Show anytime compliments", function () { it("Show anytime compliments", async function () {
return app.client const elem = await app.client.$(".compliments");
.waitUntilWindowLoaded() return elem.getText(".compliments").then(function (text) {
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Anytime here"]); expect(text).to.be.oneOf(["Anytime here"]);
}); });
}); });
@ -112,11 +102,9 @@ describe("Compliments module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_date.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_date.js";
}); });
it("Show happy new year compliment on new years day", function () { it("Show happy new year compliment on new years day", async function () {
return app.client const elem = await app.client.$(".compliments");
.waitUntilWindowLoaded() return elem.getText(".compliments").then(function (text) {
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Happy new year!"]); expect(text).to.be.oneOf(["Happy new year!"]);
}); });
}); });

View File

@ -30,8 +30,9 @@ describe("Test helloworld module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/helloworld/helloworld.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/helloworld/helloworld.js";
}); });
it("Test message helloworld module", function () { it("Test message helloworld module", async function () {
return app.client.waitUntilWindowLoaded().getText(".helloworld").should.eventually.equal("Test HelloWorld Module"); const elem = await app.client.$("helloworld");
return elem.getText(".helloworld").should.eventually.equal("Test HelloWorld Module");
}); });
}); });
@ -41,8 +42,9 @@ describe("Test helloworld module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/helloworld/helloworld_default.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/helloworld/helloworld_default.js";
}); });
it("Test message helloworld module", function () { it("Test message helloworld module", async function () {
return app.client.waitUntilWindowLoaded().getText(".helloworld").should.eventually.equal("Hello World!"); const elem = await app.client.$("helloworld");
return elem.getText(".helloworld").should.eventually.equal("Hello World!");
}); });
}); });
}); });

Some files were not shown because too many files have changed in this diff Show More