mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-08-21 04:45:17 +00:00
Release 2.27.0 (#3410)
## [2.27.0] - 2024-04-01 Thanks to: @bugsounet, @crazyscot, @illimarkangur, @jkriegshauser, @khassel, @KristjanESPERANTO, @Paranoid93, @rejas, @sdetweil and @vppencilsharpener. This release marks the first release without Michael Teeuw (@michmich). A very special thanks to him for creating MagicMirror and leading the project for so many years. For more info, please read the following post: [A New Chapter for MagicMirror: The Community Takes the Lead](https://forum.magicmirror.builders/topic/18329/a-new-chapter-for-magicmirror-the-community-takes-the-lead). ### Added - Output of system information to the console for troubleshooting (#3328 and #3337), ignore errors under aarch64 (#3349) - [chore] Add `eslint-plugin-package-json` to lint the `package.json` files (#3368) - [weather] `showHumidity` config is now a string describing where to show this element. Supported values: "wind", "temp", "feelslike", "below", "none". (#3330) - electron-rebuild test suite for electron and 3rd party modules compatibility (#3392) - Create MM² icon and attach it to electron process (#3407) ### Updated - Update updatenotification (update_helper.js): Recode with pm2 library (#3332) - Removing lodash dependency by replacing merge by spread operator (#3339) - Use node prefix for build-in modules (#3340) - Rework logging colors (#3350) - Update pm2 to v5.3.1 with no allow-ghsas (#3364) - [chore] Update husky and let lint-staged fix ESLint issues - [chore] Update dependencies including electron to v29 (#3357) and node-ical - Update translations for estonian (#3371) - Update electron to v29 and update other dependencies - [calendar] fullDay events over several days now show the left days from the first day on and 'today' on the last day - Update layout of current weather indoor values ### Fixed - Correct apibase of weathergov weatherprovider to match documentation (#2926) - Worked around several issues in the RRULE library that were causing deleted calender events to still show, some initial and recurring events to not show, and some event times to be off an hour. (#3291) - Skip changelog requirement when running tests for dependency updates (#3320) - Display precipitation probability when it is 0% instead of blank/empty (#3345) - [newsfeed] Suppress unsightly animation cases when there are 0 or 1 active news items (#3336) - [newsfeed] Always compute the feed item URL using the same helper function (#3336) - Ignore all custom css files (#3359) - [newsfeed] Fix newsfeed stall issue introduced by #3336 (#3361) - Changed `log.debug` to `log.log` in `app.js` where logLevel is not set because config is not loaded at this time (#3353) - [calendar] deny fetch interval < 60000 and set 60000 in this case (prevent fetch loop failed) (#3382) - added message in case where config.js is missing the module.export line PR #3383 - Fixed an issue where recurring events could extend past their recurrence end date (#3393) - Don't display any `npm WARN <....>` on install (#3399) - Fixed move suncalc dependency to production from dev, as it is used by clock module - [compliments] Fix mirror not responding anymore when no compliments are to be shown (#3385) ### Deleted - Unneeded file headers (#3358) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Michael Teeuw <michael@xonaymedia.nl> Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Karsten Hassel <hassel@gmx.de> Co-authored-by: Ross Younger <crazyscot@gmail.com> Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr> Co-authored-by: jkriegshauser <joshuakr@nvidia.com> Co-authored-by: illimarkangur <116028111+illimarkangur@users.noreply.github.com> Co-authored-by: sam detweiler <sdetweil@gmail.com> Co-authored-by: vppencilsharpener <tim.pray@gmail.com> Co-authored-by: Paranoid93 <6515818+Paranoid93@users.noreply.github.com>
This commit is contained in:
@@ -1,11 +1,5 @@
|
||||
/* global NotificationFx */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: alert
|
||||
*
|
||||
* By Paul-Vincent Roll https://paulvincentroll.com/
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("alert", {
|
||||
alerts: {},
|
||||
|
||||
|
@@ -1,11 +1,5 @@
|
||||
/* global CalendarUtils */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Calendar
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("calendar", {
|
||||
// Define module defaults
|
||||
defaults: {
|
||||
@@ -42,6 +36,7 @@ Module.register("calendar", {
|
||||
hideDuplicates: true,
|
||||
showTimeToday: false,
|
||||
colored: false,
|
||||
forceUseCurrentTime: false,
|
||||
tableClass: "small",
|
||||
calendars: [
|
||||
{
|
||||
@@ -97,12 +92,12 @@ Module.register("calendar", {
|
||||
Log.info(`Starting module: ${this.name}`);
|
||||
|
||||
if (this.config.colored) {
|
||||
Log.warn("Your are using the deprecated config values 'colored'. Please switch to 'coloredSymbol' & 'coloredText'!");
|
||||
Log.warn("Your are using the deprecated config values 'colored'. Please switch to 'coloredSymbol' & 'coloredText'!");
|
||||
this.config.coloredText = true;
|
||||
this.config.coloredSymbol = true;
|
||||
}
|
||||
if (this.config.coloredSymbolOnly) {
|
||||
Log.warn("Your are using the deprecated config values 'coloredSymbolOnly'. Please switch to 'coloredSymbol' & 'coloredText'!");
|
||||
Log.warn("Your are using the deprecated config values 'coloredSymbolOnly'. Please switch to 'coloredSymbol' & 'coloredText'!");
|
||||
this.config.coloredText = false;
|
||||
this.config.coloredSymbol = true;
|
||||
}
|
||||
@@ -209,6 +204,11 @@ Module.register("calendar", {
|
||||
this.updateDom(this.config.animationSpeed);
|
||||
},
|
||||
|
||||
eventEndingWithinNextFullTimeUnit (event, ONE_DAY) {
|
||||
const now = new Date();
|
||||
return event.endDate - now <= ONE_DAY;
|
||||
},
|
||||
|
||||
// Override dom generator.
|
||||
getDom () {
|
||||
const ONE_SECOND = 1000; // 1,000 milliseconds
|
||||
@@ -443,7 +443,7 @@ Module.register("calendar", {
|
||||
}
|
||||
} else {
|
||||
// Show relative times
|
||||
if (event.startDate >= now || (event.fullDayEvent && event.today)) {
|
||||
if (event.startDate >= now || (event.fullDayEvent && this.eventEndingWithinNextFullTimeUnit(event, ONE_DAY))) {
|
||||
// Use relative time
|
||||
if (!this.config.hideTime && !event.fullDayEvent) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }));
|
||||
@@ -459,7 +459,7 @@ Module.register("calendar", {
|
||||
}
|
||||
if (event.fullDayEvent) {
|
||||
// Full days events within the next two days
|
||||
if (event.today) {
|
||||
if (event.today || (event.fullDayEvent && this.eventEndingWithinNextFullTimeUnit(event, ONE_DAY))) {
|
||||
timeWrapper.innerHTML = CalendarUtils.capFirst(this.translate("TODAY"));
|
||||
} else if (event.dayBeforeYesterday) {
|
||||
if (this.translate("DAYBEFOREYESTERDAY") !== "DAYBEFOREYESTERDAY") {
|
||||
@@ -573,9 +573,16 @@ Module.register("calendar", {
|
||||
const ONE_HOUR = ONE_MINUTE * 60;
|
||||
const ONE_DAY = ONE_HOUR * 24;
|
||||
|
||||
const now = new Date();
|
||||
const today = moment().startOf("day");
|
||||
const future = moment().startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
|
||||
let now, today, future;
|
||||
if (this.config.forceUseCurrentTime || this.defaults.forceUseCurrentTime) {
|
||||
now = new Date();
|
||||
today = moment().startOf("day");
|
||||
future = moment().startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
|
||||
} else {
|
||||
now = new Date(Date.now()); // Can use overridden time
|
||||
today = moment(now).startOf("day");
|
||||
future = moment(now).startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
|
||||
}
|
||||
let events = [];
|
||||
|
||||
for (const calendarUrl in this.calendarData) {
|
||||
|
@@ -1,11 +1,4 @@
|
||||
/* MagicMirror²
|
||||
* Node Helper: Calendar - CalendarFetcher
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
const https = require("https");
|
||||
const https = require("node:https");
|
||||
const ical = require("node-ical");
|
||||
const Log = require("logger");
|
||||
const NodeHelper = require("node_helper");
|
||||
|
@@ -1,14 +1,7 @@
|
||||
/* MagicMirror²
|
||||
* Calendar Fetcher Util Methods
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external Moment
|
||||
*/
|
||||
const path = require("path");
|
||||
const path = require("node:path");
|
||||
const moment = require("moment");
|
||||
|
||||
const zoneTable = require(path.join(__dirname, "windowsZones.json"));
|
||||
@@ -63,7 +56,7 @@ const CalendarFetcherUtils = {
|
||||
event.start.tz = "";
|
||||
Log.debug(`ical offset=${current_offset} date=${date}`);
|
||||
mm = moment(date);
|
||||
let x = parseInt(moment(new Date()).utcOffset());
|
||||
let x = moment(new Date()).utcOffset();
|
||||
Log.debug(`net mins=${current_offset * 60 - x}`);
|
||||
|
||||
mm = mm.add(x - current_offset * 60, "minutes");
|
||||
@@ -135,24 +128,26 @@ const CalendarFetcherUtils = {
|
||||
};
|
||||
|
||||
const eventDate = function (event, time) {
|
||||
return CalendarFetcherUtils.isFullDayEvent(event) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
|
||||
return CalendarFetcherUtils.isFullDayEvent(event) ? moment(event[time]).startOf("day") : moment(event[time]);
|
||||
};
|
||||
|
||||
Log.debug(`There are ${Object.entries(data).length} calendar entries.`);
|
||||
|
||||
const now = new Date(Date.now());
|
||||
const todayLocal = moment(now).startOf("day").toDate();
|
||||
const futureLocalDate
|
||||
= moment(now)
|
||||
.startOf("day")
|
||||
.add(config.maximumNumberOfDays, "days")
|
||||
.subtract(1, "seconds") // Subtract 1 second so that events that start on the middle of the night will not repeat.
|
||||
.toDate();
|
||||
|
||||
Object.entries(data).forEach(([key, event]) => {
|
||||
Log.debug("Processing entry...");
|
||||
const now = new Date();
|
||||
const today = moment().startOf("day").toDate();
|
||||
const future
|
||||
= moment()
|
||||
.startOf("day")
|
||||
.add(config.maximumNumberOfDays, "days")
|
||||
.subtract(1, "seconds") // Subtract 1 second so that events that start on the middle of the night will not repeat.
|
||||
.toDate();
|
||||
let past = today;
|
||||
let pastLocalDate = todayLocal;
|
||||
|
||||
if (config.includePastEvents) {
|
||||
past = moment().startOf("day").subtract(config.maximumNumberOfDays, "days").toDate();
|
||||
pastLocalDate = moment(now).startOf("day").subtract(config.maximumNumberOfDays, "days").toDate();
|
||||
}
|
||||
|
||||
// FIXME: Ugly fix to solve the facebook birthday issue.
|
||||
@@ -166,33 +161,33 @@ const CalendarFetcherUtils = {
|
||||
|
||||
if (event.type === "VEVENT") {
|
||||
Log.debug(`Event:\n${JSON.stringify(event)}`);
|
||||
let startDate = eventDate(event, "start");
|
||||
let endDate;
|
||||
let startMoment = eventDate(event, "start");
|
||||
let endMoment;
|
||||
|
||||
if (typeof event.end !== "undefined") {
|
||||
endDate = eventDate(event, "end");
|
||||
endMoment = eventDate(event, "end");
|
||||
} else if (typeof event.duration !== "undefined") {
|
||||
endDate = startDate.clone().add(moment.duration(event.duration));
|
||||
endMoment = startMoment.clone().add(moment.duration(event.duration));
|
||||
} else {
|
||||
if (!isFacebookBirthday) {
|
||||
// make copy of start date, separate storage area
|
||||
endDate = moment(startDate.format("x"), "x");
|
||||
endMoment = moment(startMoment.valueOf());
|
||||
} else {
|
||||
endDate = moment(startDate).add(1, "days");
|
||||
endMoment = moment(startMoment).add(1, "days");
|
||||
}
|
||||
}
|
||||
|
||||
Log.debug(`start: ${startDate.toDate()}`);
|
||||
Log.debug(`end:: ${endDate.toDate()}`);
|
||||
Log.debug(`start: ${startMoment.toDate()}`);
|
||||
Log.debug(`end:: ${endMoment.toDate()}`);
|
||||
|
||||
// Calculate the duration of the event for use with recurring events.
|
||||
let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
|
||||
Log.debug(`duration: ${duration}`);
|
||||
const durationMs = endMoment.valueOf() - startMoment.valueOf();
|
||||
Log.debug(`duration: ${durationMs}`);
|
||||
|
||||
// FIXME: Since the parsed json object from node-ical comes with time information
|
||||
// this check could be removed (?)
|
||||
if (event.start.length === 8) {
|
||||
startDate = startDate.startOf("day");
|
||||
startMoment = startMoment.startOf("day");
|
||||
}
|
||||
|
||||
const title = CalendarFetcherUtils.getTitleFromEvent(event);
|
||||
@@ -252,11 +247,11 @@ const CalendarFetcherUtils = {
|
||||
const geo = event.geo || false;
|
||||
const description = event.description || false;
|
||||
|
||||
if (typeof event.rrule !== "undefined" && event.rrule !== null && !isFacebookBirthday) {
|
||||
if (event.rrule && typeof event.rrule !== "undefined" && !isFacebookBirthday) {
|
||||
const rule = event.rrule;
|
||||
|
||||
const pastMoment = moment(past);
|
||||
const futureMoment = moment(future);
|
||||
const pastMoment = moment(pastLocalDate);
|
||||
const futureMoment = moment(futureLocalDate);
|
||||
|
||||
// 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)) {
|
||||
@@ -267,8 +262,8 @@ const CalendarFetcherUtils = {
|
||||
// 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;
|
||||
let pastLocal;
|
||||
let futureLocal;
|
||||
if (CalendarFetcherUtils.isFullDayEvent(event)) {
|
||||
Log.debug("fullday");
|
||||
// if full day event, only use the date part of the ranges
|
||||
@@ -284,17 +279,57 @@ const CalendarFetcherUtils = {
|
||||
pastLocal = pastMoment.toDate();
|
||||
} else {
|
||||
// otherwise use NOW.. cause we shouldn't use any before now
|
||||
pastLocal = moment().toDate(); //now
|
||||
pastLocal = moment(now).toDate(); //now
|
||||
}
|
||||
futureLocal = futureMoment.toDate(); // future
|
||||
}
|
||||
Log.debug(`Search for recurring events between: ${pastLocal} and ${futureLocal}`);
|
||||
let dates = rule.between(pastLocal, futureLocal, true, limitFunction);
|
||||
const hasByWeekdayRule = rule.options.byweekday !== undefined && rule.options.byweekday !== null;
|
||||
const oneDayInMs = 24 * 60 * 60 * 1000;
|
||||
Log.debug(`RRule: ${rule.toString()}`);
|
||||
rule.options.tzid = null; // RRule gets *very* confused with timezones
|
||||
let dates = rule.between(new Date(pastLocal.valueOf() - oneDayInMs), new Date(futureLocal.valueOf() + oneDayInMs), true, () => { return true; });
|
||||
Log.debug(`Title: ${event.summary}, with dates: ${JSON.stringify(dates)}`);
|
||||
dates = dates.filter((d) => {
|
||||
if (JSON.stringify(d) === "null") return false;
|
||||
else return true;
|
||||
});
|
||||
|
||||
// RRule can generate dates with an incorrect recurrence date. Process the array here and apply date correction.
|
||||
if (hasByWeekdayRule) {
|
||||
Log.debug("Rule has byweekday, checking for correction");
|
||||
dates.forEach((date, index, arr) => {
|
||||
// NOTE: getTimezoneOffset() is negative of the expected value. For America/Los_Angeles under DST (GMT-7),
|
||||
// this value is +420. For Australia/Sydney under DST (GMT+11), this value is -660.
|
||||
const tzOffset = date.getTimezoneOffset() / 60;
|
||||
const hour = date.getHours();
|
||||
if ((tzOffset < 0) && (hour < -tzOffset)) { // east of GMT
|
||||
Log.debug(`East of GMT (tzOffset: ${tzOffset}) and hour=${hour} < ${-tzOffset}, Subtracting 1 day from ${date}`);
|
||||
arr[index] = new Date(date.valueOf() - oneDayInMs);
|
||||
} else if ((tzOffset > 0) && (hour >= (24 - tzOffset))) { // west of GMT
|
||||
Log.debug(`West of GMT (tzOffset: ${tzOffset}) and hour=${hour} >= 24-${tzOffset}, Adding 1 day to ${date}`);
|
||||
arr[index] = new Date(date.valueOf() + oneDayInMs);
|
||||
}
|
||||
});
|
||||
// Adjusting the dates could push it beyond the 'until' date, so filter those out here.
|
||||
if (rule.options.until !== null) {
|
||||
dates = dates.filter((date) => {
|
||||
return date.valueOf() <= rule.options.until.valueOf();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// The dates array from rrule can be confused by DST. If the event was created during DST and we
|
||||
// are querying a date range during non-DST, rrule can have the incorrect time for the date range.
|
||||
// Reprocess the array here computing and applying the time offset.
|
||||
dates.forEach((date, index, arr) => {
|
||||
let adjustHours = CalendarFetcherUtils.calculateTimezoneAdjustment(event, date);
|
||||
if (adjustHours !== 0) {
|
||||
Log.debug(`Applying timezone adjustment hours=${adjustHours} to ${date}`);
|
||||
arr[index] = new Date(date.valueOf() + (adjustHours * 60 * 60 * 1000));
|
||||
}
|
||||
});
|
||||
|
||||
// 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,
|
||||
@@ -304,108 +339,35 @@ const CalendarFetcherUtils = {
|
||||
// Would be great if there was a better way to handle this.
|
||||
Log.debug(`event.recurrences: ${event.recurrences}`);
|
||||
if (event.recurrences !== undefined) {
|
||||
for (let r in event.recurrences) {
|
||||
for (let dateKey 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));
|
||||
// we don't double-add those events.
|
||||
let d = new Date(dateKey);
|
||||
if (!moment(d).isBetween(pastMoment, futureMoment)) {
|
||||
dates.push(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, sometimes rrule doesn't include the event.start even if it is in the requested range. Ensure
|
||||
// inclusion here. Unfortunately dates.includes() doesn't find it so we have to do forEach().
|
||||
{
|
||||
let found = false;
|
||||
dates.forEach((d) => { if (d.valueOf() === event.start.valueOf()) found = true; });
|
||||
if (!found) {
|
||||
Log.debug(`event.start=${event.start} was not included in results from rrule; adding`);
|
||||
dates.splice(0, 0, event.start);
|
||||
}
|
||||
}
|
||||
|
||||
// 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];
|
||||
let curEvent = event;
|
||||
let curDurationMs = durationMs;
|
||||
let showRecurrence = true;
|
||||
|
||||
// set the time information in the date to equal the time information in the event
|
||||
date.setUTCHours(curEvent.start.getUTCHours(), curEvent.start.getUTCMinutes(), curEvent.start.getUTCSeconds(), curEvent.start.getUTCMilliseconds());
|
||||
|
||||
// Get the offset of today where we are processing
|
||||
// This will be the correction, we need to apply.
|
||||
let nowOffset = new Date().getTimezoneOffset();
|
||||
// For full day events, the time might be off from RRULE/Luxon problem
|
||||
// Get time zone offset of the rule calculated event
|
||||
let dateoffset = date.getTimezoneOffset();
|
||||
|
||||
// Reduce the time by the following offset.
|
||||
Log.debug(` recurring date is ${date} offset is ${dateoffset}`);
|
||||
|
||||
let dh = moment(date).format("HH");
|
||||
Log.debug(` recurring date is ${date} offset is ${dateoffset / 60} Hour is ${dh}`);
|
||||
|
||||
if (CalendarFetcherUtils.isFullDayEvent(event)) {
|
||||
Log.debug("Fullday");
|
||||
// If the offset is negative (east of GMT), where the problem is
|
||||
if (dateoffset < 0) {
|
||||
if (dh < Math.abs(dateoffset / 60)) {
|
||||
// if the rrule byweekday WAS explicitly set , correct it
|
||||
// reduce the time by the offset
|
||||
if (curEvent.rrule.origOptions.byweekday !== undefined) {
|
||||
// Apply the correction to the date/time to get it UTC relative
|
||||
date = new Date(date.getTime() - Math.abs(24 * 60) * 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 date1 fulldate is ${date}`);
|
||||
}
|
||||
} else {
|
||||
// if the timezones are the same, correct date if needed
|
||||
//if (event.start.tz === moment.tz.guess()) {
|
||||
// if the date hour is less than the offset
|
||||
if (24 - dh <= Math.abs(dateoffset / 60)) {
|
||||
// if the rrule byweekday WAS explicitly set , correct it
|
||||
if (curEvent.rrule.origOptions.byweekday !== undefined) {
|
||||
// apply the correction to the date/time back to right day
|
||||
date = new Date(date.getTime() + Math.abs(24 * 60) * 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 date2 fulldate is ${date}`);
|
||||
}
|
||||
//}
|
||||
}
|
||||
} else {
|
||||
// not full day, but luxon can still screw up the date on the rule processing
|
||||
// we need to correct the date to get back to the right event for
|
||||
if (dateoffset < 0) {
|
||||
// if the date hour is less than the offset
|
||||
if (dh <= Math.abs(dateoffset / 60)) {
|
||||
// if the rrule byweekday WAS explicitly set , correct it
|
||||
if (curEvent.rrule.origOptions.byweekday !== undefined) {
|
||||
// Reduce the time by t:
|
||||
// Apply the correction to the date/time to get it UTC relative
|
||||
date = new Date(date.getTime() - Math.abs(24 * 60) * 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 date1 is ${date}`);
|
||||
}
|
||||
} else {
|
||||
// if the timezones are the same, correct date if needed
|
||||
//if (event.start.tz === moment.tz.guess()) {
|
||||
// if the date hour is less than the offset
|
||||
if (24 - dh <= Math.abs(dateoffset / 60)) {
|
||||
// if the rrule byweekday WAS explicitly set , correct it
|
||||
if (curEvent.rrule.origOptions.byweekday !== undefined) {
|
||||
// apply the correction to the date/time back to right day
|
||||
date = new Date(date.getTime() + Math.abs(24 * 60) * 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 date2 is ${date}`);
|
||||
}
|
||||
//}
|
||||
}
|
||||
}
|
||||
startDate = moment(date);
|
||||
Log.debug(`Corrected startDate: ${startDate.toDate()}`);
|
||||
|
||||
let adjustDays = CalendarFetcherUtils.calculateTimezoneAdjustment(event, date);
|
||||
startMoment = moment(date);
|
||||
|
||||
// Remove the time information of each date by using its substring, using the following method:
|
||||
// .toISOString().substring(0,10).
|
||||
@@ -418,30 +380,30 @@ const CalendarFetcherUtils = {
|
||||
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.
|
||||
curEvent = curEvent.recurrences[dateKey];
|
||||
startDate = moment(curEvent.start);
|
||||
duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x"));
|
||||
startMoment = moment(curEvent.start);
|
||||
curDurationMs = curEvent.end.valueOf() - startMoment.valueOf();
|
||||
}
|
||||
// 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}`);
|
||||
Log.debug(`duration: ${curDurationMs}`);
|
||||
|
||||
endDate = moment(parseInt(startDate.format("x")) + duration, "x");
|
||||
if (startDate.format("x") === endDate.format("x")) {
|
||||
endDate = endDate.endOf("day");
|
||||
endMoment = moment(startMoment.valueOf() + curDurationMs);
|
||||
if (startMoment.valueOf() === endMoment.valueOf()) {
|
||||
endMoment = endMoment.endOf("day");
|
||||
}
|
||||
|
||||
const recurrenceTitle = CalendarFetcherUtils.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)) {
|
||||
if (endMoment.isBefore(pastLocal) || startMoment.isAfter(futureLocal)) {
|
||||
showRecurrence = false;
|
||||
}
|
||||
|
||||
if (CalendarFetcherUtils.timeFilterApplies(now, endDate, dateFilter)) {
|
||||
if (CalendarFetcherUtils.timeFilterApplies(now, endMoment, dateFilter)) {
|
||||
showRecurrence = false;
|
||||
}
|
||||
|
||||
@@ -449,8 +411,8 @@ const CalendarFetcherUtils = {
|
||||
Log.debug(`saving event: ${description}`);
|
||||
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"),
|
||||
startDate: startMoment.format("x"),
|
||||
endDate: endMoment.format("x"),
|
||||
fullDayEvent: CalendarFetcherUtils.isFullDayEvent(event),
|
||||
recurringEvent: true,
|
||||
class: event.class,
|
||||
@@ -468,43 +430,47 @@ const CalendarFetcherUtils = {
|
||||
// Log.debug("full day event")
|
||||
|
||||
// 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");
|
||||
if (fullDayEvent && startMoment.valueOf() === endMoment.valueOf()) {
|
||||
endMoment = endMoment.endOf("day");
|
||||
}
|
||||
|
||||
if (config.includePastEvents) {
|
||||
// Past event is too far in the past, so skip.
|
||||
if (endDate < past) {
|
||||
if (endMoment < pastLocalDate) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// It's not a fullday event, and it is in the past, so skip.
|
||||
if (!fullDayEvent && endDate < new Date()) {
|
||||
if (!fullDayEvent && endMoment < now) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It's a fullday event, and it is before today, So skip.
|
||||
if (fullDayEvent && endDate <= today) {
|
||||
if (fullDayEvent && endMoment <= todayLocal) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// It exceeds the maximumNumberOfDays limit, so skip.
|
||||
if (startDate > future) {
|
||||
if (startMoment > futureLocalDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CalendarFetcherUtils.timeFilterApplies(now, endDate, dateFilter)) {
|
||||
if (CalendarFetcherUtils.timeFilterApplies(now, endMoment, dateFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get correction for date saving and dst change between now and then
|
||||
let adjustDays = CalendarFetcherUtils.calculateTimezoneAdjustment(event, startDate.toDate());
|
||||
let adjustHours = CalendarFetcherUtils.calculateTimezoneAdjustment(event, startMoment.toDate());
|
||||
// This shouldn't happen
|
||||
if (adjustHours) {
|
||||
Log.warn(`Unexpected timezone adjustment of ${adjustHours} hours on non-recurring event`);
|
||||
}
|
||||
// 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"),
|
||||
startDate: startMoment.add(adjustHours, "hours").format("x"),
|
||||
endDate: endMoment.add(adjustHours, "hours").format("x"),
|
||||
fullDayEvent: fullDayEvent,
|
||||
class: event.class,
|
||||
location: location,
|
||||
@@ -585,7 +551,7 @@ const CalendarFetcherUtils = {
|
||||
increment = until[1].slice(-1) === "s" ? until[1] : `${until[1]}s`, // Massage the data for moment js
|
||||
filterUntil = moment(endDate.format()).subtract(value, increment);
|
||||
|
||||
return now < filterUntil.format("x");
|
||||
return now < filterUntil.toDate();
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@@ -1,9 +1,3 @@
|
||||
/* MagicMirror²
|
||||
* Calendar Util Methods
|
||||
*
|
||||
* By Rejas
|
||||
* MIT Licensed.
|
||||
*/
|
||||
const CalendarUtils = {
|
||||
|
||||
/**
|
||||
|
@@ -1,9 +1,6 @@
|
||||
/* CalendarFetcher Tester
|
||||
* use this script with `node debug.js` to test the fetcher without the need
|
||||
* of starting the MagicMirror² core. Adjust the values below to your desire.
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
// Alias modules mentioned in package.js under _moduleAliases.
|
||||
require("module-alias/register");
|
||||
|
@@ -1,9 +1,3 @@
|
||||
/* MagicMirror²
|
||||
* Node Helper: Calendar
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
const NodeHelper = require("node_helper");
|
||||
const Log = require("logger");
|
||||
const CalendarFetcher = require("./calendarfetcher");
|
||||
@@ -53,9 +47,14 @@ module.exports = NodeHelper.create({
|
||||
}
|
||||
|
||||
let fetcher;
|
||||
let fetchIntervalCorrected;
|
||||
if (typeof this.fetchers[identifier + url] === "undefined") {
|
||||
Log.log(`Create new calendarfetcher for url: ${url} - Interval: ${fetchInterval}`);
|
||||
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert);
|
||||
if (fetchInterval < 60000) {
|
||||
Log.warn(`fetchInterval for url ${url} must be >= 60000`);
|
||||
fetchIntervalCorrected = 60000;
|
||||
}
|
||||
Log.log(`Create new calendarfetcher for url: ${url} - Interval: ${fetchIntervalCorrected || fetchInterval}`);
|
||||
fetcher = new CalendarFetcher(url, fetchIntervalCorrected || fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert);
|
||||
|
||||
fetcher.onReceive((fetcher) => {
|
||||
this.broadcastEvents(fetcher, identifier);
|
||||
|
@@ -1,11 +1,5 @@
|
||||
/* global SunCalc, formatTime */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Clock
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("clock", {
|
||||
// Module config defaults.
|
||||
defaults: {
|
||||
@@ -105,7 +99,6 @@ Module.register("clock", {
|
||||
analogWrapper.className = "clock-circle";
|
||||
const digitalWrapper = document.createElement("div");
|
||||
digitalWrapper.className = "digital";
|
||||
digitalWrapper.style.gridArea = "center";
|
||||
|
||||
/************************************
|
||||
* Create wrappers for DIGITAL clock
|
||||
@@ -129,7 +122,7 @@ Module.register("clock", {
|
||||
// Set content of wrappers.
|
||||
// The moment().format("h") method has a bug on the Raspberry Pi.
|
||||
// So we need to generate the timestring manually.
|
||||
// See issue: https://github.com/MichMich/MagicMirror/issues/181
|
||||
// See issue: https://github.com/MagicMirrorOrg/MagicMirror/issues/181
|
||||
let timeString;
|
||||
const now = moment();
|
||||
if (this.config.timezone) {
|
||||
@@ -247,7 +240,7 @@ Module.register("clock", {
|
||||
analogWrapper.style.background = `url(${this.data.path}faces/${this.config.analogFace}.svg)`;
|
||||
analogWrapper.style.backgroundSize = "100%";
|
||||
|
||||
// The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611
|
||||
// The following line solves issue: https://github.com/MagicMirrorOrg/MagicMirror/issues/611
|
||||
// analogWrapper.style.border = "1px solid black";
|
||||
analogWrapper.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
|
||||
} else if (this.config.analogFace !== "none") {
|
||||
|
@@ -1,9 +1,3 @@
|
||||
/* MagicMirror²
|
||||
* Module: Compliments
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("compliments", {
|
||||
// Module config defaults.
|
||||
defaults: {
|
||||
@@ -56,7 +50,7 @@ Module.register("compliments", {
|
||||
* @returns {number} a random index of given array
|
||||
*/
|
||||
randomIndex (compliments) {
|
||||
if (compliments.length === 1) {
|
||||
if (compliments.length <= 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,5 @@
|
||||
/* MagicMirror² Default Modules List
|
||||
/* Default Modules List
|
||||
* Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
const defaultModules = ["alert", "calendar", "clock", "compliments", "helloworld", "newsfeed", "updatenotification", "weather"];
|
||||
|
||||
|
@@ -1,9 +1,3 @@
|
||||
/* MagicMirror²
|
||||
* Module: HelloWorld
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("helloworld", {
|
||||
// Default module config.
|
||||
defaults: {
|
||||
|
@@ -1,9 +1,3 @@
|
||||
/* MagicMirror²
|
||||
* Module: NewsFeed
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("newsfeed", {
|
||||
// Default module config.
|
||||
defaults: {
|
||||
@@ -118,27 +112,33 @@ Module.register("newsfeed", {
|
||||
|
||||
//Override template data and return whats used for the current template
|
||||
getTemplateData () {
|
||||
if (this.activeItem >= this.newsItems.length) {
|
||||
this.activeItem = 0;
|
||||
}
|
||||
this.activeItemCount = this.newsItems.length;
|
||||
// this.config.showFullArticle is a run-time configuration, triggered by optional notifications
|
||||
if (this.config.showFullArticle) {
|
||||
this.activeItemHash = this.newsItems[this.activeItem]?.hash;
|
||||
return {
|
||||
url: this.getActiveItemURL()
|
||||
};
|
||||
}
|
||||
if (this.error) {
|
||||
this.activeItemHash = undefined;
|
||||
return {
|
||||
error: this.error
|
||||
};
|
||||
}
|
||||
if (this.newsItems.length === 0) {
|
||||
this.activeItemHash = undefined;
|
||||
return {
|
||||
empty: true
|
||||
};
|
||||
}
|
||||
if (this.activeItem >= this.newsItems.length) {
|
||||
this.activeItem = 0;
|
||||
}
|
||||
|
||||
const item = this.newsItems[this.activeItem];
|
||||
this.activeItemHash = item.hash;
|
||||
|
||||
const items = this.newsItems.map(function (item) {
|
||||
item.publishDate = moment(new Date(item.pubdate)).fromNow();
|
||||
return item;
|
||||
@@ -150,7 +150,7 @@ Module.register("newsfeed", {
|
||||
sourceTitle: item.sourceTitle,
|
||||
publishDate: moment(new Date(item.pubdate)).fromNow(),
|
||||
title: item.title,
|
||||
url: this.getUrlPrefix(item) + item.url,
|
||||
url: this.getActiveItemURL(),
|
||||
description: item.description,
|
||||
items: items
|
||||
};
|
||||
@@ -312,8 +312,27 @@ Module.register("newsfeed", {
|
||||
if (this.timer) clearInterval(this.timer);
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
this.activeItem++;
|
||||
this.updateDom(this.config.animationSpeed);
|
||||
|
||||
/*
|
||||
* When animations are enabled, don't update the DOM unless we are actually changing what we are displaying.
|
||||
* (Animating from a headline to itself is unsightly.)
|
||||
* Cases:
|
||||
*
|
||||
* Number of items | Number of items | Display
|
||||
* at last update | right now | Behaviour
|
||||
* ----------------------------------------------------
|
||||
* 0 | 0 | do not update
|
||||
* 0 | >0 | update
|
||||
* 1 | 0 or >1 | update
|
||||
* 1 | 1 | update only if item details (hash value) changed
|
||||
* >1 | any | update
|
||||
*
|
||||
* (N.B. We set activeItemCount and activeItemHash in getTemplateData().)
|
||||
*/
|
||||
if (this.newsItems.length > 1 || this.newsItems.length !== this.activeItemCount || this.activeItemHash !== this.newsItems[0]?.hash) {
|
||||
this.activeItem++; // this is OK if newsItems.Length==1; getTemplateData will wrap it around
|
||||
this.updateDom(this.config.animationSpeed);
|
||||
}
|
||||
|
||||
// Broadcast NewsFeed if needed
|
||||
if (this.config.broadcastNewsFeeds) {
|
||||
|
@@ -1,11 +1,5 @@
|
||||
/* MagicMirror²
|
||||
* Node Helper: Newsfeed - NewsfeedFetcher
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
const stream = require("stream");
|
||||
const crypto = require("node:crypto");
|
||||
const stream = require("node:stream");
|
||||
const FeedMe = require("feedme");
|
||||
const iconv = require("iconv-lite");
|
||||
const { htmlToText } = require("html-to-text");
|
||||
@@ -67,7 +61,8 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
|
||||
description: description,
|
||||
pubdate: pubdate,
|
||||
url: url,
|
||||
useCorsProxy: useCorsProxy
|
||||
useCorsProxy: useCorsProxy,
|
||||
hash: crypto.createHash("sha256").update(`${pubdate} :: ${title} :: ${url}`).digest("hex")
|
||||
});
|
||||
} else if (logFeedWarnings) {
|
||||
Log.warn("Can't parse feed item:");
|
||||
|
@@ -1,10 +1,3 @@
|
||||
/* MagicMirror²
|
||||
* Node Helper: Newsfeed
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
const NodeHelper = require("node_helper");
|
||||
const Log = require("logger");
|
||||
const NewsfeedFetcher = require("./newsfeedfetcher");
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const util = require("util");
|
||||
const exec = util.promisify(require("child_process").exec);
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const util = require("node:util");
|
||||
const exec = util.promisify(require("node:child_process").exec);
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const Log = require("logger");
|
||||
|
||||
const BASE_DIR = path.normalize(`${__dirname}/../../../`);
|
||||
@@ -128,7 +128,7 @@ class GitHelper {
|
||||
const { stderr } = await this.execShell(`cd ${repo.folder} && git fetch -n --dry-run`);
|
||||
|
||||
// example output:
|
||||
// From https://github.com/MichMich/MagicMirror
|
||||
// From https://github.com/MagicMirrorOrg/MagicMirror
|
||||
// e40ddd4..06389e3 develop -> origin/develop
|
||||
// here the result is in stderr (this is a git default, don't ask why ...)
|
||||
const matches = stderr.match(this.getRefRegex(gitInfo.current));
|
||||
|
@@ -1,6 +1,7 @@
|
||||
const Exec = require("child_process").exec;
|
||||
const Spawn = require("child_process").spawn;
|
||||
const commandExists = require("command-exists");
|
||||
const Exec = require("node:child_process").exec;
|
||||
const Spawn = require("node:child_process").spawn;
|
||||
const pm2 = require("pm2");
|
||||
|
||||
const Log = require("logger");
|
||||
|
||||
/* class Updater
|
||||
@@ -138,7 +139,7 @@ class Updater {
|
||||
// restart MagicMiror with "pm2"
|
||||
pm2Restart () {
|
||||
Log.info("updatenotification: PM2 will restarting MagicMirror...");
|
||||
Exec(`pm2 restart ${this.PM2}`, (err, std, sde) => {
|
||||
pm2.restart(this.PM2, (err, proc) => {
|
||||
if (err) {
|
||||
Log.error("updatenotification:[PM2] restart Error", err);
|
||||
}
|
||||
@@ -159,53 +160,35 @@ class Updater {
|
||||
check_PM2_Process () {
|
||||
Log.info("updatenotification: Checking PM2 using...");
|
||||
return new Promise((resolve) => {
|
||||
commandExists("pm2")
|
||||
.then(async () => {
|
||||
var PM2_List = await this.PM2_GetList();
|
||||
if (!PM2_List) {
|
||||
pm2.connect((err) => {
|
||||
if (err) {
|
||||
Log.error("updatenotification: [PM2]", err);
|
||||
this.usePM2 = false;
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
pm2.list((err, list) => {
|
||||
if (err) {
|
||||
Log.error("updatenotification: [PM2] Can't get process List!");
|
||||
this.usePM2 = false;
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
PM2_List.forEach((pm) => {
|
||||
if (pm.pm2_env.version === this.version && pm.pm2_env.status === "online" && pm.pm2_env.PWD.includes(this.root_path)) {
|
||||
list.forEach((pm) => {
|
||||
if (pm.pm2_env.version === this.version && pm.pm2_env.status === "online" && pm.pm2_env.pm_cwd.includes(`${this.root_path}/`)) {
|
||||
this.PM2 = pm.name;
|
||||
this.usePM2 = true;
|
||||
Log.info("updatenotification: You are using pm2 with", this.PM2);
|
||||
Log.info("updatenotification: [PM2] You are using pm2 with", this.PM2);
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
pm2.disconnect();
|
||||
if (!this.PM2) {
|
||||
Log.info("updatenotification: You are not using pm2");
|
||||
Log.info("updatenotification: [PM2] You are not using pm2");
|
||||
this.usePM2 = false;
|
||||
resolve(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
Log.info("updatenotification: You are not using pm2");
|
||||
this.usePM2 = false;
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Get the list of pm2 process
|
||||
PM2_GetList () {
|
||||
return new Promise((resolve) => {
|
||||
Exec("pm2 jlist", (err, std, sde) => {
|
||||
if (err) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let result = JSON.parse(std);
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
Log.error("updatenotification: [PM2] can't GetList!");
|
||||
Log.debug("updatenotification: [PM2] GetList is not an JSON format", e);
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -218,7 +201,7 @@ class Updater {
|
||||
|
||||
// search update module command
|
||||
applyCommand (module) {
|
||||
if (this.isMagicMirror(module.module)) return null;
|
||||
if (this.isMagicMirror(module.module) || !this.updates.length) return null;
|
||||
let command = null;
|
||||
this.updates.forEach((updater) => {
|
||||
if (updater[module]) command = updater[module];
|
||||
|
@@ -1,9 +1,3 @@
|
||||
/* MagicMirror²
|
||||
* Module: UpdateNotification
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("updatenotification", {
|
||||
defaults: {
|
||||
updateInterval: 10 * 60 * 1000, // every 10 minutes
|
||||
@@ -18,7 +12,7 @@ Module.register("updatenotification", {
|
||||
suspended: false,
|
||||
moduleList: {},
|
||||
needRestart: false,
|
||||
updates: {},
|
||||
updates: [],
|
||||
|
||||
start () {
|
||||
Log.info(`Starting module: ${this.name}`);
|
||||
@@ -102,7 +96,7 @@ Module.register("updatenotification", {
|
||||
|
||||
const localRef = status.hash;
|
||||
const remoteRef = status.tracking.replace(/.*\//, "");
|
||||
return `<a href="https://github.com/MichMich/MagicMirror/compare/${localRef}...${remoteRef}" class="xsmall dimmed difflink" target="_blank">${text}</a>`;
|
||||
return `<a href="https://github.com/MagicMirrorOrg/MagicMirror/compare/${localRef}...${remoteRef}" class="xsmall dimmed difflink" target="_blank">${text}</a>`;
|
||||
});
|
||||
},
|
||||
|
||||
|
@@ -1,3 +1,8 @@
|
||||
{% macro humidity() %}
|
||||
{% if current.humidity %}
|
||||
<span class="humidity"><span>{{ current.humidity | decimalSymbol }}</span><sup> <i class="wi wi-humidity humidity-icon"></i></sup></span>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% if current %}
|
||||
{% if not config.onlyTemp %}
|
||||
<div class="normal medium">
|
||||
@@ -7,8 +12,7 @@
|
||||
{% if config.showWindDirection %}
|
||||
<sup>
|
||||
{% if config.showWindDirectionAsArrow %}
|
||||
<i class="fas fa-long-arrow-alt-down"
|
||||
style="transform:rotate({{ current.windFromDirection }}deg)"></i>
|
||||
<i class="fas fa-long-arrow-alt-down" style="transform:rotate({{ current.windFromDirection }}deg)"></i>
|
||||
{% else %}
|
||||
{{ current.cardinalWindDirection() | translate }}
|
||||
{% endif %}
|
||||
@@ -16,8 +20,8 @@
|
||||
</sup>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if config.showHumidity and current.humidity %}
|
||||
<span>{{ current.humidity | decimalSymbol }}</span><sup> <i class="wi wi-humidity humidity-icon"></i></sup>
|
||||
{% if config.showHumidity === "wind" %}
|
||||
{{ humidity() }}
|
||||
{% endif %}
|
||||
{% if config.showSun %}
|
||||
<span class="wi dimmed wi-{{ current.nextSunAction() }}"></span>
|
||||
@@ -37,28 +41,39 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="large light">
|
||||
<span class="wi weathericon wi-{{ current.weatherType }}"></span>
|
||||
<span class="bright">{{ current.temperature | roundValue | unit("temperature") | decimalSymbol }}</span>
|
||||
</div>
|
||||
<div class="normal light indoor">
|
||||
{% if config.showIndoorTemperature and indoor.temperature %}
|
||||
<div>
|
||||
<span class="fas fa-home"></span>
|
||||
<span class="bright">{{ indoor.temperature | roundValue | unit("temperature") | decimalSymbol }}</span>
|
||||
</div>
|
||||
<div class="large">
|
||||
{% if config.showIndoorTemperature and indoor.temperature or config.showIndoorHumidity and indoor.humidity %}
|
||||
<span class="medium fas fa-home"></span>
|
||||
<span style="display: inline-block">
|
||||
{% if config.showIndoorTemperature and indoor.temperature %}
|
||||
<sup class="small" style="position: relative; display: block; text-align: left;">
|
||||
<span>
|
||||
{{ indoor.temperature | roundValue | unit("temperature") | decimalSymbol }}
|
||||
</span>
|
||||
</sup>
|
||||
{% endif %}
|
||||
{% if config.showIndoorHumidity and indoor.humidity %}
|
||||
<sub class="small" style="position: relative; display: block; text-align: left;">
|
||||
<span>
|
||||
{{ indoor.humidity | roundValue | unit("humidity") | decimalSymbol }}
|
||||
</span>
|
||||
</sub>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if config.showIndoorHumidity and indoor.humidity %}
|
||||
<div>
|
||||
<span class="fas fa-tint"></span>
|
||||
<span class="bright">{{ indoor.humidity | roundValue | unit("humidity") | decimalSymbol }}</span>
|
||||
</div>
|
||||
<span class="light wi weathericon wi-{{ current.weatherType }}"></span>
|
||||
<span class="light bright">{{ current.temperature | roundValue | unit("temperature") | decimalSymbol }}</span>
|
||||
{% if config.showHumidity === "temp" %}
|
||||
<span class="medium bright">{{ humidity() }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if (config.showFeelsLike or config.showPrecipitationAmount or config.showPrecipitationProbability) and not config.onlyTemp %}
|
||||
<div class="normal medium feelslike">
|
||||
{% if config.showFeelsLike %}
|
||||
<span class="dimmed">
|
||||
{% if config.showHumidity === "feelslike" %}
|
||||
{{ humidity() }}
|
||||
{% endif %}
|
||||
{{ "FEELS" | translate({DEGREE: current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }) }}
|
||||
</span>
|
||||
<br />
|
||||
@@ -76,6 +91,9 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if config.showHumidity === "below" %}
|
||||
<span class="medium dimmed">{{ humidity() }}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="dimmed light small">{{ "LOADING" | translate }}</div>
|
||||
{% endif %}
|
||||
|
@@ -1,10 +1,6 @@
|
||||
/* global WeatherProvider, WeatherObject, WeatherUtils */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
* Provider: Environment Canada (EC)
|
||||
*
|
||||
* This class is a provider for Environment Canada MSC Datamart
|
||||
/* This class is a provider for Environment Canada MSC Datamart
|
||||
* Note that this is only for Canadian locations and does not require an API key (access is anonymous)
|
||||
*
|
||||
* EC Documentation at following links:
|
||||
@@ -27,8 +23,6 @@
|
||||
* with locations you can search under column B (English Names), with the corresponding siteCode under
|
||||
* column A (Codes) and provCode under column C (Province).
|
||||
*
|
||||
* Original by Kevin Godin
|
||||
*
|
||||
* License to use Environment Canada (EC) data is detailed here:
|
||||
* https://eccc-msc.github.io/open-data/licence/readme_en/
|
||||
*
|
||||
|
@@ -1,15 +1,9 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
* Provider: Open-Meteo
|
||||
*
|
||||
* By Andrés Vanegas
|
||||
* MIT Licensed
|
||||
*
|
||||
* This class is a provider for Open-Meteo, based on Andrew Pometti's class
|
||||
* for Weatherbit.
|
||||
/* This class is a provider for Open-Meteo,
|
||||
* see https://open-meteo.com/
|
||||
*/
|
||||
|
||||
// https://www.bigdatacloud.com/docs/api/free-reverse-geocode-to-city-api
|
||||
const GEOCODE_BASE = "https://api.bigdatacloud.net/data/reverse-geocode-client";
|
||||
const OPEN_METEO_BASE = "https://api.open-meteo.com/v1";
|
||||
|
@@ -1,12 +1,7 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*
|
||||
* This class is the blueprint for a weather provider.
|
||||
/* This class is a provider for Openweathermap,
|
||||
* see https://openweathermap.org/
|
||||
*/
|
||||
WeatherProvider.register("openweathermap", {
|
||||
// Set the name of the provider.
|
||||
|
@@ -1,14 +1,7 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
* Provider: Pirate Weather
|
||||
*
|
||||
* Written by Nicholas Hubbard https://github.com/nhubbard for formerly Dark Sky Provider
|
||||
* Modified by Karsten Hassel for Pirate Weather
|
||||
* MIT Licensed
|
||||
*
|
||||
* This class is a provider for Pirate Weather, it is a replacement for Dark Sky (same api).
|
||||
/* This class is a provider for Pirate Weather, it is a replacement for Dark Sky (same api),
|
||||
* see http://pirateweather.net/en/latest/
|
||||
*/
|
||||
WeatherProvider.register("pirateweather", {
|
||||
// Set the name of the provider.
|
||||
|
@@ -1,14 +1,8 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
* Provider: SMHI
|
||||
*
|
||||
* By BuXXi https://github.com/buxxi
|
||||
* MIT Licensed
|
||||
*
|
||||
* This class is a provider for SMHI (Sweden only). Metric system is the only
|
||||
* supported unit.
|
||||
/* This class is a provider for SMHI (Sweden only).
|
||||
* Metric system is the only supported unit,
|
||||
* see https://www.smhi.se/
|
||||
*/
|
||||
WeatherProvider.register("smhi", {
|
||||
providerName: "SMHI",
|
||||
|
@@ -1,12 +1,7 @@
|
||||
/* global WeatherProvider, WeatherObject, WeatherUtils */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
*
|
||||
* By Malcolm Oakes https://github.com/maloakes
|
||||
* MIT Licensed.
|
||||
*
|
||||
* This class is a provider for UK Met Office Datapoint.
|
||||
/* This class is a provider for UK Met Office Datapoint,
|
||||
* see https://www.metoffice.gov.uk/
|
||||
*/
|
||||
WeatherProvider.register("ukmetoffice", {
|
||||
// Set the name of the provider.
|
||||
|
@@ -1,13 +1,6 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
*
|
||||
* By Malcolm Oakes https://github.com/maloakes
|
||||
* Existing Met Office provider edited for new MetOffice Data Hub by CreepinJesus http://github.com/XBCreepinJesus
|
||||
* MIT Licensed.
|
||||
*
|
||||
* This class is a provider for UK Met Office Data Hub (the replacement for their Data Point services).
|
||||
/* This class is a provider for UK Met Office Data Hub (the replacement for their Data Point services).
|
||||
* For more information on Data Hub, see https://www.metoffice.gov.uk/services/data/datapoint/notifications/weather-datahub
|
||||
* Data available:
|
||||
* Hourly data for next 2 days ("hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-hourly.pdf
|
||||
|
@@ -1,14 +1,7 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
* Provider: Weatherbit
|
||||
*
|
||||
* By Andrew Pometti
|
||||
* MIT Licensed
|
||||
*
|
||||
* This class is a provider for Weatherbit, based on Nicholas Hubbard's class
|
||||
* for Dark Sky & Vince Peri's class for Weather.gov.
|
||||
/* This class is a provider for Weatherbit,
|
||||
* see https://www.weatherbit.io/
|
||||
*/
|
||||
WeatherProvider.register("weatherbit", {
|
||||
// Set the name of the provider.
|
||||
|
@@ -1,16 +1,8 @@
|
||||
/* global WeatherProvider, WeatherObject, WeatherUtils */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
* Provider: Weatherflow
|
||||
*
|
||||
* By Tobias Dreyem https://github.com/10bias
|
||||
* MIT Licensed
|
||||
*
|
||||
* This class is a provider for Weatherflow.
|
||||
/* This class is a provider for Weatherflow.
|
||||
* Note that the Weatherflow API does not provide snowfall.
|
||||
*/
|
||||
|
||||
WeatherProvider.register("weatherflow", {
|
||||
// Set the name of the provider.
|
||||
// Not strictly required, but helps for debugging
|
||||
|
@@ -1,13 +1,8 @@
|
||||
/* global WeatherProvider, WeatherObject, WeatherUtils */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
* Provider: weather.gov
|
||||
/* Provider: weather.gov
|
||||
* https://weather-gov.github.io/api/general-faqs
|
||||
*
|
||||
* Original by Vince Peri
|
||||
* MIT Licensed.
|
||||
*
|
||||
* This class is a provider for weather.gov.
|
||||
* Note that this is only for US locations (lat and lon) and does not require an API key
|
||||
* Since it is free, there are some items missing - like sunrise, sunset
|
||||
@@ -39,7 +34,6 @@ WeatherProvider.register("weathergov", {
|
||||
// Called to set the config, this config is the same as the weather module's config.
|
||||
setConfig (config) {
|
||||
this.config = config;
|
||||
this.config.apiBase = "https://api.weather.gov";
|
||||
this.fetchWxGovURLs(this.config);
|
||||
},
|
||||
|
||||
@@ -123,7 +117,7 @@ WeatherProvider.register("weathergov", {
|
||||
* Get specific URLs
|
||||
*/
|
||||
fetchWxGovURLs (config) {
|
||||
this.fetchData(`${config.apiBase}/points/${config.lat},${config.lon}`)
|
||||
this.fetchData(`${config.apiBase}/${config.lat},${config.lon}`)
|
||||
.then((data) => {
|
||||
if (!data || !data.properties) {
|
||||
// points URL did not respond with usable data.
|
||||
|
@@ -1,14 +1,6 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
* Provider: Yr.no
|
||||
*
|
||||
* By Magnus Marthinsen
|
||||
* MIT Licensed
|
||||
*
|
||||
* This class is a provider for Yr.no, a norwegian weather service.
|
||||
*
|
||||
/* This class is a provider for Yr.no, a norwegian weather service.
|
||||
* Terms of service: https://developer.yr.no/doc/TermsOfService/
|
||||
*/
|
||||
WeatherProvider.register("yr", {
|
||||
|
@@ -1,11 +1,5 @@
|
||||
/* global WeatherProvider, WeatherUtils, formatTime */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
Module.register("weather", {
|
||||
// Default module config.
|
||||
defaults: {
|
||||
@@ -20,7 +14,7 @@ Module.register("weather", {
|
||||
updateInterval: 10 * 60 * 1000, // every 10 minutes
|
||||
animationSpeed: 1000,
|
||||
showFeelsLike: true,
|
||||
showHumidity: false,
|
||||
showHumidity: "none", // this is now a string; see current.njk
|
||||
showIndoorHumidity: false,
|
||||
showIndoorTemperature: false,
|
||||
allowOverrideNotification: false,
|
||||
@@ -86,6 +80,10 @@ Module.register("weather", {
|
||||
Log.warn("Your are using the deprecated config values 'useBeaufort'. Please switch to windUnits!");
|
||||
this.windUnits = "beaufort";
|
||||
}
|
||||
if (typeof this.config.showHumidity === "boolean") {
|
||||
Log.warn("[weather] Deprecation warning: Please consider updating showHumidity to the new style (config string).");
|
||||
this.config.showHumidity = this.config.showHumidity ? "wind" : "none";
|
||||
}
|
||||
|
||||
// Initialize the weather provider.
|
||||
this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this);
|
||||
@@ -235,7 +233,7 @@ Module.register("weather", {
|
||||
}
|
||||
}
|
||||
} else if (type === "precip") {
|
||||
if (value === null || isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
|
||||
if (value === null || isNaN(value)) {
|
||||
formattedValue = "";
|
||||
} else {
|
||||
formattedValue = WeatherUtils.convertPrecipitationUnit(value, valueUnit, this.config.units);
|
||||
|
@@ -1,17 +1,5 @@
|
||||
/* global SunCalc, WeatherUtils */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*
|
||||
* This class is the blueprint for a day which includes weather information.
|
||||
*
|
||||
* Currently this is focused on the information which is necessary for the current weather.
|
||||
* As soon as we start implementing the forecast, mode properties will be added.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external Moment
|
||||
*/
|
||||
|
@@ -1,13 +1,6 @@
|
||||
/* global Class, performWebRequest, OverrideWrapper */
|
||||
|
||||
/* MagicMirror²
|
||||
* Module: Weather
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*
|
||||
* This class is the blueprint for a weather provider.
|
||||
*/
|
||||
// This class is the blueprint for a weather provider.
|
||||
const WeatherProvider = Class.extend({
|
||||
// Weather Provider Properties
|
||||
providerName: null,
|
||||
|
@@ -1,9 +1,3 @@
|
||||
/* MagicMirror²
|
||||
* Weather Util Methods
|
||||
*
|
||||
* By Rejas
|
||||
* MIT Licensed.
|
||||
*/
|
||||
const WeatherUtils = {
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user