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)
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
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.
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.

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"
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
jobs:
# Enforces the update of a changelog file on every pull request
check:
runs-on: ubuntu-latest
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
# 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:
push:
@ -11,13 +11,10 @@ on:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
steps:
- uses: actions/checkout@v2
- 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²
## [2.14.1] - 2021-03-07
## [2.15.0] - Unreleased (Develop Branch)
_This release is scheduled to be released on 2021-04-01._
### 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
@ -31,7 +74,6 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
- Calendar: new options "limitDays" and "coloredEvents".
- 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 GitHub workflows for automated testing and changelog enforcement.
### Updated

View File

@ -1,6 +1,6 @@
# The MIT License (MIT)
Copyright © 2016-2020 Michael Teeuw
Copyright © 2016-2021 Michael Teeuw
Permission is hereby granted, free of charge, to any person
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)
<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#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://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>
@ -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.
<p align="center">
<br>
<p style="text-align: center">
<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>

View File

@ -14,7 +14,6 @@
*
* @param {string} key key to look for at the command line
* @param {string} defaultValue value if no key is given at the command line
*
* @returns {string} the value of the parameter
*/
function getCommandLineParameter(key, defaultValue = undefined) {
@ -36,7 +35,6 @@
* Gets the config from the specified server url
*
* @param {string} url location where the server is running.
*
* @returns {Promise} the config
*/
function getServerConfig(url) {
@ -66,7 +64,7 @@
/**
* 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
*/
function fail(message, code = 1) {

View File

@ -28,6 +28,7 @@ var config = {
httpsCertificate: "", // HTTPS Certificate path, only require when useHttps is true
language: "en",
locale: "en-US",
logLevel: ["INFO", "LOG", "WARN", "ERROR"], // Add "DEBUG" for even more logging
timeFormat: 24,
units: "metric",
@ -66,22 +67,26 @@ var config = {
position: "lower_third"
},
{
module: "currentweather",
module: "weather",
position: "top_right",
config: {
weatherProvider: "openweathermap",
type: "current",
location: "New York",
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",
header: "Weather Forecast",
config: {
weatherProvider: "openweathermap",
type: "forecast",
location: "New York",
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.");
}
}

132
js/app.js
View File

@ -4,22 +4,23 @@
* By Michael Teeuw https://michaelteeuw.nl
* 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.
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.
global.version = JSON.parse(fs.readFileSync("package.json", "utf8")).version;
global.version = require(`${__dirname}/../package.json`).version;
Log.log("Starting MagicMirror: v" + global.version);
// global absolute root path
global.root_path = path.resolve(__dirname + "/../");
global.root_path = path.resolve(`${__dirname}/../`);
if (process.env.MM_CONFIG_FILE) {
global.configuration_file = process.env.MM_CONFIG_FILE;
@ -45,43 +46,40 @@ process.on("uncaughtException", function (err) {
*
* @class
*/
var App = function () {
var nodeHelpers = [];
function App() {
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
* callback with the found config as argument.
*
* @param {Function} callback Function to be called after loading the config
*/
var loadConfig = function (callback) {
function loadConfig(callback) {
Log.log("Loading config ...");
var defaults = require(__dirname + "/defaults.js");
const defaults = require(`${__dirname}/defaults`);
// For this check proposed to TestSuite
// https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8
var configFilename = path.resolve(global.root_path + "/config/config.js");
if (typeof global.configuration_file !== "undefined") {
configFilename = path.resolve(global.configuration_file);
}
const configFilename = path.resolve(global.configuration_file || `${global.root_path}/config/config.js`);
try {
fs.accessSync(configFilename, fs.F_OK);
var c = require(configFilename);
const c = require(configFilename);
checkDeprecatedOptions(c);
var config = Object.assign(defaults, c);
const config = Object.assign(defaults, c);
callback(config);
} catch (e) {
if (e.code === "ENOENT") {
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) {
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 {
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);
}
};
}
/**
* 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
*/
var checkDeprecatedOptions = function (userConfig) {
var deprecated = require(global.root_path + "/js/deprecated.js");
var deprecatedOptions = deprecated.configs;
function checkDeprecatedOptions(userConfig) {
const deprecated = require(`${global.root_path}/js/deprecated`);
const deprecatedOptions = deprecated.configs;
var usedDeprecated = [];
deprecatedOptions.forEach(function (option) {
if (userConfig.hasOwnProperty(option)) {
usedDeprecated.push(option);
}
});
const usedDeprecated = deprecatedOptions.filter((option) => userConfig.hasOwnProperty(option));
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.
@ -111,35 +103,35 @@ var App = function () {
* @param {string} module The name of the module (including subpath).
* @param {Function} callback Function to be called after loading
*/
var loadModule = function (module, callback) {
var elements = module.split("/");
var moduleName = elements[elements.length - 1];
var moduleFolder = __dirname + "/../modules/" + module;
function loadModule(module, callback) {
const elements = module.split("/");
const moduleName = elements[elements.length - 1];
let moduleFolder = `${__dirname}/../modules/${module}`;
if (defaultModules.indexOf(moduleName) !== -1) {
moduleFolder = __dirname + "/../modules/default/" + module;
if (defaultModules.includes(moduleName)) {
moduleFolder = `${__dirname}/../modules/default/${module}`;
}
var helperPath = moduleFolder + "/node_helper.js";
const helperPath = `${moduleFolder}/node_helper.js`;
var loadModule = true;
let loadHelper = true;
try {
fs.accessSync(helperPath, fs.R_OK);
} catch (e) {
loadModule = false;
Log.log("No helper found for module: " + moduleName + ".");
loadHelper = false;
Log.log(`No helper found for module: ${moduleName}.`);
}
if (loadModule) {
var Module = require(helperPath);
var m = new Module();
if (loadHelper) {
const Module = require(helperPath);
let m = new Module();
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) {
Log.log("Version is ok!");
} else {
Log.log("Version is incorrect. Skip module: '" + moduleName + "'");
Log.warn(`Version is incorrect. Skip module: '${moduleName}'`);
return;
}
}
@ -152,7 +144,7 @@ var App = function () {
} else {
callback();
}
};
}
/**
* Loads all modules.
@ -160,12 +152,15 @@ var App = function () {
* @param {Module[]} modules All modules to be loaded
* @param {Function} callback Function to be called after loading
*/
var loadModules = function (modules, callback) {
function loadModules(modules, callback) {
Log.log("Loading module helpers ...");
var loadNextModule = function () {
/**
*
*/
function loadNextModule() {
if (modules.length > 0) {
var nextModule = modules[0];
const nextModule = modules[0];
loadModule(nextModule, function () {
modules = modules.slice(1);
loadNextModule();
@ -175,10 +170,10 @@ var App = function () {
Log.log("All module helpers loaded.");
callback();
}
};
}
loadNextModule();
};
}
/**
* 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
*/
function cmpVersions(a, b) {
var i, diff;
var regExStrip0 = /(\.0+)+$/;
var segmentsA = a.replace(regExStrip0, "").split(".");
var segmentsB = b.replace(regExStrip0, "").split(".");
var l = Math.min(segmentsA.length, segmentsB.length);
let i, diff;
const regExStrip0 = /(\.0+)+$/;
const segmentsA = a.replace(regExStrip0, "").split(".");
const segmentsB = b.replace(regExStrip0, "").split(".");
const l = Math.min(segmentsA.length, segmentsB.length);
for (i = 0; i < l; i++) {
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
@ -219,21 +214,19 @@ var App = function () {
Log.setLogLevel(config.logLevel);
var modules = [];
let modules = [];
for (var m in config.modules) {
var module = config.modules[m];
if (modules.indexOf(module.module) === -1 && !module.disabled) {
for (const module of config.modules) {
if (!modules.includes(module.module) && !module.disabled) {
modules.push(module.module);
}
}
loadModules(modules, function () {
var server = new Server(config, function (app, io) {
const server = new Server(config, function (app, io) {
Log.log("Server started ...");
for (var h in nodeHelpers) {
var nodeHelper = nodeHelpers[h];
for (let nodeHelper of nodeHelpers) {
nodeHelper.setExpressApp(app);
nodeHelper.setSocketIO(io);
nodeHelper.start();
@ -256,8 +249,7 @@ var App = function () {
* Added to fix #1056
*/
this.stop = function () {
for (var h in nodeHelpers) {
var nodeHelper = nodeHelpers[h];
for (const nodeHelper of nodeHelpers) {
if (typeof nodeHelper.stop === "function") {
nodeHelper.stop();
}
@ -292,6 +284,6 @@ var App = function () {
this.stop();
process.exit(0);
});
};
}
module.exports = new App();

View File

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

View File

@ -20,6 +20,7 @@ var defaults = {
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
logLevel: ["INFO", "LOG", "WARN", "ERROR"],
timeFormat: 24,
units: "metric",
zoom: 1,
@ -42,7 +43,7 @@ var defaults = {
module: "helloworld",
position: "middle_center",
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",
classes: "xsmall",
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
*/
var deprecated = {
module.exports = {
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 core = require("./app.js");
const Log = require("./logger.js");
const Log = require("logger");
// 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.
const app = electron.app;
// Module to create native browser window.
@ -20,13 +20,14 @@ let mainWindow;
*/
function createWindow() {
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
var electronOptionsDefaults = {
let electronOptionsDefaults = {
width: 800,
height: 600,
x: 0,
y: 0,
darkTheme: true,
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
zoomFactor: config.zoom
},
@ -42,7 +43,7 @@ function createWindow() {
electronOptionsDefaults.autoHideMenuBar = true;
}
var electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions);
const electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions);
// Create the browser window.
mainWindow = new BrowserWindow(electronOptions);
@ -50,14 +51,14 @@ function createWindow() {
// 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
var prefix;
let prefix;
if (config["tls"] !== null && config["tls"]) {
prefix = "https://";
} else {
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}`);
// 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
// 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) {
config = c;
});

View File

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

View File

@ -295,6 +295,9 @@ var MM = (function () {
// Otherwise cancel show action.
if (module.lockStrings.length !== 0 && options.force !== true) {
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;
}
@ -440,7 +443,6 @@ var MM = (function () {
* Removes a module instance from the collection.
*
* @param {object} module The module instance to remove from the collection.
*
* @returns {Module[]} Filtered collection of modules.
*/
var exceptModule = function (module) {
@ -547,6 +549,11 @@ var MM = (function () {
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.
updateDom(module, speed);
},

View File

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

View File

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

View File

@ -4,25 +4,29 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
var express = require("express");
var app = require("express")();
var path = require("path");
var ipfilter = require("express-ipfilter").IpFilter;
var fs = require("fs");
var helmet = require("helmet");
const express = require("express");
const app = require("express")();
const path = require("path");
const ipfilter = require("express-ipfilter").IpFilter;
const fs = require("fs");
const helmet = require("helmet");
var Log = require("./logger.js");
var Utils = require("./utils.js");
const Log = require("logger");
const Utils = require("./utils.js");
var Server = function (config, callback) {
var port = config.port;
if (process.env.MM_PORT) {
port = process.env.MM_PORT;
}
/**
* Server
*
* @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) {
var options = {
const options = {
key: fs.readFileSync(config.httpsPrivateKey),
cert: fs.readFileSync(config.httpsCertificate)
};
@ -30,18 +34,24 @@ var Server = function (config, callback) {
} else {
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) {
Log.warn(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
}
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) {
return next();
}
@ -52,10 +62,9 @@ var Server = function (config, callback) {
app.use(helmet({ contentSecurityPolicy: false }));
app.use("/js", express.static(__dirname));
var directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs"];
var directory;
for (var i in directories) {
directory = directories[i];
const directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs"];
for (const directory of directories) {
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) {
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);
var configFile = "config/config.js";
let configFile = "config/config.js";
if (typeof global.configuration_file !== "undefined") {
configFile = global.configuration_file;
}
@ -83,6 +92,6 @@ var Server = function (config, callback) {
if (typeof callback === "function") {
callback(app, io);
}
};
}
module.exports = Server;

View File

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

View File

@ -4,9 +4,9 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var colors = require("colors/safe");
const colors = require("colors/safe");
var Utils = {
module.exports = {
colors: {
warn: colors.yellow,
error: colors.red,
@ -14,7 +14,3 @@ var Utils = {
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.
hidePrivate: false,
hideOngoing: false,
hideTime: false,
colored: false,
coloredSymbolOnly: false,
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: [],
sliceMultiDayEvents: false,
broadcastPastEvents: false,
nextDaysRelative: false
nextDaysRelative: false,
selfSignedCert: false
},
requiresVersion: "2.1.0",
@ -100,7 +102,8 @@ Module.register("calendar", {
var calendarConfig = {
maximumEntries: calendar.maximumEntries,
maximumNumberOfDays: calendar.maximumNumberOfDays,
broadcastPastEvents: calendar.broadcastPastEvents
broadcastPastEvents: calendar.broadcastPastEvents,
selfSignedCert: calendar.selfSignedCert
};
if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
calendarConfig.symbolClass = "";
@ -277,8 +280,11 @@ Module.register("calendar", {
if (typeof this.config.customEvents[ev].color !== "undefined" && this.config.customEvents[ev].color !== "") {
needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
if (needle.test(event.title)) {
eventWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
titleWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
// Respect parameter ColoredSymbolOnly also for custom events
if (!this.config.coloredSymbolOnly) {
eventWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
titleWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
}
if (this.config.displaySymbol) {
symbolWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
}
@ -363,7 +369,17 @@ Module.register("calendar", {
// Show relative times
if (event.startDate >= now) {
// Use relative time
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar());
if (!this.config.hideTime) {
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 is within getRelative hours, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
@ -592,7 +608,8 @@ Module.register("calendar", {
titleClass: calendarConfig.titleClass,
timeClass: calendarConfig.timeClass,
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
* MIT Licensed.
*/
const Log = require("../../../js/logger.js");
const Log = require("logger");
const ical = require("node-ical");
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 {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} selfSignedCert If true, the server certificate is not verified against the list of supplied CAs.
* @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;
let reloadTimer = null;
@ -51,6 +52,13 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
gzip: true
};
if (selfSignedCert) {
var agentOptions = {
rejectUnauthorized: false
};
opts.agentOptions = agentOptions;
}
if (auth) {
if (auth.method === "bearer") {
opts.auth = {

View File

@ -7,7 +7,7 @@
const NodeHelper = require("node_helper");
const validUrl = require("valid-url");
const CalendarFetcher = require("./calendarfetcher.js");
const Log = require("../../../js/logger");
const Log = require("logger");
module.exports = NodeHelper.create({
// Override start method.
@ -19,7 +19,7 @@ module.exports = NodeHelper.create({
// Override socketNotificationReceived method.
socketNotificationReceived: function (notification, payload) {
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 {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} selfSignedCert If true, the server certificate is not verified against the list of supplied CAs.
* @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;
if (!validUrl.isUri(url)) {
@ -47,7 +48,7 @@ module.exports = NodeHelper.create({
var fetcher;
if (typeof self.fetchers[identifier + url] === "undefined") {
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) {
self.sendSocketNotification("CALENDAR_EVENTS", {

View File

@ -182,34 +182,14 @@ Module.register("compliments", {
},
// From data currentweather set weather type
setCurrentWeatherType: function (data) {
var weatherIconTable = {
"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];
setCurrentWeatherType: function (type) {
this.currentWeatherType = type;
},
// Override notification handler.
notificationReceived: function (notification, payload, sender) {
if (notification === "CURRENTWEATHER_DATA") {
this.setCurrentWeatherType(payload.data);
if (notification === "CURRENTWEATHER_TYPE") {
this.setCurrentWeatherType(payload.type);
}
}
});

View File

@ -1,5 +1,7 @@
# 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.
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
* MIT Licensed.
*
* This module is deprecated. Any additional feature will no longer be merged.
*/
Module.register("currentweather", {
// Default module config.
@ -47,24 +49,24 @@ Module.register("currentweather", {
roundTemp: false,
iconTable: {
"01d": "wi-day-sunny",
"02d": "wi-day-cloudy",
"03d": "wi-cloudy",
"04d": "wi-cloudy-windy",
"09d": "wi-showers",
"10d": "wi-rain",
"11d": "wi-thunderstorm",
"13d": "wi-snow",
"50d": "wi-fog",
"01n": "wi-night-clear",
"02n": "wi-night-cloudy",
"03n": "wi-night-cloudy",
"04n": "wi-night-cloudy",
"09n": "wi-night-showers",
"10n": "wi-night-rain",
"11n": "wi-night-thunderstorm",
"13n": "wi-night-snow",
"50n": "wi-night-alt-cloudy-windy"
"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"
}
},
@ -219,7 +221,7 @@ Module.register("currentweather", {
if (this.config.hideTemp === false) {
var weatherIcon = document.createElement("span");
weatherIcon.className = "wi weathericon " + this.weatherType;
weatherIcon.className = "wi weathericon wi-" + this.weatherType;
large.appendChild(weatherIcon);
var temperature = document.createElement("span");
@ -258,13 +260,9 @@ Module.register("currentweather", {
var feelsLike = document.createElement("span");
feelsLike.className = "dimmed";
var feelsLikeHtml = this.translate("FEELS");
if (feelsLikeHtml.indexOf("{DEGREE}") > -1) {
feelsLikeHtml = this.translate("FEELS", {
DEGREE: this.feelsLike + degreeLabel
});
} else feelsLikeHtml += " " + this.feelsLike + degreeLabel;
feelsLike.innerHTML = feelsLikeHtml;
feelsLike.innerHTML = this.translate("FEELS", {
DEGREE: this.feelsLike + degreeLabel
});
small.appendChild(feelsLike);
wrapper.appendChild(small);
@ -506,6 +504,7 @@ Module.register("currentweather", {
this.loaded = true;
this.updateDom(this.config.animationSpeed);
this.sendNotification("CURRENTWEATHER_DATA", { data: data });
this.sendNotification("CURRENTWEATHER_TYPE", { type: this.config.iconTable[data.weather[0].icon].replace("-", "_") });
},
/* scheduleUpdate()
@ -593,6 +592,7 @@ Module.register("currentweather", {
*/
roundValue: function (temperature) {
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"];
},
//Define required styles.
getStyles: function () {
return ["newsfeed.css"];
},
// Define required translations.
getTranslations: function () {
// The translations for the default modules are defined in the core translation files.
@ -75,6 +80,9 @@ Module.register("newsfeed", {
this.generateFeed(payload);
if (!this.loaded) {
if (this.config.hideLoading) {
this.show();
}
this.scheduleUpdateInterval();
}
@ -82,123 +90,43 @@ Module.register("newsfeed", {
}
},
// Override dom generator.
getDom: function () {
const wrapper = document.createElement("div");
//Override fetching of template name
getTemplate: function () {
if (this.config.feedUrl) {
wrapper.className = "small bright";
wrapper.innerHTML = this.translate("MODULE_CONFIG_CHANGED", { MODULE_NAME: "Newsfeed" });
return wrapper;
return "oldconfig.njk";
} else if (this.config.showFullArticle) {
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) {
this.activeItem = 0;
}
const item = this.newsItems[this.activeItem];
if (this.newsItems.length > 0) {
// this.config.showFullArticle is a run-time configuration, triggered by optional notifications
if (!this.config.showFullArticle && (this.config.showSourceTitle || this.config.showPublishDate)) {
const sourceAndTimestamp = document.createElement("div");
sourceAndTimestamp.className = "newsfeed-source light small dimmed";
if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") {
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;
return {
loaded: true,
config: this.config,
sourceTitle: item.sourceTitle,
publishDate: moment(new Date(item.pubdate)).fromNow(),
title: item.title,
description: item.description
};
},
getActiveItemURL: function () {
@ -257,6 +185,45 @@ Module.register("newsfeed", {
}, 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
var updatedItems = [];
newsItems.forEach((value) => {
@ -335,8 +302,7 @@ Module.register("newsfeed", {
this.config.showFullArticle = false;
this.scrollPosition = 0;
// reset bottom bar alignment
document.getElementsByClassName("region bottom bar")[0].style.bottom = "0";
document.getElementsByClassName("region bottom bar")[0].style.top = "inherit";
document.getElementsByClassName("region bottom bar")[0].classList.remove("newsfeed-fullarticle");
if (!this.timer) {
this.scheduleUpdateInterval();
}
@ -344,7 +310,9 @@ Module.register("newsfeed", {
notificationReceived: function (notification, payload, sender) {
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++;
if (this.activeItem >= this.newsItems.length) {
this.activeItem = 0;
@ -406,8 +374,7 @@ Module.register("newsfeed", {
this.config.showFullArticle = !this.isShowingDescription;
// make bottom bar align to top to allow scrolling
if (this.config.showFullArticle === true) {
document.getElementsByClassName("region bottom bar")[0].style.bottom = "inherit";
document.getElementsByClassName("region bottom bar")[0].style.top = "-90px";
document.getElementsByClassName("region bottom bar")[0].classList.add("newsfeed-fullarticle");
}
clearInterval(this.timer);
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
* MIT Licensed.
*/
const Log = require("../../../js/logger.js");
const Log = require("logger");
const FeedMe = require("feedme");
const request = require("request");
const iconv = require("iconv-lite");

View File

@ -8,7 +8,7 @@
const NodeHelper = require("node_helper");
const validUrl = require("valid-url");
const NewsfeedFetcher = require("./newsfeedfetcher.js");
const Log = require("../../../js/logger");
const Log = require("logger");
module.exports = NodeHelper.create({
// 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 path = require("path");
const defaultModules = require(__dirname + "/../defaultmodules.js");
const Log = require(__dirname + "/../../../js/logger.js");
const Log = require("logger");
const NodeHelper = require("node_helper");
module.exports = NodeHelper.create({

View File

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

View File

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

View File

@ -1,7 +1,4 @@
{% if hourly or weatherData %}
{% if weatherData %}
{% set hourly = weatherData.hours %}
{% endif %}
{% if hourly %}
{% set numSteps = hourly | calcNumEntries %}
{% set currentStep = 0 %}
<table class="{{ config.tableClass }}">
@ -24,9 +21,9 @@
</table>
{% else %}
<div class="dimmed light small">
{{ "LOADING" | translate | safe }}
{{ "LOADING" | translate }}
</div>
{% endif %}
<!-- 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()`
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.
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.
#### `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.
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.
### 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.
#### `weatherHourly()`
This returns an array of WeatherDay objects for the hourly weather forecast.
#### `fetchedLocation()`
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.
#### `setWeatherHourly(weatherHourlyArray)`
Set the weatherHourlyArray and notify the delegate that new information is available.
#### `setFetchedLocation(name)`
Set the fetched location name.

View File

@ -15,6 +15,15 @@ WeatherProvider.register("darksky", {
// Not strictly required, but helps for debugging.
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: {
imperial: "us",
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.
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.
fetchCurrentWeather() {
this.fetchData(this.getUrl())
@ -56,8 +68,8 @@ WeatherProvider.register("openweathermap", {
.finally(() => this.updateAvailable());
},
// Overwrite the fetchWeatherData method.
fetchWeatherData() {
// Overwrite the fetchWeatherHourly method.
fetchWeatherHourly() {
this.fetchData(this.getUrl())
.then((data) => {
if (!data) {
@ -69,7 +81,7 @@ WeatherProvider.register("openweathermap", {
this.setFetchedLocation(`(${data.lat},${data.lon})`);
const weatherData = this.generateWeatherObjectsFromOnecall(data);
this.setWeatherData(weatherData);
this.setWeatherHourly(weatherData.hours);
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
@ -77,6 +89,31 @@ WeatherProvider.register("openweathermap", {
.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 */
/*
* Gets the complete url for the request

View File

@ -14,6 +14,13 @@
WeatherProvider.register("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
*/
@ -55,7 +62,7 @@ WeatherProvider.register("smhi", {
this.config = config;
if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) == -1) {
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.
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: {
imperial: "us",
metric: "si"

View File

@ -44,6 +44,16 @@ WeatherProvider.register("ukmetofficedatahub", {
// Set the name of the provider.
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)
getUrl(forecastType) {
let queryStrings = "?";

View File

@ -14,6 +14,15 @@ WeatherProvider.register("weatherbit", {
// Not strictly required, but helps for debugging.
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: {
imperial: "I",
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.
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
configURLs: false,

View File

@ -12,10 +12,6 @@ Module.register("weather", {
weatherProvider: "openweathermap",
roundTemp: false,
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,
useKmh: false,
tempUnits: config.units,
@ -40,20 +36,13 @@ Module.register("weather", {
fade: true,
fadePoint: 0.25, // Start on 1/4th of the list.
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,
calendarClass: "calendar",
tableClass: "small",
onlyTemp: false,
showPrecipitationAmount: false,
colored: false,
showFeelsLike: true,
feelsLikeWithDegree: false
showFeelsLike: true
},
// Module properties.
@ -89,8 +78,6 @@ Module.register("weather", {
// Let the weather provider know we are starting.
this.weatherProvider.start();
this.config.feelsLikeWithDegree = this.translate("FEELS").indexOf("{DEGREE}") > -1;
// Add custom filters
this.addFilters();
@ -133,8 +120,9 @@ Module.register("weather", {
case "daily":
case "forecast":
return `forecast.njk`;
//Make the invalid values use the "Loading..." from forecast
default:
return `${this.config.type.toLowerCase()}.njk`;
return `forecast.njk`;
}
},
@ -144,7 +132,7 @@ Module.register("weather", {
config: this.config,
current: this.weatherProvider.currentWeather(),
forecast: this.weatherProvider.weatherForecast(),
weatherData: this.weatherProvider.weatherData(),
hourly: this.weatherProvider.weatherHourly(),
indoor: {
humidity: this.indoorHumidity,
temperature: this.indoorTemperature
@ -157,6 +145,10 @@ Module.register("weather", {
Log.log("New weather information available.");
this.updateDom(0);
this.scheduleUpdate();
if (this.weatherProvider.currentWeather()) {
this.sendNotification("CURRENTWEATHER_TYPE", { type: this.weatherProvider.currentWeather().weatherType.replace("-", "_") });
}
},
scheduleUpdate: function (delay = null) {
@ -166,19 +158,27 @@ Module.register("weather", {
}
setTimeout(() => {
if (this.config.weatherEndpoint === "/onecall") {
this.weatherProvider.fetchWeatherData();
} else if (this.config.type === "forecast") {
this.weatherProvider.fetchWeatherForecast();
} else {
this.weatherProvider.fetchCurrentWeather();
switch (this.config.type.toLowerCase()) {
case "current":
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);
},
roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals);
var roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
},
addFilters() {

View File

@ -11,12 +11,13 @@
var WeatherProvider = Class.extend({
// Weather Provider Properties
providerName: null,
defaults: {},
// The following properties have accessor methods.
// Try to not access them directly.
currentWeatherObject: null,
weatherForecastArray: null,
weatherDataObject: null,
weatherHourlyArray: null,
fetchedLocationName: null,
// 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.`);
},
// 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.
fetchWeatherData: function () {
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherData method.`);
fetchWeatherHourly: function () {
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherHourly method.`);
},
// 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.
weatherData: function () {
return this.weatherDataObject;
weatherHourly: function () {
return this.weatherHourlyArray;
},
// This returns the name of the fetched location or an empty string.
@ -95,9 +96,9 @@ var WeatherProvider = Class.extend({
this.weatherForecastArray = weatherForecastArray;
},
// Set the weatherDataObject and notify the delegate that new information is available.
setWeatherData: function (weatherDataObject) {
this.weatherDataObject = weatherDataObject;
// Set the weatherHourlyArray and notify the delegate that new information is available.
setWeatherHourly: function (weatherHourlyArray) {
this.weatherHourlyArray = weatherHourlyArray;
},
// Set the fetched location name.
@ -154,10 +155,11 @@ WeatherProvider.register = function (providerIdentifier, providerDetails) {
WeatherProvider.initialize = function (providerIdentifier, delegate) {
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.setConfig(delegate.config);
provider.setConfig(config);
provider.providerIdentifier = providerIdentifier;
if (!provider.providerName) {

View File

@ -1,5 +1,7 @@
# 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.
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
* MIT Licensed.
*
* This module is deprecated. Any additional feature will no longer be merged.
*/
Module.register("weatherforecast", {
// Default module config.
@ -351,6 +353,13 @@ Module.register("weatherforecast", {
this.forecast = [];
var lastDay = null;
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
var forecastList = null;
@ -371,10 +380,10 @@ Module.register("weatherforecast", {
var hour;
if (forecast.dt_txt) {
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 {
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) {
@ -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)
// 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];
}
}
@ -462,7 +471,8 @@ Module.register("weatherforecast", {
*/
roundValue: function (temperature) {
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)

5745
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,26 @@
{
"name": "magicmirror",
"version": "2.14.0",
"version": "2.15.0-develop",
"description": "The open source modular smart mirror platform.",
"main": "js/electron.js",
"scripts": {
"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",
"install": "echo \"Installing vendor files ...\n\" && cd vendor && 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\"",
"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:unit": "NODE_ENV=test mocha tests/unit --recursive",
"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:calendar": "node ./modules/default/calendar/debug.js",
"config:check": "node js/check_config.js",
"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"
},
"repository": {
@ -42,53 +43,57 @@
},
"homepage": "https://magicmirror.builders",
"devDependencies": {
"chai": "^4.2.0",
"chai": "^4.3.0",
"chai-as-promised": "^7.1.1",
"danger": "^10.5.4",
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-jsdoc": "^30.7.8",
"eslint-plugin-prettier": "^3.2.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-jsdoc": "^32.2.0",
"eslint-plugin-prettier": "^3.3.1",
"express-basic-auth": "^1.2.0",
"husky": "^4.3.5",
"husky": "^4.3.8",
"jsdom": "^16.4.0",
"lodash": "^4.17.20",
"mocha": "^8.2.1",
"lodash": "^4.17.21",
"mocha": "^8.3.0",
"mocha-each": "^2.0.1",
"mocha-logger": "^1.0.7",
"nyc": "^15.1.0",
"prettier": "^2.2.1",
"pretty-quick": "^3.1.0",
"spectron": "^10.0.1",
"stylelint": "^13.8.0",
"sinon": "^9.2.4",
"spectron": "^13.0.0",
"stylelint": "^13.11.0",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^20.0.0",
"stylelint-prettier": "^1.1.2"
},
"optionalDependencies": {
"electron": "^8.5.3"
"electron": "^11.3.0"
},
"dependencies": {
"colors": "^1.4.0",
"console-stamp": "^3.0.0-rc4.2",
"eslint": "^7.15.0",
"eslint": "^7.20.0",
"express": "^4.17.1",
"express-ipfilter": "^1.1.2",
"feedme": "^2.0.2",
"helmet": "^4.2.0",
"helmet": "^4.4.1",
"ical": "^0.8.0",
"iconv-lite": "^0.6.2",
"module-alias": "^2.2.2",
"moment": "^2.29.1",
"node-ical": "^0.12.7",
"node-ical": "^0.12.8",
"request": "^2.88.2",
"rrule": "^2.6.6",
"rrule": "^2.6.8",
"rrule-alt": "^2.2.8",
"simple-git": "^2.31.0",
"socket.io": "^3.0.4",
"simple-git": "^2.35.2",
"socket.io": "^3.1.2",
"valid-url": "^1.0.9"
},
"_moduleAliases": {
"node_helper": "js/node_helper.js"
"node_helper": "js/node_helper.js",
"logger": "js/logger.js"
},
"engines": {
"node": ">=10"
},
"husky": {
"hooks": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,8 @@ var config = {
units: "metric",
electronOptions: {
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",
electronOptions: {
webPreferences: {
nodeIntegration: true
nodeIntegration: true,
enableRemoteModule: true
}
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ global.before(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") {
electronPath += ".cmd";
}
@ -42,9 +42,9 @@ exports.startApplication = function (options) {
options.startTimeout = 30000;
}
var app = new Application(options);
const app = new Application(options);
return app.start().then(function () {
assert.equal(app.isRunning(), true);
assert.strictEqual(app.isRunning(), true);
chaiAsPromised.transferPromiseness = app.transferPromiseness;
return app;
});
@ -56,6 +56,6 @@ exports.stopApplication = function (app) {
}
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 () {
serverBasicAuth.listen(8010);
// 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) {
@ -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 () {
before(function () {
serverBasicAuth.listen(8011);
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/auth-default.js";
});
after(function (done) {
serverBasicAuth.close(done());
});
it("should return TestEvents", function () {
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
});
@ -110,15 +116,10 @@ describe("Calendar module", function () {
describe("Basic auth backward compatibility configuration: DEPRECATED", function () {
before(function () {
serverBasicAuth.listen(8012);
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/old-basic-auth.js";
});
after(function (done) {
serverBasicAuth.close(done());
});
it("should return TestEvents", function () {
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";
});
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}$/;
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$/;
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";
});
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}$/;
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$/;
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";
});
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$/;
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";
});
it("shows week with correct format", function () {
it("shows week with correct format", async function () {
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";
});
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}$/;
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$/;
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";
});
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}$/;
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$/;
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";
});
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$/;
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";
});
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$/;
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";
});
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}$/;
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 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,42 +31,36 @@ describe("Compliments module", function () {
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();
if (hour >= 3 && hour < 12) {
// if morning check
return app.client
.waitUntilWindowLoaded()
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Hi", "Good Morning", "Morning test"]);
});
const elem = await app.client.$(".compliments");
return elem.getText(".compliments").then(function (text) {
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();
if (hour >= 12 && hour < 17) {
// if morning check
return app.client
.waitUntilWindowLoaded()
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Hello", "Good Afternoon", "Afternoon test"]);
});
const elem = await app.client.$(".compliments");
return elem.getText(".compliments").then(function (text) {
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();
if (!(hour >= 3 && hour < 12) && !(hour >= 12 && hour < 17)) {
// if evening check
return app.client
.waitUntilWindowLoaded()
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Hello There", "Good Evening", "Evening test"]);
});
const elem = await app.client.$(".compliments");
return elem.getText(".compliments").then(function (text) {
expect(text).to.be.oneOf(["Hello There", "Good Evening", "Evening test"]);
});
}
});
});
@ -78,13 +72,11 @@ describe("Compliments module", function () {
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 () {
return app.client
.waitUntilWindowLoaded()
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Anytime here"]);
});
it("Show anytime because if configure empty parts of day compliments and set anytime compliments", async function () {
const elem = await app.client.$(".compliments");
return elem.getText(".compliments").then(function (text) {
expect(text).to.be.oneOf(["Anytime here"]);
});
});
});
@ -94,13 +86,11 @@ describe("Compliments module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_only_anytime.js";
});
it("Show anytime compliments", function () {
return app.client
.waitUntilWindowLoaded()
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Anytime here"]);
});
it("Show anytime compliments", async function () {
const elem = await app.client.$(".compliments");
return elem.getText(".compliments").then(function (text) {
expect(text).to.be.oneOf(["Anytime here"]);
});
});
});
});
@ -112,13 +102,11 @@ describe("Compliments module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_date.js";
});
it("Show happy new year compliment on new years day", function () {
return app.client
.waitUntilWindowLoaded()
.getText(".compliments")
.then(function (text) {
expect(text).to.be.oneOf(["Happy new year!"]);
});
it("Show happy new year compliment on new years day", async function () {
const elem = await app.client.$(".compliments");
return elem.getText(".compliments").then(function (text) {
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";
});
it("Test message helloworld module", function () {
return app.client.waitUntilWindowLoaded().getText(".helloworld").should.eventually.equal("Test HelloWorld Module");
it("Test message helloworld module", async function () {
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";
});
it("Test message helloworld module", function () {
return app.client.waitUntilWindowLoaded().getText(".helloworld").should.eventually.equal("Hello World!");
it("Test message helloworld module", async function () {
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