Compare commits

..

254 Commits

Author SHA1 Message Date
Michael Teeuw
491f5aa776 Merge pull request #2510 from MichMich/develop
Release v2.15.0
2021-04-01 14:23:02 +02:00
Michael Teeuw
d401e59031 Prepare for release: v2.15.0 2021-04-01 14:09:08 +02:00
Michael Teeuw
63ce69ce82 Merge pull request #2506 from rejas/fix_self 2021-03-28 11:28:13 +02:00
veeck
f15972c823 Update CHANGELOG
just to please the action and my inner OCD ;-)
2021-03-28 10:50:26 +02:00
veeck
b210fcdcf3 Update dependencies 2021-03-28 10:48:21 +02:00
veeck
8ea37a5a45 Fix left over self reference 2021-03-28 10:48:12 +02:00
Michael Teeuw
daf98c36fc Merge pull request #2504 from rejas/translation 2021-03-26 07:08:50 +01:00
rejas
6a0e6eb84e Fix styling issue 2021-03-25 17:35:36 +01:00
rejas
7bcdd2a42c Update CHANGELOG 2021-03-25 17:30:27 +01:00
rejas
670a760404 Use some es6 notation 2021-03-25 17:29:27 +01:00
veeck
a99de1ad13 Sort translation entries consistently 2021-03-25 16:43:27 +01:00
Michael Teeuw
108fe2082d Merge pull request #2503 from sdetweil/fixnews 2021-03-25 08:31:50 +01:00
Sam Detweiler
e67dc38890 fix newsreader template 2021-03-24 15:06:12 -05:00
sam detweiler
20a87656ff Merge pull request #4 from MichMich/develop
sync Develop
2021-03-24 15:03:29 -05:00
Michael Teeuw
70c3b68e67 Add FUNDING.yml 2021-03-24 16:20:36 +01:00
Michael Teeuw
bfcb7dc601 Merge pull request #2500 from sdetweil/daylight_fullday
fix fullday recurring event where start date is before 2007
2021-03-23 15:56:53 +01:00
Michael Teeuw
39e16221fb Merge pull request #2498 from rejas/md
Update markdown files for github
2021-03-23 15:56:34 +01:00
Sam Detweiler
d11e4e5c92 refix #2483 , recurring FULL Day event, start date before 2007 2021-03-23 08:44:14 -05:00
sam detweiler
cccaa1f33d Merge pull request #3 from MichMich/develop
sync Develop
2021-03-23 08:39:03 -05:00
rejas
37d3aa25b1 Update CHANGELOG 2021-03-23 09:55:35 +01:00
rejas
ebfe96d31d Update CONTRIBUTING guidelines 2021-03-23 09:47:55 +01:00
rejas
e902b8c52f Update PR template 2021-03-23 08:15:33 +01:00
veeck
201b36d62c Update README 2021-03-23 07:55:06 +01:00
Michael Teeuw
7b29070516 Merge pull request #2495 from qu1que/develop 2021-03-22 06:50:04 +01:00
qu1que
9ed2e4d557 Update translations.js
added galician language
2021-03-21 22:59:13 +01:00
qu1que
38544f2368 Update CHANGELOG.md
Added galician language
2021-03-21 22:58:09 +01:00
qu1que
df39408411 Create gl.json
gl.json for Galician language
2021-03-21 22:57:23 +01:00
Michael Teeuw
2e01f988f5 Merge pull request #2489 from sdetweil/fixday2 2021-03-19 16:54:44 +01:00
Sam Detweiler
2b940c9cfb fix formatting 2021-03-19 10:42:14 -05:00
Sam Detweiler
b6f4737ecc fix 2488, west of UTC day shift 2021-03-19 10:32:04 -05:00
Michael Teeuw
89d6473052 Merge pull request #2484 from rejas/newsfeed_cleanup 2021-03-18 22:49:20 +01:00
rejas
99d838f9cd Remove console log 2021-03-18 16:55:54 +01:00
rejas
f3bdddfaa9 Final es6 notation stuff 2021-03-18 16:55:54 +01:00
rejas
87f952c87b Update CHANGELOG 2021-03-18 16:55:54 +01:00
rejas
639305ecc8 Add test for prohibited words 2021-03-18 16:55:54 +01:00
rejas
a70bb517e1 Use es6 notation in newsfeed module 2021-03-18 16:55:54 +01:00
rejas
e4f671c898 Update error to use translatable text 2021-03-18 16:55:54 +01:00
rejas
a269b5cd93 Show invalid url error on UI, add test case 2021-03-18 16:55:54 +01:00
Michael Teeuw
2ec275957f Merge pull request #2486 from sdetweil/fixdaylight 2021-03-17 13:52:20 +01:00
Sam Detweiler
4f9fc032e5 fix for issue 2483, calendar shows wrong date, recurring start before 2007 2021-03-17 07:36:03 -05:00
sam detweiler
5c9dbccc10 Merge pull request #2 from MichMich/develop
synch
2021-03-17 07:33:20 -05:00
Michael Teeuw
b2cf470ec9 Merge pull request #2482 from rejas/valid_url 2021-03-16 20:06:25 +01:00
veeck
bceb181b47 Update CHANGELOG 2021-03-16 19:28:38 +01:00
veeck
1a314b741a Remove valid-url from dependencies 2021-03-16 19:27:56 +01:00
rejas
7635dea3e9 Replace valid-url library by standard node method 2021-03-16 19:25:23 +01:00
Michael Teeuw
90112d1a7d Merge pull request #2481 from rejas/fix_basic_auth 2021-03-15 21:03:02 +01:00
rejas
ad0cf12e53 Update dependencies 2021-03-15 12:50:41 +01:00
rejas
a53029f11e Use es6 notation in basic auth server 2021-03-15 12:36:58 +01:00
rejas
848529f9f4 Update CHANGELOG
so the github action is quiet ;-)
2021-03-14 21:12:36 +01:00
rejas
b5dc91fd07 Remove now unnecessary require 2021-03-14 21:04:38 +01:00
rejas
0a2b939514 Undo husky upgrade 2021-03-14 19:43:00 +01:00
rejas
52584f36d7 Fix base64 encoding for basic auth in calendar 2021-03-14 19:38:03 +01:00
Michael Teeuw
30c7a24fc2 Merge pull request #2376 from rejas/calendar_refactor 2021-03-14 12:55:24 +01:00
veeck
0643a103ac Update dependencies 2021-03-14 10:40:14 +01:00
rejas
71bd45dfd4 Fix errors introduced after latest rebase 2021-03-14 10:40:14 +01:00
rejas
85c9d3b331 More es6 notations 2021-03-14 10:40:14 +01:00
rejas
5e6cbeb9ba Convert some code to es6 2021-03-14 10:40:14 +01:00
rejas
3d4429d418 Remove copypasted-function that doesnt exist 2021-03-14 10:40:14 +01:00
rejas
d3d64d3ca0 Cleanups 2021-03-14 10:40:14 +01:00
rejas
6de983aeb2 Update CHANGELOG 2021-03-14 10:40:14 +01:00
rejas
05c3a5bf83 Remove self variable 2021-03-14 10:40:14 +01:00
rejas
c2f5d038de Move filter function into utils class too 2021-03-14 09:03:12 +01:00
rejas
0ac5032db9 Move filter function into seperate method 2021-03-14 08:59:50 +01:00
rejas
9b93066cbe Remove unused variable 2021-03-14 08:53:40 +01:00
rejas
bc60ae21c4 Cleanup node_helper to look more like the one from newsfeed module 2021-03-14 08:53:40 +01:00
rejas
0b37ed072c Refactor fetcher methods into util class 2021-03-14 08:53:40 +01:00
Michael Teeuw
57174f09b9 Merge pull request #2479 from buxxi/updatenotification-async-timeout 2021-03-14 07:23:47 +01:00
buxxi
61057d1a25 fix package-lock merge conflict 2021-03-13 22:58:30 +01:00
buxxi
03964d6f68 Merge with upstream 2021-03-13 22:56:14 +01:00
Michael Teeuw
7515fc10d1 Merge pull request #2478 from thomasrockhu/codecov-badge
Add Codecov badge to README
2021-03-13 19:22:34 +01:00
Michael Teeuw
6583f05858 Merge pull request #2477 from PostLogical/openweathermaplatlon
Allowing openweathermap config lat and lon to function without onecall, overrides locationID and location since more specific.
2021-03-13 19:20:59 +01:00
Michael Teeuw
2b1dbbde68 Merge pull request #2475 from MystaraTheGreat/master
Added hiddenOnStartup flag to module config
2021-03-13 19:20:29 +01:00
Michael Teeuw
49c95a9f46 Merge pull request #2474 from khassel/node-fetch
Node fetch
2021-03-13 19:19:22 +01:00
Michael Teeuw
a6214c8da3 Merge pull request #2473 from khassel/remove-ical
remove ical
2021-03-13 19:18:35 +01:00
buxxi
d2b3414ac6 changelog for zombie processes and fixing linting error 2021-03-13 12:02:56 +01:00
buxxi
401a6f3417 Updating simple-git and using timeout when checking for module updates 2021-03-13 11:58:33 +01:00
buxxi
3ed223a550 Refactoring update notification to use async/await 2021-03-13 11:12:08 +01:00
Tom Hu
47bd48e0a3 Add Codecov badge to README 2021-03-12 23:36:24 -05:00
Robby Griffin
cdd1853369 Fix weather module openweathermap not loading if lat and lon set without onecall. Lat and Lon take precedence over LocationID and Location. 2021-03-11 12:48:41 -05:00
MystaraTheGreat
8f2980c23d Fixed unnecessarily verbose way of looping thanks to @rejas :) 2021-03-07 20:34:26 +00:00
MystaraTheGreat
b72556b9a9 Merge branch 'master' of https://github.com/MystaraTheGreat/MagicMirror 2021-03-07 20:14:36 +00:00
MystaraTheGreat
49be3cbd6b Changed var to let as requested 2021-03-07 20:13:57 +00:00
MystaraTheGreat
fbb0c59b4e Merge branch 'develop' into master 2021-03-07 13:16:18 +00:00
MystaraTheGreat
d83e696a8d Changed variable name to appease tester 2021-03-07 11:23:52 +00:00
MystaraTheGreat
a467d900c9 Changed iterative variable from m to n to appease tester 2021-03-07 11:20:19 +00:00
MystaraTheGreat
96be8d6fea Updated CHANGELOG.md 2021-03-07 11:14:23 +00:00
MystaraTheGreat
8b1ce26fa6 Added hiddenOnStartup flag to module configuration options to cause a module to be iniitally hidden after starting up 2021-03-07 11:05:29 +00:00
Karsten Hassel
514b9453f8 removed getFetcher function 2021-03-06 21:19:14 +01:00
Karsten Hassel
fa0f997928 replace request with node-fetch 2021-03-05 22:17:55 +01:00
Karsten Hassel
504e7cadd7 getFetcher 2021-03-05 00:13:32 +01:00
Karsten Hassel
c80aeff945 fixes calendarfetcher 2021-03-04 21:12:53 +01:00
Karsten Hassel
454206c803 remove ical 2021-03-03 21:41:48 +01:00
Michael Teeuw
e4f47178fc Merge pull request #2466 from rejas/electron_update
Enable contextIsolation in Electron
2021-03-03 10:43:10 +01:00
Karsten Hassel
1f9109f8e4 snapshot 2021-03-03 00:09:32 +01:00
Karsten Hassel
f09c54184a moved tests to fetch, run prettier 2021-03-02 21:26:25 +01:00
Karsten Hassel
92a35692f2 snapshot 2021-03-02 00:40:55 +01:00
Karsten Hassel
53e300bd31 snapshot 2021-03-02 00:17:13 +01:00
veeck
e8be6ad4f1 Update CHANGELOG 2021-02-27 14:01:10 +01:00
rejas
01b9ecb339 Update dependencies 2021-02-27 13:58:59 +01:00
rejas
6ff85ebe54 Enable contextIsolation in electron
Fixes warning when starting MM
2021-02-27 13:19:16 +01:00
Michael Teeuw
ea6eebd809 Merge pull request #2439 from fewieden/feature/add-error-to-callback
Added error to callback
2021-02-23 14:18:07 +01:00
Michael Teeuw
b2a21b937d Change error string. 2021-02-23 14:11:54 +01:00
Michael Teeuw
13010ecaee Merge branch 'develop' into feature/add-error-to-callback 2021-02-23 14:10:35 +01:00
Michael Teeuw
2e2e157017 Merge pull request #2458 from codac/patch-2
This is supposed to make self signed certs work with the calendar module
2021-02-23 14:09:13 +01:00
Michael Teeuw
e23e33852e Merge pull request #2464 from fewieden/feature/expose-logger-for-modules
exposed logger as node module
2021-02-23 14:08:07 +01:00
config
5b270b84b4 linted calendarfetcher.js 2021-02-21 19:56:02 +01:00
Krouty
9094240024 Merge branch 'develop' into patch-2 2021-02-21 11:48:47 +01:00
Krouty
1db0dbf52b Added support for self-signed certificates
Added support for self-signed certificates
2021-02-21 11:45:15 +01:00
Krouty
beb5faef8b Added support for self-signed certificates
Added support for self-signed certificates
2021-02-21 11:32:03 +01:00
Krouty
c6aff8b7dc Added suppoert for self-signed certificates
Added support for self-signed certificates
2021-02-21 11:29:19 +01:00
Krouty
1aacc37c83 Added selfSignedCert as a parameter
Added selfSignedCert as a parameter, so that it can be enabled only when it is required.
2021-02-21 10:29:40 +01:00
Felix Wiedenbach
b18d98f5ea exposed logger as node module 2021-02-18 19:14:53 +01:00
Michael Teeuw
09ddd3d925 Merge pull request #2461 from fewieden/feature/add-locale 2021-02-16 23:55:51 +01:00
Felix Wiedenbach
e2b4823e43 added locale to sample config 2021-02-16 22:06:53 +01:00
Krouty
04491ac5ac This is supposed to make self signed certs work with the calendar module
Many people use Own-/Nextcloud together witht he https protocol. This is supposed to make self-signed certificates work with the calendar module and fix the issue #466.
2021-02-13 17:13:00 +01:00
Michael Teeuw
e3bee5aae7 Merge pull request #2454 from rejas/cleanup_tests 2021-02-13 17:00:58 +01:00
Michael Teeuw
89152e537e Merge pull request #2452 from TheDuffman85/develop 2021-02-13 17:00:07 +01:00
Felix Wiedenbach
652e1a528f Merge branch 'develop' into feature/add-error-to-callback
# Conflicts:
#	CHANGELOG.md
2021-02-13 08:43:23 +01:00
Felix Wiedenbach
9d85baee37 move error callback into options and rename it 2021-02-13 08:29:13 +01:00
rejas
bdc4ed4d86 Add specific change-port test and fix the others 2021-02-12 08:45:54 +01:00
rejas
35f3b315a2 Update CHANGELOG 2021-02-11 21:22:56 +01:00
rejas
efec49bb47 Use es6 notations 2021-02-11 21:22:03 +01:00
rejas
68bc77c81e Cleanup global-setup code 2021-02-11 21:21:08 +01:00
veeck
dbea348779 Cleanup port usage in tests 2021-02-11 21:20:34 +01:00
rejas
b46160bcbc Simplify weather-compliments test, increase timeWaitout for it 2021-02-11 21:20:16 +01:00
TheDuffman85
ddb06ca214 Added translation for today and tomorrow
I had missread the documentation of moment.js. We've to provide the translation for today and tomorrow ourselves. For the translation I use the standard MM² translation mechanism.
2021-02-10 14:24:59 +01:00
Michael Teeuw
ef2fd16b69 Merge pull request #2448 from khassel/electron11
fix e2e tests after spectron update
2021-02-10 10:01:57 +01:00
Michael Teeuw
d874ad9988 Merge pull request #2451 from TheDuffman85/develop 2021-02-09 17:41:05 +01:00
TheDuffman85
57dc349675 Added a new parameter to hide time portion on relative times 2021-02-09 16:06:21 +01:00
TheDuffman85
120f0299b2 Added a new parameter to hide time portion on relative times
Added a new parameter hideTime with default value false. This parameter hides the time portion on relative times.
2021-02-09 16:05:38 +01:00
Michael Teeuw
b5b6df5e48 Merge pull request #2450 from TheDuffman85/develop 2021-02-09 15:37:47 +01:00
TheDuffman85
5ffd20b843 Removed unnecessary tabs 2021-02-09 15:21:14 +01:00
TheDuffman85
3dacce6675 Respect parameter ColoredSymbolOnly also for custom events 2021-02-09 13:05:11 +01:00
TheDuffman85
8e4ba4fe93 Respect parameter ColoredSymbolOnly also for custom events 2021-02-09 13:01:57 +01:00
Michael Teeuw
37d488760f Merge pull request #2449 from rejas/update_jsdoc 2021-02-07 09:09:14 +01:00
veeck
7cdeceedf1 Update lock file 2021-02-07 08:47:18 +01:00
Karsten Hassel
7ba76020d8 fix e2e tests after spectron update 2021-02-07 00:20:30 +01:00
rejas
aab1b97653 Update CHANGELOG 2021-02-06 22:57:31 +01:00
rejas
221eadcc20 Make function callbacks and returns more readable 2021-02-06 22:55:59 +01:00
rejas
4a11cdc564 Update dependencies 2021-02-06 22:48:24 +01:00
rejas
b9333134c7 Fix lint script 2021-02-06 22:21:08 +01:00
veeck
8c83dc9494 Cleanup jsdoc 2021-02-06 22:20:49 +01:00
Felix Wiedenbach
1ed721fb15 updated changelog 2021-02-06 21:32:57 +01:00
Felix Wiedenbach
88ed5ed373 add error separate callback 2021-02-06 21:22:13 +01:00
Felix Wiedenbach
84995c9252 Merge branch 'develop' into feature/add-error-to-callback
# Conflicts:
#	CHANGELOG.md
2021-02-06 11:29:17 +01:00
Michael Teeuw
6fadc76fe3 Merge pull request #2447 from rejas/warn_dom
Warn dom
2021-02-06 11:01:21 +01:00
Michael Teeuw
d240986fdc Merge branch 'develop' into warn_dom 2021-02-06 11:01:08 +01:00
Michael Teeuw
afc73920ad Merge pull request #2443 from khassel/electron11 2021-02-06 10:58:38 +01:00
rejas
16615c3da2 Update CHANGELOG 2021-02-05 22:40:44 +01:00
rejas
88c80973f1 Dont updateDOM when the module is not displayed 2021-02-05 22:40:05 +01:00
Karsten Hassel
e322be2624 Merge branch 'develop' into electron11 2021-01-30 22:58:35 +01:00
Karsten Hassel
059b87bbb4 bump electron to v11 2021-01-30 21:58:49 +01:00
Michael Teeuw
911675f995 Merge pull request #2442 from fewieden/fix/translation-fallbacks
Fix/translation fallbacks
2021-01-30 20:21:39 +01:00
Felix Wiedenbach
e76fe5e25a updated changelog 2021-01-29 22:42:57 +01:00
Felix Wiedenbach
308774c2a6 remove callback hell 2021-01-29 22:34:12 +01:00
Felix Wiedenbach
db24f20289 cleaned up function and added test in case no file should be loaded 2021-01-29 22:25:49 +01:00
Felix Wiedenbach
94bb8e6c03 added sinon, tests for module.loadTranslations 2021-01-29 22:13:44 +01:00
Felix Wiedenbach
41da6f455a use error object for callback to include stack trace 2021-01-28 21:23:48 +01:00
Felix Wiedenbach
d2a7a3b0bb more error like message 2021-01-28 21:13:17 +01:00
Felix Wiedenbach
afbdacf136 updated changelog 2021-01-28 07:46:23 +01:00
Felix Wiedenbach
2324579057 add error to module show callback 2021-01-28 07:33:02 +01:00
Michael Teeuw
3c357f057b Merge pull request #2438 from khassel/fix_socket_v2 2021-01-27 21:15:01 +01:00
Karsten Hassel
5116a2fc82 fix socket.io backward compatibility with socket v2 clients 2021-01-27 20:52:50 +01:00
Michael Teeuw
c0ddc020d5 Merge pull request #2433 from buxxi/deprecate-old-weather 2021-01-24 11:04:13 +01:00
buxxi
0683734d5a Make a sane default for weatherEndpoint based on the type 2021-01-24 10:32:43 +01:00
buxxi
6cbd267384 Merge branch 'develop' into deprecate-old-weather 2021-01-24 10:22:39 +01:00
Michael Teeuw
925113fa20 Merge pull request #2432 from buxxi/weather-provider-hourly 2021-01-23 16:56:03 +01:00
buxxi
3696d45e94 Merge branch 'develop' into deprecate-old-weather 2021-01-23 13:56:13 +01:00
Michael Teeuw
cb3ae66652 Merge pull request #2430 from EdgardosReis/patch-4 2021-01-23 13:31:27 +01:00
buxxi
948b6c8de8 deprecate module currentweather and weatherforecast 2021-01-23 13:12:56 +01:00
buxxi
3c4d7a33e0 Fixing code style issue with no return before default 2021-01-23 12:07:10 +01:00
buxxi
5a421220c9 Updating readme and changelog and fixing typo in method 2021-01-23 11:40:02 +01:00
buxxi
41508931be Moving default values regarding specific providers into the providers themselves 2021-01-23 11:21:56 +01:00
buxxi
e2cfa24686 make weatherprovider have a method for hourly fetching instead of a generic weatherData 2021-01-23 10:45:55 +01:00
buxxi
d48113f2d9 Moving openweathermap specific check for hourly into its provider and make invalid types fail nicer 2021-01-23 10:13:41 +01:00
Edgar dos Reis
16c5bddbb7 Update CHANGELOG.md
updated changelog
2021-01-22 14:07:40 +00:00
Edgar dos Reis
ef7556f6d3 Update pt.json
Added MODULE_CONFIG_CHANGED and PRECIP translations.
2021-01-22 13:02:13 +00:00
Michael Teeuw
a3cb0b7b96 Merge pull request #2428 from rejas/update_defaults
Update documentation and help screen about invalid config files
2021-01-21 15:11:39 +01:00
Michael Teeuw
4d28688f30 Merge branch 'develop' into update_defaults 2021-01-21 15:11:31 +01:00
Michael Teeuw
01ff00fa31 Merge pull request #2427 from dannoh/Issue-TranslateEncodedHtml
Issue translate encoded html
2021-01-21 15:10:28 +01:00
Michael Teeuw
78190d9c7f Merge branch 'develop' into Issue-TranslateEncodedHtml 2021-01-21 15:10:14 +01:00
Michael Teeuw
b1565e4047 Merge pull request #2423 from rejas/add_start_dev
Added start:dev command to npm scripts
2021-01-21 15:08:52 +01:00
Michael Teeuw
db220b7861 Merge branch 'develop' into add_start_dev 2021-01-21 15:08:44 +01:00
Michael Teeuw
eaf27b837e Merge pull request #2422 from buxxi/develop
Refactoring newsfeed-module to use templates instead of dom-generation
2021-01-21 15:07:22 +01:00
Michael Teeuw
74410344af Merge pull request #2421 from klaernie/no-empty-subdirs
prevent empty path components for module main scripts
2021-01-21 15:07:07 +01:00
Michael Teeuw
998f64f983 Merge branch 'develop' into no-empty-subdirs 2021-01-21 15:06:57 +01:00
Michael Teeuw
684dfb643b Merge pull request #2420 from rejas/issue_2416
Add new Notification CURRENTWEATHER_TYPE
2021-01-21 15:05:09 +01:00
Michael Teeuw
314c3cb516 Merge pull request #2417 from ashishtank/Issue2221
Fixed Unit test case error for #2221
2021-01-21 15:04:33 +01:00
veeck
58939bfd8c Update documentation and help screen about invalid config files 2021-01-20 22:44:37 +01:00
Dan Forsyth
33592b3c0e Removed |safe from translates in the default module templates 2021-01-20 14:06:00 -05:00
Dan Forsyth
ad9c2549bc Removed |safe from translates in the default module templates 2021-01-20 13:47:32 -05:00
Dan Forsyth
5152e0b114 Updated changelog 2021-01-20 07:36:26 -05:00
Dan Forsyth
ca48663efd Merge remote-tracking branch 'origin/develop' into Issue-TranslateEncodedHtml 2021-01-20 07:31:11 -05:00
Dan Forsyth
b520b4c37a Marked all translated strings as safe before passing them to the nunjuck template 2021-01-20 07:16:09 -05:00
veeck
90f07295b1 Add extra check for currentweather 2021-01-17 15:00:34 +01:00
veeck
3895c18466 Add test case 2021-01-17 14:57:06 +01:00
veeck
2b6a9fc5bb Update dependencies 2021-01-17 12:41:42 +01:00
rejas
052f0b8709 Added start:dev script 2021-01-16 22:23:55 +01:00
buxxi
a5bb9d962d Fixing eslint issues 2021-01-16 14:06:07 +01:00
buxxi
2d9d28aa0f updating changelog with newsfeed changes 2021-01-16 13:47:16 +01:00
buxxi
69c053a94f refactoring newsfeed to use templates instead of generating dom in the code 2021-01-16 13:37:18 +01:00
buxxi
aaaf1f660c refactoring newsfeed, moving hiding of module while loading away from dom-generation 2021-01-16 12:26:38 +01:00
buxxi
8538d83520 Moving newsfeed styling from js to a new css file 2021-01-16 11:52:55 +01:00
buxxi
132c98b767 refactoring newsfeed, moving tag stripping to loading instead of presentation logic 2021-01-16 11:10:53 +01:00
rejas
42cac81953 Fix tests 2021-01-15 23:12:44 +01:00
Andre Klärner
3ee4bd65c6 prevent empty path components for module main scripts
The module.path component has by definition in line 97 a trailing slash.
Hence adding another is unneeded, but results in an additional folder in the inspector.
2021-01-15 23:09:07 +01:00
rejas
fcc7e80bf9 Update CHANGELOG 2021-01-15 21:49:12 +01:00
rejas
e0d43a4c1e Add new Event CURRENTWEATHER_TYPE
- send it from the weather and currentweather module
- use it in the compliments module directly instead of the data
2021-01-15 21:47:14 +01:00
Ashish Tank
4966d6c920 Fixed Unit test case error for #2221 2021-01-14 19:10:04 +01:00
Michael Teeuw
1fd506f25d Merge pull request #2414 from ashishtank/FeelsLikeCleanup
Feels like translation code cleanup
2021-01-13 09:50:11 +01:00
Michael Teeuw
a99698d1a9 Merge pull request #2413 from ashishtank/Issue2221
Issue 2221 Weather module - Always displays night icons because of fixed day start and end time
2021-01-12 16:14:06 +01:00
Ashish Tank
774b86c7dc Code cleanup for feels like translation 2021-01-10 17:37:10 +01:00
Ashish Tank
2c3e8533c7 Issue #2221 - Weather forecast always shows night icons in day time 2021-01-10 16:24:46 +01:00
ashishtank
3eda8af671 Merge pull request #9 from MichMich/develop
Develop
2021-01-10 16:03:49 +01:00
Michael Teeuw
aa61874848 Merge pull request #2411 from khassel/fix_cors
fix socket.io cors errors
2021-01-10 10:13:16 +01:00
Karsten Hassel
2deab31187 fix socket.io cors errors, see breaking change since socket.io v3 https://socket.io/docs/v3/handling-cors/ 2021-01-09 23:20:36 +01:00
Michael Teeuw
b177a56fa2 Fix wrong placement of changelog item. 2021-01-07 12:45:03 +01:00
Michael Teeuw
6f0f75cf27 Merge pull request #2388 from drewski3420/no_negative_zero
No negative zero
2021-01-07 12:41:30 +01:00
Michael Teeuw
39bb2eb9b0 Add Changelog. 2021-01-07 11:53:21 +01:00
Michael Teeuw
7b36bb025a Improve readabiliy. 2021-01-07 11:51:10 +01:00
Michael Teeuw
aa9a1b7af2 Add CodeCov badge to Readme. 2021-01-06 11:03:15 +01:00
Michael Teeuw
caf3552d6b Merge pull request #2401 from rejas/github_codecov_action
Add github action for uploading codecov results
2021-01-06 10:05:50 +01:00
veeck
6e9897f7fc Remove now unnecessary file 2021-01-06 09:33:06 +01:00
veeck
fa9258761e Change on-trigger 2021-01-06 09:33:06 +01:00
veeck
003e948899 Use codecov action instead of bash command 2021-01-06 09:33:06 +01:00
veeck
9cd998f219 Update CHANGELOG 2021-01-06 09:33:06 +01:00
rejas
d75b894d9a Add lcov reporter 2021-01-06 09:33:06 +01:00
rejas
5a3d3b76a7 Cleanups 2021-01-06 09:33:06 +01:00
rejas
5bc2c207db Add codecov github action 2021-01-06 09:33:06 +01:00
Michael Teeuw
612cf25878 Merge pull request #2407 from fewieden/feature/update-node-js-code
update node js code
2021-01-06 09:01:42 +01:00
Felix Wiedenbach
5ae4912b45 added changelog entry 2021-01-05 20:08:18 +01:00
Felix Wiedenbach
a9a70fd2e9 linting and fix defualt module test 2021-01-05 20:04:02 +01:00
Felix Wiedenbach
f90856808b fix config file path 2021-01-05 19:39:31 +01:00
Felix Wiedenbach
7dbcaa83bc clean up deprecated 2021-01-05 19:36:20 +01:00
Felix Wiedenbach
38f10b6e3e clean up app 2021-01-05 19:35:11 +01:00
Felix Wiedenbach
9c8fa06ce1 clean up config checker 2021-01-05 19:01:59 +01:00
Felix Wiedenbach
4efe04774c clean up electron 2021-01-05 18:48:55 +01:00
Felix Wiedenbach
5d60534dc9 clean up node helper 2021-01-05 18:44:36 +01:00
Felix Wiedenbach
ac141a4316 clean up server 2021-01-05 18:37:16 +01:00
Felix Wiedenbach
d22064c6f4 clean up utils 2021-01-05 18:37:01 +01:00
Michael Teeuw
189721ebba Merge pull request #2406 from rejas/danger
Remove now unused danger library
2021-01-05 16:54:55 +01:00
Michael Teeuw
7aa9c63dba Merge pull request #2405 from rejas/markdown
Update markdown for 2021
2021-01-05 16:53:46 +01:00
veeck
16e894e300 Update CHANGELOG 2021-01-05 15:07:40 +01:00
veeck
d466705ec0 Fix eslint error due to now js files being in root anymore 2021-01-05 15:05:50 +01:00
veeck
0e97d863ce Remove now unused danger library 2021-01-05 14:43:41 +01:00
veeck
5de64d2ae8 Added engine fields into package json 2021-01-05 09:54:03 +01:00
veeck
ced0398e49 Update markdowns 2021-01-05 09:53:47 +01:00
Michael Teeuw
d4b57924a7 Merge pull request #2404 from rejas/issue_2402
Update default log levels
2021-01-04 13:52:37 +01:00
veeck
d2def2bea3 Update CHANGELOG 2021-01-04 10:03:35 +01:00
veeck
e65bb84f9f Add default values for log levels 2021-01-04 10:01:56 +01:00
Michael Teeuw
69b0aa6118 Prepare v2.15.0-develop 2021-01-01 19:27:07 +01:00
drewski3420@gmail.com
e51f6597ed This change prevents returning '-0' (negative zero) when roundTemp is true. 2020-12-30 09:03:19 -05:00
drewski3420@gmail.com
e80a65a3cd This change prevents returning '-0' (negative zero) when roundTemp is true 2020-12-30 08:51:07 -05:00
drewski3420@gmail.com
7c3675c9e1 This change prevents returning '-0' (negative zero) when roundTemp is true. 2020-12-29 21:07:14 -05:00
161 changed files with 6461 additions and 6625 deletions

