Merge with upstream

This commit is contained in:
buxxi 2021-03-13 22:56:14 +01:00
commit 03964d6f68
13 changed files with 1194 additions and 1697 deletions

View File

@ -22,6 +22,7 @@ _This release is scheduled to be released on 2021-04-01._
- `module.show` has now the option for a callback on error. - `module.show` has now the option for a callback on error.
- Added locale to sample config file - Added locale to sample config file
- Added support for self-signed certificates for the default calendar module (#466) - Added support for self-signed certificates for the default calendar module (#466)
- Added hiddenOnStartup flag to module config (#2475)
### Updated ### Updated
@ -36,10 +37,12 @@ _This release is scheduled to be released on 2021-04-01._
- Dont update the DOM when a module is not displayed. - Dont update the DOM when a module is not displayed.
- Cleaned up jsdoc and tests. - Cleaned up jsdoc and tests.
- Exposed logger as node module for easier access for 3rd party modules - Exposed logger as node module for easier access for 3rd party modules
- Replaced deprecated `request` package with `node-fetch` and `digest-fetch`
### Removed ### Removed
- Removed danger.js library. - Removed danger.js library.
- Removed `ical` which was substituted by `node-ical` in release `v2.13.0`. Module developers must install this dependency themselves in the module folder if needed.
### Fixed ### Fixed
@ -53,6 +56,7 @@ _This release is scheduled to be released on 2021-04-01._
- 3rd party module language loading if language is English - 3rd party module language loading if language is English
- Fix e2e tests after spectron update - Fix e2e tests after spectron update
- Fix updatenotification creating zombie processes by setting a timeout for the git process - Fix updatenotification creating zombie processes by setting a timeout for the git process
- Fix weather module openweathermap not loading if lat and lon set without onecall.
## [2.14.0] - 2021-01-01 ## [2.14.0] - 2021-01-01

View File

@ -7,6 +7,7 @@
<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://codecov.io/gh/MichMich/MagicMirror"><img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg?token=LEG1KitZR6"/></a>
<a href="https://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a> <a href="https://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
<a href="https://github.com/MichMich/MagicMirror/actions?query=workflow%3A%22Automated+Tests%22"><img src="https://github.com/MichMich/MagicMirror/workflows/Automated%20Tests/badge.svg" alt="Tests"></a> <a href="https://github.com/MichMich/MagicMirror/actions?query=workflow%3A%22Automated+Tests%22"><img src="https://github.com/MichMich/MagicMirror/workflows/Automated%20Tests/badge.svg" alt="Tests"></a>
<a href="https://codecov.io/gh/MichMich/MagicMirror"><img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg" /></a>
</p> </p>
**MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](https://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MichMich/MagicMirror/graphs/contributors). **MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](https://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MichMich/MagicMirror/graphs/contributors).

View File

@ -54,6 +54,14 @@ var Loader = (function () {
// Notify core of loaded modules. // Notify core of loaded modules.
MM.modulesStarted(moduleObjects); MM.modulesStarted(moduleObjects);
// Starting modules also hides any modules that have requested to be initially hidden
for (let thisModule of moduleObjects) {
if (thisModule.data.hiddenOnStartup) {
Log.info("Initially hiding " + thisModule.name);
thisModule.hide();
}
}
}; };
/** /**
@ -97,6 +105,7 @@ var Loader = (function () {
path: moduleFolder + "/", path: moduleFolder + "/",
file: moduleName + ".js", file: moduleName + ".js",
position: moduleData.position, position: moduleData.position,
hiddenOnStartup: moduleData.hiddenOnStartup,
header: moduleData.header, header: moduleData.header,
configDeepMerge: typeof moduleData.configDeepMerge === "boolean" ? moduleData.configDeepMerge : false, configDeepMerge: typeof moduleData.configDeepMerge === "boolean" ? moduleData.configDeepMerge : false,
config: moduleData.config, config: moduleData.config,

View File

@ -6,7 +6,10 @@
*/ */
const Log = require("logger"); const Log = require("logger");
const ical = require("node-ical"); const ical = require("node-ical");
const request = require("request"); const fetch = require("node-fetch");
const digest = require("digest-fetch");
const https = require("https");
const base64 = require("base-64");
/** /**
* Moment date * Moment date
@ -43,385 +46,382 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
const fetchCalendar = function () { const fetchCalendar = function () {
clearTimeout(reloadTimer); clearTimeout(reloadTimer);
reloadTimer = null; reloadTimer = null;
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
const opts = { let fetcher = null;
headers: { let httpsAgent = null;
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)" let headers = {
}, "User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"
gzip: true
}; };
if (selfSignedCert) { if (selfSignedCert) {
var agentOptions = { httpsAgent = new https.Agent({
rejectUnauthorized: false rejectUnauthorized: false
}; });
opts.agentOptions = agentOptions;
} }
if (auth) { if (auth) {
if (auth.method === "bearer") { if (auth.method === "bearer") {
opts.auth = { headers.Authorization = "Bearer " + auth.pass;
bearer: auth.pass } else if (auth.method === "digest") {
}; fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, httpsAgent: httpsAgent });
} else { } else {
opts.auth = { headers.Authorization = "Basic " + base64.encode(auth.user + ":" + auth.pass);
user: auth.user,
pass: auth.pass,
sendImmediately: auth.method !== "digest"
};
} }
} }
if (fetcher === null) {
fetcher = fetch(url, { headers: headers, httpsAgent: httpsAgent });
}
request(url, opts, function (err, r, requestData) { fetcher
if (err) { .catch((error) => {
fetchFailedCallback(self, err); fetchFailedCallback(self, error);
scheduleTimer(); scheduleTimer();
return; })
} else if (r.statusCode !== 200) { .then((response) => {
fetchFailedCallback(self, r.statusCode + ": " + r.statusMessage); if (response.status !== 200) {
scheduleTimer(); fetchFailedCallback(self, response.statusText);
return; scheduleTimer();
} }
return response;
})
.then((response) => response.text())
.then((responseData) => {
let data = [];
let data = []; try {
data = ical.parseICS(responseData);
try { } catch (error) {
data = ical.parseICS(requestData); fetchFailedCallback(self, error.message);
} catch (error) { scheduleTimer();
fetchFailedCallback(self, error.message);
scheduleTimer();
return;
}
Log.debug(" parsed data=" + JSON.stringify(data));
const newEvents = [];
// limitFunction doesn't do much limiting, see comment re: the dates array in rrule section below as to why we need to do the filtering ourselves
const limitFunction = function (date, i) {
return true;
};
const eventDate = function (event, time) {
return isFullDayEvent(event) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
};
Log.debug("there are " + Object.entries(data).length + " calendar entries");
Object.entries(data).forEach(([key, event]) => {
const now = new Date();
const today = moment().startOf("day").toDate();
const future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1, "seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
let past = today;
Log.debug("have entries ");
if (includePastEvents) {
past = moment().startOf("day").subtract(maximumNumberOfDays, "days").toDate();
} }
// FIXME: Ugly fix to solve the facebook birthday issue. Log.debug(" parsed data=" + JSON.stringify(data));
// Otherwise, the recurring events only show the birthday for next year.
let isFacebookBirthday = false; const newEvents = [];
if (typeof event.uid !== "undefined") {
if (event.uid.indexOf("@facebook.com") !== -1) { // limitFunction doesn't do much limiting, see comment re: the dates array in rrule section below as to why we need to do the filtering ourselves
isFacebookBirthday = true; const limitFunction = function (date, i) {
return true;
};
const eventDate = function (event, time) {
return isFullDayEvent(event) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
};
Log.debug("there are " + Object.entries(data).length + " calendar entries");
Object.entries(data).forEach(([key, event]) => {
const now = new Date();
const today = moment().startOf("day").toDate();
const future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1, "seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
let past = today;
Log.debug("have entries ");
if (includePastEvents) {
past = moment().startOf("day").subtract(maximumNumberOfDays, "days").toDate();
} }
}
if (event.type === "VEVENT") { // FIXME: Ugly fix to solve the facebook birthday issue.
let startDate = eventDate(event, "start"); // Otherwise, the recurring events only show the birthday for next year.
let endDate; let isFacebookBirthday = false;
if (typeof event.uid !== "undefined") {
if (event.uid.indexOf("@facebook.com") !== -1) {
isFacebookBirthday = true;
}
}
Log.debug("\nevent=" + JSON.stringify(event)); if (event.type === "VEVENT") {
if (typeof event.end !== "undefined") { let startDate = eventDate(event, "start");
endDate = eventDate(event, "end"); let endDate;
} else if (typeof event.duration !== "undefined") {
endDate = startDate.clone().add(moment.duration(event.duration)); Log.debug("\nevent=" + JSON.stringify(event));
} else { if (typeof event.end !== "undefined") {
if (!isFacebookBirthday) { endDate = eventDate(event, "end");
// make copy of start date, separate storage area } else if (typeof event.duration !== "undefined") {
endDate = moment(startDate.format("x"), "x"); endDate = startDate.clone().add(moment.duration(event.duration));
} else { } else {
endDate = moment(startDate).add(1, "days"); if (!isFacebookBirthday) {
} // make copy of start date, separate storage area
} endDate = moment(startDate.format("x"), "x");
Log.debug(" start=" + startDate.toDate() + " end=" + endDate.toDate());
// calculate the duration of the event for use with recurring events.
let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
if (event.start.length === 8) {
startDate = startDate.startOf("day");
}
const title = getTitleFromEvent(event);
let excluded = false,
dateFilter = null;
for (let f in excludedEvents) {
let filter = excludedEvents[f],
testTitle = title.toLowerCase(),
until = null,
useRegex = false,
regexFlags = "g";
if (filter instanceof Object) {
if (typeof filter.until !== "undefined") {
until = filter.until;
}
if (typeof filter.regex !== "undefined") {
useRegex = filter.regex;
}
// If additional advanced filtering is added in, this section
// must remain last as we overwrite the filter object with the
// filterBy string
if (filter.caseSensitive) {
filter = filter.filterBy;
testTitle = title;
} else if (useRegex) {
filter = filter.filterBy;
testTitle = title;
regexFlags += "i";
} else { } else {
filter = filter.filterBy.toLowerCase(); endDate = moment(startDate).add(1, "days");
} }
} else {
filter = filter.toLowerCase();
} }
if (testTitleByFilter(testTitle, filter, useRegex, regexFlags)) { Log.debug(" start=" + startDate.toDate() + " end=" + endDate.toDate());
if (until) {
dateFilter = until;
} else {
excluded = true;
}
break;
}
}
if (excluded) { // calculate the duration of the event for use with recurring events.
return; let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
}
const location = event.location || false; if (event.start.length === 8) {
const geo = event.geo || false; startDate = startDate.startOf("day");
const description = event.description || false;
if (typeof event.rrule !== "undefined" && event.rrule !== null && !isFacebookBirthday) {
const rule = event.rrule;
let addedEvents = 0;
const pastMoment = moment(past);
const futureMoment = moment(future);
// can cause problems with e.g. birthdays before 1900
if ((rule.options && rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900) || (rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900)) {
rule.origOptions.dtstart.setYear(1900);
rule.options.dtstart.setYear(1900);
} }
// For recurring events, get the set of start dates that fall within the range const title = getTitleFromEvent(event);
// of dates we're looking for.
// kblankenship1989 - to fix issue #1798, converting all dates to locale time first, then converting back to UTC time let excluded = false,
let pastLocal = 0; dateFilter = null;
let futureLocal = 0;
if (isFullDayEvent(event)) { for (let f in excludedEvents) {
// if full day event, only use the date part of the ranges let filter = excludedEvents[f],
pastLocal = pastMoment.toDate(); testTitle = title.toLowerCase(),
futureLocal = futureMoment.toDate(); until = null,
} else { useRegex = false,
// if we want past events regexFlags = "g";
if (includePastEvents) {
// use the calculated past time for the between from if (filter instanceof Object) {
pastLocal = pastMoment.toDate(); if (typeof filter.until !== "undefined") {
} else { until = filter.until;
// otherwise use NOW.. cause we shouldnt use any before now
pastLocal = moment().toDate(); //now
}
futureLocal = futureMoment.toDate(); // future
}
Log.debug(" between=" + pastLocal + " to " + futureLocal);
const dates = rule.between(pastLocal, futureLocal, true, limitFunction);
Log.debug("title=" + event.summary + " dates=" + JSON.stringify(dates));
// The "dates" array contains the set of dates within our desired date range range that are valid
// for the recurrence rule. *However*, it's possible for us to have a specific recurrence that
// had its date changed from outside the range to inside the range. For the time being,
// we'll handle this by adding *all* recurrence entries into the set of dates that we check,
// because the logic below will filter out any recurrences that don't actually belong within
// our display range.
// Would be great if there was a better way to handle this.
if (event.recurrences !== undefined) {
for (let r in event.recurrences) {
// Only add dates that weren't already in the range we added from the rrule so that
// we don"t double-add those events.
if (moment(new Date(r)).isBetween(pastMoment, futureMoment) !== true) {
dates.push(new Date(r));
} }
if (typeof filter.regex !== "undefined") {
useRegex = filter.regex;
}
// If additional advanced filtering is added in, this section
// must remain last as we overwrite the filter object with the
// filterBy string
if (filter.caseSensitive) {
filter = filter.filterBy;
testTitle = title;
} else if (useRegex) {
filter = filter.filterBy;
testTitle = title;
regexFlags += "i";
} else {
filter = filter.filterBy.toLowerCase();
}
} else {
filter = filter.toLowerCase();
}
if (testTitleByFilter(testTitle, filter, useRegex, regexFlags)) {
if (until) {
dateFilter = until;
} else {
excluded = true;
}
break;
} }
} }
// Loop through the set of date entries to see which recurrences should be added to our event list.
for (let d in dates) {
let date = dates[d];
// ical.js started returning recurrences and exdates as ISOStrings without time information.
// .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same
// (see https://github.com/peterbraden/ical.js/pull/84 )
const dateKey = date.toISOString().substring(0, 10);
let curEvent = event;
let showRecurrence = true;
// for full day events, the time might be off from RRULE/Luxon problem if (excluded) {
return;
}
const location = event.location || false;
const geo = event.geo || false;
const description = event.description || false;
if (typeof event.rrule !== "undefined" && event.rrule !== null && !isFacebookBirthday) {
const rule = event.rrule;
let addedEvents = 0;
const pastMoment = moment(past);
const futureMoment = moment(future);
// can cause problems with e.g. birthdays before 1900
if ((rule.options && rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900) || (rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900)) {
rule.origOptions.dtstart.setYear(1900);
rule.options.dtstart.setYear(1900);
}
// For recurring events, get the set of start dates that fall within the range
// of dates we're looking for.
// kblankenship1989 - to fix issue #1798, converting all dates to locale time first, then converting back to UTC time
let pastLocal = 0;
let futureLocal = 0;
if (isFullDayEvent(event)) { if (isFullDayEvent(event)) {
Log.debug("fullday"); // if full day event, only use the date part of the ranges
// if the offset is negative, east of GMT where the problem is pastLocal = pastMoment.toDate();
if (date.getTimezoneOffset() < 0) { futureLocal = futureMoment.toDate();
// get the offset of today where we are processing } else {
// this will be the correction we need to apply // if we want past events
let nowOffset = new Date().getTimezoneOffset(); if (includePastEvents) {
Log.debug("now offset is " + nowOffset); // use the calculated past time for the between from
// reduce the time by the offset pastLocal = pastMoment.toDate();
Log.debug(" recurring date is " + date + " offset is " + date.getTimezoneOffset()); } else {
// apply the correction to the date/time to get it UTC relative // otherwise use NOW.. cause we shouldnt use any before now
date = new Date(date.getTime() - Math.abs(nowOffset) * 60000); pastLocal = moment().toDate(); //now
// the duration was calculated way back at the top before we could correct the start time.. }
// fix it for this event entry futureLocal = futureMoment.toDate(); // future
duration = 24 * 60 * 60 * 1000; }
Log.debug("new recurring date is " + date); Log.debug(" between=" + pastLocal + " to " + futureLocal);
const dates = rule.between(pastLocal, futureLocal, true, limitFunction);
Log.debug("title=" + event.summary + " dates=" + JSON.stringify(dates));
// The "dates" array contains the set of dates within our desired date range range that are valid
// for the recurrence rule. *However*, it's possible for us to have a specific recurrence that
// had its date changed from outside the range to inside the range. For the time being,
// we'll handle this by adding *all* recurrence entries into the set of dates that we check,
// because the logic below will filter out any recurrences that don't actually belong within
// our display range.
// Would be great if there was a better way to handle this.
if (event.recurrences !== undefined) {
for (let r in event.recurrences) {
// Only add dates that weren't already in the range we added from the rrule so that
// we don"t double-add those events.
if (moment(new Date(r)).isBetween(pastMoment, futureMoment) !== true) {
dates.push(new Date(r));
}
} }
} }
startDate = moment(date); // Loop through the set of date entries to see which recurrences should be added to our event list.
for (let d in dates) {
let date = dates[d];
// ical.js started returning recurrences and exdates as ISOStrings without time information.
// .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same
// (see https://github.com/peterbraden/ical.js/pull/84 )
const dateKey = date.toISOString().substring(0, 10);
let curEvent = event;
let showRecurrence = true;
let adjustDays = getCorrection(event, date); // for full day events, the time might be off from RRULE/Luxon problem
if (isFullDayEvent(event)) {
Log.debug("fullday");
// if the offset is negative, east of GMT where the problem is
if (date.getTimezoneOffset() < 0) {
// get the offset of today where we are processing
// this will be the correction we need to apply
let nowOffset = new Date().getTimezoneOffset();
Log.debug("now offset is " + nowOffset);
// reduce the time by the offset
Log.debug(" recurring date is " + date + " offset is " + date.getTimezoneOffset());
// apply the correction to the date/time to get it UTC relative
date = new Date(date.getTime() - Math.abs(nowOffset) * 60000);
// the duration was calculated way back at the top before we could correct the start time..
// fix it for this event entry
duration = 24 * 60 * 60 * 1000;
Log.debug("new recurring date is " + date);
}
}
startDate = moment(date);
// For each date that we're checking, it's possible that there is a recurrence override for that one day. let adjustDays = getCorrection(event, date);
if (curEvent.recurrences !== undefined && curEvent.recurrences[dateKey] !== undefined) {
// We found an override, so for this recurrence, use a potentially different title, start date, and duration. // For each date that we're checking, it's possible that there is a recurrence override for that one day.
curEvent = curEvent.recurrences[dateKey]; if (curEvent.recurrences !== undefined && curEvent.recurrences[dateKey] !== undefined) {
startDate = moment(curEvent.start); // We found an override, so for this recurrence, use a potentially different title, start date, and duration.
duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x")); curEvent = curEvent.recurrences[dateKey];
startDate = moment(curEvent.start);
duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x"));
}
// If there's no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
else if (curEvent.exdate !== undefined && curEvent.exdate[dateKey] !== undefined) {
// This date is an exception date, which means we should skip it in the recurrence pattern.
showRecurrence = false;
}
Log.debug("duration=" + duration);
endDate = moment(parseInt(startDate.format("x")) + duration, "x");
if (startDate.format("x") === endDate.format("x")) {
endDate = endDate.endOf("day");
}
const recurrenceTitle = getTitleFromEvent(curEvent);
// If this recurrence ends before the start of the date range, or starts after the end of the date range, don"t add
// it to the event list.
if (endDate.isBefore(past) || startDate.isAfter(future)) {
showRecurrence = false;
}
if (timeFilterApplies(now, endDate, dateFilter)) {
showRecurrence = false;
}
if (showRecurrence === true) {
Log.debug("saving event =" + description);
addedEvents++;
newEvents.push({
title: recurrenceTitle,
startDate: (adjustDays ? (adjustDays > 0 ? startDate.add(adjustDays, "hours") : startDate.subtract(Math.abs(adjustDays), "hours")) : startDate).format("x"),
endDate: (adjustDays ? (adjustDays > 0 ? endDate.add(adjustDays, "hours") : endDate.subtract(Math.abs(adjustDays), "hours")) : endDate).format("x"),
fullDayEvent: isFullDayEvent(event),
recurringEvent: true,
class: event.class,
firstYear: event.start.getFullYear(),
location: location,
geo: geo,
description: description
});
}
} }
// If there's no recurrence override, check for an exception date. Exception dates represent exceptions to the rule. // end recurring event parsing
else if (curEvent.exdate !== undefined && curEvent.exdate[dateKey] !== undefined) { } else {
// This date is an exception date, which means we should skip it in the recurrence pattern. // Single event.
showRecurrence = false; const fullDayEvent = isFacebookBirthday ? true : isFullDayEvent(event);
} // Log.debug("full day event")
Log.debug("duration=" + duration);
endDate = moment(parseInt(startDate.format("x")) + duration, "x"); if (includePastEvents) {
if (startDate.format("x") === endDate.format("x")) { // Past event is too far in the past, so skip.
endDate = endDate.endOf("day"); if (endDate < past) {
return;
}
} else {
// It's not a fullday event, and it is in the past, so skip.
if (!fullDayEvent && endDate < new Date()) {
return;
}
// It's a fullday event, and it is before today, So skip.
if (fullDayEvent && endDate <= today) {
return;
}
} }
const recurrenceTitle = getTitleFromEvent(curEvent); // It exceeds the maximumNumberOfDays limit, so skip.
if (startDate > future) {
// If this recurrence ends before the start of the date range, or starts after the end of the date range, don"t add return;
// it to the event list.
if (endDate.isBefore(past) || startDate.isAfter(future)) {
showRecurrence = false;
} }
if (timeFilterApplies(now, endDate, dateFilter)) { if (timeFilterApplies(now, endDate, dateFilter)) {
showRecurrence = false;
}
if (showRecurrence === true) {
Log.debug("saving event =" + description);
addedEvents++;
newEvents.push({
title: recurrenceTitle,
startDate: (adjustDays ? (adjustDays > 0 ? startDate.add(adjustDays, "hours") : startDate.subtract(Math.abs(adjustDays), "hours")) : startDate).format("x"),
endDate: (adjustDays ? (adjustDays > 0 ? endDate.add(adjustDays, "hours") : endDate.subtract(Math.abs(adjustDays), "hours")) : endDate).format("x"),
fullDayEvent: isFullDayEvent(event),
recurringEvent: true,
class: event.class,
firstYear: event.start.getFullYear(),
location: location,
geo: geo,
description: description
});
}
}
// end recurring event parsing
} else {
// Single event.
const fullDayEvent = isFacebookBirthday ? true : isFullDayEvent(event);
// Log.debug("full day event")
if (includePastEvents) {
// Past event is too far in the past, so skip.
if (endDate < past) {
return;
}
} else {
// It's not a fullday event, and it is in the past, so skip.
if (!fullDayEvent && endDate < new Date()) {
return; return;
} }
// It's a fullday event, and it is before today, So skip. // Adjust start date so multiple day events will be displayed as happening today even though they started some days ago already
if (fullDayEvent && endDate <= today) { if (fullDayEvent && startDate <= today) {
return; startDate = moment(today);
} }
// if the start and end are the same, then make end the 'end of day' value (start is at 00:00:00)
if (fullDayEvent && startDate.format("x") === endDate.format("x")) {
endDate = endDate.endOf("day");
}
// get correction for date saving and dst change between now and then
let adjustDays = getCorrection(event, startDate.toDate());
// Every thing is good. Add it to the list.
newEvents.push({
title: title,
startDate: (adjustDays ? (adjustDays > 0 ? startDate.add(adjustDays, "hours") : startDate.subtract(Math.abs(adjustDays), "hours")) : startDate).format("x"),
endDate: (adjustDays ? (adjustDays > 0 ? endDate.add(adjustDays, "hours") : endDate.subtract(Math.abs(adjustDays), "hours")) : endDate).format("x"),
fullDayEvent: fullDayEvent,
class: event.class,
location: location,
geo: geo,
description: description
});
} }
// It exceeds the maximumNumberOfDays limit, so skip.
if (startDate > future) {
return;
}
if (timeFilterApplies(now, endDate, dateFilter)) {
return;
}
// Adjust start date so multiple day events will be displayed as happening today even though they started some days ago already
if (fullDayEvent && startDate <= today) {
startDate = moment(today);
}
// if the start and end are the same, then make end the 'end of day' value (start is at 00:00:00)
if (fullDayEvent && startDate.format("x") === endDate.format("x")) {
endDate = endDate.endOf("day");
}
// get correction for date saving and dst change between now and then
let adjustDays = getCorrection(event, startDate.toDate());
// Every thing is good. Add it to the list.
newEvents.push({
title: title,
startDate: (adjustDays ? (adjustDays > 0 ? startDate.add(adjustDays, "hours") : startDate.subtract(Math.abs(adjustDays), "hours")) : startDate).format("x"),
endDate: (adjustDays ? (adjustDays > 0 ? endDate.add(adjustDays, "hours") : endDate.subtract(Math.abs(adjustDays), "hours")) : endDate).format("x"),
fullDayEvent: fullDayEvent,
class: event.class,
location: location,
geo: geo,
description: description
});
} }
});
newEvents.sort(function (a, b) {
return a.startDate - b.startDate;
});
// include up to maximumEntries current or upcoming events
// If past events should be included, include all past events
const now = moment();
var entries = 0;
events = [];
for (let ne of newEvents) {
if (moment(ne.endDate, "x").isBefore(now)) {
if (includePastEvents) events.push(ne);
continue;
}
entries++;
// If max events has been saved, skip the rest
if (entries > maximumEntries) break;
events.push(ne);
} }
self.broadcastEvents();
scheduleTimer();
}); });
newEvents.sort(function (a, b) {
return a.startDate - b.startDate;
});
// include up to maximumEntries current or upcoming events
// If past events should be included, include all past events
const now = moment();
var entries = 0;
events = [];
for (let ne of newEvents) {
if (moment(ne.endDate, "x").isBefore(now)) {
if (includePastEvents) events.push(ne);
continue;
}
entries++;
// If max events has been saved, skip the rest
if (entries > maximumEntries) break;
events.push(ne);
}
self.broadcastEvents();
scheduleTimer();
});
}; };
/* /*

View File

@ -6,7 +6,7 @@
*/ */
const Log = require("logger"); const Log = require("logger");
const FeedMe = require("feedme"); const FeedMe = require("feedme");
const request = require("request"); const fetch = require("node-fetch");
const iconv = require("iconv-lite"); const iconv = require("iconv-lite");
/** /**
@ -79,22 +79,20 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
}); });
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
const opts = { const headers = {
headers: { "User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)",
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)", "Cache-Control": "max-age=0, no-cache, no-store, must-revalidate",
"Cache-Control": "max-age=0, no-cache, no-store, must-revalidate", Pragma: "no-cache"
Pragma: "no-cache"
},
encoding: null
}; };
request(url, opts) fetch(url, { headers: headers })
.on("error", function (error) { .catch((error) => {
fetchFailedCallback(self, error); fetchFailedCallback(self, error);
scheduleTimer(); scheduleTimer();
}) })
.pipe(iconv.decodeStream(encoding)) .then((res) => {
.pipe(parser); res.body.pipe(iconv.decodeStream(encoding)).pipe(parser);
});
}; };
/** /**

View File

@ -465,6 +465,8 @@ WeatherProvider.register("openweathermap", {
} else { } else {
params += "&exclude=minutely"; params += "&exclude=minutely";
} }
} else if (this.config.lat && this.config.lon) {
params += "lat=" + this.config.lat + "&lon=" + this.config.lon;
} else if (this.config.locationID) { } else if (this.config.locationID) {
params += "id=" + this.config.locationID; params += "id=" + this.config.locationID;
} else if (this.config.location) { } else if (this.config.location) {

2147
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -71,17 +71,17 @@
"dependencies": { "dependencies": {
"colors": "^1.4.0", "colors": "^1.4.0",
"console-stamp": "^3.0.0-rc4.2", "console-stamp": "^3.0.0-rc4.2",
"digest-fetch": "^1.1.6",
"eslint": "^7.20.0", "eslint": "^7.20.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-ipfilter": "^1.1.2", "express-ipfilter": "^1.1.2",
"feedme": "^2.0.2", "feedme": "^2.0.2",
"helmet": "^4.4.1", "helmet": "^4.4.1",
"ical": "^0.8.0",
"iconv-lite": "^0.6.2", "iconv-lite": "^0.6.2",
"module-alias": "^2.2.2", "module-alias": "^2.2.2",
"moment": "^2.29.1", "moment": "^2.29.1",
"node-fetch": "^2.6.1",
"node-ical": "^0.12.8", "node-ical": "^0.12.8",
"request": "^2.88.2",
"rrule": "^2.6.8", "rrule": "^2.6.8",
"rrule-alt": "^2.2.8", "rrule-alt": "^2.2.8",
"simple-git": "^2.36.2", "simple-git": "^2.36.2",

View File

@ -1,5 +1,5 @@
const helpers = require("./global-setup"); const helpers = require("./global-setup");
const request = require("request"); const fetch = require("node-fetch");
const expect = require("chai").expect; const expect = require("chai").expect;
const describe = global.describe; const describe = global.describe;
@ -46,15 +46,15 @@ describe("Electron app environment", function () {
}); });
it("get request from http://localhost:8080 should return 200", function (done) { it("get request from http://localhost:8080 should return 200", function (done) {
request.get("http://localhost:8080", function (err, res, body) { fetch("http://localhost:8080").then((res) => {
expect(res.statusCode).to.equal(200); expect(res.status).to.equal(200);
done(); done();
}); });
}); });
it("get request from http://localhost:8080/nothing should return 404", function (done) { it("get request from http://localhost:8080/nothing should return 404", function (done) {
request.get("http://localhost:8080/nothing", function (err, res, body) { fetch("http://localhost:8080/nothing").then((res) => {
expect(res.statusCode).to.equal(404); expect(res.status).to.equal(404);
done(); done();
}); });
}); });

View File

@ -1,5 +1,5 @@
const helpers = require("./global-setup"); const helpers = require("./global-setup");
const request = require("request"); const fetch = require("node-fetch");
const expect = require("chai").expect; const expect = require("chai").expect;
const forEach = require("mocha-each"); const forEach = require("mocha-each");
@ -40,8 +40,8 @@ describe("All font files from roboto.css should be downloadable", function () {
forEach(fontFiles).it("should return 200 HTTP code for file '%s'", (fontFile, done) => { forEach(fontFiles).it("should return 200 HTTP code for file '%s'", (fontFile, done) => {
var fontUrl = "http://localhost:8080/fonts/" + fontFile; var fontUrl = "http://localhost:8080/fonts/" + fontFile;
request.get(fontUrl, function (err, res, body) { fetch(fontUrl).then((res) => {
expect(res.statusCode).to.equal(200); expect(res.status).to.equal(200);
done(); done();
}); });
}); });

View File

@ -1,5 +1,5 @@
const helpers = require("./global-setup"); const helpers = require("./global-setup");
const request = require("request"); const fetch = require("node-fetch");
const expect = require("chai").expect; const expect = require("chai").expect;
const describe = global.describe; const describe = global.describe;
@ -32,8 +32,8 @@ describe("ipWhitelist directive configuration", function () {
process.env.MM_CONFIG_FILE = "tests/configs/noIpWhiteList.js"; process.env.MM_CONFIG_FILE = "tests/configs/noIpWhiteList.js";
}); });
it("should return 403", function (done) { it("should return 403", function (done) {
request.get("http://localhost:8080", function (err, res, body) { fetch("http://localhost:8080").then((res) => {
expect(res.statusCode).to.equal(403); expect(res.status).to.equal(403);
done(); done();
}); });
}); });
@ -45,8 +45,8 @@ describe("ipWhitelist directive configuration", function () {
process.env.MM_CONFIG_FILE = "tests/configs/empty_ipWhiteList.js"; process.env.MM_CONFIG_FILE = "tests/configs/empty_ipWhiteList.js";
}); });
it("should return 200", function (done) { it("should return 200", function (done) {
request.get("http://localhost:8080", function (err, res, body) { fetch("http://localhost:8080").then((res) => {
expect(res.statusCode).to.equal(200); expect(res.status).to.equal(200);
done(); done();
}); });
}); });

View File

@ -1,5 +1,5 @@
const helpers = require("./global-setup"); const helpers = require("./global-setup");
const request = require("request"); const fetch = require("node-fetch");
const expect = require("chai").expect; const expect = require("chai").expect;
const describe = global.describe; const describe = global.describe;
@ -33,8 +33,8 @@ describe("port directive configuration", function () {
}); });
it("should return 200", function (done) { it("should return 200", function (done) {
request.get("http://localhost:8090", function (err, res, body) { fetch("http://localhost:8090").then((res) => {
expect(res.statusCode).to.equal(200); expect(res.status).to.equal(200);
done(); done();
}); });
}); });
@ -52,8 +52,8 @@ describe("port directive configuration", function () {
}); });
it("should return 200", function (done) { it("should return 200", function (done) {
request.get("http://localhost:8100", function (err, res, body) { fetch("http://localhost:8100").then((res) => {
expect(res.statusCode).to.equal(200); expect(res.status).to.equal(200);
done(); done();
}); });
}); });

View File

@ -1,5 +1,5 @@
const helpers = require("./global-setup"); const helpers = require("./global-setup");
const request = require("request"); const fetch = require("node-fetch");
const expect = require("chai").expect; const expect = require("chai").expect;
const describe = global.describe; const describe = global.describe;
@ -32,8 +32,8 @@ describe("Vendors", function () {
Object.keys(vendors).forEach((vendor) => { Object.keys(vendors).forEach((vendor) => {
it(`should return 200 HTTP code for vendor "${vendor}"`, function () { it(`should return 200 HTTP code for vendor "${vendor}"`, function () {
var urlVendor = "http://localhost:8080/vendor/" + vendors[vendor]; var urlVendor = "http://localhost:8080/vendor/" + vendors[vendor];
request.get(urlVendor, function (err, res, body) { fetch(urlVendor).then((res) => {
expect(res.statusCode).to.equal(200); expect(res.status).to.equal(200);
}); });
}); });
}); });
@ -41,8 +41,8 @@ describe("Vendors", function () {
Object.keys(vendors).forEach((vendor) => { Object.keys(vendors).forEach((vendor) => {
it(`should return 404 HTTP code for vendor https://localhost/"${vendor}"`, function () { it(`should return 404 HTTP code for vendor https://localhost/"${vendor}"`, function () {
var urlVendor = "http://localhost:8080/" + vendors[vendor]; var urlVendor = "http://localhost:8080/" + vendors[vendor];
request.get(urlVendor, function (err, res, body) { fetch(urlVendor).then((res) => {
expect(res.statusCode).to.equal(404); expect(res.status).to.equal(404);
}); });
}); });
}); });