View File

@@ -25,13 +25,14 @@ To run StyleLint, use `npm run lint:style`.
Please only submit reproducible issues.
If you're not sure if it's a real bug or if it's just you, please open a topic on the forum: [https://forum.magicmirror.builders/category/15/bug-hunt](https://forum.magicmirror.builders/category/15/bug-hunt)
Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting)
When submitting a new issue, please supply the following information:
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX).
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX).
**Node Version**: Make sure it's version 0.12.13 or later.
**Node Version**: Make sure it's version 10 or later.
**MagicMirror Version**: Now that the versions have split, tell us if you are using the PHP version (v1) or the newer JavaScript version (v2).

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: MichMich
custom: ['https://magicmirror.builders/#donate']

View File

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

View File

@@ -1,13 +1,28 @@
> Please send your pull requests the develop branch.
> Don't forget to add the change to CHANGELOG.md.
Hello and thank you for wanting to contribute to the MagicMirror project
**Please make sure that you have followed these 4 rules before submitting your Pull Request:**
> 1) Base your pull requests against the `develop` branch.
>
>
> 2) Include these infos in the description:
> * Does the pull request solve a **related** issue?
> * If so, can you reference the issue like this `Fixes #<issue_number>`?
> * What does the pull request accomplish? Use a list if needed.
> * If it includes major visual changes please add screenshots.
>
>
> 3) Please run `npm run lint:prettier` before submitting so that
> style issues are fixed.
>
>
> 4) Don't forget to add an entry about your changes to
> the CHANGELOG.md file.
**Note**: Sometimes the development moves very fast. It is highly
recommended that you update your branch of `develop` before creating a
pull request to send us your changes. This makes everyone's lives
easier (including yours) and helps us out on the development team.
Thanks!
- Does the pull request solve a **related** issue?
- If so, can you reference the issue?
- What does the pull request accomplish? Use a list if needed.
- If it includes major visual changes please add screenshots.
Thanks again and have a nice day!

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,70 @@ This project adheres to [Semantic Versioning](https://semver.org/).
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
## [2.15.0] - 2021-04-01
Special thanks to the following contributors: @EdgardosReis, @MystaraTheGreat, @TheDuffman85, @ashishtank, @buxxi, @codac, @fewieden, @khassel, @klaernie, @qu1que, @rejas, @sdetweil & @thomasrockhu.
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`.
### Added
- Added Galician language.
- Added GitHub workflows for automated testing and changelog enforcement.
- Added CodeCov badge to Readme.
- Added CURRENTWEATHER_TYPE notification to currentweather and weather module, use it in compliments module.
- Added `start:dev` command to the npm scripts for starting electron with devTools open.
- Added logging when using deprecated modules weatherforecast or currentweather.
- Added Portuguese translations for "MODULE_CONFIG_CHANGED" and "PRECIP".
- Respect parameter ColoredSymbolOnly also for custom events.
- Added a new parameter to hide time portion on relative times.
- `module.show` has now the option for a callback on error.
- Added locale to sample config file.
- Added support for self-signed certificates for the default calendar module (#466).
- Added hiddenOnStartup flag to module config (#2475).
### Updated
- Updated markdown files for github.
- Cleaned up old code on server side.
- Convert `-0` to `0` when displaying temperature.
- Code cleanup for FEELS like and added {DEGREE} placeholder for FEELSLIKE for each language.
- Converted newsfeed module to use templates.
- Updated documentation and help screen about invalid config files.
- Moving weather provider specific code and configuration into each provider and making hourly part of the interface.
- Bump electron to v11 and enable contextIsolation.
- Don't update the DOM when a module is not displayed.
- Cleaned up jsdoc and tests.
- Exposed logger as node module for easier access for 3rd party modules.
- Replaced deprecated `request` package with `node-fetch` and `digest-fetch`.
- Refactored calendar fetcher.
- Cleaned up newsfeed module.
- Cleaned up translations and translator code.
### Removed
- 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.
- Removed valid-url library.
### Fixed
- Added default log levels to stop calendar log spamming.
- Fix socket.io cors errors, see [breaking change since socket.io v3](https://socket.io/docs/v3/handling-cors/).
- Fix Issue with weather forecast icons due to fixed day start and end time (#2221).
- Fix empty directory for each module's main javascript file in the inspector.
- Fix Issue with weather forecast icons unit tests with different timezones (#2221).
- Fix issue with unencoded characters in translated strings when using nunjuck template (`Loading &hellip;` as an example).
- Fix socket.io backward compatibility with socket v2 clients.
- Fix 3rd party module language loading if language is English.
- Fix e2e tests after spectron update.
- 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.
- Fix calendar daylight savings offset calculation if recurring start date before 2007.
- Fix calendar time/date adjustment when time with GMT offset is different day (#2488).
- Fix calendar daylight savings offset calculation if recurring FULL DAY start date before 2007 (#2483).
- Fix newsreaders template, for wrong test for nowrap in 2 places (should be if not).
## [2.14.0] - 2021-01-01
Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank, @bluemanos, @flopp999, @jakemulley, @jakobsarwary1, @marvai-vgtu, @mirontoli, @rejas, @sdetweil, @Snille & @Sub028.
@@ -15,17 +79,15 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
- Added new log level "debug" to the logger.
- Added new parameter "useKmh" to weather module for displaying wind speed as kmh.
- Chuvash translation.
- Added Chuvash translation.
- Added Weatherbit as a provider to Weather module.
- Added SMHI as a provider to Weather module.
- Added Hindi & Gujarati translation.
- Added optional support for DEGREE position in Feels like translation.
- Added support for variables in nunjucks templates for translate filter.
- Added Chuvash translation.
- Calendar: new options "limitDays" and "coloredEvents".
- Added new option "limitDays" - limit the number of discreet days displayed.
- Added new option "customEvents" - use custom symbol/color based on keyword in event title.
- Added GitHub workflows for automated testing and changelog enforcement.
### Updated
@@ -44,7 +106,7 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
### Deleted
- Removed Travis CI intergration.
- Removed Travis CI integration.
### Fixed
@@ -61,8 +123,8 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
- Fix non-fullday recurring rule processing. (#2216)
- Catch errors when parsing calendar data with ical. (#2022)
- Fix Default Alert Module does not hide black overlay when alert is dismissed manually. (#2228)
- Weather module - Always displays night icons when local is other then English. (#2221)
- Update Node-ical 0.12.4 , fix invalid RRULE format in cal entries
- Weather module - Always displays night icons when local is other than English. (#2221)
- Update node-ical 0.12.4, fix invalid RRULE format in cal entries
- Fix package.json for optional electron dependency (2378)
- Update node-ical version again, 0.12.5, change RRULE fix (#2371, #2379)
- Remove undefined objects from modules array (#2382)
@@ -77,11 +139,11 @@ Special thanks to the following contributors: @bryanzzhu, @bugsounet, @chamakura
### Added
- `--dry-run` option adde in fetch call within updatenotification node_helper. This is to prevent
- `--dry-run` Added option in fetch call within updatenotification node_helper. This is to prevent
MagicMirror from consuming any fetch result. Causes conflict with MMPM when attempting to check
for updates to MagicMirror and/or MagicMirror modules.
- Test coverage with Istanbul, run it with `npm run test:coverage`.
- Add lithuanian language.
- Added lithuanian language.
- Added support in weatherforecast for OpenWeather onecall API.
- Added config option to calendar-icons for recurring- and fullday-events.
- Added current, hourly (max 48), and daily (max 7) weather forecasts to weather module via OpenWeatherMap One Call API.
@@ -145,7 +207,7 @@ Special thanks to the following contributors: @AndreKoepke, @andrezibaia, @bryan
- Fix the use of "maxNumberOfDays" in the module "weatherforecast". [#2018](https://github.com/MichMich/MagicMirror/issues/2018)
- Throw error when check_config fails. [#1928](https://github.com/MichMich/MagicMirror/issues/1928)
- Bug fix related to 'maxEntries' not displaying Calendar events. [#2050](https://github.com/MichMich/MagicMirror/issues/2050)
- Updated ical library to latest version. [#1926](https://github.com/MichMich/MagicMirror/issues/1926)
- Updated ical library to the latest version. [#1926](https://github.com/MichMich/MagicMirror/issues/1926)
- Fix config check after merge of prettier [#2109](https://github.com/MichMich/MagicMirror/issues/2109)
## [2.11.0] - 2020-04-01
@@ -439,7 +501,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
### Fixed
- Fixed gzip encoded calendar loading issue #1400.
- Mixup between german and spanish translation for newsfeed.
- Fixed mixup between german and spanish translation for newsfeed.
- Fixed close dates to be absolute, if no configured in the config.js - module Calendar
- Fixed the updatenotification module message about new commits in the repository, so they can be correctly localized in singular and plural form.
- Fix for weatherforecast rainfall rounding [#1374](https://github.com/MichMich/MagicMirror/issues/1374)

View File

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

View File

@@ -1,11 +1,13 @@
![MagicMirror²: The open source modular smart mirror platform. ](.github/header.png)
<p align="center">
<p style="text-align: center">
<a href="https://david-dm.org/MichMich/MagicMirror"><img src="https://david-dm.org/MichMich/MagicMirror.svg" alt="Dependency Status"></a>
<a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge"></a>
<a href="https://david-dm.org/MichMich/MagicMirror?type=dev"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge" alt="CLI Best Practices"></a>
<a href="https://codecov.io/gh/MichMich/MagicMirror"><img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg?token=LEG1KitZR6" alt="CodeCov Status"/></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://codecov.io/gh/MichMich/MagicMirror"><img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg" /></a>
</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).
@@ -27,7 +29,13 @@ For the full documentation including **[installation instructions](https://docs.
## Contributing Guidelines
Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation. For the full contribution guidelines, check out: [https://docs.magicmirror.builders/getting-started/contributing.html](https://docs.magicmirror.builders/getting-started/contributing.html)
Contributions of all kinds are welcome, not only in the form of code but also with regards to
- bug reports
- documentation
- translations
For the full contribution guidelines, check out: [https://docs.magicmirror.builders/getting-started/contributing.html](https://docs.magicmirror.builders/getting-started/contributing.html)
## Enjoying MagicMirror? Consider a donation!
@@ -38,7 +46,6 @@ If we receive enough donations we might even be able to free up some working hou
To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link.
<p align="center">
<br>
<p style="text-align: center">
<a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a>
</p>

View File

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

View File

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

View File

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

132
js/app.js
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -54,6 +54,14 @@ var Loader = (function () {
// Notify core of loaded modules.
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 + "/",
file: moduleName + ".js",
position: moduleData.position,
hiddenOnStartup: moduleData.hiddenOnStartup,
header: moduleData.header,
configDeepMerge: typeof moduleData.configDeepMerge === "boolean" ? moduleData.configDeepMerge : false,
config: moduleData.config,
@@ -114,7 +123,7 @@ var Loader = (function () {
* @param {Function} callback Function called when done.
*/
var loadModule = function (module, callback) {
var url = module.path + "/" + module.file;
var url = module.path + module.file;
var afterLoad = function () {
var moduleObject = Module.create(module.name);

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ var Translator = (function () {
* @param {Function} callback Function called when done.
*/
function loadJSON(file, callback) {
var xhr = new XMLHttpRequest();
const xhr = new XMLHttpRequest();
xhr.overrideMimeType("application/json");
xhr.open("GET", file, true);
xhr.onreadystatechange = function () {
@@ -103,26 +103,19 @@ var Translator = (function () {
* @param {boolean} isFallback Flag to indicate fallback translations.
* @param {Function} callback Function called when done.
*/
load: function (module, file, isFallback, callback) {
if (!isFallback) {
Log.log(module.name + " - Load translation: " + file);
} else {
Log.log(module.name + " - Load translation fallback: " + file);
load(module, file, isFallback, callback) {
Log.log(`${module.name} - Load translation${isFallback && " fallback"}: ${file}`);
if (this.translationsFallback[module.name]) {
callback();
return;
}
var self = this;
if (!this.translationsFallback[module.name]) {
loadJSON(module.file(file), function (json) {
if (!isFallback) {
self.translations[module.name] = json;
} else {
self.translationsFallback[module.name] = json;
}
callback();
});
} else {
loadJSON(module.file(file), (json) => {
const property = isFallback ? "translationsFallback" : "translations";
this[property][module.name] = json;
callback();
}
});
},
/**
@@ -131,18 +124,16 @@ var Translator = (function () {
* @param {string} lang The language identifier of the core language.
*/
loadCoreTranslations: function (lang) {
var self = this;
if (lang in translations) {
Log.log("Loading core translation file: " + translations[lang]);
loadJSON(translations[lang], function (translations) {
self.coreTranslations = translations;
loadJSON(translations[lang], (translations) => {
this.coreTranslations = translations;
});
} else {
Log.log("Configured language not found in core translations.");
}
self.loadCoreTranslationsFallback();
this.loadCoreTranslationsFallback();
},
/**
@@ -150,8 +141,6 @@ var Translator = (function () {
* The first language defined in translations.js will be used.
*/
loadCoreTranslationsFallback: function () {
var self = this;
// The variable `first` will contain the first
// defined translation after the following line.
for (var first in translations) {
@@ -160,8 +149,8 @@ var Translator = (function () {
if (first) {
Log.log("Loading core translation fallback file: " + translations[first]);
loadJSON(translations[first], function (translations) {
self.coreTranslationsFallback = translations;
loadJSON(translations[first], (translations) => {
this.coreTranslationsFallback = translations;
});
}
}

View File

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

View File

@@ -36,6 +36,7 @@ Module.register("calendar", {
fadePoint: 0.25, // Start on 1/4th of the list.
hidePrivate: false,
hideOngoing: false,
hideTime: false,
colored: false,
coloredSymbolOnly: false,
customEvents: [], // Array of {keyword: "", symbol: "", color: ""} where Keyword is a regexp and symbol/color are to be applied for matched
@@ -57,7 +58,8 @@ Module.register("calendar", {
excludedEvents: [],
sliceMultiDayEvents: false,
broadcastPastEvents: false,
nextDaysRelative: false
nextDaysRelative: false,
selfSignedCert: false
},
requiresVersion: "2.1.0",
@@ -75,7 +77,7 @@ Module.register("calendar", {
// Define required translations.
getTranslations: function () {
// The translations for the default modules are defined in the core translation files.
// Therefor we can just return false. Otherwise we should have returned a dictionary.
// Therefore we can just return false. Otherwise we should have returned a dictionary.
// If you're trying to build your own module including translations, check out the documentation.
return false;
},
@@ -93,15 +95,16 @@ Module.register("calendar", {
// indicate no data available yet
this.loaded = false;
for (var c in this.config.calendars) {
var calendar = this.config.calendars[c];
this.config.calendars.forEach((calendar) => {
calendar.url = calendar.url.replace("webcal://", "http://");
var calendarConfig = {
const calendarConfig = {
maximumEntries: calendar.maximumEntries,
maximumNumberOfDays: calendar.maximumNumberOfDays,
broadcastPastEvents: calendar.broadcastPastEvents
broadcastPastEvents: calendar.broadcastPastEvents,
selfSignedCert: calendar.selfSignedCert
};
if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
calendarConfig.symbolClass = "";
}
@@ -125,7 +128,7 @@ Module.register("calendar", {
// tell helper to start a fetcher for this calendar
// fetcher till cycle
this.addCalendar(calendar.url, calendar.auth, calendarConfig);
}
});
},
// Override socket notification handler.
@@ -161,8 +164,8 @@ Module.register("calendar", {
const oneHour = oneMinute * 60;
const oneDay = oneHour * 24;
var events = this.createEventList();
var wrapper = document.createElement("table");
const events = this.createEventList();
const wrapper = document.createElement("table");
wrapper.className = this.config.tableClass;
if (events.length === 0) {
@@ -171,37 +174,37 @@ Module.register("calendar", {
return wrapper;
}
let currentFadeStep = 0;
let startFade;
let fadeSteps;
if (this.config.fade && this.config.fadePoint < 1) {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startFade = events.length * this.config.fadePoint;
var fadeSteps = events.length - startFade;
startFade = events.length * this.config.fadePoint;
fadeSteps = events.length - startFade;
}
var currentFadeStep = 0;
var lastSeenDate = "";
var ev;
var needle;
let lastSeenDate = "";
for (var e in events) {
var event = events[e];
var dateAsString = moment(event.startDate, "x").format(this.config.dateFormat);
events.forEach((event, index) => {
const dateAsString = moment(event.startDate, "x").format(this.config.dateFormat);
if (this.config.timeFormat === "dateheaders") {
if (lastSeenDate !== dateAsString) {
var dateRow = document.createElement("tr");
const dateRow = document.createElement("tr");
dateRow.className = "normal";
var dateCell = document.createElement("td");
const dateCell = document.createElement("td");
dateCell.colSpan = "3";
dateCell.innerHTML = dateAsString;
dateCell.style.paddingTop = "10px";
dateRow.appendChild(dateCell);
wrapper.appendChild(dateRow);
if (e >= startFade) {
if (this.config.fade && index >= startFade) {
//fading
currentFadeStep = e - startFade;
currentFadeStep = index - startFade;
dateRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
}
@@ -209,7 +212,7 @@ Module.register("calendar", {
}
}
var eventWrapper = document.createElement("tr");
const eventWrapper = document.createElement("tr");
if (this.config.colored && !this.config.coloredSymbolOnly) {
eventWrapper.style.cssText = "color:" + this.colorForUrl(event.url);
@@ -217,22 +220,22 @@ Module.register("calendar", {
eventWrapper.className = "normal event";
if (this.config.displaySymbol) {
var symbolWrapper = document.createElement("td");
const symbolWrapper = document.createElement("td");
if (this.config.displaySymbol) {
if (this.config.colored && this.config.coloredSymbolOnly) {
symbolWrapper.style.cssText = "color:" + this.colorForUrl(event.url);
}
var symbolClass = this.symbolClassForUrl(event.url);
const symbolClass = this.symbolClassForUrl(event.url);
symbolWrapper.className = "symbol align-right " + symbolClass;
var symbols = this.symbolsForEvent(event);
const symbols = this.symbolsForEvent(event);
// If symbols are displayed and custom symbol is set, replace event symbol
if (this.config.displaySymbol && this.config.customEvents.length > 0) {
for (ev in this.config.customEvents) {
for (let ev in this.config.customEvents) {
if (typeof this.config.customEvents[ev].symbol !== "undefined" && this.config.customEvents[ev].symbol !== "") {
needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
let needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
if (needle.test(event.title)) {
symbols[0] = this.config.customEvents[ev].symbol;
break;
@@ -240,31 +243,29 @@ Module.register("calendar", {
}
}
}
for (var i = 0; i < symbols.length; i++) {
var symbol = document.createElement("span");
symbol.className = "fa fa-fw fa-" + symbols[i];
if (i > 0) {
symbols.forEach((s, index) => {
const symbol = document.createElement("span");
symbol.className = "fa fa-fw fa-" + s;
if (index > 0) {
symbol.style.paddingLeft = "5px";
}
symbolWrapper.appendChild(symbol);
}
});
eventWrapper.appendChild(symbolWrapper);
} else if (this.config.timeFormat === "dateheaders") {
var blankCell = document.createElement("td");
const blankCell = document.createElement("td");
blankCell.innerHTML = "&nbsp;&nbsp;&nbsp;";
eventWrapper.appendChild(blankCell);
}
var titleWrapper = document.createElement("td"),
repeatingCountTitle = "";
const titleWrapper = document.createElement("td");
let repeatingCountTitle = "";
if (this.config.displayRepeatingCountTitle && event.firstYear !== undefined) {
repeatingCountTitle = this.countTitleForUrl(event.url);
if (repeatingCountTitle !== "") {
var thisYear = new Date(parseInt(event.startDate)).getFullYear(),
const thisYear = new Date(parseInt(event.startDate)).getFullYear(),
yearDiff = thisYear - event.firstYear;
repeatingCountTitle = ", " + yearDiff + ". " + repeatingCountTitle;
@@ -273,12 +274,15 @@ Module.register("calendar", {
// Color events if custom color is specified
if (this.config.customEvents.length > 0) {
for (ev in this.config.customEvents) {
for (let ev in this.config.customEvents) {
if (typeof this.config.customEvents[ev].color !== "undefined" && this.config.customEvents[ev].color !== "") {
needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
let needle = new RegExp(this.config.customEvents[ev].keyword, "gi");
if (needle.test(event.title)) {
eventWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
titleWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
// Respect parameter ColoredSymbolOnly also for custom events
if (!this.config.coloredSymbolOnly) {
eventWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
titleWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
}
if (this.config.displaySymbol) {
symbolWrapper.style.cssText = "color:" + this.config.customEvents[ev].color;
}
@@ -290,7 +294,7 @@ Module.register("calendar", {
titleWrapper.innerHTML = this.titleTransform(event.title, this.config.titleReplace, this.config.wrapEvents, this.config.maxTitleLength, this.config.maxTitleLines) + repeatingCountTitle;
var titleClass = this.titleClassForUrl(event.url);
const titleClass = this.titleClassForUrl(event.url);
if (!this.config.colored) {
titleWrapper.className = "title bright " + titleClass;
@@ -298,14 +302,12 @@ Module.register("calendar", {
titleWrapper.className = "title " + titleClass;
}
var timeWrapper;
if (this.config.timeFormat === "dateheaders") {
if (event.fullDayEvent) {
titleWrapper.colSpan = "2";
titleWrapper.align = "left";
} else {
timeWrapper = document.createElement("td");
const timeWrapper = document.createElement("td");
timeWrapper.className = "time light " + this.timeClassForUrl(event.url);
timeWrapper.align = "left";
timeWrapper.style.paddingLeft = "2px";
@@ -316,10 +318,10 @@ Module.register("calendar", {
eventWrapper.appendChild(titleWrapper);
} else {
timeWrapper = document.createElement("td");
const timeWrapper = document.createElement("td");
eventWrapper.appendChild(titleWrapper);
var now = new Date();
const now = new Date();
if (this.config.timeFormat === "absolute") {
// Use dateFormat
@@ -363,7 +365,17 @@ Module.register("calendar", {
// Show relative times
if (event.startDate >= now) {
// Use relative time
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar());
if (!this.config.hideTime) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar());
} else {
timeWrapper.innerHTML = this.capFirst(
moment(event.startDate, "x").calendar(null, {
sameDay: "[" + this.translate("TODAY") + "]",
nextDay: "[" + this.translate("TOMORROW") + "]",
nextWeek: "dddd"
})
);
}
if (event.startDate - now < this.config.getRelative * oneHour) {
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
@@ -385,22 +397,22 @@ Module.register("calendar", {
wrapper.appendChild(eventWrapper);
// Create fade effect.
if (e >= startFade) {
currentFadeStep = e - startFade;
if (index >= startFade) {
currentFadeStep = index - startFade;
eventWrapper.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
}
if (this.config.showLocation) {
if (event.location !== false) {
var locationRow = document.createElement("tr");
const locationRow = document.createElement("tr");
locationRow.className = "normal xsmall light";
if (this.config.displaySymbol) {
var symbolCell = document.createElement("td");
const symbolCell = document.createElement("td");
locationRow.appendChild(symbolCell);
}
var descCell = document.createElement("td");
const descCell = document.createElement("td");
descCell.className = "location";
descCell.colSpan = "2";
descCell.innerHTML = this.titleTransform(event.location, this.config.locationTitleReplace, this.config.wrapLocationEvents, this.config.maxLocationTitleLength, this.config.maxEventTitleLines);
@@ -408,13 +420,13 @@ Module.register("calendar", {
wrapper.appendChild(locationRow);
if (e >= startFade) {
currentFadeStep = e - startFade;
if (index >= startFade) {
currentFadeStep = index - startFade;
locationRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
}
}
}
}
});
return wrapper;
},
@@ -448,8 +460,7 @@ Module.register("calendar", {
* @returns {boolean} True if the calendar config contains the url, False otherwise
*/
hasCalendarURL: function (url) {
for (var c in this.config.calendars) {
var calendar = this.config.calendars[c];
for (const calendar of this.config.calendars) {
if (calendar.url === url) {
return true;
}
@@ -464,14 +475,15 @@ Module.register("calendar", {
* @returns {object[]} Array with events.
*/
createEventList: function () {
var events = [];
var today = moment().startOf("day");
var now = new Date();
var future = moment().startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
for (var c in this.calendarData) {
var calendar = this.calendarData[c];
for (var e in calendar) {
var event = JSON.parse(JSON.stringify(calendar[e])); // clone object
const now = new Date();
const today = moment().startOf("day");
const future = moment().startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
let events = [];
for (const calendarUrl in this.calendarData) {
const calendar = this.calendarData[calendarUrl];
for (const e in calendar) {
const event = JSON.parse(JSON.stringify(calendar[e])); // clone object
if (event.endDate < now) {
continue;
@@ -490,19 +502,19 @@ Module.register("calendar", {
if (this.listContainsEvent(events, event)) {
continue;
}
event.url = c;
event.url = calendarUrl;
event.today = event.startDate >= today && event.startDate < today + 24 * 60 * 60 * 1000;
/* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days,
* otherwise, esp. in dateheaders mode it is not clear how long these events are.
*/
var maxCount = Math.ceil((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / (1000 * 60 * 60 * 24)) + 1;
const maxCount = Math.ceil((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / (1000 * 60 * 60 * 24)) + 1;
if (this.config.sliceMultiDayEvents && maxCount > 1) {
var splitEvents = [];
var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x");
var count = 1;
const splitEvents = [];
let midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x");
let count = 1;
while (event.endDate > midnight) {
var thisEvent = JSON.parse(JSON.stringify(event)); // clone object
const thisEvent = JSON.parse(JSON.stringify(event)); // clone object
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < today + 24 * 60 * 60 * 1000;
thisEvent.endDate = midnight;
thisEvent.title += " (" + count + "/" + maxCount + ")";
@@ -516,9 +528,9 @@ Module.register("calendar", {
event.title += " (" + count + "/" + maxCount + ")";
splitEvents.push(event);
for (event of splitEvents) {
if (event.endDate > now && event.endDate <= future) {
events.push(event);
for (let splitEvent of splitEvents) {
if (splitEvent.endDate > now && splitEvent.endDate <= future) {
events.push(splitEvent);
}
}
} else {
@@ -534,12 +546,11 @@ Module.register("calendar", {
// Limit the number of days displayed
// If limitDays is set > 0, limit display to that number of days
if (this.config.limitDays > 0) {
var newEvents = [];
var lastDate = today.clone().subtract(1, "days").format("YYYYMMDD");
var days = 0;
var eventDate;
for (var ev of events) {
eventDate = moment(ev.startDate, "x").format("YYYYMMDD");
let newEvents = [];
let lastDate = today.clone().subtract(1, "days").format("YYYYMMDD");
let days = 0;
for (const ev of events) {
let eventDate = moment(ev.startDate, "x").format("YYYYMMDD");
// if date of event is later than lastdate
// check if we already are showing max unique days
if (eventDate > lastDate) {
@@ -563,7 +574,7 @@ Module.register("calendar", {
},
listContainsEvent: function (eventList, event) {
for (var evt of eventList) {
for (const evt of eventList) {
if (evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)) {
return true;
}
@@ -579,8 +590,6 @@ Module.register("calendar", {
* @param {object} calendarConfig The config of the specific calendar
*/
addCalendar: function (url, auth, calendarConfig) {
var self = this;
this.sendSocketNotification("ADD_CALENDAR", {
id: this.identifier,
url: url,
@@ -592,7 +601,8 @@ Module.register("calendar", {
titleClass: calendarConfig.titleClass,
timeClass: calendarConfig.timeClass,
auth: auth,
broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents
broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents,
selfSignedCert: calendarConfig.selfSignedCert || this.config.selfSignedCert
});
},
@@ -693,8 +703,7 @@ Module.register("calendar", {
* @returns {*} The property
*/
getCalendarProperty: function (url, property, defaultValue) {
for (var c in this.config.calendars) {
var calendar = this.config.calendars[c];
for (const calendar of this.config.calendars) {
if (calendar.url === url && calendar.hasOwnProperty(property)) {
return calendar[property];
}
@@ -728,13 +737,13 @@ Module.register("calendar", {
}
if (wrapEvents === true) {
var temp = "";
var currentLine = "";
var words = string.split(" ");
var line = 0;
const words = string.split(" ");
let temp = "";
let currentLine = "";
let line = 0;
for (var i = 0; i < words.length; i++) {
var word = words[i];
for (let i = 0; i < words.length; i++) {
const word = words[i];
if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) {
// max - 1 to account for a space
currentLine += word + " ";
@@ -789,10 +798,10 @@ Module.register("calendar", {
* @returns {string} The transformed title.
*/
titleTransform: function (title, titleReplace, wrapEvents, maxTitleLength, maxTitleLines) {
for (var needle in titleReplace) {
var replacement = titleReplace[needle];
for (let needle in titleReplace) {
const replacement = titleReplace[needle];
var regParts = needle.match(/^\/(.+)\/([gim]*)$/);
const regParts = needle.match(/^\/(.+)\/([gim]*)$/);
if (regParts) {
// the parsed pattern is a regexp.
needle = new RegExp(regParts[1], regParts[2]);
@@ -810,11 +819,10 @@ Module.register("calendar", {
* The all events available in one array, sorted on startdate.
*/
broadcastEvents: function () {
var eventList = [];
for (var url in this.calendarData) {
var calendar = this.calendarData[url];
for (var e in calendar) {
var event = cloneObject(calendar[e]);
const eventList = [];
for (const url in this.calendarData) {
for (const ev of this.calendarData[url]) {
const event = cloneObject(ev);
event.symbol = this.symbolsForEvent(event);
event.calendarName = this.calendarNameForUrl(url);
event.color = this.colorForUrl(url);

View File

@@ -4,17 +4,12 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
const Log = require("../../../js/logger.js");
const CalendarUtils = require("./calendarutils");
const Log = require("logger");
const ical = require("node-ical");
const request = require("request");
/**
* Moment date
*
* @external Moment
* @see {@link http://momentjs.com}
*/
const moment = require("moment");
const fetch = require("node-fetch");
const digest = require("digest-fetch");
const https = require("https");
/**
*
@@ -25,11 +20,10 @@ const moment = require("moment");
* @param {number} maximumNumberOfDays The maximum number of days an event should be in the future.
* @param {object} auth The object containing options for authentication against the calendar.
* @param {boolean} includePastEvents If true events from the past maximumNumberOfDays will be fetched too
* @param {boolean} selfSignedCert If true, the server certificate is not verified against the list of supplied CAs.
* @class
*/
const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) {
const self = this;
const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents, selfSignedCert) {
let reloadTimer = null;
let events = [];
@@ -39,492 +33,67 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
/**
* Initiates calendar fetch.
*/
const fetchCalendar = function () {
const fetchCalendar = () => {
clearTimeout(reloadTimer);
reloadTimer = null;
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
const opts = {
headers: {
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"
},
gzip: true
let fetcher = null;
let httpsAgent = null;
let headers = {
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"
};
if (selfSignedCert) {
httpsAgent = new https.Agent({
rejectUnauthorized: false
});
}
if (auth) {
if (auth.method === "bearer") {
opts.auth = {
bearer: auth.pass
};
headers.Authorization = "Bearer " + auth.pass;
} else if (auth.method === "digest") {
fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, httpsAgent: httpsAgent });
} else {
opts.auth = {
user: auth.user,
pass: auth.pass,
sendImmediately: auth.method !== "digest"
};
headers.Authorization = "Basic " + Buffer.from(auth.user + ":" + auth.pass).toString("base64");
}
}
if (fetcher === null) {
fetcher = fetch(url, { headers: headers, httpsAgent: httpsAgent });
}
request(url, opts, function (err, r, requestData) {
if (err) {
fetchFailedCallback(self, err);
fetcher
.catch((error) => {
fetchFailedCallback(this, error);
scheduleTimer();
return;
} else if (r.statusCode !== 200) {
fetchFailedCallback(self, r.statusCode + ": " + r.statusMessage);
})
.then((response) => {
if (response.status !== 200) {
fetchFailedCallback(this, response.statusText);
scheduleTimer();
}
return response;
})
.then((response) => response.text())
.then((responseData) => {
let data = [];
try {
data = ical.parseICS(responseData);
Log.debug("parsed data=" + JSON.stringify(data));
events = CalendarUtils.filterEvents(data, {
excludedEvents,
includePastEvents,
maximumEntries,
maximumNumberOfDays
});
} catch (error) {
fetchFailedCallback(this, error.message);
scheduleTimer();
return;
}
this.broadcastEvents();
scheduleTimer();
return;
}
let data = [];
try {
data = ical.parseICS(requestData);
} catch (error) {
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.
// Otherwise, the recurring events only show the birthday for next year.
let isFacebookBirthday = false;
if (typeof event.uid !== "undefined") {
if (event.uid.indexOf("@facebook.com") !== -1) {
isFacebookBirthday = true;
}
}
if (event.type === "VEVENT") {
let startDate = eventDate(event, "start");
let endDate;
Log.debug("\nevent=" + JSON.stringify(event));
if (typeof event.end !== "undefined") {
endDate = eventDate(event, "end");
} else if (typeof event.duration !== "undefined") {
endDate = startDate.clone().add(moment.duration(event.duration));
} else {
if (!isFacebookBirthday) {
// make copy of start date, separate storage area
endDate = moment(startDate.format("x"), "x");
} else {
endDate = moment(startDate).add(1, "days");
}
}
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 {
filter = filter.filterBy.toLowerCase();
}
} else {
filter = filter.toLowerCase();
}
if (testTitleByFilter(testTitle, filter, useRegex, regexFlags)) {
if (until) {
dateFilter = until;
} else {
excluded = true;
}
break;
}
}
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 full day event, only use the date part of the ranges
pastLocal = pastMoment.toDate();
futureLocal = futureMoment.toDate();
} else {
// if we want past events
if (includePastEvents) {
// use the calculated past time for the between from
pastLocal = pastMoment.toDate();
} else {
// 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));
}
}
}
// 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 (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);
let adjustDays = getCorrection(event, date);
// For each date that we're checking, it's possible that there is a recurrence override for that one day.
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"));
}
// 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
});
}
}
// 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;
}
// It's a fullday event, and it is before today, So skip.
if (fullDayEvent && endDate <= today) {
return;
}
}
// 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();
});
};
/*
*
* get the time correction, either dst/std or full day in cases where utc time is day before plus offset
*
*/
const getCorrection = function (event, date) {
let adjustHours = 0;
// if a timezone was specified
if (!event.start.tz) {
Log.debug(" if no tz, guess based on now");
event.start.tz = moment.tz.guess();
}
Log.debug("initial tz=" + event.start.tz);
// if there is a start date specified
if (event.start.tz) {
// if this is a windows timezone
if (event.start.tz.includes(" ")) {
// use the lookup table to get theIANA name as moment and date don't know MS timezones
let tz = getIanaTZFromMS(event.start.tz);
Log.debug("corrected TZ=" + tz);
// watch out for unregistered windows timezone names
// if we had a successfule lookup
if (tz) {
// change the timezone to the IANA name
event.start.tz = tz;
// Log.debug("corrected timezone="+event.start.tz)
}
}
Log.debug("corrected tz=" + event.start.tz);
let current_offset = 0; // offset from TZ string or calculated
let mm = 0; // date with tz or offset
let start_offset = 0; // utc offset of created with tz
// if there is still an offset, lookup failed, use it
if (event.start.tz.startsWith("(")) {
const regex = /[+|-]\d*:\d*/;
const start_offsetString = event.start.tz.match(regex).toString().split(":");
let start_offset = parseInt(start_offsetString[0]);
start_offset *= event.start.tz[1] === "-" ? -1 : 1;
adjustHours = start_offset;
Log.debug("defined offset=" + start_offset + " hours");
current_offset = start_offset;
event.start.tz = "";
Log.debug("ical offset=" + current_offset + " date=" + date);
mm = moment(date);
let x = parseInt(moment(new Date()).utcOffset());
Log.debug("net mins=" + (current_offset * 60 - x));
mm = mm.add(x - current_offset * 60, "minutes");
adjustHours = (current_offset * 60 - x) / 60;
event.start = mm.toDate();
Log.debug("adjusted date=" + event.start);
} else {
// get the start time in that timezone
Log.debug("start date/time=" + moment(event.start).toDate());
start_offset = moment.tz(moment(event.start), event.start.tz).utcOffset();
Log.debug("start offset=" + start_offset);
Log.debug("start date/time w tz =" + moment.tz(moment(event.start), event.start.tz).toDate());
// get the specified date in that timezone
mm = moment.tz(moment(date), event.start.tz);
Log.debug("event date=" + mm.toDate());
current_offset = mm.utcOffset();
}
Log.debug("event offset=" + current_offset + " hour=" + mm.format("H") + " event date=" + mm.toDate());
// if the offset is greater than 0, east of london
if (current_offset !== start_offset) {
// big offset
Log.debug("offset");
let h = parseInt(mm.format("H"));
// check if the event time is less than the offset
if (h > 0 && h < Math.abs(current_offset) / 60) {
// if so, rrule created a wrong date (utc day, oops, with utc yesterday adjusted time)
// we need to fix that
adjustHours = 24;
// Log.debug("adjusting date")
}
//-300 > -240
//if (Math.abs(current_offset) > Math.abs(start_offset)){
if (current_offset > start_offset) {
adjustHours -= 1;
Log.debug("adjust down 1 hour dst change");
//} else if (Math.abs(current_offset) < Math.abs(start_offset)) {
} else if (current_offset < start_offset) {
adjustHours += 1;
Log.debug("adjust up 1 hour dst change");
}
}
}
Log.debug("adjustHours=" + adjustHours);
return adjustHours;
};
/**
*
* lookup iana tz from windows
*/
let zoneTable = null;
const getIanaTZFromMS = function (msTZName) {
if (!zoneTable) {
const p = require("path");
zoneTable = require(p.join(__dirname, "windowsZones.json"));
}
// Get hash entry
const he = zoneTable[msTZName];
// If found return iana name, else null
return he ? he.iana[0] : null;
};
/**
@@ -537,82 +106,6 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
}, reloadInterval);
};
/**
* Checks if an event is a fullday event.
*
* @param {object} event The event object to check.
* @returns {boolean} True if the event is a fullday event, false otherwise
*/
const isFullDayEvent = function (event) {
if (event.start.length === 8 || event.start.dateOnly || event.datetype === "date") {
return true;
}
const start = event.start || 0;
const startDate = new Date(start);
const end = event.end || 0;
if ((end - start) % (24 * 60 * 60 * 1000) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
// Is 24 hours, and starts on the middle of the night.
return true;
}
return false;
};
/**
* Determines if the user defined time filter should apply
*
* @param {Date} now Date object using previously created object for consistency
* @param {Moment} endDate Moment object representing the event end date
* @param {string} filter The time to subtract from the end date to determine if an event should be shown
* @returns {boolean} True if the event should be filtered out, false otherwise
*/
const timeFilterApplies = function (now, endDate, filter) {
if (filter) {
const until = filter.split(" "),
value = parseInt(until[0]),
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 false;
};
/**
* Gets the title from the event.
*
* @param {object} event The event object to check.
* @returns {string} The title of the event, or "Event" if no title is found.
*/
const getTitleFromEvent = function (event) {
let title = "Event";
if (event.summary) {
title = typeof event.summary.val !== "undefined" ? event.summary.val : event.summary;
} else if (event.description) {
title = event.description;
}
return title;
};
const testTitleByFilter = function (title, filter, useRegex, regexFlags) {
if (useRegex) {
// Assume if leading slash, there is also trailing slash
if (filter[0] === "/") {
// Strip leading and trailing slashes
filter = filter.substr(1).slice(0, -1);
}
filter = new RegExp(filter, regexFlags);
return filter.test(title);
} else {
return title.includes(filter);
}
};
/* public methods */
/**
@@ -627,7 +120,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
*/
this.broadcastEvents = function () {
Log.info("Calendar-Fetcher: Broadcasting " + events.length + " events.");
eventsReceivedCallback(self);
eventsReceivedCallback(this);
};
/**

View File

@@ -0,0 +1,600 @@
/* Magic Mirror
* Calendar Util Methods
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
/**
* @external Moment
*/
const moment = require("moment");
const path = require("path");
const zoneTable = require(path.join(__dirname, "windowsZones.json"));
const Log = require("../../../js/logger.js");
const CalendarUtils = {
/**
* Calculate the time correction, either dst/std or full day in cases where
* utc time is day before plus offset
*
* @param {object} event
* @param {Date} date
* @returns {number} the necessary adjustment in hours
*/
calculateTimezoneAdjustment: function (event, date) {
let adjustHours = 0;
// if a timezone was specified
if (!event.start.tz) {
Log.debug(" if no tz, guess based on now");
event.start.tz = moment.tz.guess();
}
Log.debug("initial tz=" + event.start.tz);
// if there is a start date specified
if (event.start.tz) {
// if this is a windows timezone
if (event.start.tz.includes(" ")) {
// use the lookup table to get theIANA name as moment and date don't know MS timezones
let tz = CalendarUtils.getIanaTZFromMS(event.start.tz);
Log.debug("corrected TZ=" + tz);
// watch out for unregistered windows timezone names
// if we had a successful lookup
if (tz) {
// change the timezone to the IANA name
event.start.tz = tz;
// Log.debug("corrected timezone="+event.start.tz)
}
}
Log.debug("corrected tz=" + event.start.tz);
let current_offset = 0; // offset from TZ string or calculated
let mm = 0; // date with tz or offset
let start_offset = 0; // utc offset of created with tz
// if there is still an offset, lookup failed, use it
if (event.start.tz.startsWith("(")) {
const regex = /[+|-]\d*:\d*/;
const start_offsetString = event.start.tz.match(regex).toString().split(":");
let start_offset = parseInt(start_offsetString[0]);
start_offset *= event.start.tz[1] === "-" ? -1 : 1;
adjustHours = start_offset;
Log.debug("defined offset=" + start_offset + " hours");
current_offset = start_offset;
event.start.tz = "";
Log.debug("ical offset=" + current_offset + " date=" + date);
mm = moment(date);
let x = parseInt(moment(new Date()).utcOffset());
Log.debug("net mins=" + (current_offset * 60 - x));
mm = mm.add(x - current_offset * 60, "minutes");
adjustHours = (current_offset * 60 - x) / 60;
event.start = mm.toDate();
Log.debug("adjusted date=" + event.start);
} else {
// get the start time in that timezone
let es = moment(event.start);
// check for start date prior to start of daylight changing date
if (es.format("YYYY") < 2007) {
es.set("year", 2013); // if so, use a closer date
}
Log.debug("start date/time=" + es.toDate());
start_offset = moment.tz(es, event.start.tz).utcOffset();
Log.debug("start offset=" + start_offset);
Log.debug("start date/time w tz =" + moment.tz(moment(event.start), event.start.tz).toDate());
// get the specified date in that timezone
mm = moment.tz(moment(date), event.start.tz);
Log.debug("event date=" + mm.toDate());
current_offset = mm.utcOffset();
}
Log.debug("event offset=" + current_offset + " hour=" + mm.format("H") + " event date=" + mm.toDate());
// if the offset is greater than 0, east of london
if (current_offset !== start_offset) {
// big offset
Log.debug("offset");
let h = parseInt(mm.format("H"));
// check if the event time is less than the offset
if (h > 0 && h < Math.abs(current_offset) / 60) {
// if so, rrule created a wrong date (utc day, oops, with utc yesterday adjusted time)
// we need to fix that
adjustHours = 24;
// Log.debug("adjusting date")
}
//-300 > -240
//if (Math.abs(current_offset) > Math.abs(start_offset)){
if (current_offset > start_offset) {
adjustHours -= 1;
Log.debug("adjust down 1 hour dst change");
//} else if (Math.abs(current_offset) < Math.abs(start_offset)) {
} else if (current_offset < start_offset) {
adjustHours += 1;
Log.debug("adjust up 1 hour dst change");
}
}
}
Log.debug("adjustHours=" + adjustHours);
return adjustHours;
},
filterEvents: function (data, config) {
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 CalendarUtils.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(config.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 (config.includePastEvents) {
past = moment().startOf("day").subtract(config.maximumNumberOfDays, "days").toDate();
}
// FIXME: Ugly fix to solve the facebook birthday issue.
// Otherwise, the recurring events only show the birthday for next year.
let isFacebookBirthday = false;
if (typeof event.uid !== "undefined") {
if (event.uid.indexOf("@facebook.com") !== -1) {
isFacebookBirthday = true;
}
}
if (event.type === "VEVENT") {
let startDate = eventDate(event, "start");
let endDate;
Log.debug("\nevent=" + JSON.stringify(event));
if (typeof event.end !== "undefined") {
endDate = eventDate(event, "end");
} else if (typeof event.duration !== "undefined") {
endDate = startDate.clone().add(moment.duration(event.duration));
} else {
if (!isFacebookBirthday) {
// make copy of start date, separate storage area
endDate = moment(startDate.format("x"), "x");
} else {
endDate = moment(startDate).add(1, "days");
}
}
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 = CalendarUtils.getTitleFromEvent(event);
let excluded = false,
dateFilter = null;
for (let f in config.excludedEvents) {
let filter = config.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 {
filter = filter.filterBy.toLowerCase();
}
} else {
filter = filter.toLowerCase();
}
if (CalendarUtils.titleFilterApplies(testTitle, filter, useRegex, regexFlags)) {
if (until) {
dateFilter = until;
} else {
excluded = true;
}
break;
}
}
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 (CalendarUtils.isFullDayEvent(event)) {
// if full day event, only use the date part of the ranges
pastLocal = pastMoment.toDate();
futureLocal = futureMoment.toDate();
} else {
// if we want past events
if (config.includePastEvents) {
// use the calculated past time for the between from
pastLocal = pastMoment.toDate();
} else {
// otherwise use NOW.. cause we shouldn't 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));
}
}
}
// 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;
// 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 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 (CalendarUtils.isFullDayEvent(event)) {
Log.debug("fullday");
// if the offset is negative, east of GMT where the problem is
if (dateoffset < 0) {
// if the date hour is less than the offset
if (dh < Math.abs(dateoffset / 60)) {
// reduce the time by the offset
Log.debug(" recurring date is " + date + " offset is " + dateoffset);
// 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 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)) {
// 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);
}
}
}
} 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)) {
// reduce the time by the offset
Log.debug(" recurring date is " + date + " offset is " + dateoffset);
// 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 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)) {
// 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);
let adjustDays = CalendarUtils.calculateTimezoneAdjustment(event, date);
// For each date that we're checking, it's possible that there is a recurrence override for that one day.
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"));
}
// 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 = CalendarUtils.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 (CalendarUtils.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: CalendarUtils.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 : CalendarUtils.isFullDayEvent(event);
// Log.debug("full day event")
if (config.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;
}
// It's a fullday event, and it is before today, So skip.
if (fullDayEvent && endDate <= today) {
return;
}
}
// It exceeds the maximumNumberOfDays limit, so skip.
if (startDate > future) {
return;
}
if (CalendarUtils.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 = CalendarUtils.calculateTimezoneAdjustment(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();
let entries = 0;
let events = [];
for (let ne of newEvents) {
if (moment(ne.endDate, "x").isBefore(now)) {
if (config.includePastEvents) events.push(ne);
continue;
}
entries++;
// If max events has been saved, skip the rest
if (entries > config.maximumEntries) break;
events.push(ne);
}
return events;
},
/**
* Lookup iana tz from windows
*
* @param msTZName
* @returns {*|null}
*/
getIanaTZFromMS: function (msTZName) {
// Get hash entry
const he = zoneTable[msTZName];
// If found return iana name, else null
return he ? he.iana[0] : null;
},
/**
* Gets the title from the event.
*
* @param {object} event The event object to check.
* @returns {string} The title of the event, or "Event" if no title is found.
*/
getTitleFromEvent: function (event) {
let title = "Event";
if (event.summary) {
title = typeof event.summary.val !== "undefined" ? event.summary.val : event.summary;
} else if (event.description) {
title = event.description;
}
return title;
},
/**
* Checks if an event is a fullday event.
*
* @param {object} event The event object to check.
* @returns {boolean} True if the event is a fullday event, false otherwise
*/
isFullDayEvent: function (event) {
if (event.start.length === 8 || event.start.dateOnly || event.datetype === "date") {
return true;
}
const start = event.start || 0;
const startDate = new Date(start);
const end = event.end || 0;
if ((end - start) % (24 * 60 * 60 * 1000) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
// Is 24 hours, and starts on the middle of the night.
return true;
}
return false;
},
/**
* Determines if the user defined time filter should apply
*
* @param {Date} now Date object using previously created object for consistency
* @param {Moment} endDate Moment object representing the event end date
* @param {string} filter The time to subtract from the end date to determine if an event should be shown
* @returns {boolean} True if the event should be filtered out, false otherwise
*/
timeFilterApplies: function (now, endDate, filter) {
if (filter) {
const until = filter.split(" "),
value = parseInt(until[0]),
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 false;
},
/**
*
* @param title
* @param filter
* @param useRegex
* @param regexFlags
* @returns {boolean|*}
*/
titleFilterApplies: function (title, filter, useRegex, regexFlags) {
if (useRegex) {
// Assume if leading slash, there is also trailing slash
if (filter[0] === "/") {
// Strip leading and trailing slashes
filter = filter.substr(1).slice(0, -1);
}
filter = new RegExp(filter, regexFlags);
return filter.test(title);
} else {
return title.includes(filter);
}
}
};
if (typeof module !== "undefined") {
module.exports = CalendarUtils;
}

View File

@@ -5,9 +5,8 @@
* MIT Licensed.
*/
const NodeHelper = require("node_helper");
const validUrl = require("valid-url");
const CalendarFetcher = require("./calendarfetcher.js");
const Log = require("../../../js/logger");
const Log = require("logger");
module.exports = NodeHelper.create({
// Override start method.
@@ -19,7 +18,7 @@ module.exports = NodeHelper.create({
// Override socketNotificationReceived method.
socketNotificationReceived: function (notification, payload) {
if (notification === "ADD_CALENDAR") {
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.id);
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.selfSignedCert, payload.id);
}
},
@@ -34,44 +33,55 @@ module.exports = NodeHelper.create({
* @param {number} maximumNumberOfDays The maximum number of days an event should be in the future.
* @param {object} auth The object containing options for authentication against the calendar.
* @param {boolean} broadcastPastEvents If true events from the past maximumNumberOfDays will be included in event broadcasts
* @param {boolean} selfSignedCert If true, the server certificate is not verified against the list of supplied CAs.
* @param {string} identifier ID of the module
*/
createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, identifier) {
var self = this;
if (!validUrl.isUri(url)) {
self.sendSocketNotification("INCORRECT_URL", { id: identifier, url: url });
createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert, identifier) {
try {
new URL(url);
} catch (error) {
this.sendSocketNotification("INCORRECT_URL", { id: identifier, url: url });
return;
}
var fetcher;
if (typeof self.fetchers[identifier + url] === "undefined") {
let fetcher;
if (typeof this.fetchers[identifier + url] === "undefined") {
Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert);
fetcher.onReceive(function (fetcher) {
self.sendSocketNotification("CALENDAR_EVENTS", {
id: identifier,
url: fetcher.url(),
events: fetcher.events()
});
fetcher.onReceive((fetcher) => {
this.broadcastEvents(fetcher, identifier);
});
fetcher.onError(function (fetcher, error) {
fetcher.onError((fetcher, error) => {
Log.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error);
self.sendSocketNotification("FETCH_ERROR", {
this.sendSocketNotification("FETCH_ERROR", {
id: identifier,
url: fetcher.url(),
error: error
});
});
self.fetchers[identifier + url] = fetcher;
this.fetchers[identifier + url] = fetcher;
} else {
Log.log("Use existing calendar fetcher for url: " + url);
fetcher = self.fetchers[identifier + url];
fetcher = this.fetchers[identifier + url];
fetcher.broadcastEvents();
}
fetcher.startFetch();
},
/**
*
* @param {object} fetcher the fetcher associated with the calendar
* @param {string} identifier the identifier of the calendar
*/
broadcastEvents: function (fetcher, identifier) {
this.sendSocketNotification("CALENDAR_EVENTS", {
id: identifier,
url: fetcher.url(),
events: fetcher.events()
});
}
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,6 +44,11 @@ Module.register("newsfeed", {
return ["moment.js"];
},
//Define required styles.
getStyles: function () {
return ["newsfeed.css"];
},
// Define required translations.
getTranslations: function () {
// The translations for the default modules are defined in the core translation files.
@@ -61,6 +66,7 @@ Module.register("newsfeed", {
this.newsItems = [];
this.loaded = false;
this.error = null;
this.activeItem = 0;
this.scrollPosition = 0;
@@ -75,130 +81,62 @@ Module.register("newsfeed", {
this.generateFeed(payload);
if (!this.loaded) {
if (this.config.hideLoading) {
this.show();
}
this.scheduleUpdateInterval();
}
this.loaded = true;
this.error = null;
} else if (notification === "INCORRECT_URL") {
this.error = `Incorrect url: ${payload.url}`;
this.scheduleUpdateInterval();
}
},
// Override dom generator.
getDom: function () {
const wrapper = document.createElement("div");
//Override fetching of template name
getTemplate: function () {
if (this.config.feedUrl) {
wrapper.className = "small bright";
wrapper.innerHTML = this.translate("MODULE_CONFIG_CHANGED", { MODULE_NAME: "Newsfeed" });
return wrapper;
return "oldconfig.njk";
} else if (this.config.showFullArticle) {
return "fullarticle.njk";
}
return "newsfeed.njk";
},
//Override template data and return whats used for the current template
getTemplateData: function () {
// this.config.showFullArticle is a run-time configuration, triggered by optional notifications
if (this.config.showFullArticle) {
return {
url: this.getActiveItemURL()
};
}
if (this.error) {
return {
error: this.error
};
}
if (this.newsItems.length === 0) {
return {
loaded: false
};
}
if (this.activeItem >= this.newsItems.length) {
this.activeItem = 0;
}
if (this.newsItems.length > 0) {
// this.config.showFullArticle is a run-time configuration, triggered by optional notifications
if (!this.config.showFullArticle && (this.config.showSourceTitle || this.config.showPublishDate)) {
const sourceAndTimestamp = document.createElement("div");
sourceAndTimestamp.className = "newsfeed-source light small dimmed";
const item = this.newsItems[this.activeItem];
if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") {
sourceAndTimestamp.innerHTML = this.newsItems[this.activeItem].sourceTitle;
}
if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "" && this.config.showPublishDate) {
sourceAndTimestamp.innerHTML += ", ";
}
if (this.config.showPublishDate) {
sourceAndTimestamp.innerHTML += moment(new Date(this.newsItems[this.activeItem].pubdate)).fromNow();
}
if ((this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") || this.config.showPublishDate) {
sourceAndTimestamp.innerHTML += ":";
}
wrapper.appendChild(sourceAndTimestamp);
}
//Remove selected tags from the beginning of rss feed items (title or description)
if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") {
for (let f = 0; f < this.config.startTags.length; f++) {
if (this.newsItems[this.activeItem].title.slice(0, this.config.startTags[f].length) === this.config.startTags[f]) {
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(this.config.startTags[f].length, this.newsItems[this.activeItem].title.length);
}
}
}
if (this.config.removeStartTags === "description" || this.config.removeStartTags === "both") {
if (this.isShowingDescription) {
for (let f = 0; f < this.config.startTags.length; f++) {
if (this.newsItems[this.activeItem].description.slice(0, this.config.startTags[f].length) === this.config.startTags[f]) {
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(this.config.startTags[f].length, this.newsItems[this.activeItem].description.length);
}
}
}
}
//Remove selected tags from the end of rss feed items (title or description)
if (this.config.removeEndTags) {
for (let f = 0; f < this.config.endTags.length; f++) {
if (this.newsItems[this.activeItem].title.slice(-this.config.endTags[f].length) === this.config.endTags[f]) {
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(0, -this.config.endTags[f].length);
}
}
if (this.isShowingDescription) {
for (let f = 0; f < this.config.endTags.length; f++) {
if (this.newsItems[this.activeItem].description.slice(-this.config.endTags[f].length) === this.config.endTags[f]) {
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(0, -this.config.endTags[f].length);
}
}
}
}
if (!this.config.showFullArticle) {
const title = document.createElement("div");
title.className = "newsfeed-title bright medium light" + (!this.config.wrapTitle ? " no-wrap" : "");
title.innerHTML = this.newsItems[this.activeItem].title;
wrapper.appendChild(title);
}
if (this.isShowingDescription) {
const description = document.createElement("div");
description.className = "newsfeed-desc small light" + (!this.config.wrapDescription ? " no-wrap" : "");
const txtDesc = this.newsItems[this.activeItem].description;
description.innerHTML = this.config.truncDescription ? (txtDesc.length > this.config.lengthDescription ? txtDesc.substring(0, this.config.lengthDescription) + "..." : txtDesc) : txtDesc;
wrapper.appendChild(description);
}
if (this.config.showFullArticle) {
const fullArticle = document.createElement("iframe");
fullArticle.className = "";
fullArticle.style.width = "100vw";
// very large height value to allow scrolling
fullArticle.height = "3000";
fullArticle.style.height = "3000";
fullArticle.style.top = "0";
fullArticle.style.left = "0";
fullArticle.style.border = "none";
fullArticle.src = this.getActiveItemURL();
fullArticle.style.zIndex = 1;
wrapper.appendChild(fullArticle);
}
if (this.config.hideLoading) {
this.show();
}
} else {
if (this.config.hideLoading) {
this.hide();
} else {
wrapper.innerHTML = this.translate("LOADING");
wrapper.className = "small dimmed";
}
}
return wrapper;
return {
loaded: true,
config: this.config,
sourceTitle: item.sourceTitle,
publishDate: moment(new Date(item.pubdate)).fromNow(),
title: item.title,
description: item.description
};
},
getActiveItemURL: function () {
@@ -209,8 +147,7 @@ Module.register("newsfeed", {
* Registers the feeds to be used by the backend.
*/
registerFeeds: function () {
for (var f in this.config.feeds) {
var feed = this.config.feeds[f];
for (let feed of this.config.feeds) {
this.sendSocketNotification("ADD_FEED", {
feed: feed,
config: this.config
@@ -224,12 +161,11 @@ Module.register("newsfeed", {
* @param {object} feeds An object with feeds returned by the node helper.
*/
generateFeed: function (feeds) {
var newsItems = [];
for (var feed in feeds) {
var feedItems = feeds[feed];
let newsItems = [];
for (let feed in feeds) {
const feedItems = feeds[feed];
if (this.subscribedToFeed(feed)) {
for (var i in feedItems) {
var item = feedItems[i];
for (let item of feedItems) {
item.sourceTitle = this.titleForFeed(feed);
if (!(this.config.ignoreOldItems && Date.now() - new Date(item.pubdate) > this.config.ignoreOlderThan)) {
newsItems.push(item);
@@ -238,8 +174,8 @@ Module.register("newsfeed", {
}
}
newsItems.sort(function (a, b) {
var dateA = new Date(a.pubdate);
var dateB = new Date(b.pubdate);
const dateA = new Date(a.pubdate);
const dateB = new Date(b.pubdate);
return dateB - dateA;
});
if (this.config.maxNewsItems > 0) {
@@ -248,8 +184,8 @@ Module.register("newsfeed", {
if (this.config.prohibitedWords.length > 0) {
newsItems = newsItems.filter(function (value) {
for (var i = 0; i < this.config.prohibitedWords.length; i++) {
if (value["title"].toLowerCase().indexOf(this.config.prohibitedWords[i].toLowerCase()) > -1) {
for (let word of this.config.prohibitedWords) {
if (value["title"].toLowerCase().indexOf(word.toLowerCase()) > -1) {
return false;
}
}
@@ -257,8 +193,47 @@ Module.register("newsfeed", {
}, this);
}
newsItems.forEach((item) => {
//Remove selected tags from the beginning of rss feed items (title or description)
if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") {
for (let startTag of this.config.startTags) {
if (item.title.slice(0, startTag.length) === startTag) {
item.title = item.title.slice(startTag.length, item.title.length);
}
}
}
if (this.config.removeStartTags === "description" || this.config.removeStartTags === "both") {
if (this.isShowingDescription) {
for (let startTag of this.config.startTags) {
if (item.description.slice(0, startTag.length) === startTag) {
item.description = item.description.slice(startTag.length, item.description.length);
}
}
}
}
//Remove selected tags from the end of rss feed items (title or description)
if (this.config.removeEndTags) {
for (let endTag of this.config.endTags) {
if (item.title.slice(-endTag.length) === endTag) {
item.title = item.title.slice(0, -endTag.length);
}
}
if (this.isShowingDescription) {
for (let endTag of this.config.endTags) {
if (item.description.slice(-endTag.length) === endTag) {
item.description = item.description.slice(0, -endTag.length);
}
}
}
}
});
// get updated news items and broadcast them
var updatedItems = [];
const updatedItems = [];
newsItems.forEach((value) => {
if (this.newsItems.findIndex((value1) => value1 === value) === -1) {
// Add item to updated items list
@@ -281,8 +256,7 @@ Module.register("newsfeed", {
* @returns {boolean} True if it is subscribed, false otherwise
*/
subscribedToFeed: function (feedUrl) {
for (var f in this.config.feeds) {
var feed = this.config.feeds[f];
for (let feed of this.config.feeds) {
if (feed.url === feedUrl) {
return true;
}
@@ -297,8 +271,7 @@ Module.register("newsfeed", {
* @returns {string} The title of the feed
*/
titleForFeed: function (feedUrl) {
for (var f in this.config.feeds) {
var feed = this.config.feeds[f];
for (let feed of this.config.feeds) {
if (feed.url === feedUrl) {
return feed.title || "";
}
@@ -310,22 +283,20 @@ Module.register("newsfeed", {
* Schedule visual update.
*/
scheduleUpdateInterval: function () {
var self = this;
self.updateDom(self.config.animationSpeed);
this.updateDom(this.config.animationSpeed);
// Broadcast NewsFeed if needed
if (self.config.broadcastNewsFeeds) {
self.sendNotification("NEWS_FEED", { items: self.newsItems });
if (this.config.broadcastNewsFeeds) {
this.sendNotification("NEWS_FEED", { items: this.newsItems });
}
this.timer = setInterval(function () {
self.activeItem++;
self.updateDom(self.config.animationSpeed);
this.timer = setInterval(() => {
this.activeItem++;
this.updateDom(this.config.animationSpeed);
// Broadcast NewsFeed if needed
if (self.config.broadcastNewsFeeds) {
self.sendNotification("NEWS_FEED", { items: self.newsItems });
if (this.config.broadcastNewsFeeds) {
this.sendNotification("NEWS_FEED", { items: this.newsItems });
}
}, this.config.updateInterval);
},
@@ -335,8 +306,7 @@ Module.register("newsfeed", {
this.config.showFullArticle = false;
this.scrollPosition = 0;
// reset bottom bar alignment
document.getElementsByClassName("region bottom bar")[0].style.bottom = "0";
document.getElementsByClassName("region bottom bar")[0].style.top = "inherit";
document.getElementsByClassName("region bottom bar")[0].classList.remove("newsfeed-fullarticle");
if (!this.timer) {
this.scheduleUpdateInterval();
}
@@ -344,7 +314,9 @@ Module.register("newsfeed", {
notificationReceived: function (notification, payload, sender) {
const before = this.activeItem;
if (notification === "ARTICLE_NEXT") {
if (notification === "MODULE_DOM_CREATED" && this.config.hideLoading) {
this.hide();
} else if (notification === "ARTICLE_NEXT") {
this.activeItem++;
if (this.activeItem >= this.newsItems.length) {
this.activeItem = 0;
@@ -406,8 +378,7 @@ Module.register("newsfeed", {
this.config.showFullArticle = !this.isShowingDescription;
// make bottom bar align to top to allow scrolling
if (this.config.showFullArticle === true) {
document.getElementsByClassName("region bottom bar")[0].style.bottom = "inherit";
document.getElementsByClassName("region bottom bar")[0].style.top = "-90px";
document.getElementsByClassName("region bottom bar")[0].classList.add("newsfeed-fullarticle");
}
clearInterval(this.timer);
this.timer = null;

View File

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

View File

@@ -4,9 +4,9 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
const Log = require("../../../js/logger.js");
const Log = require("logger");
const FeedMe = require("feedme");
const request = require("request");
const fetch = require("node-fetch");
const iconv = require("iconv-lite");
/**
@@ -19,8 +19,6 @@ const iconv = require("iconv-lite");
* @class
*/
const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
const self = this;
let reloadTimer = null;
let items = [];
@@ -36,14 +34,14 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
/**
* Request the new items.
*/
const fetchNews = function () {
const fetchNews = () => {
clearTimeout(reloadTimer);
reloadTimer = null;
items = [];
const parser = new FeedMe();
parser.on("item", function (item) {
parser.on("item", (item) => {
const title = item.title;
let description = item.description || item.summary || item.content || "";
const pubdate = item.pubdate || item.published || item.updated || item["dc:date"];
@@ -68,33 +66,31 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
}
});
parser.on("end", function () {
self.broadcastItems();
parser.on("end", () => {
this.broadcastItems();
scheduleTimer();
});
parser.on("error", function (error) {
fetchFailedCallback(self, error);
parser.on("error", (error) => {
fetchFailedCallback(this, error);
scheduleTimer();
});
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
const opts = {
headers: {
"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",
Pragma: "no-cache"
},
encoding: null
const headers = {
"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",
Pragma: "no-cache"
};
request(url, opts)
.on("error", function (error) {
fetchFailedCallback(self, error);
fetch(url, { headers: headers })
.catch((error) => {
fetchFailedCallback(this, error);
scheduleTimer();
})
.pipe(iconv.decodeStream(encoding))
.pipe(parser);
.then((res) => {
res.body.pipe(iconv.decodeStream(encoding)).pipe(parser);
});
};
/**
@@ -136,7 +132,7 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
return;
}
Log.info("Newsfeed-Fetcher: Broadcasting " + items.length + " items.");
itemsReceivedCallback(self);
itemsReceivedCallback(this);
};
this.onReceive = function (callback) {

View File

@@ -6,9 +6,8 @@
*/
const NodeHelper = require("node_helper");
const validUrl = require("valid-url");
const NewsfeedFetcher = require("./newsfeedfetcher.js");
const Log = require("../../../js/logger");
const Log = require("logger");
module.exports = NodeHelper.create({
// Override start method.
@@ -36,8 +35,10 @@ module.exports = NodeHelper.create({
const encoding = feed.encoding || "UTF-8";
const reloadInterval = feed.reloadInterval || config.reloadInterval || 5 * 60 * 1000;
if (!validUrl.isUri(url)) {
this.sendSocketNotification("INCORRECT_URL", url);
try {
new URL(url);
} catch (error) {
this.sendSocketNotification("INCORRECT_URL", { url: url });
return;
}
@@ -73,8 +74,8 @@ module.exports = NodeHelper.create({
* and broadcasts these using sendSocketNotification.
*/
broadcastFeeds: function () {
var feeds = {};
for (var f in this.fetchers) {
const feeds = {};
for (let f in this.fetchers) {
feeds[f] = this.fetchers[f].items();
}
this.sendSocketNotification("NEWS_ITEMS", feeds);

View File

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

View File

@@ -3,7 +3,7 @@ const simpleGits = [];
const fs = require("fs");
const path = require("path");
const defaultModules = require(__dirname + "/../defaultmodules.js");
const Log = require(__dirname + "/../../../js/logger.js");
const Log = require("logger");
const NodeHelper = require("node_helper");
module.exports = NodeHelper.create({
@@ -14,32 +14,32 @@ module.exports = NodeHelper.create({
start: function () {},
configureModules: function (modules) {
configureModules: async function (modules) {
// Push MagicMirror itself , biggest chance it'll show up last in UI and isn't overwritten
// others will be added in front
// this method returns promises so we can't wait for every one to resolve before continuing
simpleGits.push({ module: "default", git: SimpleGit(path.normalize(__dirname + "/../../../")) });
simpleGits.push({ module: "default", git: this.createGit(path.normalize(__dirname + "/../../../")) });
var promises = [];
for (var moduleName in modules) {
for (let moduleName in modules) {
if (!this.ignoreUpdateChecking(moduleName)) {
// Default modules are included in the main MagicMirror repo
var moduleFolder = path.normalize(__dirname + "/../../" + moduleName);
let moduleFolder = path.normalize(__dirname + "/../../" + moduleName);
try {
Log.info("Checking git for module: " + moduleName);
let stat = fs.statSync(path.join(moduleFolder, ".git"));
promises.push(this.resolveRemote(moduleName, moduleFolder));
// Throws error if file doesn't exist
fs.statSync(path.join(moduleFolder, ".git"));
// Fetch the git or throw error if no remotes
let git = await this.resolveRemote(moduleFolder);
// Folder has .git and has at least one git remote, watch this folder
simpleGits.unshift({ module: moduleName, git: git });
} catch (err) {
// Error when directory .git doesn't exist
// Error when directory .git doesn't exist or doesn't have any remotes
// This module is not managed with git, skip
continue;
}
}
}
return Promise.all(promises);
},
socketNotificationReceived: function (notification, payload) {
@@ -54,36 +54,36 @@ module.exports = NodeHelper.create({
}
},
resolveRemote: function (moduleName, moduleFolder) {
return new Promise((resolve, reject) => {
var git = SimpleGit(moduleFolder);
git.getRemotes(true, (err, remotes) => {
if (remotes.length < 1 || remotes[0].name.length < 1) {
// No valid remote for folder, skip
return resolve();
}
// Folder has .git and has at least one git remote, watch this folder
simpleGits.unshift({ module: moduleName, git: git });
resolve();
});
});
resolveRemote: async function (moduleFolder) {
let git = this.createGit(moduleFolder);
let remotes = await git.getRemotes(true);
if (remotes.length < 1 || remotes[0].name.length < 1) {
throw new Error("No valid remote for folder " + moduleFolder);
}
return git;
},
performFetch: function () {
var self = this;
simpleGits.forEach((sg) => {
sg.git.fetch(["--dry-run"]).status((err, data) => {
data.module = sg.module;
if (!err) {
sg.git.log({ "-1": null }, (err, data2) => {
if (!err && data2.latest && "hash" in data2.latest) {
data.hash = data2.latest.hash;
self.sendSocketNotification("STATUS", data);
}
performFetch: async function () {
for (let sg of simpleGits) {
try {
let fetchData = await sg.git.fetch(["--dry-run"]).status();
let logData = await sg.git.log({ "-1": null });
if (logData.latest && "hash" in logData.latest) {
this.sendSocketNotification("STATUS", {
module: sg.module,
behind: fetchData.behind,
current: fetchData.current,
hash: logData.latest.hash,
tracking: fetchData.tracking
});
}
});
});
} catch (err) {
Log.error("Failed to fetch git data for " + sg.module + ": " + err);
}
}
this.scheduleNextFetch(this.config.updateInterval);
},
@@ -93,13 +93,17 @@ module.exports = NodeHelper.create({
delay = 60 * 1000;
}
var self = this;
let self = this;
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(function () {
self.performFetch();
}, delay);
},
createGit: function (folder) {
return SimpleGit({ baseDir: folder, timeout: { block: this.config.timeout } });
},
ignoreUpdateChecking: function (moduleName) {
// Should not check for updates for default modules
if (defaultModules.indexOf(moduleName) >= 0) {

View File

@@ -8,7 +8,8 @@ Module.register("updatenotification", {
defaults: {
updateInterval: 10 * 60 * 1000, // every 10 minutes
refreshInterval: 24 * 60 * 60 * 1000, // one day
ignoreModules: []
ignoreModules: [],
timeout: 1000
},
suspended: false,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

6961
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
@@ -13,7 +12,8 @@ var config = {
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true
nodeIntegration: true,
enableRemoteModule: true
}
},

View File

@@ -0,0 +1,38 @@
/* Magic Mirror Test config newsfeed module
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [
{
module: "newsfeed",
position: "bottom_bar",
config: {
feeds: [
{
title: "Incorrect Url",
url: "this is not a valid url"
}
]
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}

View File

@@ -0,0 +1,39 @@
/* Magic Mirror Test config newsfeed module
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [
{
module: "newsfeed",
position: "bottom_bar",
config: {
feeds: [
{
title: "Rodrigo Ramirez Blog",
url: "http://localhost:8080/tests/configs/data/feed_test_rodrigoramirez.xml"
}
],
prohibitedWords: ["QPanel"]
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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