diff --git a/MagicMirror/.DS_Store b/MagicMirror/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ad7ce5f369ff7ac8f8811bfdaf4d443f944a78b8 Binary files /dev/null and b/MagicMirror/.DS_Store differ diff --git a/MagicMirror/.editorconfig b/MagicMirror/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..db01ad9e903484a7a7c81fcac9b339808baa5f8a --- /dev/null +++ b/MagicMirror/.editorconfig @@ -0,0 +1,15 @@ +# editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 250 +trim_trailing_whitespace = true + +[*.{js,json}] +indent_size = 4 +indent_style = tab diff --git a/MagicMirror/.eslintignore b/MagicMirror/.eslintignore new file mode 100644 index 0000000000000000000000000000000000000000..fa7668825af84b4905998ab93ab2710858dae356 --- /dev/null +++ b/MagicMirror/.eslintignore @@ -0,0 +1 @@ +modules/default/calendar/vendor/* diff --git a/MagicMirror/.eslintrc.json b/MagicMirror/.eslintrc.json new file mode 100644 index 0000000000000000000000000000000000000000..c1bf1cb51be0af14bae4223f9091444a82d1ab09 --- /dev/null +++ b/MagicMirror/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": ["eslint:recommended", "plugin:prettier/recommended", "plugin:jsdoc/recommended"], + "plugins": ["prettier", "import", "jsdoc", "jest"], + "env": { + "browser": true, + "es2022": true, + "jest/globals": true, + "node": true + }, + "globals": { + "config": true, + "Log": true, + "MM": true, + "Module": true, + "moment": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 2022, + "ecmaFeatures": { + "globalReturn": true + } + }, + "rules": { + "eqeqeq": "error", + "import/order": "error", + "no-prototype-builtins": "off", + "no-throw-literal": "error", + "no-unused-vars": "off", + "no-useless-return": "error", + "prefer-template": "error" + } +} diff --git a/MagicMirror/.husky/pre-commit b/MagicMirror/.husky/pre-commit new file mode 100755 index 0000000000000000000000000000000000000000..7ee201c6c705b12e4342e0a8bead263293e43a24 --- /dev/null +++ b/MagicMirror/.husky/pre-commit @@ -0,0 +1,7 @@ +#!/bin/sh + +[ -f "$(dirname "$0")/_/husky.sh" ] && . "$(dirname "$0")/_/husky.sh" + +if command -v npm &> /dev/null; then + npm run lint:staged +fi diff --git a/MagicMirror/.prettierignore b/MagicMirror/.prettierignore new file mode 100644 index 0000000000000000000000000000000000000000..b92f216692577011c955b27fdf057da35c28f1f0 --- /dev/null +++ b/MagicMirror/.prettierignore @@ -0,0 +1,3 @@ +/config +/coverage +package-lock.json diff --git a/MagicMirror/.prettierrc.json b/MagicMirror/.prettierrc.json new file mode 100644 index 0000000000000000000000000000000000000000..1518304d4c1c15ac4d8a471d2db7c0fade63c1a0 --- /dev/null +++ b/MagicMirror/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "trailingComma": "none" +} diff --git a/MagicMirror/.stylelintrc.json b/MagicMirror/.stylelintrc.json new file mode 100644 index 0000000000000000000000000000000000000000..18ff23e703e0ec3f2de24abb5431bdb66551ebed --- /dev/null +++ b/MagicMirror/.stylelintrc.json @@ -0,0 +1,7 @@ +{ + "extends": ["stylelint-config-standard"], + "plugins": ["stylelint-prettier"], + "rules": { + "prettier/prettier": true + } +} diff --git a/MagicMirror/CHANGELOG.md b/MagicMirror/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8b565fab085e7f77288629898220c9de11d5b8ac --- /dev/null +++ b/MagicMirror/CHANGELOG.md @@ -0,0 +1,1322 @@ +# MagicMirror² Change Log + +All notable changes to this project will be documented in this file. +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.23.0] - 2023-04-04 + +Thanks to: @angeldeejay, @buxxi, @CarJem, @dariom, @DaveChild, @dWoolridge, @grenagit, @Hirschberger, @KristjanESPERANTO, @MagMar94, @naveensrinivasan, @nfogal, @psieg, @rajniszp, @retroflex, @SkySails and @tomzt. + +Special thanks to @khassel, @rejas and @sdetweil for taking over most (if not all) of the work on this release as project collaborators. This version would not be there without their effort. Thank you guys! You are awesome! + +### Added + +- Added increments for hourly forecasts in weather module (#2996) +- Added tests for hourly weather forecast +- Added possibility to ignore MagicMirror repo in updatenotification module +- Added Pirate Weather as new weather provider (#3005) +- Added possibility to use your own templates in Alert module +- Added error message if `<modulename>.js` file is missing in module folder to get a hint in the logs (#2403) +- Added possibility to use environment variables in `config.js` (#1756) +- Added option `pastDaysCount` to default calendar module to control of how many days past events should be displayed +- Added thai language to alert module +- Added option `sendNotifications` in clock module (#3056) + +### Removed + +- Removed darksky weather provider +- Removed unneeded (and unwanted) '.' after the year in calendar repeatingCountTitle (#2896) + +### Updated + +- Use develop as target branch for dependabot +- Update issue template, contributing doc and sample config +- The weather modules clearly separates precipitation amount and probability (risk of rain/snow) + - This requires all providers that only supports probability to change the config from `showPrecipitationAmount` to `showPrecipitationProbability`. +- Update tests for weather and calendar module +- Changed updatenotification module for MagicMirror repo only: Send only notifications for `master` if there is a tag on a newer commit +- Update dates in Calendar widgets every minute +- Cleanup jest coverage for patches +- Update `stylelint` dependencies, switch to `stylelint-config-standard` and handle `stylelint` issues, update `main.css` matching new rules +- Update Eslint config, add new rule and handle issue +- Convert lots of callbacks to async/await +- Revise require imports (#3071 and #3072) + +### Fixed + +- Fix wrong day labels in envcanada forecast (#2987) +- Fix for missing default class name prefix for customEvents in calendar +- Fix electron flashing white screen on startup (#1919) +- Fix weathergov provider hourly forecast (#3008) +- Fix message display with HTML code into alert module (#2828) +- Fix typo in french translation +- Yr wind direction is no longer inverted +- Fix async node_helper stopping electron start (#2487) +- The wind direction arrow now points in the direction the wind is flowing, not into the wind (#3019) +- Fix precipitation css styles and rounding value +- Fix wrong vertical alignment of calendar title column when wrapEvents is true (#3053) +- Fix empty news feed stopping the reload forever +- Fix e2e tests (failed after async changes) by running calendar and newsfeed tests last +- Lint: Use template literals instead of string concatenation +- Fix default alert module to render HTML for title and message +- Fix Open-Meteo wind speed units + +## [2.22.0] - 2023-01-01 + +Thanks to: @angeldeejay, @buxxi, @dariom, @dWoolridge, @KristjanESPERANTO, @MagMar94, @naveensrinivasan, @retroflex, @SkySails and @Tom. + +Special thanks to @khassel, @rejas and @sdetweil for taking over most (if not all) of the work on this release as project collaborators. This version would not be there without their effort. Thank you! + +### Added + +- Added new calendar options for colored entries and improved styling (#3033) +- Added test for remoteFile option in compliments module +- Added hourlyWeather functionality to Weather.gov weather provider +- Added css class names "today" and "tomorrow" for default calendar +- Added Collaboration.md +- Added new github action for dependency review (#2862) +- Added a WeatherProvider for Open-Meteo +- Added Yr as a weather provider +- Added config options "ignoreXOriginHeader" and "ignoreContentSecurityPolicy" +- Added thai language +- Added workflow rule to make sure PRs are based against develop + +### Removed + +- Removed usage of internal fetch function of node until it is more stable +- Removed weatherEndpoint definition from weathergov.js (not used) + +### Updated + +- Cleaned up test directory (#2937) and jest config (#2959) +- Wait for all modules to start before declaring the system ready (#2487) +- Updated e2e tests (moved `done()` in helper functions) and use es6 syntax in all tests +- Updated da translation +- Rework weather module + - Make sure smhi provider api only gets a maximum of 6 digits coordinates (#2955) + - Use fetch instead of XMLHttpRequest in weatherprovider (#2935) + - Reworked how weatherproviders handle units (#2849) + - Use unix() method for parsing times, fix suntimes on the way (#2950) + - Refactor conversion functions into utils class (#2958) +- The `cors`-method in `server.js` now supports sending and receiving HTTP headers +- Replace `…` by `…` +- Cleanup compliments module +- Updated dependencies including electron to v22 (#2903) + +### Fixed + +- Correctly show apparent temperature in SMHI weather provider +- Ensure updatenotification module isn't shown when local is _ahead_ of remote +- Handle node_helper errors during startup (#2944) +- Possibility to change FontAwesome class in calendar, so icons like `fab fa-facebook-square` works. +- Fix cors problems with newsfeed articles (as far as possible), allow disabling cors per feed with option `useCorsProxy: false` (#2840) +- Tests not waiting for the application to start and stop before starting the next test +- Fix electron tests failing sometimes in github workflow +- Fixed gap in clock module when displayed on the left side with displayType=digital +- Fixed playwright issue by upgrading to v1.29.1 (#2969) + +## [2.21.0] - 2022-10-01 + +Special thanks to: @BKeyport, @buxxi, @davide125, @khassel, @kolbyjack, @krukle, @MikeBishop, @rejas, @sdetweil, @SkySails and @veeck + +### Added + +- Added possibility to fetch calendars through socket notifications. +- New scripts `install-mm` (and `install-mm:dev`) for simplifying mm installation (now: `npm run install-mm`) and adding params `--no-audit --no-fund --no-update-notifier` for less noise. +- New `showTimeToday` option in calendar module shows time for current-day events even if `timeFormat` is `"relative"`. +- Added hourly forecasts, apparent temperature & custom location name to SMHI weather provider. +- Added new electron tests for calendar and moved some compliments tests from `e2e` to `electron` because of date mocking, removed mock stuff from compliments module. + +### Removed + +- Removed old and deprecated weather modules `currentweather` and `weatherforecast`. +- Removed `DAYAFTERTOMORROW` from English. + +### Updated + +- Updated dependencies. +- Updated jsdoc. +- Updated font tree to use variables consistently. +- Removed deprecated Docker Repository from issue template. + +### Fixed + +- Broadcast all calendar events while still honoring global and per-calendar maximumEntries. +- Respect rss ttl provided by newsfeed (#2883). +- Fix multi day calendar events always presented as "(1/X)" instead of the amount of days the event has progressed. +- Fix weatherbit provider to use type config value instead of endpoint. +- Fix calendar events which DO NOT specify rrule byday adjusted incorrectly (#2885). +- Fix e2e tests not failing on errors (#2911). + +## [2.20.0] - 2022-07-02 + +Special thanks to the following contributors: @eouia, @khassel, @kolbyjack, @KristjanESPERANTO, @nathannaveen, @naveensrinivasan, @rejas, @rohitdharavath and @sdetweil. + +### Added + +- Added a new config option `httpHeaders` used by helmet (see https://helmetjs.github.io/). You can now set own httpHeaders which will override the defaults in `js/defauls.js` which is useful e.g. if you want to embed MagicMirror into annother website (solves #2847). +- Show endDate for calendar events when dateHeader is enabled and showEnd is set to true (#2192). +- Added the notification emitting from the weather module on information updated. +- Use recommended file extension for YAML files (#2864). + +### Updated + +- Use latest node 18 when running tests on github actions. +- Updated `electron` to v19 and other dependencies. +- Use internal fetch function of node instead external `node-fetch` library if used node version >= `v18`. +- Include duplicate events in broadcasts. + +### Fixed + +- Fix problems with non latin fonds caused by updating to fontsource (fixes #2835). + +## [2.19.0] - 2022-04-01 + +Special thanks to the following contributors: @10bias, @CFenner, @JHWelch, @k1rd3rf, @khassel, @kolbyjack, @krekos, @KristjanESPERANTO, @Nerfzooka, @oraclesean, @oscarb, @philnagel, @rejas, @sdetweil, @shin10, @SiderealArt and @Tom-Hirschberger. + +### Added + +- Added a config option under the weather module, `absoluteDates`, providing an option to format weather forecast date output with either absolute or relative dates. +- Added test for new weather forecast `absoluteDates` property. +- The modules get a class hidden added/removed if they get hidden/shown which will also toggle pointer-events. +- Added new config option `showTitleAsUrl` to newsfeed module. If set, the displayed title is a link to the article which is useful when running in a browser and you want to read this article. +- Added internal cors proxy to get weather providers working without public proxies (fixes #2714). The new url `http(s)://address:port/cors?url=https://whatever-to-proxy` can be used in other modules too. +- Added a WeatherProvider for Weatherflow. +- Added new env var `ELECTRON_DISABLE_GPU` which disable gpu under electron if set (fixes #2831). +- Added missing Czech translations. + +### Updated + +- Deprecated roboto fonts package `roboto-fontface-bower` replaced with `fontsource`. +- Updated `electron` to v17, `helmet` to v5 (use defaults of v4) and other dependencies +- Updated Font Awesome css class to new default style (fixes #2768) +- Replaced deprecated modules `currentweather` and `weatherforecast` with dummy modules only displaying that they have to be replaced. +- Include all calendar events from the configured date range when broadcasting. +- Updated Danish and German translation. +- Updated `node-ical` to v0.15 and added `luxon` as dependency for not breaking the "no-optional" install (see #2718 and #2824). + +### Fixed + +- Improved and speedup e2e tests, artificial wait after mm start removed. +- Improved husky setup not blocking `git commit` if `husky` or `npm` is not installed. +- Using a consistent spelling of MagicMirror². +- Fix minor console output issue for loading translations (#2814). +- Don't adjust startDate for full day events if endDate is in the past. +- Fix windspeed conversion error in openweathermap provider. (#2812) +- Fix conflicting parms turning off showEnd for full day events. (#2629) +- Fix regression, calendar.maximumEntries not used to filter calendar level entries (#2868) + +## [2.18.0] - 2022-01-01 + +Special thanks to the following contributors: @AmpioRosso, @eouia, @fewieden, @jupadin, @khassel, @kolbyjack, @KristjanESPERANTO, @MariusVaice, @rejas, @rico24 and @sdetweil. + +### Added + +- Added test for calendar recurring event with checks the correct date displayed (related to #2752). + +### Updated + +- ESLint version supports now ECMAScript 2018. +- Cleaned up `updatenotification` module and switched to nunjuck template. +- Moved calendar tests from category `electron` to `e2e`. +- Updated missed translations for Korean language (ko.json). +- Updated missed translations for Dutch language (nl.json). +- Cleaned up `alert` module and switched to nunjuck template. +- Moved weather tests from category `electron` to `e2e`. +- Updated github actions. +- Replace spectron with playwright, update dependencies including electron update to v16. +- Added lithuanian language to translations.js. +- Show info message if newsfeed is empty (fixes #2731). +- Added dangerouslyDisableAutoEscaping config option for newsfeed templates (fixes #2712). +- Added missing shebang to `installers/mm.sh`. +- Node versions in templates and github workflows. +- Updated translations for Traditional Chinese (Taiwan) (zh-tw.json). + +### Fixed + +- Fixed wrong file `kr.json` to `ko.json`. Use language code 'ko' instead of 'kr' for Korean language. +- Fixed `feels_like` data from openweathermaps current weather being ignored (#2678). +- Fixed chaotic newsfeed display after network connection loss thanks to @jalibu (#2638). +- Fixed incorrect time zone correction of recurring full day events (#2632 and #2634). +- Fixed e2e tests by increasing testTimeout. +- Revert node-ical update due to missing luxon package. +- Fixed User-Agent-Header for newsfeed and calendar module (#2729). +- Replace broken shields in Readme and use https for links. +- Fixed electron tests with retry. +- Fixed Calendar recurring cross timezone error (add/subtract a day, not just offset hours) (#2632). +- Fixed Calendar showEnd and Full Date overlay (#2629). +- Fixed regression on #2632, #2752. +- Broadcast custom symbols in CALENDAR_EVENTS. + +## [2.17.1] - 2021-10-01 + +### Fixed + +- Fixed error when accessing letsencrypt certificates +- Fixed Calendar module enhancement: displaying full events without time (#2424) + +## [2.17.0] - 2021-10-01 + +Special thanks to the following contributors: @apiontek, @eouia, @jupadin, @khassel and @rejas. + +### Added + +- Added showTime parameter to clock module for enabling/disabling time display in analog clock. +- Added custom electron switches from user config (`config.electronSwitches`). +- Added unit tests for updatenotification module. + +### Updated + +- Bump electron to v13 (and spectron to v15) and update other dependencies in package.json. +- Refactor test configs, use default test config for all tests. +- Updated github templates. +- Actually test all js and css files when lint script is run. +- Updated jsdocs and print warnings during testing too. +- Updated weathergov provider to try fetching not just current, but also foreacst, when API URLs available. +- Refactored clock layout. +- Refactored methods from weatherproviders into weatherobject (isDaytime, updateSunTime). +- Use of `logger.js` in jest tests. +- Run prettier over all relevant files. +- Move tests needing electron in new category `electron`, use `server only` mode in `e2e` tests. +- Updated dependencies in package.json. + +### Fixed + +- Fix undefined error with ignoreToday option in weather module (#2620). +- Fix time zone correction in calendar module when the date hour is equal to the time zone correction value (#2632). +- Fix black cursor on startup when using electron. +- Fix update notification not working for own repository (#2644). + +## [2.16.0] - 2021-07-01 + +Special thanks to the following contributors: @210954, @B1gG, @codac, @Crazylegstoo, @daniel, @earlman, @ezeholz, @FrancoisRmn, @jupadin, @khassel, @KristjanESPERANTO, @njwilliams, @oemel09, @r3wald, @rejas, @rico24, Faizan Ahmed. + +### Added + +- Added French translations for "MODULE_CONFIG_ERROR" and "PRECIP". +- Added German translation for "PRECIP". +- Added Dutch translation for "WEEK", "PRECIP", "MODULE_CONFIG_CHANGED" and "MODULE_CONFIG_ERROR". +- Added first test for Alert module. +- Added support for `dateFormat` when not using `timeFormat: "absolute"`. +- Added custom-properties for colors and fonts for improved styling experience, see `custom.css.sample` file. +- Added custom-properties for gaps around body and between modules. +- Added test case for recurring calendar events. +- Added new Environment Canada provider for default WEATHER module (weather data for Canadian locations only). +- Added list view for newsfeed module. +- Added dev dependency jest, switching from mocha to jest. + +### Updated + +- Bump node-ical to v0.13.0 (now last runtime dependency using deprecated `request` package is removed). +- Use codecov in informational mode. +- Refactor code into es6 where possible (e.g. var -> let/const). +- Use node v16 in github workflow (replacing node v10). +- Moved some files into better suited directories. +- Updated dependencies in package.json, require node >= v12, remove `rrule-alt` and `rrule`. +- Updated dependencies in package.json and migrate husky to v6, fix husky setup in prod environment. +- Cleaned up error handling in newsfeed and calendar modules for real. +- Updated default WEATHER module such that a provider can optionally set a custom unit-of-measure for precipitation (`weatherObject.precipitationUnits`). +- Updated documentation. +- Updated jest tests: Reset changes on js/logger.js, mock logger.js in global_vars tests. +- Updated dependencies in package.json. + +### Removed + +- Switching from mocha to jest so removed following dev dependencies: chai, chai-as-promised, mocha, mocha-each, mocha-logger. + +### Fixed + +- Fix calendar start function logging inconsistency. +- Fix updatenotification start function logging inconsistency. +- Checks and applies the showDescription setting for the newsfeed module again. +- Fix issue with openweathermap not showing current or forecast info when using onecall API. +- Fix tests in weather module and add one for decimalPoint in forecast. +- Fix decimalSymbol in the forecast part of the new weather module (#2530). +- Fix wrong treatment of `appendLocationNameToHeader` when using `ukmetofficedatahub`. +- Fix alert not recognizing multiple alerts (#2522). +- Fix fetch option httpsAgent to agent in calendar module (#466). +- Fix module updatenotification which did not work for repos with many refs (#1907). +- Fix config check failing when encountering let syntax ("Parsing error: Unexpected token config"). +- Fix calendar debug check. +- Really run prettier over all files. +- Fix logger.js after jest changes, use --forceExit running jest. +- Workaround for dev_console test using getWindowCount. + +## [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 …` 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. + +â„¹ï¸ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. + +### Added + +- Added new log level "debug" to the logger. +- Added new parameter "useKmh" to weather module for displaying wind speed as kmh. +- 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. +- 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. + +### Updated + +- Merging .gitignore in the config-folder with the .gitignore in the root-folder. +- Weather module - forecast now show TODAY and TOMORROW instead of weekday, to make it easier to understand. +- Updated dependencies to latest versions. +- Updated dependencies eslint, feedme, simple-git and socket.io to latest versions. +- Updated lithuanian translation. +- Updated config sample. +- Highlight required version mismatch. +- No select Text for TouchScreen use. +- Corrected logic for timeFormat "relative" and "absolute". +- Added missing function call in module.show() +- Translator variables can have falsy values (e.g. empty string) +- Fix issue with weather module with DEGREE label in FEELS like + +### Deleted + +- Removed Travis CI integration. + +### Fixed + +- JSON Parse translation files with comments crashing UI. (#2149) +- Calendar parsing where RRULE bug returns wrong date, add Windows timezone name support. (#2145, #2151) +- Wrong node-ical version installed (package.json) requested version. (#2153) +- Fix calendar fetcher subsequent timing. (#2160) +- Rename Greek translation to correct ISO 639-1 alpha-2 code (gr > el). (#2155) +- Add a space after icons of sunrise and sunset. (#2169) +- Fix calendar when no DTEND record found in event, startDate overlay when endDate set. (#2177) +- Fix windspeed conversion error in ukmetoffice weather provider. (#2189) +- Fix console.debug not having timestamps. (#2199) +- Fix calendar full day event east of UTC start time. (#2200) +- 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 than English. (#2221) +- Updated node-ical 0.12.4, fix invalid RRULE format in cal entries +- Fix package.json for optional electron dependency (2378) +- Updated node-ical version again, 0.12.5, change RRULE fix (#2371, #2379) +- Remove undefined objects from modules array (#2382) +- Updated node-ical version again, 0.12.7, change RRULE fix (#2371, #2379), node-ical now throws error (which we catch) +- Updated simple-git version to 2.31 unhandled promise rejection (#2383) + +## [2.13.0] - 2020-10-01 + +Special thanks to the following contributors: @bryanzzhu, @bugsounet, @chamakura, @cjbrunner, @easyas314, @larryare, @oemel09, @rejas, @sdetweil & @sthuber90. + +â„¹ï¸ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. + +### Added + +- `--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`. +- 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. +- Added eslint-plugin for jsdoc comments. +- Added new configDeepMerge option for module developers. + +### Updated + +- Change incorrect weather.js default properties. +- Cleaned up newsfeed module. +- Cleaned up jsdoc comments. +- Cleaned up clock tests. +- Move lodash into devDependencies, update other dependencies. +- Switch from ical to node-ical library. + +### Fixed + +- Fix backward compatibility issues for Safari < 11. +- Fix the use of "maxNumberOfDays" in the module "weatherforecast depending on the endpoint (forecast/daily or forecast)". [#2018](https://github.com/MichMich/MagicMirror/issues/2018) +- Fix calendar display. Account for current timezone. [#2068](https://github.com/MichMich/MagicMirror/issues/2068) +- Fix logLevel being set before loading config. +- Fix incorrect namespace links in svg clockfaces. [#2072](https://github.com/MichMich/MagicMirror/issues/2072) +- Fix weather/providers/weathergov for API guidelines. [#2045](https://github.com/MichMich/MagicMirror/issues/2045) +- Fix "undefined" in weather modules header. [#1985](https://github.com/MichMich/MagicMirror/issues/1985) +- Fix #2110, #2111, #2118: Recurring full day events should not use timezone adjustment. Just compare month/day. + +## [2.12.0] - 2020-07-01 + +Special thanks to the following contributors: @AndreKoepke, @andrezibaia, @bryanzzhu, @chamakura, @DarthBrento, @Ekristoffe, @khassel, @Legion2, @ndom91, @radokristof, @rejas, @XBCreepinJesus & @ZoneMR. + +â„¹ï¸ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. + +### Added + +- Added option to config the level of logging. +- Added prettier for an even cleaner codebase. +- Hide Sunrise/Sunset in Weather module. +- Hide Sunrise/Sunset in Current Weather module. +- Added Met Office DataHub (UK) provider. + +### Updated + +- Cleaned up alert module code. +- Cleaned up check_config code. +- Replaced grunt-based linters with their non-grunt equivalents. +- Switch to most of the eslint:recommended rules and fix warnings. +- Replaced insecure links with https ones. +- Cleaned up all "no-undef" warnings from eslint. +- Added location title wrapping for calendar module. +- Updated the BG translation. + +### Deleted + +- Removed truetype (ttf) fonts. + +### Fixed + +- The broken modules due to Socket.io change from last release. [#1973](https://github.com/MichMich/MagicMirror/issues/1973) +- Add backward compatibility for old module code in socketclient.js. [#1973](https://github.com/MichMich/MagicMirror/issues/1973) +- Support multiple instances of calendar module with different config. [#1109](https://github.com/MichMich/MagicMirror/issues/1109) +- 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 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 + +🚨 READ THIS BEFORE UPDATING 🚨 + +In the past years the project has grown a lot. This came with a huge downside: poor maintainability. If I let the project continue the way it was, it would eventually crash and burn. More important: I would completely lose the drive and interest to continue the project. Because of this the decision was made to simplify the core by removing all side features like automatic installers and support for exotic platforms. This release (2.11.0) is the first real release that will reflect (parts) of these changes. As a result of this, some things might break. So before you continue make sure to backup your installation. Your config, your modules or better yet: your full MagicMirror² folder. In other words: update at your own risk. + +For more information regarding this major change, please check issue [#1860](https://github.com/MichMich/MagicMirror/issues/1860). + +### Deleted + +- Remove installers. +- Remove externalized scripts. +- Remove jshint dependency, instead eslint checks your config file now + +### Added + +- Brazilian translation for "FEELS". +- Ukrainian translation. +- Finnish translation for "PRECIP", "UPDATE_INFO_MULTIPLE" and "UPDATE_INFO_SINGLE". +- Added the ability to hide the temp label and weather icon in the `currentweather` module to allow showing only information such as wind and sunset/rise. +- The `clock` module now optionally displays sun and moon data, including rise/set times, remaining daylight, and percent of moon illumination. +- Added Hebrew translation. +- Add HTTPS support and update config.js.sample +- Run tests on long term support and latest stable version of nodejs +- Added the ability to configure a list of modules that shouldn't be update checked. +- Run linters on git commits +- Added date functionality to compliments: display birthday wishes or celebrate an anniversary +- Add HTTPS support for clientonly-mode. + +### Fixed + +- Force declaration of public ip address in config file (ISSUE #1852) +- Fixes `run-start.sh`: If running in docker-container, don't check the environment, just start electron (ISSUE #1859) +- Fix calendar time offset for recurring events crossing Daylight Savings Time (ISSUE #1798) +- Fix regression in currentweather module causing 'undefined' to show up when config.hideTemp is false +- Fix FEELS translation for Croatian +- Fixed weather tests [#1840](https://github.com/MichMich/MagicMirror/issues/1840) +- Fixed Socket.io can't be used with Reverse Proxy in serveronly mode [#1934](https://github.com/MichMich/MagicMirror/issues/1934) +- Fix update checking skipping 3rd party modules the first time + +### Changed + +- Remove documentation from core repository and link to new dedicated docs site: [docs.magicmirror.builders](https://docs.magicmirror.builders). +- Updated config.js.sample: Corrected some grammar on `config.js.sample` comment section. +- Removed `run-start.sh` script and update start commands: + - To start using electron, use `npm run start`. + - To start in server only mode, use `npm run server`. +- Remove redundant logging from modules. +- Timestamp in log output now also contains the date +- Turkish translation. +- Option to configure the size of the currentweather module. +- Changed "Gevoelstemperatuur" to "Voelt als" shorter text. + +## [2.10.1] - 2020-01-10 + +### Changed + +- Updated README.md: Added links to the official documentation website and remove links to broken installer. + +## [2.10.0] - 2020-01-01 + +Special thanks to @sdetweil for all his great contributions! + +â„¹ï¸ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. + +### Added + +- Timestamps in log output. +- Padding in dateheader mode of the calendar module. +- New upgrade script to help users consume regular updates installers/upgrade-script.sh. +- New script to help setup pm2, without install installers/fixuppm2.sh. + +### Updated + +- Updated lower bound of `lodash` and `helmet` dependencies for security patches. +- Updated compliments.js to handle newline in text, as textfields to not interpolate contents. +- Updated raspberry.sh installer script to handle new platform issues, split node/npm, pm2, and screen saver changes. +- Improve handling for armv6l devices, where electron support has gone away, add optional serveronly config option. +- Improved run-start.sh to handle for serveronly mode, by choice, or when electron not available. +- Only check for xwindows running if not on macOS. + +### Fixed + +- Fixed issue in weatherforecast module where predicted amount of rain was not using the decimal symbol specified in config.js. +- Module header now updates correctly, if a module need to dynamically show/hide its header based on a condition. +- Fix handling of config.js for serverOnly mode commented out. +- Fixed issue in calendar module where the debug script didn't work correctly with authentication. +- Fixed issue that some full day events were not correctly recognized as such. +- Display full day events lasting multiple days as happening today instead of some days ago if they are still ongoing. + +## [2.9.0] - 2019-10-01 + +â„¹ï¸ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md). + +### Added + +- Spanish translation for "PRECIP". +- Adding a Malay (Malaysian) translation for MagicMirror². +- Add test check URLs of vendors 200 and 404 HTTP CODE. +- Add tests for new weather module and helper to stub ajax requests. + +### Updated + +- Updatenotification module: Display update notification for a limited (configurable) time. +- Enabled e2e/vendor_spec.js tests. +- The css/custom.css will be renamed after the next release. We've added into `run-start.sh` an instruction by GIT to ignore with `--skip-worktree` and `rm --cached`. [#1540](https://github.com/MichMich/MagicMirror/issues/1540) +- Disable sending of notification CLOCK_SECOND when displaySeconds is false. + +### Fixed + +- Updatenotification module: Properly handle race conditions, prevent crash. +- Send `NEWS_FEED` notification also for the first news messages which are shown. +- Fixed issue where weather module would not refresh data after a network or API outage. [#1722](https://github.com/MichMich/MagicMirror/issues/1722) +- Fixed weatherforecast module not displaying rain amount on fallback endpoint. +- Notifications CLOCK_SECOND & CLOCK_MINUTE being from startup instead of matched against the clock and avoid drifting. + +## [2.8.0] - 2019-07-01 + +â„¹ï¸ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md). + +### Added + +- Option to show event location in calendar +- Finnish translation for "Feels" and "Weeks" +- Russian translation for “Feels†+- Calendar module: added `nextDaysRelative` config option +- Add `broadcastPastEvents` config option for calendars to include events from the past `maximumNumberOfDays` in event broadcasts +- Added feature to broadcast news feed items `NEWS_FEED` and updated news items `NEWS_FEED_UPDATED` in default [newsfeed](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/newsfeed) module (when news is updated) with documented default and `config.js` options in [README.md](https://github.com/MichMich/MagicMirror/blob/develop/modules/default/newsfeed/README.md) +- Added notifications to default `clock` module broadcasting `CLOCK_SECOND` and `CLOCK_MINUTE` for the respective time elapsed. +- Added UK Met Office Datapoint feed as a provider in the default weather module. +- Added new provider class +- Added suncalc.js dependency to calculate sun times (not provided in UK Met Office feed) +- Added "tempUnits" and "windUnits" to allow, for example, temp in metric (i.e. celsius) and wind in imperial (i.e. mph). These will override "units" if specified, otherwise the "units" value will be used. +- Use Feels Like temp from feed if present +- Optionally display probability of precipitation (PoP) in current weather (UK Met Office data) +- Automatically try to fix eslint errors by passing `--fix` option to it +- Added sunrise and sunset times to weathergov weather provider [#1705](https://github.com/MichMich/MagicMirror/issues/1705) +- Added "useLocationAsHeader" to display "location" in `config.js` as header when location name is not returned +- Added to `newsfeed.js`: in order to design the news article better with css, three more class-names were introduced: newsfeed-desc, newsfeed-desc, newsfeed-desc + +### Updated + +- English translation for "Feels" to "Feels like" +- Fixed the example calendar url in `config.js.sample` +- Updated `ical.js` to solve various calendar issues. +- Updated weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676) +- Only update clock once per minute when seconds aren't shown +- Updated weatherprovider documentation. + +### Fixed + +- Fixed uncaught exception, race condition on module update +- Fixed issue [#1696](https://github.com/MichMich/MagicMirror/issues/1696), some ical files start date to not parse to date type +- Allowance HTML5 autoplay-policy (policy is changed from Chrome 66 updates) +- Handle SIGTERM messages +- Fixes sliceMultiDayEvents so it respects maximumNumberOfDays +- Minor types in default NewsFeed [README.md](https://github.com/MichMich/MagicMirror/blob/develop/modules/default/newsfeed/README.md) +- Fix typos and small syntax errors, cleanup dependencies, remove multiple-empty-lines, add semi-rule +- Fixed issues with calendar not displaying one-time changes to repeating events +- Updated the fetchedLocationName variable in currentweather.js so that city shows up in the header + +### Updated installer + +- give non-pi2+ users (pi0, odroid, jetson nano, mac, windows, ...) option to continue install +- use current username vs hardcoded 'pi' to support non-pi install +- check for npm installed. node install doesn't do npm anymore +- check for mac as part of PM2 install, add install option string +- Updated pm2 config with current username instead of hard coded 'pi' +- check for screen saver config, "/etc/xdg/lxsession", bypass if not setup + +## [2.7.1] - 2019-04-02 + +Fixed `package.json` version number. + +## [2.7.0] - 2019-04-01 + +â„¹ï¸ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md). + +### Added + +- Italian translation for "Feels" +- Basic Klingon (tlhIngan Hol) translations +- Disabled the screensaver on raspbian with installation script +- Added option to truncate the number of vertical lines a calendar item can span if `wrapEvents` is enabled. +- Danish translation for "Feels" and "Weeks" +- Added option to split multiple day events in calendar to separate numbered events +- Slovakian translation +- Alerts now can contain Font Awesome icons +- Notifications display time can be set in request +- Newsfeed: added support for `ARTICLE_INFO_REQUEST` notification +- Add `name` config option for calendars to be sent along with event broadcasts + +### Updated + +- Bumped the Electron dependency to v3.0.13 to support the most recent Raspbian. [#1500](https://github.com/MichMich/MagicMirror/issues/1500) +- Updated modernizr code in alert module, fixed a small typo there too +- More verbose error message on console if the config is malformed +- Updated installer script to install Node.js version 10.x + +### Fixed + +- Fixed temperature displays in currentweather and weatherforecast modules [#1503](https://github.com/MichMich/MagicMirror/issues/1503), [#1511](https://github.com/MichMich/MagicMirror/issues/1511). +- Fixed unhandled error on bad git data in updatenotification module [#1285](https://github.com/MichMich/MagicMirror/issues/1285). +- Weather forecast now works with openweathermap in new weather module. Daily data are displayed, see issue [#1504](https://github.com/MichMich/MagicMirror/issues/1504). +- Fixed analogue clock border display issue where non-black backgrounds used (previous fix for issue 611) +- Fixed compatibility issues caused when modules request different versions of Font Awesome, see issue [#1522](https://github.com/MichMich/MagicMirror/issues/1522). MagicMirror² now uses [Font Awesome 5 with v4 shims included for backwards compatibility](https://fontawesome.com/how-to-use/on-the-web/setup/upgrading-from-version-4#shims). +- Installation script problems with raspbian +- Calendar: only show repeating count if the event is actually repeating [#1534](https://github.com/MichMich/MagicMirror/pull/1534) +- Calendar: Fix exdate handling when multiple values are specified (comma separated) +- Calendar: Fix relative date handling for fulldate events, calculate difference always from start of day [#1572](https://github.com/MichMich/MagicMirror/issues/1572) +- Fix null dereference in moduleNeedsUpdate when the module isn't visible +- Calendar: Fixed event end times by setting default calendarEndTime to "LT" (Local time format). [#1479] +- Calendar: Fixed missing calendar fetchers after server process restarts [#1589](https://github.com/MichMich/MagicMirror/issues/1589) +- Notification: fixed background color (was white text on white background) +- Use getHeader instead of data.header when creating the DOM so overwriting the function also propagates into it +- Fix documentation of `useKMPHwind` option in currentweather + +### New weather module + +- Fixed weather forecast table display [#1499](https://github.com/MichMich/MagicMirror/issues/1499). +- Dimmed loading indicator for weather forecast. +- Implemented config option `decimalSymbol` [#1499](https://github.com/MichMich/MagicMirror/issues/1499). +- Aligned indoor values in current weather vertical [#1499](https://github.com/MichMich/MagicMirror/issues/1499). +- Added humidity support to nunjuck unit filter. +- Do not display degree symbol for temperature in Kelvin [#1503](https://github.com/MichMich/MagicMirror/issues/1503). +- Weather forecast now works with openweathermap for both, `/forecast` and `/forecast/daily`, in new weather module. If you use the `/forecast`-weatherEndpoint, the hourly data are converted to daily data, see issues [#1504](https://github.com/MichMich/MagicMirror/issues/1504), [#1513](https://github.com/MichMich/MagicMirror/issues/1513). +- Added fade, fadePoint and maxNumberOfDays properties to the forecast mode [#1516](https://github.com/MichMich/MagicMirror/issues/1516) +- Fixed Loading string and decimalSymbol string replace [#1538](https://github.com/MichMich/MagicMirror/issues/1538) +- Show Snow amounts in new weather module [#1545](https://github.com/MichMich/MagicMirror/issues/1545) +- Added weather.gov as a new weather provider for US locations + +## [2.6.0] - 2019-01-01 + +â„¹ï¸ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues updating, make sure you are running the latest version of Node. + +### ✨ Experimental ✨ + +- New default [module weather](modules/default/weather). This module will eventually replace the current `currentweather` and `weatherforecast` modules. The new module is still pretty experimental, but it's included so you can give it a try and help us improve this module. Please give us you feedback using [this forum post](https://forum.magicmirror.builders/topic/9335/default-weather-module-refactoring). + +A huge, huge, huge thanks to user @fewieden for all his hard work on the new `weather` module! + +### Added + +- Possibility to add classes to the cell of symbol, title and time of the events of calendar. +- Font-awesome 5, still has 4 for backwards compatibility. +- Missing `showEnd` in calendar documentation +- Screenshot for the new feed module +- Screenshot for the compliments module +- Screenshot for the clock module +- Screenshot for the current weather +- Screenshot for the weather forecast module +- Portuguese translation for "Feels" +- Croatian translation +- Fading for dateheaders timeFormat in Calendar [#1464](https://github.com/MichMich/MagicMirror/issues/1464) +- Documentation for the existing `scale` option in the Weather Forecast module. + +### Fixed + +- Allow parsing recurring calendar events where the start date is before 1900 +- Fixed Polish translation for Single Update Info +- Ignore entries with unparseable details in the calendar module +- Bug showing FullDayEvents one day too long in calendar fixed +- Bug in newsfeed when `removeStartTags` is used on the description [#1478](https://github.com/MichMich/MagicMirror/issues/1478) + +### Updated + +- The default calendar setting `showEnd` is changed to `false`. + +### Changed + +- The Weather Forecast module by default displays the ° symbol after every numeric value to be consistent with the Current Weather module. + +## [2.5.0] - 2018-10-01 + +### Added + +- Romanian translation for "Feels" +- Support multi-line compliments +- Simplified Chinese translation for "Feels" +- Polish translate for "Feels" +- French translate for "Feels" +- Translations for newsfeed module +- Support for toggling news article in fullscreen +- Hungarian translation for "Feels" and "Week" +- Spanish translation for "Feels" +- Add classes instead of inline style to the message from the module Alert +- Support for events having a duration instead of an end +- Support for showing end of events through config parameters showEnd and dateEndFormat + +### Fixed + +- Fixed gzip encoded calendar loading issue #1400. +- 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) +- Fix calendar parsing issue for Midori on RasperryPi Zero w, related to issue #694. +- Fix weather city ID link in sample config +- Fixed issue with clientonly not updating with IP address and port provided on command line. + +### Updated + +- Updated Simplified Chinese translation +- Swedish translations +- Hungarian translations for the updatenotification module +- Updated Norsk bokmÃ¥l translation +- Updated Norsk nynorsk translation +- Consider multi days event as full day events + +## [2.4.1] - 2018-07-04 + +### Fixed + +- Fix weather parsing issue #1332. + +## [2.4.0] - 2018-07-01 + +âš ï¸ **Warning:** This release includes an updated version of Electron. This requires a Raspberry Pi configuration change to allow the best performance and prevent the CPU from overheating. Please read the information on the [MagicMirror² Wiki](https://github.com/michmich/magicmirror/wiki/configuring-the-raspberry-pi#enable-the-open-gl-driver-to-decrease-electrons-cpu-usage). + +â„¹ï¸ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install` + +### Added + +- Enabled translation of feelsLike for module currentweather +- Added support for on-going calendar events +- Added scroll up in fullscreen newsfeed article view +- Changed fullscreen newsfeed width from 100% to 100vw (better results) +- Added option to calendar module that colors only the symbol instead of the whole line +- Added option for new display format in the calendar module with date headers with times/events below. +- Ability to fetch compliments from a remote server +- Add regex filtering to calendar module +- Customize classes for table +- Added option to newsfeed module to only log error parsing a news article if enabled +- Add update translations for Português Brasileiro + +### Changed + +- Upgrade to Electron 2.0.0. +- Remove yarn-or-npm which breaks production builds. +- Invoke module suspend even if no dom content. [#1308](https://github.com/MichMich/MagicMirror/issues/1308) + +### Fixed + +- Fixed issue where wind chill could not be displayed in Fahrenheit. [#1247](https://github.com/MichMich/MagicMirror/issues/1247) +- Fixed issues where a module crashes when it tries to dismiss a non existing alert. [#1240](https://github.com/MichMich/MagicMirror/issues/1240) +- In default module currentWeather/currentWeather.js line 296, 300, self.config.animationSpeed can not be found because the notificationReceived function does not have "self" variable. +- Fixed browser-side code to work on the Midori browser. +- Fixed issue where heat index was reporting incorrect values in Celsius and Fahrenheit. [#1263](https://github.com/MichMich/MagicMirror/issues/1263) +- Fixed weatherforecast to use dt_txt field instead of dt to handle timezones better +- Newsfeed now remembers to show the description when `"ARTICLE_LESS_DETAILS"` is called if the user wants to always show the description. [#1282](https://github.com/MichMich/MagicMirror/issues/1282) +- `clientonly/*.js` is now linted, and one linting error is fixed +- Fix issue #1196 by changing underscore to hyphen in locale id, in align with momentjs. +- Fixed issue where heat index and wind chill were reporting incorrect values in Kelvin. [#1263](https://github.com/MichMich/MagicMirror/issues/1263) + +### Updated + +- Updated Italian translation +- Updated German translation +- Updated Dutch translation + +## [2.3.1] - 2018-04-01 + +### Fixed + +- Downgrade electron to 1.4.15 to solve the black screen issue.[#1243](https://github.com/MichMich/MagicMirror/issues/1243) + +## [2.3.0] - 2018-04-01 + +### Added + +- Add new settings in compliments module: setting time intervals for morning and afternoon +- Add system notification `MODULE_DOM_CREATED` for notifying each module when their Dom has been fully loaded. +- Add types for module. +- Implement Danger.js to notify contributors when CHANGELOG.md is missing in PR. +- Allow scrolling in full page article view of default newsfeed module with gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures) +- Changed 'compliments.js' - Updated DOM if remote compliments are loaded instead of waiting one updateInterval to show custom compliments +- Automated unit tests utils, deprecated, translator, cloneObject(lockstrings) +- Automated integration tests translations +- Add advanced filtering to the excludedEvents configuration of the default calendar module +- New currentweather module config option: `showFeelsLike`: Shows how it actually feels like. (wind chill or heat index) +- New currentweather module config option: `useKMPHwind`: adds an option to see wind speed in Kmph instead of just m/s or Beaufort. +- Add dc:date to parsing in newsfeed module, which allows parsing of more rss feeds. + +### Changed + +- Add link to GitHub repository which contains the respective Dockerfile. +- Optimized automated unit tests cloneObject, cmpVersions +- Updated notifications use now translation templates instead of normal strings. +- Yarn can be used now as an installation tool +- Changed Electron dependency to v1.7.13. + +### Fixed + +- News article in fullscreen (iframe) is now shown in front of modules. +- Forecast respects maxNumberOfDays regardless of endpoint. +- Fix exception on translation of objects. + +## [2.2.2] - 2018-01-02 + +### Added + +- Add missing `package-lock.json`. + +### Changed + +- Changed Electron dependency to v1.7.10. + +## [2.2.1] - 2018-01-01 + +### Fixed + +- Fixed linting errors. + +## [2.2.0] - 2018-01-01 + +**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install` + +### Changed + +- Calendar week is now handled with a variable translation in order to move number language specific. +- Reverted the Electron dependency back to 1.4.15 since newer version don't seem to work on the Raspberry Pi very well. + +### Added + +- Add option to use [Nunjucks](https://mozilla.github.io/nunjucks/) templates in modules. (See `helloworld` module as an example.) +- Add Bulgarian translations for MagicMirror² and Alert module. +- Add graceful shutdown of modules by calling `stop` function of each `node_helper` on SIGINT before exiting. +- Link update subtext to Github diff of current version versus tracking branch. +- Add Catalan translation. +- Add ability to filter out newsfeed items based on prohibited words found in title (resolves #1071) +- Add options to truncate description support of a feed in newsfeed module +- Add reloadInterval option for particular feed in newsfeed module +- Add no-cache entries of HTTP headers in newsfeed module (fetcher) +- Add Czech translation. +- Add option for decimal symbols other than the decimal point for temperature values in both default weather modules: WeatherForecast and CurrentWeather. + +### Fixed + +- Fixed issue with calendar module showing more than `maximumEntries` allows +- WeatherForecast and CurrentWeather are now using HTTPS instead of HTTP +- Correcting translation for Indonesian language +- Fix issue where calendar icons wouldn't align correctly + +## [2.1.3] - 2017-10-01 + +**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install` + +### Changed + +- Remove Roboto fonts files inside `fonts` and these are installed by npm install command. + +### Added + +- Add `clientonly` script to start only the electron client for a remote server. +- Add symbol and color properties of event when `CALENDAR_EVENTS` notification is broadcasted from `default/calendar` module. +- Add `.vscode/` folder to `.gitignore` to keep custom Visual Studio Code config out of git. +- Add unit test the capitalizeFirstLetter function of newsfeed module. +- Add new unit tests for function `shorten` in calendar module. +- Add new unit tests for function `getLocaleSpecification` in calendar module. +- Add unit test for js/class.js. +- Add unit tests for function `roundValue` in currentweather module. +- Add test e2e showWeek feature in spanish language. +- Add warning Log when is used old authentication method in the calendar module. +- Add test e2e for helloworld module with default config text. +- Add ability for `currentweather` module to display indoor humidity via INDOOR_HUMIDITY notification. +- Add Welsh (Cymraeg) translation. +- Add Slack badge to Readme. + +### Updated + +- Changed 'default.js' - listen on all attached interfaces by default. +- Add execution of `npm list` after the test are ran in Travis CI. +- Change hooks for the vendors e2e tests. +- Add log when clientonly failed on starting. +- Add warning color when are using full ip whitelist. +- Set version of the `express-ipfilter` on 0.3.1. + +### Fixed + +- Fixed issue with incorrect alignment of analog clock when displayed in the center column of the MM. +- Fixed ipWhitelist behaviour to make empty whitelist ([]) allow any and all hosts access to the MM. +- Fixed issue with calendar module where 'excludedEvents' count towards 'maximumEntries'. +- Fixed issue with calendar module where global configuration of maximumEntries was not overridden by calendar specific config (see module doc). +- Fixed issue where `this.file(filename)` returns a path with two hashes. +- Workaround for the WeatherForecast API limitation. + +## [2.1.2] - 2017-07-01 + +### Changed + +- Revert Docker related changes in favor of [docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror). All Docker images are outsourced. ([#856](https://github.com/MichMich/MagicMirror/pull/856)) +- Change Docker base image (Debian + Node) to an arm based distro (AlpineARM + Node) ([#846](https://github.com/MichMich/MagicMirror/pull/846)) +- Fix the dockerfile to have it running from the first time. + +### Added + +- Add in option to wrap long calendar events to multiple lines using `wrapEvents` configuration option. +- Add test e2e `show title newsfeed` for newsfeed module. +- Add task to check configuration file. +- Add test check URLs of vendors. +- Add test of match current week number on clock module with showWeek configuration. +- Add test default modules present modules/default/defaultmodules.js. +- Add unit test calendar_modules function capFirst. +- Add test for check if exists the directories present in defaults modules. +- Add support for showing wind direction as an arrow instead of abbreviation in currentWeather module. +- Add support for writing translation functions to support flexible word order +- Add test for check if exits the directories present in defaults modules. +- Add calendar option to set a separate date format for full day events. +- Add ability for `currentweather` module to display indoor temperature via INDOOR_TEMPERATURE notification +- Add ability to change the path of the `custom.css`. +- Add translation Dutch to Alert module. +- Added Romanian translation. + +### Updated + +- Added missing keys to Polish translation. +- Added missing key to German translation. +- Added better translation with flexible word order to Finnish translation. + +### Fixed + +- Fix instruction in README for using automatically installer script. +- Bug of duplicated compliments as described in [here](https://forum.magicmirror.builders/topic/2381/compliments-module-stops-cycling-compliments). +- Fix double message about port when server is starting +- Corrected Swedish translations for TODAY/TOMORROW/DAYAFTERTOMORROW. +- Removed unused import from js/electron.js +- Made calendar.js respect config.timeFormat irrespective of locale setting. +- Fixed alignment of analog clock when a large calendar is displayed in the same side bar. + +## [2.1.1] - 2017-04-01 + +**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install` + +### Changed + +- Add `anytime` group for Compliments module. +- Compliments module can use remoteFile without default daytime arrays defined. +- Installer: Use init config.js from config.js.sample. +- Switched out `rrule` package for `rrule-alt` and fixes in `ical.js` in order to fix calendar issues. ([#565](https://github.com/MichMich/MagicMirror/issues/565)) +- Make mouse events pass through the region fullscreen_above to modules below. +- Scaled the splash screen down to make it a bit more subtle. +- Replace HTML tables with markdown tables in README files. +- Added `DAYAFTERTOMORROW`, `UPDATE_NOTIFICATION` and `UPDATE_NOTIFICATION_MODULE` to Finnish translations. +- Run `npm test` on Travis automatically. +- Show the splash screen image even when is reboot or halted. +- Added some missing translation strings in the sv.json file. +- Run task jsonlint to check translation files. +- Restructured Test Suite. + +### Added + +- Added Docker support (Pull Request [#673](https://github.com/MichMich/MagicMirror/pull/673)). +- Calendar-specific support for `maximumEntries`, and `maximumNumberOfDays`. +- Add loaded function to modules, providing an async callback. +- Made default newsfeed module aware of gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures) +- Add use pm2 for manager process into Installer RaspberryPi script. +- Russian Translation. +- Afrikaans Translation. +- Add postinstall script to notify user that MagicMirror² installed successfully despite warnings from NPM. +- Init tests using mocha. +- Option to use RegExp in Calendar's titleReplace. +- Hungarian Translation. +- Icelandic Translation. +- Add use a script to prevent when is run by SSH session set DISPLAY environment. +- Enable ability to set configuration file by the environment variable called MM_CONFIG_FILE. +- Option to give each calendar a different color. +- Option for colored min-temp and max-temp. +- Add test e2e helloworld. +- Add test e2e environment. +- Add `chai-as-promised` npm module to devDependencies. +- Basic set of tests for clock module. +- Run e2e test in Travis. +- Estonian Translation. +- Add test for compliments module for parts of day. +- Korean Translation. +- Added console warning on startup when deprecated config options are used. +- Add option to display temperature unit label to the current weather module. +- Added ability to disable wrapping of news items. +- Added in the ability to hide events in the calendar module based on simple string filters. +- Updated Norwegian translation. +- Added hideLoading option for News Feed module. +- Added configurable dateFormat to clock module. +- Added multiple calendar icon support. +- Added tests for Translations, dev argument, version, dev console. +- Added test anytime feature compliments module. +- Added test ipwhitelist configuration directive. +- Added test for calendar module: default, basic-auth, backward compatibility, fail-basic-auth. +- Added meta tags to support fullscreen mode on iOS (for server mode) +- Added `ignoreOldItems` and `ignoreOlderThan` options to the News Feed module +- Added test for MM_PORT environment variable. +- Added a configurable Week section to the clock module. + +### Fixed + +- Updated .gitignore to not ignore default modules folder. +- Remove white flash on boot up. +- Added `update` in Raspberry Pi installation script. +- Fix an issue where the analog clock looked scrambled. ([#611](https://github.com/MichMich/MagicMirror/issues/611)) +- If units are set to imperial, the showRainAmount option of weatherforecast will show the correct unit. +- Module currentWeather: check if temperature received from api is defined. +- Fix an issue with module hidden status changing to `true` although lock string prevented showing it. +- Fix newsfeed module bug (removeStartTags) +- Fix when is set MM_PORT environment variable. +- Fixed missing animation on `this.show(speed)` when module is alone in a region. + +## [2.1.0] - 2016-12-31 + +**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install` + +### Added + +- Finnish translation. +- Danish translation. +- Turkish translation. +- Option to limit access to certain IP addresses based on the value of `ipWhitelist` in the `config.js`, default is access from localhost only (Issue [#456](https://github.com/MichMich/MagicMirror/issues/456)). +- Added ability to change the point of time when calendar events get relative. +- Add Splash screen on boot. +- Add option to show humidity in currentWeather module. +- Add VSCode IntelliSense support. +- Module API: Add Visibility locking to module system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#visibility-locking) for more information. +- Module API: Method to overwrite the module's header. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#getheader) for more information. +- Module API: Option to define the minimum MagicMirror² version to run a module. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#requiresversion) for more information. +- Calendar module now broadcasts the event list to all other modules using the notification system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/calendar) for more information. +- Possibility to use the calendar feed as the source for the weather (currentweather & weatherforecast) location data. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/weatherforecast) for more information. +- Added option to show rain amount in the weatherforecast default module +- Add module `updatenotification` to get an update whenever a new version is available. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/updatenotification) for more information. +- Add the ability to set timezone on the date display in the Clock Module +- Ability to set date format in calendar module +- Possibility to use currentweather for the compliments +- Added option `disabled` for modules. +- Added option `address` to set bind address. +- Added option `onlyTemp` for currentweather module to show only current temperature and weather icon. +- Added option `remoteFile` to compliments module to load compliment array from filesystem. +- Added option `zoom` to scale the whole mirror display with a given factor. +- Added option `roundTemp` for currentweather and weatherforecast modules to display temperatures rounded to nearest integer. +- Added ability set the classes option to compliments module for style and text size of compliments. +- Added ability to configure electronOptions +- Calendar module: option to hide private events +- Add root_path for global vars + +### Updated + +- Modified translations for Frysk. +- Modified core English translations. +- Updated package.json as a result of Snyk security update. +- Improve object instantiation to prevent reference errors. +- Improve logger. `Log.log()` now accepts multiple arguments. +- Remove extensive logging in newsfeed node helper. +- Calendar times are now uniformly capitalized. +- Modules are now secure, and Helmet is now used to prevent abuse of the Mirror's API. + +### Fixed + +- Solve an issue where module margins would appear when the first module of a section was hidden. +- Solved visual display errors on chrome, if all modules in one of the right sections are hidden. +- Global and Module default config values are no longer modified when setting config values. +- Hide a region if all modules in a region are hidden. Prevention unwanted margins. +- Replaced `electron-prebuilt` package with `electron` in order to fix issues that would happen after 2017. +- Documentation of alert module + +## [2.0.5] - 2016-09-20 + +### Added + +- Added ability to remove tags from the beginning or end of newsfeed items in 'newsfeed.js'. +- Added ability to define "the day after tomorrow" for calendar events (Definition for German and Dutch already included). +- Added CII Badge (we are compliant with the CII Best Practices) +- Add support for doing http basic auth when loading calendars +- Add the ability to turn off and on the date display in the Clock Module + +### Fixed + +- Fix typo in installer. +- Add message to unsupported Pi error to mention that Pi Zeros must use server only mode, as ARMv6 is unsupported. Closes #374. +- Fix API url for weather API. + +### Updated + +- Force fullscreen when kioskmode is active. +- Updated the .github templates and information with more modern information. +- Updated the Gruntfile with a more functional StyleLint implementation. + +## [2.0.4] - 2016-08-07 + +### Added + +- Brazilian Portuguese Translation. +- Option to enable Kiosk mode. +- Added ability to start the app with Dev Tools. +- Added ability to turn off the date display in `clock.js` when in analog mode. +- Greek Translation + +### Fixed + +- Prevent `getModules()` selectors from returning duplicate entries. +- Append endpoints of weather modules with `/` to retrieve the correct data. (Issue [#337](https://github.com/MichMich/MagicMirror/issues/337)) +- Corrected grammar in `module.js` from 'suspend' to 'suspended'. +- Fixed openweathermap.org URL in config sample. +- Prevent currentweather module from crashing when received data object is incorrect. +- Fix issue where translation loading prevented the UI start-up when the language was set to 'en'. (Issue [#388](https://github.com/MichMich/MagicMirror/issues/388)) + +### Updated + +- Updated package.json to fix possible vulnerabilities. (Using Snyk) +- Updated weathericons +- Updated default weatherforecast to work with the new icons. +- More detailed error message in case config file couldn't be loaded. + +## [2.0.3] - 2016-07-12 + +### Added + +- Add max newsitems parameter to the newsfeed module. +- Translations for Simplified Chinese, Traditional Chinese and Japanese. +- Polish Translation +- Add an analog clock in addition to the digital one. + +### Fixed + +- Edit Alert Module to display title & message if they are provided in the notification (Issue [#300](https://github.com/MichMich/MagicMirror/issues/300)) +- Removed 'null' reference from updateModuleContent(). This fixes recent Edge and Internet Explorer browser displays (Issue [#319](https://github.com/MichMich/MagicMirror/issues/319)) + +### Changed + +- Added default string to calendar titleReplace. + +## [2.0.2] - 2016-06-05 + +### Added + +- Norwegian Translations (nb and nn) +- Portuguese Translation +- Swedish Translation + +### Fixed + +- Added reference to Italian Translation. +- Added the missing NE translation to all languages. [#344](https://github.com/MichMich/MagicMirror/issues/344) +- Added proper User-Agent string to calendar call. + +### Changed + +- Add option to use locationID in weather modules. + +## [2.0.1] - 2016-05-18 + +### Added + +- Changelog +- Italian Translation + +### Changed + +- Improve the installer by fetching the latest Node.js without any 3rd party interferences. + +## [2.0.0] - 2016-05-03 + +### Initial release of MagicMirror² + +It includes (but is not limited to) the following features: + +- Modular system allowing 3rd party plugins. +- An Node/Electron based application taking away the need for external servers or browsers. +- A complete development API documentation. +- Small cute fairies that kiss you while you sleep. + +## [1.0.0] - 2014-02-16 + +### Initial release of MagicMirror + +This was part of the blogpost: [https://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the](https://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the) diff --git a/MagicMirror/Collaboration.md b/MagicMirror/Collaboration.md new file mode 100644 index 0000000000000000000000000000000000000000..079a35541951be85a5ace3e56b377629138051fd --- /dev/null +++ b/MagicMirror/Collaboration.md @@ -0,0 +1,18 @@ +This document describes how collaborators of this repository should work together. + +## Pull Requests + +- never merge your own PR's +- never merge without someone having approved (approving and merging from same person is allowed) +- wait for all approvals requested (or the author decides something different in the comments) +- never merge to `master`, except for releases (because of update notification) +- merges to master should be tagged with the "mastermerge" label so that the test runs through + +## Issues + +- "real" Issues are closed if the problem is solved and the fix is released +- unrelated Issues (e.g. related to a foreign module) are closed immediately with a comment to open an issue in the module repository or to discuss this further in the forum or discord + +## Releases + +- are done by @MichMich only diff --git a/MagicMirror/LICENSE.md b/MagicMirror/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..e3fe88029ffa9c080c3d337896d050c814af3a63 --- /dev/null +++ b/MagicMirror/LICENSE.md @@ -0,0 +1,17 @@ +# The MIT License (MIT) + +Copyright © 2016-2022 Michael Teeuw + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Softwareâ€), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +**The software is provided “as isâ€, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.** diff --git a/MagicMirror/README.md b/MagicMirror/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a7b795a6a84a95d6677a5d8d725b2029c6fd3051 --- /dev/null +++ b/MagicMirror/README.md @@ -0,0 +1,56 @@ + + +<p style="text-align: center"> + <a href="https://choosealicense.com/licenses/mit"> + <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"> + </a> + <img src="https://img.shields.io/github/actions/workflow/status/michmich/magicmirror/automated-tests.yaml" alt="GitHub Actions"> + <img src="https://img.shields.io/github/checks-status/michmich/magicmirror/master" alt="Build Status"> + <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://github.com/MichMich/MagicMirror"> + <img src="https://img.shields.io/github/stars/michmich/magicmirror?style=social"> + </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). + +MagicMirror² focuses on a modular plugin system and uses [Electron](https://www.electronjs.org/) as an application wrapper. So no more web server or browser installs necessary! + +## Documentation + +For the full documentation including **[installation instructions](https://docs.magicmirror.builders/getting-started/installation.html)**, please visit our dedicated documentation website: [https://docs.magicmirror.builders](https://docs.magicmirror.builders). + +## Links + +- Website: [https://magicmirror.builders](https://magicmirror.builders) +- Documentation: [https://docs.magicmirror.builders](https://docs.magicmirror.builders) +- Forum: [https://forum.magicmirror.builders](https://forum.magicmirror.builders) + - Technical discussions: https://forum.magicmirror.builders/category/11/core-system +- Discord: [https://discord.gg/J5BAtvx](https://discord.gg/J5BAtvx) +- Blog: [https://michaelteeuw.nl/tagged/magicmirror](https://michaelteeuw.nl/tagged/magicmirror) +- Donations: [https://magicmirror.builders/#donate](https://magicmirror.builders/#donate) + +## Contributing Guidelines + +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/about/contributing.html](https://docs.magicmirror.builders/about/contributing.html) + +## Enjoying MagicMirror? Consider a donation! + +MagicMirror² is opensource and free. That doesn't mean we don't need any money. + +Please consider a donation to help us cover the ongoing costs like webservers and email services. +If we receive enough donations we might even be able to free up some working hours and spend some extra time improving the MagicMirror² core. + +To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link. + +<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> diff --git a/MagicMirror/clientonly/index.js b/MagicMirror/clientonly/index.js new file mode 100644 index 0000000000000000000000000000000000000000..1a0a7970098b485e2588af3976b118b2fc22d493 --- /dev/null +++ b/MagicMirror/clientonly/index.js @@ -0,0 +1,126 @@ +"use strict"; + +// Use separate scope to prevent global scope pollution +(function () { + const config = {}; + + /** + * Helper function to get server address/hostname from either the commandline or env + */ + function getServerAddress() { + /** + * Get command line parameters + * Assumes that a cmdline parameter is defined with `--key [value]` + * + * @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) { + const index = process.argv.indexOf(`--${key}`); + const value = index > -1 ? process.argv[index + 1] : undefined; + return value !== undefined ? String(value) : defaultValue; + } + + // Prefer command line arguments over environment variables + ["address", "port"].forEach((key) => { + config[key] = getCommandLineParameter(key, process.env[key.toUpperCase()]); + }); + + // determine if "--use-tls"-flag was provided + config["tls"] = process.argv.indexOf("--use-tls") > 0; + } + + /** + * Gets the config from the specified server url + * + * @param {string} url location where the server is running. + * @returns {Promise} the config + */ + function getServerConfig(url) { + // Return new pending promise + return new Promise((resolve, reject) => { + // Select http or https module, depending on requested url + const lib = url.startsWith("https") ? require("https") : require("http"); + const request = lib.get(url, (response) => { + let configData = ""; + + // Gather incoming data + response.on("data", function (chunk) { + configData += chunk; + }); + // Resolve promise at the end of the HTTP/HTTPS stream + response.on("end", function () { + resolve(JSON.parse(configData)); + }); + }); + + request.on("error", function (error) { + reject(new Error(`Unable to read config from server (${url} (${error.message}`)); + }); + }); + } + + /** + * Print a message to the console in case of errors + * + * @param {string} message error message to print + * @param {number} code error code for the exit call + */ + function fail(message, code = 1) { + if (message !== undefined && typeof message === "string") { + console.log(message); + } else { + console.log("Usage: 'node clientonly --address 192.168.1.10 --port 8080 [--use-tls]'"); + } + process.exit(code); + } + + getServerAddress(); + + (config.address && config.port) || fail(); + const prefix = config.tls ? "https://" : "http://"; + + // Only start the client if a non-local server was provided + if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) { + getServerConfig(`${prefix}${config.address}:${config.port}/config/`) + .then(function (configReturn) { + // Pass along the server config via an environment variable + const env = Object.create(process.env); + const options = { env: env }; + configReturn.address = config.address; + configReturn.port = config.port; + configReturn.tls = config.tls; + env.config = JSON.stringify(configReturn); + + // Spawn electron application + const electron = require("electron"); + const child = require("child_process").spawn(electron, ["js/electron.js"], options); + + // Pipe all child process output to current stdout + child.stdout.on("data", function (buf) { + process.stdout.write(`Client: ${buf}`); + }); + + // Pipe all child process errors to current stderr + child.stderr.on("data", function (buf) { + process.stderr.write(`Client: ${buf}`); + }); + + child.on("error", function (err) { + process.stdout.write(`Client: ${err}`); + }); + + child.on("close", (code) => { + if (code !== 0) { + console.log(`There something wrong. The clientonly is not running code ${code}`); + } + }); + }) + .catch(function (reason) { + fail(`Unable to connect to server: (${reason})`); + }); + } else { + fail(); + } +})(); diff --git a/MagicMirror/config/config.js b/MagicMirror/config/config.js new file mode 100644 index 0000000000000000000000000000000000000000..40d9460dd7fc624258a07ac6e32400b4447506f7 --- /dev/null +++ b/MagicMirror/config/config.js @@ -0,0 +1,182 @@ +/* Magic Mirror Config Sample + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + * + * For more information on how you can configure this file + * see https://docs.magicmirror.builders/getting-started/configuration.html#general + * and https://docs.magicmirror.builders/modules/configuration.html + */ +let config = { + address: "localhost", // Address to listen on, can be: + // - "localhost", "127.0.0.1", "::1" to listen on loopback interface + // - another specific IPv4/6 to listen on a specific interface + // - "0.0.0.0", "::" to listen on any interface + // Default, when address config is left out or empty, is "localhost" + port: 8081, + basePath: "/", // The URL path where MagicMirror is hosted. If you are using a Reverse proxy + // you must set the sub path here. basePath must end with a / + ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], // Set [] to allow all IP addresses + // or add a specific IPv4 of 192.168.1.5 : + // ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.1.5"], + // or IPv4 range of 192.168.3.0 --> 192.168.3.15 use CIDR format : + // ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.3.0/28"], + + useHttps: false, // Support HTTPS or not, default "false" will use HTTP + httpsPrivateKey: "", // HTTPS private key path, only require when useHttps is true + 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", + // serverOnly: true/false/"local" , + // local for armv6l processors, default + // starts serveronly and then starts chrome browser + // false, default for all NON-armv6l devices + // true, force serveronly mode, because you want to.. no UI on this device + electronOptions: { + webPreferences: { + webviewTag: true, + }, + }, + + modules: [ + { + module: 'MMM-WebView', + position: 'top_center', + config: { + //url: 'https://www.google.com/', + url: 'http://localhost:8080/', + width: '1024px', + height: '768px', + }, + }, + + { + module: "calendar", + header: 'US Holidays', + position: "top_left", + config: { + // The config property is optional. + // If no config is set, an example calendar is shown. + // See 'Configuration options' for more information. + calendars: [ + { + url: 'https://p24-calendars.icloud.com/holiday/US_en.ics', + symbol: 'calendar', + } + ], + }, + }, + { + module: "alert", + config: { + // The config property is optional. + // See 'Configuration options' for more information. + welcome_message: 'Please do workout today', + display_time: 5000 + } + }, + { + module: "updatenotification", + position: "top_bar", + }, + { + module: "clock", + position: "top_center", + }, + { + module: "compliments", + position: "middle_center", + }, + { + module: "weather", + //position: "top_left", + position: "bottom_right", + config: { + weatherProvider: "openweathermap", + type: "current", + location: "Urbana, IL", + locationID: "4914570", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city + apiKey: "010dcf248e695643b0e7c17a539546cf" + } + }, + { + module: "weather", + position:"top_right", + header: "Weather Forecast", + config: { + weatherProvider: "openweathermap", + type: "forecast", + location: "Urbana, IL", + locationID: "4914570", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city + apiKey: "010dcf248e695643b0e7c17a539546cf" + } + }, + { + module: "newsfeed", + position: "bottom_bar", + config: { + feeds: [ + { + title: "New York Times - US", + url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml" + }, + { + title: "BBC", + url: "https://feeds.bbci.co.uk/news/video_and_audio/news_front_page/rss.xml?edition=uk", + }, + ], + //showAsList: true, + /*showSourceTitle: true, + showPublishDate: true, + broadcastNewsFeeds: true, + broadcastNewsUpdates: true*/ + }, + }, + + { + module: 'MMM-KeyBindings', + config: { + evdev: { enabled: false }, + enableKeyboard: true + }, + }, + { + module: 'MMM-Carousel', + position: 'bottom_bar', // Required only for navigation controls + config: { + transitionInterval: 100000, + //ignoreModules: ['compliments'], + mode: 'slides', + showPageIndicators: true, + showPageControls: true, + slides: { + //main: ['clock', 'weather','compliments','MMM-Covid19'], + //"Slide 2": ['newsfeed'], + main: ['clock', 'weather','calendar','compliments', 'newsfeed'], + "Slide 2": ['MMM-WebView'], + + }, + keyBindings: { + enabled: false, + enableKeyboard: true, + mode: "DEFAULT", + map: { + NextSlide: "ArrowRight", + PrevSlide: "ArrowLeft", + Slide0: "Home" + }, + + }}, + }, + + ] + + +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") {module.exports = config;} \ No newline at end of file diff --git a/MagicMirror/config/config.js.sample b/MagicMirror/config/config.js.sample new file mode 100644 index 0000000000000000000000000000000000000000..799153b60c4e652cf0030545a8bd9e64f02d7716 --- /dev/null +++ b/MagicMirror/config/config.js.sample @@ -0,0 +1,111 @@ +/* MagicMirror² Config Sample + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + * + * For more information on how you can configure this file + * see https://docs.magicmirror.builders/configuration/introduction.html + * and https://docs.magicmirror.builders/modules/configuration.html + * + * You can use environment variables using a `config.js.template` file instead of `config.js` + * which will be converted to `config.js` while starting. For more information + * see https://docs.magicmirror.builders/configuration/introduction.html#enviromnent-variables + */ +let config = { + address: "localhost", // Address to listen on, can be: + // - "localhost", "127.0.0.1", "::1" to listen on loopback interface + // - another specific IPv4/6 to listen on a specific interface + // - "0.0.0.0", "::" to listen on any interface + // Default, when address config is left out or empty, is "localhost" + port: 8080, + basePath: "/", // The URL path where MagicMirror² is hosted. If you are using a Reverse proxy + // you must set the sub path here. basePath must end with a / + ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], // Set [] to allow all IP addresses + // or add a specific IPv4 of 192.168.1.5 : + // ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.1.5"], + // or IPv4 range of 192.168.3.0 --> 192.168.3.15 use CIDR format : + // ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.3.0/28"], + + useHttps: false, // Support HTTPS or not, default "false" will use HTTP + httpsPrivateKey: "", // HTTPS private key path, only require when useHttps is true + 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", + + modules: [ + { + module: "alert", + }, + { + module: "updatenotification", + position: "top_bar" + }, + { + module: "clock", + position: "top_left" + }, + { + module: "calendar", + header: "US Holidays", + position: "top_left", + config: { + calendars: [ + { + symbol: "calendar-check", + url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics" + } + ] + } + }, + { + module: "compliments", + position: "lower_third" + }, + { + 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 + apiKey: "YOUR_OPENWEATHER_API_KEY" + } + }, + { + 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 + apiKey: "YOUR_OPENWEATHER_API_KEY" + } + }, + { + module: "newsfeed", + position: "bottom_bar", + config: { + feeds: [ + { + title: "New York Times", + url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml" + } + ], + showSourceTitle: true, + showPublishDate: true, + broadcastNewsFeeds: true, + broadcastNewsUpdates: true + } + }, + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") {module.exports = config;} diff --git a/MagicMirror/css/custom.css.sample b/MagicMirror/css/custom.css.sample new file mode 100644 index 0000000000000000000000000000000000000000..48cc17a0af81b5008744b0dc482c09fc36750513 --- /dev/null +++ b/MagicMirror/css/custom.css.sample @@ -0,0 +1,31 @@ +/* MagicMirror² Custom CSS Sample + * + * Change color and fonts here. + * + * Beware that properties cannot be unitless, so for example write '--gap-body: 0px;' instead of just '--gap-body: 0;' + * + * MIT Licensed. + */ + +/* Uncomment and adjust accordingly if you want to import another font from the google-fonts-api: */ +/* @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;300;400;700&display=swap'); */ + +:root { + --color-text: #999; + --color-text-dimmed: #666; + --color-text-bright: #fff; + --color-background: black; + + --font-primary: "Roboto Condensed"; + --font-secondary: "Roboto"; + + --font-size: 20px; + --font-size-small: 0.75rem; + + --gap-body-top: 60px; + --gap-body-right: 60px; + --gap-body-bottom: 60px; + --gap-body-left: 60px; + + --gap-modules: 30px; +} diff --git a/MagicMirror/css/main.css b/MagicMirror/css/main.css new file mode 100644 index 0000000000000000000000000000000000000000..0aa5c3418e31a08d0f3510bf93dd8ee12f5855c4 --- /dev/null +++ b/MagicMirror/css/main.css @@ -0,0 +1,241 @@ +:root { + --color-text: #999; + --color-text-dimmed: #666; + --color-text-bright: #fff; + --color-background: #000; + --font-primary: "Roboto Condensed"; + --font-secondary: "Roboto"; + --font-size: 20px; + --font-size-xsmall: 0.75rem; + --font-size-small: 1rem; + --font-size-medium: 1.5rem; + --font-size-large: 3.25rem; + --font-size-xlarge: 3.75rem; + --gap-body-top: 60px; + --gap-body-right: 60px; + --gap-body-bottom: 60px; + --gap-body-left: 60px; + --gap-modules: 30px; +} + +html { + cursor: none; + overflow: hidden; + background: var(--color-background); + user-select: none; + font-size: var(--font-size); +} + +::-webkit-scrollbar { + display: none; +} + +body { + margin: var(--gap-body-top) var(--gap-body-right) var(--gap-body-bottom) var(--gap-body-left); + position: absolute; + height: calc(100% - var(--gap-body-top) - var(--gap-body-bottom)); + width: calc(100% - var(--gap-body-right) - var(--gap-body-left)); + background: var(--color-background); + color: var(--color-text); + font-family: var(--font-primary), sans-serif; + font-weight: 400; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +/** + * Default styles. + */ + +.dimmed { + color: var(--color-text-dimmed); +} + +.normal { + color: var(--color-text); +} + +.bright { + color: var(--color-text-bright); +} + +.xsmall { + font-size: var(--font-size-xsmall); + line-height: 1.275; +} + +.small { + font-size: var(--font-size-small); + line-height: 1.25; +} + +.medium { + font-size: var(--font-size-medium); + line-height: 1.225; +} + +.large { + font-size: var(--font-size-large); + line-height: 1; +} + +.xlarge { + font-size: var(--font-size-xlarge); + line-height: 1; + letter-spacing: -3px; +} + +.thin { + font-family: var(--font-secondary), sans-serif; + font-weight: 100; +} + +.light { + font-family: var(--font-primary), sans-serif; + font-weight: 300; +} + +.regular { + font-family: var(--font-primary), sans-serif; + font-weight: 400; +} + +.bold { + font-family: var(--font-primary), sans-serif; + font-weight: 700; +} + +.align-right { + text-align: right; +} + +.align-left { + text-align: left; +} + +header { + text-transform: uppercase; + font-size: var(--font-size-xsmall); + font-family: var(--font-primary), Arial, Helvetica, sans-serif; + font-weight: 400; + border-bottom: 1px solid var(--color-text-dimmed); + line-height: 15px; + padding-bottom: 5px; + margin-bottom: 10px; + color: var(--color-text); +} + +sup { + font-size: 50%; + line-height: 50%; +} + +/** + * Module styles. + */ + +.module { + margin-bottom: var(--gap-modules); +} + +.module.hidden { + pointer-events: none; +} + +.module:not(.hidden) { + pointer-events: auto; +} + +.region.bottom .module { + margin-top: var(--gap-modules); + margin-bottom: 0; +} + +.no-wrap { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.pre-line { + white-space: pre-line; +} + +/** + * Region Definitions. + */ + +.region { + position: absolute; +} + +.region.fullscreen { + position: absolute; + inset: calc(-1 * var(--gap-body-top)) calc(-1 * var(--gap-body-right)) calc(-1 * var(--gap-body-bottom)) calc(-1 * var(--gap-body-left)); + pointer-events: none; +} + +.region.right { + right: 0; + text-align: right; +} + +.region.top { + top: 0; +} + +.region.top.center, +.region.bottom.center { + left: 50%; + transform: translateX(-50%); +} + +.region.top.right, +.region.top.left, +.region.top.center { + top: 100%; +} + +.region.bottom { + bottom: 0; +} + +.region.bottom.right, +.region.bottom.center, +.region.bottom.left { + bottom: 100%; +} + +.region.bar { + width: 100%; + text-align: center; +} + +.region.third, +.region.middle.center { + width: 100%; + text-align: center; + transform: translateY(-50%); +} + +.region.upper.third { + top: 33%; +} + +.region.middle.center { + top: 50%; +} + +.region.lower.third { + top: 66%; +} + +.region.left { + text-align: left; +} + +.region table { + width: 100%; + border-spacing: 0; + border-collapse: separate; +} diff --git a/MagicMirror/fonts/package-lock.json b/MagicMirror/fonts/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..c35712f59cfb33cad510647c47cd4142d2b4098e --- /dev/null +++ b/MagicMirror/fonts/package-lock.json @@ -0,0 +1,37 @@ +{ + "name": "magicmirror-fonts", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "magicmirror-fonts", + "license": "MIT", + "dependencies": { + "@fontsource/roboto": "^4.5.8", + "@fontsource/roboto-condensed": "^4.5.9" + } + }, + "node_modules/@fontsource/roboto": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz", + "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA==" + }, + "node_modules/@fontsource/roboto-condensed": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz", + "integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg==" + } + }, + "dependencies": { + "@fontsource/roboto": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz", + "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA==" + }, + "@fontsource/roboto-condensed": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz", + "integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg==" + } + } +} diff --git a/MagicMirror/fonts/package.json b/MagicMirror/fonts/package.json new file mode 100644 index 0000000000000000000000000000000000000000..2d094689397685bc7ff157c4146a70c775e4fb80 --- /dev/null +++ b/MagicMirror/fonts/package.json @@ -0,0 +1,16 @@ +{ + "name": "magicmirror-fonts", + "description": "Package for fonts use by MagicMirror² Core.", + "repository": { + "type": "git", + "url": "git+https://github.com/MichMich/MagicMirror.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/MichMich/MagicMirror/issues" + }, + "dependencies": { + "@fontsource/roboto": "^4.5.8", + "@fontsource/roboto-condensed": "^4.5.9" + } +} diff --git a/MagicMirror/fonts/roboto.css b/MagicMirror/fonts/roboto.css new file mode 100644 index 0000000000000000000000000000000000000000..40c643903cfcabf2ca3b365c852b4d3976f46d3d --- /dev/null +++ b/MagicMirror/fonts/roboto.css @@ -0,0 +1,55 @@ +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 100; + src: local("Roboto Thin"), local("Roboto-Thin"), url("node_modules/@fontsource/roboto/files/roboto-all-100-normal.woff") format("woff"); +} + +@font-face { + font-family: "Roboto Condensed"; + font-style: normal; + font-weight: 300; + src: local("Roboto Condensed Light"), local("RobotoCondensed-Light"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-300-normal.woff") format("woff"); +} + +@font-face { + font-family: "Roboto Condensed"; + font-style: normal; + font-weight: 400; + src: local("Roboto Condensed"), local("RobotoCondensed-Regular"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-400-normal.woff") format("woff"); +} + +@font-face { + font-family: "Roboto Condensed"; + font-style: normal; + font-weight: 700; + src: local("Roboto Condensed Bold"), local("RobotoCondensed-Bold"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-700-normal.woff") format("woff"); +} + +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 400; + src: local("Roboto"), local("Roboto-Regular"), url("node_modules/@fontsource/roboto/files/roboto-all-400-normal.woff") format("woff"); +} + +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 500; + src: local("Roboto Medium"), local("Roboto-Medium"), url("node_modules/@fontsource/roboto/files/roboto-all-500-normal.woff") format("woff"); +} + +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 700; + src: local("Roboto Bold"), local("Roboto-Bold"), url("node_modules/@fontsource/roboto/files/roboto-all-700-normal.woff") format("woff"); +} + +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 300; + src: local("Roboto Light"), local("Roboto-Light"), url("node_modules/@fontsource/roboto/files/roboto-all-300-normal.woff") format("woff"); +} diff --git a/MagicMirror/index.html b/MagicMirror/index.html new file mode 100644 index 0000000000000000000000000000000000000000..37fc2f6efbb47ac225baefb6574ec0386aa80d5f --- /dev/null +++ b/MagicMirror/index.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html> + <head> + <title>MagicMirror²</title> + <meta name="google" content="notranslate" /> + <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> + + <meta name="apple-mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-status-bar-style" content="black" /> + <meta name="format-detection" content="telephone=no" /> + <meta name="mobile-web-app-capable" content="yes" /> + + <link rel="icon" href="data:;base64,iVBORw0KGgo=" /> + <link rel="stylesheet" type="text/css" href="css/main.css" /> + <link rel="stylesheet" type="text/css" href="fonts/roboto.css" /> + <!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. --> + + <script type="text/javascript"> + window.mmVersion = "#VERSION#"; + </script> + </head> + <body> + <div class="region fullscreen below"><div class="container"></div></div> + <div class="region top bar"> + <div class="container"></div> + <div class="region top left"><div class="container"></div></div> + <div class="region top center"><div class="container"></div></div> + <div class="region top right"><div class="container"></div></div> + </div> + <div class="region upper third"><div class="container"></div></div> + <div class="region middle center"><div class="container"></div></div> + <div class="region lower third"> + <div class="container"><br /></div> + </div> + <div class="region bottom bar"> + <div class="container"></div> + <div class="region bottom left"><div class="container"></div></div> + <div class="region bottom center"><div class="container"></div></div> + <div class="region bottom right"><div class="container"></div></div> + </div> + <div class="region fullscreen above"><div class="container"></div></div> + <script type="text/javascript" src="socket.io/socket.io.js"></script> + <script type="text/javascript" src="vendor/node_modules/nunjucks/browser/nunjucks.min.js"></script> + <script type="text/javascript" src="js/defaults.js"></script> + <script type="text/javascript" src="#CONFIG_FILE#"></script> + <script type="text/javascript" src="vendor/vendor.js"></script> + <script type="text/javascript" src="modules/default/defaultmodules.js"></script> + <script type="text/javascript" src="js/logger.js"></script> + <script type="text/javascript" src="translations/translations.js"></script> + <script type="text/javascript" src="js/translator.js"></script> + <script type="text/javascript" src="js/class.js"></script> + <script type="text/javascript" src="js/module.js"></script> + <script type="text/javascript" src="js/loader.js"></script> + <script type="text/javascript" src="js/socketclient.js"></script> + <script type="text/javascript" src="js/main.js"></script> + </body> +</html> diff --git a/MagicMirror/installers/mm.sh b/MagicMirror/installers/mm.sh new file mode 100755 index 0000000000000000000000000000000000000000..d56ffca359c4495c12bd8dfdac5caaa71e84a2cd --- /dev/null +++ b/MagicMirror/installers/mm.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This file is still here to keep PM2 working on older installations. +cd ~/MagicMirror +DISPLAY=:0 npm start diff --git a/MagicMirror/jest.config.js b/MagicMirror/jest.config.js new file mode 100644 index 0000000000000000000000000000000000000000..8a2403c28980e2bb141ccecf4d8e846e4a75efcc --- /dev/null +++ b/MagicMirror/jest.config.js @@ -0,0 +1,32 @@ +module.exports = async () => { + return { + verbose: true, + testTimeout: 20000, + testSequencer: "<rootDir>/tests/utils/test_sequencer.js", + projects: [ + { + displayName: "unit", + moduleNameMapper: { + logger: "<rootDir>/js/logger.js" + }, + testMatch: ["**/tests/unit/**/*.[jt]s?(x)"], + testPathIgnorePatterns: ["<rootDir>/tests/unit/mocks"] + }, + { + displayName: "electron", + testMatch: ["**/tests/electron/**/*.[jt]s?(x)"], + testPathIgnorePatterns: ["<rootDir>/tests/electron/helpers/"] + }, + { + displayName: "e2e", + setupFilesAfterEnv: ["<rootDir>/tests/e2e/helpers/mock-console.js"], + testMatch: ["**/tests/e2e/**/*.[jt]s?(x)"], + modulePaths: ["<rootDir>/js/"], + testPathIgnorePatterns: ["<rootDir>/tests/e2e/helpers/", "<rootDir>/tests/e2e/mocks"] + } + ], + collectCoverageFrom: ["./clientonly/**/*.js", "./js/**/*.js", "./modules/default/**/*.js", "./serveronly/**/*.js"], + coverageReporters: ["lcov", "text"], + coverageProvider: "v8" + }; +}; diff --git a/MagicMirror/js/app.js b/MagicMirror/js/app.js new file mode 100644 index 0000000000000000000000000000000000000000..22127b666b9af1107ee708a964df2d60163120c8 --- /dev/null +++ b/MagicMirror/js/app.js @@ -0,0 +1,382 @@ +/* MagicMirror² + * The Core App (Server) + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ + +// Alias modules mentioned in package.js under _moduleAliases. +require("module-alias/register"); + +const fs = require("fs"); +const path = require("path"); +const envsub = require("envsub"); +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 = require(`${__dirname}/../package.json`).version; +Log.log(`Starting MagicMirror: v${global.version}`); + +// global absolute root path +global.root_path = path.resolve(`${__dirname}/../`); + +if (process.env.MM_CONFIG_FILE) { + global.configuration_file = process.env.MM_CONFIG_FILE; +} + +// FIXME: Hotfix Pull Request +// https://github.com/MichMich/MagicMirror/pull/673 +if (process.env.MM_PORT) { + global.mmPort = process.env.MM_PORT; +} + +// The next part is here to prevent a major exception when there +// is no internet connection. This could probable be solved better. +process.on("uncaughtException", function (err) { + Log.error("Whoops! There was an uncaught exception..."); + Log.error(err); + Log.error("MagicMirror² will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?"); + Log.error("If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues"); +}); + +/** + * The core app. + * + * @class + */ +function App() { + let nodeHelpers = []; + let httpServer; + + /** + * Loads the config file. Combines it with the defaults and returns the config + * + * @async + * @returns {Promise<object>} the loaded config or the defaults if something goes wrong + */ + async function loadConfig() { + Log.log("Loading config ..."); + const defaults = require(`${__dirname}/defaults`); + + // For this check proposed to TestSuite + // https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8 + const configFilename = path.resolve(global.configuration_file || `${global.root_path}/config/config.js`); + let templateFile = `${configFilename}.template`; + + // check if templateFile exists + try { + fs.accessSync(templateFile, fs.F_OK); + } catch (err) { + templateFile = null; + Log.debug("config template file not exists, no envsubst"); + } + + if (templateFile) { + // save current config.js + try { + if (fs.existsSync(configFilename)) { + fs.copyFileSync(configFilename, `${configFilename}_${Date.now()}`); + } + } catch (err) { + Log.warn(`Could not copy ${configFilename}: ${err.message}`); + } + + // check if config.env exists + const envFiles = []; + const configEnvFile = `${configFilename.substr(0, configFilename.lastIndexOf("."))}.env`; + try { + if (fs.existsSync(configEnvFile)) { + envFiles.push(configEnvFile); + } + } catch (err) { + Log.debug(`${configEnvFile} does not exist. ${err.message}`); + } + + let options = { + all: true, + diff: false, + envFiles: envFiles, + protect: false, + syntax: "default", + system: true + }; + + // envsubst variables in templateFile and create new config.js + // naming for envsub must be templateFile and outputFile + const outputFile = configFilename; + try { + await envsub({ templateFile, outputFile, options }); + } catch (err) { + Log.error(`Could not envsubst variables: ${err.message}`); + } + } + + try { + fs.accessSync(configFilename, fs.F_OK); + const c = require(configFilename); + checkDeprecatedOptions(c); + return Object.assign(defaults, c); + } 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}`)); + } else { + Log.error(Utils.colors.error(`WARNING! Could not load config file. Starting with default configuration. Error found: ${e}`)); + } + } + + return defaults; + } + + /** + * Checks the config for deprecated options and throws a warning in the logs + * if it encounters one option from the deprecated.js list + * + * @param {object} userConfig The user config + */ + function checkDeprecatedOptions(userConfig) { + const deprecated = require(`${global.root_path}/js/deprecated`); + const deprecatedOptions = deprecated.configs; + + 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.`)); + } + } + + /** + * Loads a specific module. + * + * @param {string} module The name of the module (including subpath). + */ + function loadModule(module) { + const elements = module.split("/"); + const moduleName = elements[elements.length - 1]; + let moduleFolder = `${__dirname}/../modules/${module}`; + + if (defaultModules.includes(moduleName)) { + moduleFolder = `${__dirname}/../modules/default/${module}`; + } + + const moduleFile = `${moduleFolder}/${module}.js`; + + try { + fs.accessSync(moduleFile, fs.R_OK); + } catch (e) { + Log.warn(`No ${moduleFile} found for module: ${moduleName}.`); + } + + const helperPath = `${moduleFolder}/node_helper.js`; + + let loadHelper = true; + try { + fs.accessSync(helperPath, fs.R_OK); + } catch (e) { + loadHelper = false; + Log.log(`No helper found for module: ${moduleName}.`); + } + + 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}`); + if (cmpVersions(global.version, m.requiresVersion) >= 0) { + Log.log("Version is ok!"); + } else { + Log.warn(`Version is incorrect. Skip module: '${moduleName}'`); + return; + } + } + + m.setName(moduleName); + m.setPath(path.resolve(moduleFolder)); + nodeHelpers.push(m); + + m.loaded(); + } + } + + /** + * Loads all modules. + * + * @param {string[]} modules All modules to be loaded + */ + async function loadModules(modules) { + return new Promise((resolve) => { + Log.log("Loading module helpers ..."); + + /** + * + */ + function loadNextModule() { + if (modules.length > 0) { + const nextModule = modules[0]; + loadModule(nextModule); + modules = modules.slice(1); + loadNextModule(); + } else { + // All modules are loaded + Log.log("All module helpers loaded."); + resolve(); + } + } + + loadNextModule(); + }); + } + + /** + * Compare two semantic version numbers and return the difference. + * + * @param {string} a Version number a. + * @param {string} b Version number b. + * @returns {number} A positive number if a is larger than b, a negative + * number if a is smaller and 0 if they are the same + */ + function cmpVersions(a, b) { + 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); + if (diff) { + return diff; + } + } + return segmentsA.length - segmentsB.length; + } + + /** + * Start the core app. + * + * It loads the config, then it loads all modules. + * + * @async + * @returns {Promise<object>} the config used + */ + this.start = async function () { + config = await loadConfig(); + + Log.setLogLevel(config.logLevel); + + let modules = []; + for (const module of config.modules) { + if (!modules.includes(module.module) && !module.disabled) { + modules.push(module.module); + } + } + await loadModules(modules); + + httpServer = new Server(config); + const { app, io } = await httpServer.open(); + Log.log("Server started ..."); + + const nodePromises = []; + for (let nodeHelper of nodeHelpers) { + nodeHelper.setExpressApp(app); + nodeHelper.setSocketIO(io); + + try { + nodePromises.push(nodeHelper.start()); + } catch (error) { + Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`); + Log.error(error); + } + } + + const results = await Promise.allSettled(nodePromises); + + // Log errors that happened during async node_helper startup + results.forEach((result) => { + if (result.status === "rejected") { + Log.error(result.reason); + } + }); + + Log.log("Sockets connected & modules started ..."); + + return config; + }; + + /** + * Stops the core app. This calls each node_helper's STOP() function, if it + * exists. + * + * Added to fix #1056 + * + * @returns {Promise} A promise that is resolved when all node_helpers and + * the http server has been closed + */ + this.stop = async function () { + const nodePromises = []; + for (let nodeHelper of nodeHelpers) { + try { + if (typeof nodeHelper.stop === "function") { + nodePromises.push(nodeHelper.stop()); + } + } catch (error) { + Log.error(`Error when stopping node_helper for module ${nodeHelper.name}:`); + console.error(error); + } + } + + const results = await Promise.allSettled(nodePromises); + + // Log errors that happened during async node_helper stopping + results.forEach((result) => { + if (result.status === "rejected") { + Log.error(result.reason); + } + }); + + Log.log("Node_helpers stopped ..."); + + // To be able to stop the app even if it hasn't been started (when + // running with Electron against another server) + if (!httpServer) { + return Promise.resolve(); + } + + return httpServer.close(); + }; + + /** + * Listen for SIGINT signal and call stop() function. + * + * Added to fix #1056 + * Note: this is only used if running `server-only`. Otherwise + * this.stop() is called by app.on("before-quit"... in `electron.js` + */ + process.on("SIGINT", async () => { + Log.log("[SIGINT] Received. Shutting down server..."); + setTimeout(() => { + process.exit(0); + }, 3000); // Force quit after 3 seconds + await this.stop(); + process.exit(0); + }); + + /** + * Listen to SIGTERM signals so we can stop everything when we + * are asked to stop by the OS. + */ + process.on("SIGTERM", async () => { + Log.log("[SIGTERM] Received. Shutting down server..."); + setTimeout(() => { + process.exit(0); + }, 3000); // Force quit after 3 seconds + await this.stop(); + process.exit(0); + }); +} + +module.exports = new App(); diff --git a/MagicMirror/js/check_config.js b/MagicMirror/js/check_config.js new file mode 100644 index 0000000000000000000000000000000000000000..24368734e863fccfd343dfee6391369d29ecff14 --- /dev/null +++ b/MagicMirror/js/check_config.js @@ -0,0 +1,73 @@ +/* MagicMirror² + * + * Check the configuration file for errors + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +const path = require("path"); +const fs = require("fs"); +const { Linter } = require("eslint"); + +const linter = new Linter(); + +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. + * Check if set by environment variable MM_CONFIG_FILE + * + * @returns {string} path and filename of the config file + */ +function getConfigFile() { + // FIXME: This function should be in core. Do you want refactor me ;) ?, be good! + return path.resolve(process.env.MM_CONFIG_FILE || `${rootPath}/config/config.js`); +} + +/** + * Checks the config file using eslint. + */ +function checkConfigFile() { + const configFileName = getConfigFile(); + + // Check if file is present + if (fs.existsSync(configFileName) === false) { + Log.error(Utils.colors.error("File not found: "), configFileName); + throw new Error("No config file present!"); + } + + // Check permission + try { + fs.accessSync(configFileName, fs.F_OK); + } catch (e) { + Log.error(Utils.colors.error(e)); + throw new Error("No permission to access config file!"); + } + + // Validate syntax of the configuration file. + Log.info(Utils.colors.info("Checking file... "), configFileName); + + // I'm not sure if all ever is utf-8 + const configFile = fs.readFileSync(configFileName, "utf-8"); + + // Explicitly tell linter that he might encounter es6 syntax ("let config = {...}") + const errors = linter.verify(configFile, { + env: { + es6: true + } + }); + + 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}`); + } + } +} + +checkConfigFile(); diff --git a/MagicMirror/js/class.js b/MagicMirror/js/class.js new file mode 100644 index 0000000000000000000000000000000000000000..339e24b2a6ba869366ea5d36a0d0c4a016b98861 --- /dev/null +++ b/MagicMirror/js/class.js @@ -0,0 +1,109 @@ +/* global Class, xyz */ + +/* Simple JavaScript Inheritance + * By John Resig https://johnresig.com/ + * + * Inspired by base2 and Prototype + * + * MIT Licensed. + */ +(function () { + let initializing = false; + const fnTest = /xyz/.test(function () { + xyz; + }) + ? /\b_super\b/ + : /.*/; + + // The base Class implementation (does nothing) + this.Class = function () {}; + + // Create a new Class that inherits from this class + Class.extend = function (prop) { + let _super = this.prototype; + + // Instantiate a base class (but only create the instance, + // don't run the init constructor) + initializing = true; + const prototype = new this(); + initializing = false; + + // Make a copy of all prototype properties, to prevent reference issues. + for (const p in prototype) { + prototype[p] = cloneObject(prototype[p]); + } + + // Copy the properties over onto the new prototype + for (const name in prop) { + // Check if we're overwriting an existing function + prototype[name] = + typeof prop[name] === "function" && typeof _super[name] === "function" && fnTest.test(prop[name]) + ? (function (name, fn) { + return function () { + const tmp = this._super; + + // Add a new ._super() method that is the same method + // but on the super-class + this._super = _super[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + const ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]) + : prop[name]; + } + + /** + * The dummy class constructor + */ + function Class() { + // All construction is actually done in the init method + if (!initializing && this.init) { + this.init.apply(this, arguments); + } + } + + // Populate our constructed prototype object + Class.prototype = prototype; + + // Enforce the constructor to be what we expect + Class.prototype.constructor = Class; + + // And make this class extendable + Class.extend = arguments.callee; + + return Class; + }; +})(); + +/** + * Define the clone method for later use. Helper Method. + * + * @param {object} obj Object to be cloned + * @returns {object} the cloned object + */ +function cloneObject(obj) { + if (obj === null || typeof obj !== "object") { + return obj; + } + + const temp = obj.constructor(); // give temp the original obj's constructor + for (const key in obj) { + temp[key] = cloneObject(obj[key]); + + if (key === "lockStrings") { + Log.log(key); + } + } + + return temp; +} + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = Class; +} diff --git a/MagicMirror/js/defaults.js b/MagicMirror/js/defaults.js new file mode 100644 index 0000000000000000000000000000000000000000..b2edb8e4ff71df4b985318bf40d6e861f2809b3c --- /dev/null +++ b/MagicMirror/js/defaults.js @@ -0,0 +1,87 @@ +/* global mmPort */ + +/* MagicMirror² + * Config Defaults + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +const address = "localhost"; +let port = 8080; +if (typeof mmPort !== "undefined") { + port = mmPort; +} +const defaults = { + address: address, + port: port, + basePath: "/", + kioskmode: false, + electronOptions: {}, + ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], + + language: "en", + logLevel: ["INFO", "LOG", "WARN", "ERROR"], + timeFormat: 24, + units: "metric", + zoom: 1, + customCss: "css/custom.css", + // httpHeaders used by helmet, see https://helmetjs.github.io/. You can add other/more object values by overriding this in config.js, + // e.g. you need to add `frameguard: false` for embedding MagicMirror in another website, see https://github.com/MichMich/MagicMirror/issues/2847 + httpHeaders: { contentSecurityPolicy: false, crossOriginOpenerPolicy: false, crossOriginEmbedderPolicy: false, crossOriginResourcePolicy: false, originAgentCluster: false }, + + modules: [ + { + module: "updatenotification", + position: "top_center" + }, + { + module: "helloworld", + position: "upper_third", + classes: "large thin", + config: { + text: "MagicMirror²" + } + }, + { + module: "helloworld", + position: "middle_center", + config: { + text: "Please create a config file or check the existing one for errors." + } + }, + { + module: "helloworld", + position: "middle_center", + classes: "small dimmed", + config: { + text: "See README for more information." + } + }, + { + module: "helloworld", + position: "middle_center", + classes: "xsmall", + config: { + 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>" + } + }, + { + module: "helloworld", + position: "bottom_bar", + classes: "xsmall dimmed", + config: { + text: "www.michaelteeuw.nl" + } + } + ], + + paths: { + modules: "modules", + vendor: "vendor" + } +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = defaults; +} diff --git a/MagicMirror/js/deprecated.js b/MagicMirror/js/deprecated.js new file mode 100644 index 0000000000000000000000000000000000000000..e2c70322ac1a89e4650084fe99be6ce3b3ccee11 --- /dev/null +++ b/MagicMirror/js/deprecated.js @@ -0,0 +1,11 @@ +/* MagicMirror² Deprecated Config Options List + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + * + * Olex S. original idea this deprecated option + */ + +module.exports = { + configs: ["kioskmode"] +}; diff --git a/MagicMirror/js/electron.js b/MagicMirror/js/electron.js new file mode 100644 index 0000000000000000000000000000000000000000..8a474bb32e26b30bb561e4d3eedd53806c3b7b62 --- /dev/null +++ b/MagicMirror/js/electron.js @@ -0,0 +1,182 @@ +"use strict"; + +const electron = require("electron"); +const core = require("./app"); +const Log = require("./logger"); + +// Config +let config = process.env.config ? JSON.parse(process.env.config) : {}; +// Module to control application life. +const app = electron.app; +// If ELECTRON_DISABLE_GPU is set electron is started with --disable-gpu flag. +// See https://www.electronjs.org/docs/latest/tutorial/offscreen-rendering for more info. +if (process.env.ELECTRON_DISABLE_GPU !== undefined) { + app.disableHardwareAcceleration(); +} + +// Module to create native browser window. +const BrowserWindow = electron.BrowserWindow; + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow; + +/** + * + */ +function createWindow() { + let electronSwitchesDefaults = ["autoplay-policy", "no-user-gesture-required"]; + app.commandLine.appendSwitch(...new Set(electronSwitchesDefaults, config.electronSwitches)); + let electronOptionsDefaults = { + width: 800, + height: 600, + x: 0, + y: 0, + darkTheme: true, + webPreferences: { + contextIsolation: true, + nodeIntegration: false, + zoomFactor: config.zoom + }, + backgroundColor: "#000000" + }; + + // DEPRECATED: "kioskmode" backwards compatibility, to be removed + // settings these options directly instead provides cleaner interface + if (config.kioskmode) { + electronOptionsDefaults.kiosk = true; + } else { + electronOptionsDefaults.show = false; + electronOptionsDefaults.frame = false; + electronOptionsDefaults.transparent = true; + electronOptionsDefaults.hasShadow = false; + } + + const electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions); + + // Create the browser window. + mainWindow = new BrowserWindow(electronOptions); + + // 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 + + let prefix; + if ((config["tls"] !== null && config["tls"]) || config.useHttps) { + prefix = "https://"; + } else { + prefix = "http://"; + } + + 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" + if (process.argv.includes("dev")) { + if (process.env.JEST_WORKER_ID !== undefined) { + // if we are running with jest + const devtools = new BrowserWindow(electronOptions); + mainWindow.webContents.setDevToolsWebContents(devtools.webContents); + } + mainWindow.webContents.openDevTools(); + } + + // simulate mouse move to hide black cursor on start + mainWindow.webContents.on("dom-ready", (event) => { + mainWindow.webContents.sendInputEvent({ type: "mouseMove", x: 0, y: 0 }); + }); + + // Set responders for window events. + mainWindow.on("closed", function () { + mainWindow = null; + }); + + if (config.kioskmode) { + mainWindow.on("blur", function () { + mainWindow.focus(); + }); + + mainWindow.on("leave-full-screen", function () { + mainWindow.setFullScreen(true); + }); + + mainWindow.on("resize", function () { + setTimeout(function () { + mainWindow.reload(); + }, 1000); + }); + } + + //remove response headers that prevent sites of being embedded into iframes if configured + mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => { + let curHeaders = details.responseHeaders; + if (config["ignoreXOriginHeader"] || false) { + curHeaders = Object.fromEntries(Object.entries(curHeaders).filter((header) => !/x-frame-options/i.test(header[0]))); + } + + if (config["ignoreContentSecurityPolicy"] || false) { + curHeaders = Object.fromEntries(Object.entries(curHeaders).filter((header) => !/content-security-policy/i.test(header[0]))); + } + + callback({ responseHeaders: curHeaders }); + }); + + mainWindow.once("ready-to-show", () => { + mainWindow.setFullScreen(true); + mainWindow.show(); + }); +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +app.on("ready", function () { + Log.log("Launching application."); + createWindow(); +}); + +// Quit when all windows are closed. +app.on("window-all-closed", function () { + if (process.env.JEST_WORKER_ID !== undefined) { + // if we are running with jest + app.quit(); + } else { + createWindow(); + } +}); + +app.on("activate", function () { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow(); + } +}); + +/* This method will be called when SIGINT is received and will call + * each node_helper's stop function if it exists. Added to fix #1056 + * + * Note: this is only used if running Electron. Otherwise + * core.stop() is called by process.on("SIGINT"... in `app.js` + */ +app.on("before-quit", async (event) => { + Log.log("Shutting down server..."); + event.preventDefault(); + setTimeout(() => { + process.exit(0); + }, 3000); // Force-quit after 3 seconds. + await core.stop(); + process.exit(0); +}); + +/** + * Handle errors from self-signed certificates + */ +app.on("certificate-error", (event, webContents, url, error, certificate, callback) => { + event.preventDefault(); + callback(true); +}); + +// 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].includes(config.address)) { + core.start().then((c) => (config = c)); +} diff --git a/MagicMirror/js/fetch.js b/MagicMirror/js/fetch.js new file mode 100644 index 0000000000000000000000000000000000000000..3ae874c813c80593423e4cc49d29af5cbdcd7f70 --- /dev/null +++ b/MagicMirror/js/fetch.js @@ -0,0 +1,28 @@ +/** + * Helper class to provide either third party fetch library or (if node >= 18) + * return internal node fetch implementation. + * + * Attention: After some discussion we always return the third party + * implementation until the node implementation is stable and more tested + * + * @see https://github.com/MichMich/MagicMirror/pull/2952 + * @see https://github.com/MichMich/MagicMirror/issues/2649 + * @param {string} url to be fetched + * @param {object} options object e.g. for headers + * @class + */ +async function fetch(url, options = {}) { + // const nodeVersion = process.version.match(/^v(\d+)\.*/)[1]; + // if (nodeVersion >= 18) { + // // node version >= 18 + // return global.fetch(url, options); + // } else { + // // node version < 18 + // const nodefetch = require("node-fetch"); + // return nodefetch(url, options); + // } + const nodefetch = require("node-fetch"); + return nodefetch(url, options); +} + +module.exports = fetch; diff --git a/MagicMirror/js/loader.js b/MagicMirror/js/loader.js new file mode 100644 index 0000000000000000000000000000000000000000..930d57c86beb074a568e5eab6a41168879da5c28 --- /dev/null +++ b/MagicMirror/js/loader.js @@ -0,0 +1,263 @@ +/* global defaultModules, vendor */ + +/* MagicMirror² + * Module and File loaders. + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +const Loader = (function () { + /* Create helper variables */ + + const loadedModuleFiles = []; + const loadedFiles = []; + const moduleObjects = []; + + /* Private Methods */ + + /** + * Loops through all modules and requests start for every module. + */ + const startModules = async function () { + const modulePromises = []; + for (const module of moduleObjects) { + try { + modulePromises.push(module.start()); + } catch (error) { + Log.error(`Error when starting node_helper for module ${module.name}:`); + Log.error(error); + } + } + + const results = await Promise.allSettled(modulePromises); + + // Log errors that happened during async node_helper startup + results.forEach((result) => { + if (result.status === "rejected") { + Log.error(result.reason); + } + }); + + // Notify core of loaded modules. + MM.modulesStarted(moduleObjects); + + // Starting modules also hides any modules that have requested to be initially hidden + for (const thisModule of moduleObjects) { + if (thisModule.data.hiddenOnStartup) { + Log.info(`Initially hiding ${thisModule.name}`); + thisModule.hide(); + } + } + }; + + /** + * Retrieve list of all modules. + * + * @returns {object[]} module data as configured in config + */ + const getAllModules = function () { + return config.modules; + }; + + /** + * Generate array with module information including module paths. + * + * @returns {object[]} Module information. + */ + const getModuleData = function () { + const modules = getAllModules(); + const moduleFiles = []; + + modules.forEach(function (moduleData, index) { + const module = moduleData.module; + + const elements = module.split("/"); + const moduleName = elements[elements.length - 1]; + let moduleFolder = `${config.paths.modules}/${module}`; + + if (defaultModules.indexOf(moduleName) !== -1) { + moduleFolder = `${config.paths.modules}/default/${module}`; + } + + if (moduleData.disabled === true) { + return; + } + + moduleFiles.push({ + index: index, + identifier: `module_${index}_${module}`, + name: moduleName, + 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, + classes: typeof moduleData.classes !== "undefined" ? `${moduleData.classes} ${module}` : module + }); + }); + + return moduleFiles; + }; + + /** + * Load modules via ajax request and create module objects. + * + * @param {object} module Information about the module we want to load. + * @returns {Promise<void>} resolved when module is loaded + */ + const loadModule = async function (module) { + const url = module.path + module.file; + + /** + * @returns {Promise<void>} + */ + const afterLoad = async function () { + const moduleObject = Module.create(module.name); + if (moduleObject) { + await bootstrapModule(module, moduleObject); + } + }; + + if (loadedModuleFiles.indexOf(url) !== -1) { + await afterLoad(); + } else { + await loadFile(url); + loadedModuleFiles.push(url); + await afterLoad(); + } + }; + + /** + * Bootstrap modules by setting the module data and loading the scripts & styles. + * + * @param {object} module Information about the module we want to load. + * @param {Module} mObj Modules instance. + */ + const bootstrapModule = async function (module, mObj) { + Log.info(`Bootstrapping module: ${module.name}`); + mObj.setData(module); + + await mObj.loadScripts(); + Log.log(`Scripts loaded for: ${module.name}`); + + await mObj.loadStyles(); + Log.log(`Styles loaded for: ${module.name}`); + + await mObj.loadTranslations(); + Log.log(`Translations loaded for: ${module.name}`); + + moduleObjects.push(mObj); + }; + + /** + * Load a script or stylesheet by adding it to the dom. + * + * @param {string} fileName Path of the file we want to load. + * @returns {Promise} resolved when the file is loaded + */ + const loadFile = async function (fileName) { + const extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1); + let script, stylesheet; + + switch (extension.toLowerCase()) { + case "js": + return new Promise((resolve) => { + Log.log(`Load script: ${fileName}`); + script = document.createElement("script"); + script.type = "text/javascript"; + script.src = fileName; + script.onload = function () { + resolve(); + }; + script.onerror = function () { + Log.error("Error on loading script:", fileName); + resolve(); + }; + document.getElementsByTagName("body")[0].appendChild(script); + }); + case "css": + return new Promise((resolve) => { + Log.log(`Load stylesheet: ${fileName}`); + + stylesheet = document.createElement("link"); + stylesheet.rel = "stylesheet"; + stylesheet.type = "text/css"; + stylesheet.href = fileName; + stylesheet.onload = function () { + resolve(); + }; + stylesheet.onerror = function () { + Log.error("Error on loading stylesheet:", fileName); + resolve(); + }; + document.getElementsByTagName("head")[0].appendChild(stylesheet); + }); + } + }; + + /* Public Methods */ + return { + /** + * Load all modules as defined in the config. + */ + loadModules: async function () { + let moduleData = getModuleData(); + + /** + * @returns {Promise<void>} when all modules are loaded + */ + const loadNextModule = async function () { + if (moduleData.length > 0) { + const nextModule = moduleData[0]; + await loadModule(nextModule); + moduleData = moduleData.slice(1); + await loadNextModule(); + } else { + // All modules loaded. Load custom.css + // This is done after all the modules so we can + // overwrite all the defined styles. + await loadFile(config.customCss); + // custom.css loaded. Start all modules. + await startModules(); + } + }; + await loadNextModule(); + }, + + /** + * Load a file (script or stylesheet). + * Prevent double loading and search for files in the vendor folder. + * + * @param {string} fileName Path of the file we want to load. + * @param {Module} module The module that calls the loadFile function. + * @returns {Promise} resolved when the file is loaded + */ + loadFileForModule: async function (fileName, module) { + if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) { + Log.log(`File already loaded: ${fileName}`); + return; + } + + if (fileName.indexOf("http://") === 0 || fileName.indexOf("https://") === 0 || fileName.indexOf("/") !== -1) { + // This is an absolute or relative path. + // Load it and then return. + loadedFiles.push(fileName.toLowerCase()); + return loadFile(fileName); + } + + if (vendor[fileName] !== undefined) { + // This file is available in the vendor folder. + // Load it from this vendor folder. + loadedFiles.push(fileName.toLowerCase()); + return loadFile(`${config.paths.vendor}/${vendor[fileName]}`); + } + + // File not loaded yet. + // Load it based on the module path. + loadedFiles.push(fileName.toLowerCase()); + return loadFile(module.file(fileName)); + } + }; +})(); diff --git a/MagicMirror/js/logger.js b/MagicMirror/js/logger.js new file mode 100644 index 0000000000000000000000000000000000000000..e6a244cb73475ea2ed6e510b157bbf4bf2a6b2a4 --- /dev/null +++ b/MagicMirror/js/logger.js @@ -0,0 +1,79 @@ +/* MagicMirror² + * Log + * + * This logger is very simple, but needs to be extended. + * This system can eventually be used to push the log messages to an external target. + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +(function (root, factory) { + if (typeof exports === "object") { + if (process.env.JEST_WORKER_ID === undefined) { + // add timestamps in front of log messages + require("console-stamp")(console, { + pattern: "yyyy-mm-dd HH:MM:ss.l", + include: ["debug", "log", "info", "warn", "error"] + }); + } + // Node, CommonJS-like + module.exports = factory(root.config); + } else { + // Browser globals (root is window) + root.Log = factory(root.config); + } +})(this, function (config) { + let logLevel; + let enableLog; + if (typeof exports === "object") { + // in nodejs and not running with jest + enableLog = process.env.JEST_WORKER_ID === undefined; + } else { + // in browser and not running with jsdom + enableLog = typeof window === "object" && window.name !== "jsdom"; + } + + if (enableLog) { + logLevel = { + debug: Function.prototype.bind.call(console.debug, console), + log: Function.prototype.bind.call(console.log, console), + info: Function.prototype.bind.call(console.info, console), + warn: Function.prototype.bind.call(console.warn, console), + error: Function.prototype.bind.call(console.error, console), + group: Function.prototype.bind.call(console.group, console), + groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console), + groupEnd: Function.prototype.bind.call(console.groupEnd, console), + time: Function.prototype.bind.call(console.time, console), + timeEnd: Function.prototype.bind.call(console.timeEnd, console), + timeStamp: Function.prototype.bind.call(console.timeStamp, console) + }; + + logLevel.setLogLevel = function (newLevel) { + if (newLevel) { + Object.keys(logLevel).forEach(function (key, index) { + if (!newLevel.includes(key.toLocaleUpperCase())) { + logLevel[key] = function () {}; + } + }); + } + }; + } else { + logLevel = { + debug: function () {}, + log: function () {}, + info: function () {}, + warn: function () {}, + error: function () {}, + group: function () {}, + groupCollapsed: function () {}, + groupEnd: function () {}, + time: function () {}, + timeEnd: function () {}, + timeStamp: function () {} + }; + + logLevel.setLogLevel = function () {}; + } + + return logLevel; +}); diff --git a/MagicMirror/js/main.js b/MagicMirror/js/main.js new file mode 100644 index 0000000000000000000000000000000000000000..6e3ec64caa0457cb2557efc93501a101c4382827 --- /dev/null +++ b/MagicMirror/js/main.js @@ -0,0 +1,617 @@ +/* global Loader, defaults, Translator */ + +/* MagicMirror² + * Main System + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +const MM = (function () { + let modules = []; + + /* Private Methods */ + + /** + * Create dom objects for all modules that are configured for a specific position. + */ + const createDomObjects = function () { + const domCreationPromises = []; + + modules.forEach(function (module) { + if (typeof module.data.position !== "string") { + return; + } + + const wrapper = selectWrapper(module.data.position); + + const dom = document.createElement("div"); + dom.id = module.identifier; + dom.className = module.name; + + if (typeof module.data.classes === "string") { + dom.className = `module ${dom.className} ${module.data.classes}`; + } + + dom.opacity = 0; + wrapper.appendChild(dom); + + const moduleHeader = document.createElement("header"); + moduleHeader.innerHTML = module.getHeader(); + moduleHeader.className = "module-header"; + dom.appendChild(moduleHeader); + + if (typeof module.getHeader() === "undefined" || module.getHeader() !== "") { + moduleHeader.style.display = "none;"; + } else { + moduleHeader.style.display = "block;"; + } + + const moduleContent = document.createElement("div"); + moduleContent.className = "module-content"; + dom.appendChild(moduleContent); + + const domCreationPromise = updateDom(module, 0); + domCreationPromises.push(domCreationPromise); + domCreationPromise + .then(function () { + sendNotification("MODULE_DOM_CREATED", null, null, module); + }) + .catch(Log.error); + }); + + updateWrapperStates(); + + Promise.all(domCreationPromises).then(function () { + sendNotification("DOM_OBJECTS_CREATED"); + }); + }; + + /** + * Select the wrapper dom object for a specific position. + * + * @param {string} position The name of the position. + * @returns {HTMLElement | void} the wrapper element + */ + const selectWrapper = function (position) { + const classes = position.replace("_", " "); + const parentWrapper = document.getElementsByClassName(classes); + if (parentWrapper.length > 0) { + const wrapper = parentWrapper[0].getElementsByClassName("container"); + if (wrapper.length > 0) { + return wrapper[0]; + } + } + }; + + /** + * Send a notification to all modules. + * + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. + * @param {Module} sender The module that sent the notification. + * @param {Module} [sendTo] The (optional) module to send the notification to. + */ + const sendNotification = function (notification, payload, sender, sendTo) { + for (const m in modules) { + const module = modules[m]; + if (module !== sender && (!sendTo || module === sendTo)) { + module.notificationReceived(notification, payload, sender); + } + } + }; + + /** + * Update the dom for a specific module. + * + * @param {Module} module The module that needs an update. + * @param {number} [speed] The (optional) number of microseconds for the animation. + * @returns {Promise} Resolved when the dom is fully updated. + */ + const updateDom = function (module, speed) { + return new Promise(function (resolve) { + const newHeader = module.getHeader(); + let newContentPromise = module.getDom(); + + if (!(newContentPromise instanceof Promise)) { + // convert to a promise if not already one to avoid if/else's everywhere + newContentPromise = Promise.resolve(newContentPromise); + } + + newContentPromise + .then(function (newContent) { + const updatePromise = updateDomWithContent(module, speed, newHeader, newContent); + + updatePromise.then(resolve).catch(Log.error); + }) + .catch(Log.error); + }); + }; + + /** + * Update the dom with the specified content + * + * @param {Module} module The module that needs an update. + * @param {number} [speed] The (optional) number of microseconds for the animation. + * @param {string} newHeader The new header that is generated. + * @param {HTMLElement} newContent The new content that is generated. + * @returns {Promise} Resolved when the module dom has been updated. + */ + const updateDomWithContent = function (module, speed, newHeader, newContent) { + return new Promise(function (resolve) { + if (module.hidden || !speed) { + updateModuleContent(module, newHeader, newContent); + resolve(); + return; + } + + if (!moduleNeedsUpdate(module, newHeader, newContent)) { + resolve(); + return; + } + + if (!speed) { + updateModuleContent(module, newHeader, newContent); + resolve(); + return; + } + + hideModule(module, speed / 2, function () { + updateModuleContent(module, newHeader, newContent); + if (!module.hidden) { + showModule(module, speed / 2); + } + resolve(); + }); + }); + }; + + /** + * Check if the content has changed. + * + * @param {Module} module The module to check. + * @param {string} newHeader The new header that is generated. + * @param {HTMLElement} newContent The new content that is generated. + * @returns {boolean} True if the module need an update, false otherwise + */ + const moduleNeedsUpdate = function (module, newHeader, newContent) { + const moduleWrapper = document.getElementById(module.identifier); + if (moduleWrapper === null) { + return false; + } + + const contentWrapper = moduleWrapper.getElementsByClassName("module-content"); + const headerWrapper = moduleWrapper.getElementsByClassName("module-header"); + + let headerNeedsUpdate = false; + let contentNeedsUpdate; + + if (headerWrapper.length > 0) { + headerNeedsUpdate = newHeader !== headerWrapper[0].innerHTML; + } + + const tempContentWrapper = document.createElement("div"); + tempContentWrapper.appendChild(newContent); + contentNeedsUpdate = tempContentWrapper.innerHTML !== contentWrapper[0].innerHTML; + + return headerNeedsUpdate || contentNeedsUpdate; + }; + + /** + * Update the content of a module on screen. + * + * @param {Module} module The module to check. + * @param {string} newHeader The new header that is generated. + * @param {HTMLElement} newContent The new content that is generated. + */ + const updateModuleContent = function (module, newHeader, newContent) { + const moduleWrapper = document.getElementById(module.identifier); + if (moduleWrapper === null) { + return; + } + const headerWrapper = moduleWrapper.getElementsByClassName("module-header"); + const contentWrapper = moduleWrapper.getElementsByClassName("module-content"); + + contentWrapper[0].innerHTML = ""; + contentWrapper[0].appendChild(newContent); + + headerWrapper[0].innerHTML = newHeader; + if (headerWrapper.length > 0 && newHeader) { + headerWrapper[0].style.display = "block"; + } else { + headerWrapper[0].style.display = "none"; + } + }; + + /** + * Hide the module. + * + * @param {Module} module The module to hide. + * @param {number} speed The speed of the hide animation. + * @param {Function} callback Called when the animation is done. + * @param {object} [options] Optional settings for the hide method. + */ + const hideModule = function (module, speed, callback, options) { + options = options || {}; + + // set lockString if set in options. + if (options.lockString) { + // Log.log("Has lockstring: " + options.lockString); + if (module.lockStrings.indexOf(options.lockString) === -1) { + module.lockStrings.push(options.lockString); + } + } + + const moduleWrapper = document.getElementById(module.identifier); + if (moduleWrapper !== null) { + moduleWrapper.style.transition = `opacity ${speed / 1000}s`; + moduleWrapper.style.opacity = 0; + moduleWrapper.classList.add("hidden"); + + clearTimeout(module.showHideTimer); + module.showHideTimer = setTimeout(function () { + // To not take up any space, we just make the position absolute. + // since it's fade out anyway, we can see it lay above or + // below other modules. This works way better than adjusting + // the .display property. + moduleWrapper.style.position = "fixed"; + + updateWrapperStates(); + + if (typeof callback === "function") { + callback(); + } + }, speed); + } else { + // invoke callback even if no content, issue 1308 + if (typeof callback === "function") { + callback(); + } + } + }; + + /** + * Show the module. + * + * @param {Module} module The module to show. + * @param {number} speed The speed of the show animation. + * @param {Function} callback Called when the animation is done. + * @param {object} [options] Optional settings for the show method. + */ + const showModule = function (module, speed, callback, options) { + options = options || {}; + + // remove lockString if set in options. + if (options.lockString) { + const index = module.lockStrings.indexOf(options.lockString); + if (index !== -1) { + module.lockStrings.splice(index, 1); + } + } + + // Check if there are no more lockstrings set, or the force option is set. + // 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; + } + + module.hidden = false; + + // If forced show, clean current lockstrings. + if (module.lockStrings.length !== 0 && options.force === true) { + Log.log(`Force show of module: ${module.name}`); + module.lockStrings = []; + } + + const moduleWrapper = document.getElementById(module.identifier); + if (moduleWrapper !== null) { + moduleWrapper.style.transition = `opacity ${speed / 1000}s`; + // Restore the position. See hideModule() for more info. + moduleWrapper.style.position = "static"; + moduleWrapper.classList.remove("hidden"); + + updateWrapperStates(); + + // Waiting for DOM-changes done in updateWrapperStates before we can start the animation. + const dummy = moduleWrapper.parentElement.parentElement.offsetHeight; + moduleWrapper.style.opacity = 1; + + clearTimeout(module.showHideTimer); + module.showHideTimer = setTimeout(function () { + if (typeof callback === "function") { + callback(); + } + }, speed); + } else { + // invoke callback + if (typeof callback === "function") { + callback(); + } + } + }; + + /** + * Checks for all positions if it has visible content. + * If not, if will hide the position to prevent unwanted margins. + * This method should be called by the show and hide methods. + * + * Example: + * If the top_bar only contains the update notification. And no update is available, + * the update notification is hidden. The top bar still occupies space making for + * an ugly top margin. By using this function, the top bar will be hidden if the + * update notification is not visible. + */ + const updateWrapperStates = function () { + const positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"]; + + positions.forEach(function (position) { + const wrapper = selectWrapper(position); + const moduleWrappers = wrapper.getElementsByClassName("module"); + + let showWrapper = false; + Array.prototype.forEach.call(moduleWrappers, function (moduleWrapper) { + if (moduleWrapper.style.position === "" || moduleWrapper.style.position === "static") { + showWrapper = true; + } + }); + + wrapper.style.display = showWrapper ? "block" : "none"; + }); + }; + + /** + * Loads the core config and combines it with the system defaults. + */ + const loadConfig = function () { + // FIXME: Think about how to pass config around without breaking tests + /* eslint-disable */ + if (typeof config === "undefined") { + config = defaults; + Log.error("Config file is missing! Please create a config file."); + return; + } + + config = Object.assign({}, defaults, config); + /* eslint-enable */ + }; + + /** + * Adds special selectors on a collection of modules. + * + * @param {Module[]} modules Array of modules. + */ + const setSelectionMethodsForModules = function (modules) { + /** + * Filter modules with the specified classes. + * + * @param {string|string[]} className one or multiple classnames (array or space divided). + * @returns {Module[]} Filtered collection of modules. + */ + const withClass = function (className) { + return modulesByClass(className, true); + }; + + /** + * Filter modules without the specified classes. + * + * @param {string|string[]} className one or multiple classnames (array or space divided). + * @returns {Module[]} Filtered collection of modules. + */ + const exceptWithClass = function (className) { + return modulesByClass(className, false); + }; + + /** + * Filters a collection of modules based on classname(s). + * + * @param {string|string[]} className one or multiple classnames (array or space divided). + * @param {boolean} include if the filter should include or exclude the modules with the specific classes. + * @returns {Module[]} Filtered collection of modules. + */ + const modulesByClass = function (className, include) { + let searchClasses = className; + if (typeof className === "string") { + searchClasses = className.split(" "); + } + + const newModules = modules.filter(function (module) { + const classes = module.data.classes.toLowerCase().split(" "); + + for (const searchClass of searchClasses) { + if (classes.indexOf(searchClass.toLowerCase()) !== -1) { + return include; + } + } + + return !include; + }); + + setSelectionMethodsForModules(newModules); + return newModules; + }; + + /** + * Removes a module instance from the collection. + * + * @param {object} module The module instance to remove from the collection. + * @returns {Module[]} Filtered collection of modules. + */ + const exceptModule = function (module) { + const newModules = modules.filter(function (mod) { + return mod.identifier !== module.identifier; + }); + + setSelectionMethodsForModules(newModules); + return newModules; + }; + + /** + * Walks thru a collection of modules and executes the callback with the module as an argument. + * + * @param {Function} callback The function to execute with the module as an argument. + */ + const enumerate = function (callback) { + modules.map(function (module) { + callback(module); + }); + }; + + if (typeof modules.withClass === "undefined") { + Object.defineProperty(modules, "withClass", { value: withClass, enumerable: false }); + } + if (typeof modules.exceptWithClass === "undefined") { + Object.defineProperty(modules, "exceptWithClass", { value: exceptWithClass, enumerable: false }); + } + if (typeof modules.exceptModule === "undefined") { + Object.defineProperty(modules, "exceptModule", { value: exceptModule, enumerable: false }); + } + if (typeof modules.enumerate === "undefined") { + Object.defineProperty(modules, "enumerate", { value: enumerate, enumerable: false }); + } + }; + + return { + /* Public Methods */ + + /** + * Main init method. + */ + init: async function () { + Log.info("Initializing MagicMirror²."); + loadConfig(); + + Log.setLogLevel(config.logLevel); + + await Translator.loadCoreTranslations(config.language); + await Loader.loadModules(); + }, + + /** + * Gets called when all modules are started. + * + * @param {Module[]} moduleObjects All module instances. + */ + modulesStarted: function (moduleObjects) { + modules = []; + moduleObjects.forEach((module) => modules.push(module)); + + Log.info("All modules started!"); + sendNotification("ALL_MODULES_STARTED"); + + createDomObjects(); + }, + + /** + * Send a notification to all modules. + * + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. + * @param {Module} sender The module that sent the notification. + */ + sendNotification: function (notification, payload, sender) { + if (arguments.length < 3) { + Log.error("sendNotification: Missing arguments."); + return; + } + + if (typeof notification !== "string") { + Log.error("sendNotification: Notification should be a string."); + return; + } + + if (!(sender instanceof Module)) { + Log.error("sendNotification: Sender should be a module."); + return; + } + + // Further implementation is done in the private method. + sendNotification(notification, payload, sender); + }, + + /** + * Update the dom for a specific module. + * + * @param {Module} module The module that needs an update. + * @param {number} [speed] The number of microseconds for the animation. + */ + updateDom: function (module, speed) { + if (!(module instanceof Module)) { + Log.error("updateDom: Sender should be a module."); + 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); + }, + + /** + * Returns a collection of all modules currently active. + * + * @returns {Module[]} A collection of all modules currently active. + */ + getModules: function () { + setSelectionMethodsForModules(modules); + return modules; + }, + + /** + * Hide the module. + * + * @param {Module} module The module to hide. + * @param {number} speed The speed of the hide animation. + * @param {Function} callback Called when the animation is done. + * @param {object} [options] Optional settings for the hide method. + */ + hideModule: function (module, speed, callback, options) { + module.hidden = true; + hideModule(module, speed, callback, options); + }, + + /** + * Show the module. + * + * @param {Module} module The module to show. + * @param {number} speed The speed of the show animation. + * @param {Function} callback Called when the animation is done. + * @param {object} [options] Optional settings for the show method. + */ + showModule: function (module, speed, callback, options) { + // do not change module.hidden yet, only if we really show it later + showModule(module, speed, callback, options); + } + }; +})(); + +// Add polyfill for Object.assign. +if (typeof Object.assign !== "function") { + (function () { + Object.assign = function (target) { + "use strict"; + if (target === undefined || target === null) { + throw new TypeError("Cannot convert undefined or null to object"); + } + const output = Object(target); + for (let index = 1; index < arguments.length; index++) { + const source = arguments[index]; + if (source !== undefined && source !== null) { + for (const nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output; + }; + })(); +} + +MM.init(); diff --git a/MagicMirror/js/module.js b/MagicMirror/js/module.js new file mode 100644 index 0000000000000000000000000000000000000000..7f6b3804e21173926118ef9b66d34ef93063977b --- /dev/null +++ b/MagicMirror/js/module.js @@ -0,0 +1,529 @@ +/* global Class, cloneObject, Loader, MMSocket, nunjucks, Translator */ + +/* MagicMirror² + * Module Blueprint. + * @typedef {Object} Module + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +const Module = Class.extend({ + /********************************************************* + * All methods (and properties) below can be subclassed. * + *********************************************************/ + + // Set the minimum MagicMirror² module version for this module. + requiresVersion: "2.0.0", + + // Module config defaults. + defaults: {}, + + // Timer reference used for showHide animation callbacks. + showHideTimer: null, + + // Array to store lockStrings. These strings are used to lock + // visibility when hiding and showing module. + lockStrings: [], + + // Storage of the nunjucks Environment, + // This should not be referenced directly. + // Use the nunjucksEnvironment() to get it. + _nunjucksEnvironment: null, + + /** + * Called when the module is instantiated. + */ + init: function () { + //Log.log(this.defaults); + }, + + /** + * Called when the module is started. + */ + start: async function () { + Log.info(`Starting module: ${this.name}`); + }, + + /** + * Returns a list of scripts the module requires to be loaded. + * + * @returns {string[]} An array with filenames. + */ + getScripts: function () { + return []; + }, + + /** + * Returns a list of stylesheets the module requires to be loaded. + * + * @returns {string[]} An array with filenames. + */ + getStyles: function () { + return []; + }, + + /** + * Returns a map of translation files the module requires to be loaded. + * + * return Map<String, String> - + * + * @returns {*} A map with langKeys and filenames. + */ + getTranslations: function () { + return false; + }, + + /** + * Generates the dom which needs to be displayed. This method is called by the MagicMirror² core. + * This method can to be subclassed if the module wants to display info on the mirror. + * Alternatively, the getTemplate method could be subclassed. + * + * @returns {HTMLElement|Promise} The dom or a promise with the dom to display. + */ + getDom: function () { + return new Promise((resolve) => { + const div = document.createElement("div"); + const template = this.getTemplate(); + const templateData = this.getTemplateData(); + + // Check to see if we need to render a template string or a file. + if (/^.*((\.html)|(\.njk))$/.test(template)) { + // the template is a filename + this.nunjucksEnvironment().render(template, templateData, function (err, res) { + if (err) { + Log.error(err); + } + + div.innerHTML = res; + + resolve(div); + }); + } else { + // the template is a template string. + div.innerHTML = this.nunjucksEnvironment().renderString(template, templateData); + + resolve(div); + } + }); + }, + + /** + * Generates the header string which needs to be displayed if a user has a header configured for this module. + * This method is called by the MagicMirror² core, but only if the user has configured a default header for the module. + * This method needs to be subclassed if the module wants to display modified headers on the mirror. + * + * @returns {string} The header to display above the header. + */ + getHeader: function () { + return this.data.header; + }, + + /** + * Returns the template for the module which is used by the default getDom implementation. + * This method needs to be subclassed if the module wants to use a template. + * It can either return a template sting, or a template filename. + * If the string ends with '.html' it's considered a file from within the module's folder. + * + * @returns {string} The template string of filename. + */ + getTemplate: function () { + return `<div class="normal">${this.name}</div><div class="small dimmed">${this.identifier}</div>`; + }, + + /** + * Returns the data to be used in the template. + * This method needs to be subclassed if the module wants to use a custom data. + * + * @returns {object} The data for the template + */ + getTemplateData: function () { + return {}; + }, + + /** + * Called by the MagicMirror² core when a notification arrives. + * + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. + * @param {Module} sender The module that sent the notification. + */ + notificationReceived: function (notification, payload, sender) { + if (sender) { + // Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name); + } else { + // Log.log(this.name + " received a system notification: " + notification); + } + }, + + /** + * Returns the nunjucks environment for the current module. + * The environment is checked in the _nunjucksEnvironment instance variable. + * + * @returns {object} The Nunjucks Environment + */ + nunjucksEnvironment: function () { + if (this._nunjucksEnvironment !== null) { + return this._nunjucksEnvironment; + } + + this._nunjucksEnvironment = new nunjucks.Environment(new nunjucks.WebLoader(this.file(""), { async: true }), { + trimBlocks: true, + lstripBlocks: true + }); + + this._nunjucksEnvironment.addFilter("translate", (str, variables) => { + return nunjucks.runtime.markSafe(this.translate(str, variables)); + }); + + return this._nunjucksEnvironment; + }, + + /** + * Called when a socket notification arrives. + * + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. + */ + socketNotificationReceived: function (notification, payload) { + Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`); + }, + + /** + * Called when the module is hidden. + */ + suspend: function () { + Log.log(`${this.name} is suspended.`); + }, + + /** + * Called when the module is shown. + */ + resume: function () { + Log.log(`${this.name} is resumed.`); + }, + + /********************************************* + * The methods below don"t need subclassing. * + *********************************************/ + + /** + * Set the module data. + * + * @param {object} data The module data + */ + setData: function (data) { + this.data = data; + this.name = data.name; + this.identifier = data.identifier; + this.hidden = false; + + this.setConfig(data.config, data.configDeepMerge); + }, + + /** + * Set the module config and combine it with the module defaults. + * + * @param {object} config The combined module config. + * @param {boolean} deep Merge module config in deep. + */ + setConfig: function (config, deep) { + this.config = deep ? configMerge({}, this.defaults, config) : Object.assign({}, this.defaults, config); + }, + + /** + * Returns a socket object. If it doesn't exist, it's created. + * It also registers the notification callback. + * + * @returns {MMSocket} a socket object + */ + socket: function () { + if (typeof this._socket === "undefined") { + this._socket = new MMSocket(this.name); + } + + this._socket.setNotificationCallback((notification, payload) => { + this.socketNotificationReceived(notification, payload); + }); + + return this._socket; + }, + + /** + * Retrieve the path to a module file. + * + * @param {string} file Filename + * @returns {string} the file path + */ + file: function (file) { + return `${this.data.path}/${file}`.replace("//", "/"); + }, + + /** + * Load all required stylesheets by requesting the MM object to load the files. + * + * @returns {Promise<void>} + */ + loadStyles: function () { + return this.loadDependencies("getStyles"); + }, + + /** + * Load all required scripts by requesting the MM object to load the files. + * + * @returns {Promise<void>} + */ + loadScripts: function () { + return this.loadDependencies("getScripts"); + }, + + /** + * Helper method to load all dependencies. + * + * @param {string} funcName Function name to call to get scripts or styles. + * @returns {Promise<void>} + */ + loadDependencies: async function (funcName) { + let dependencies = this[funcName](); + + const loadNextDependency = async () => { + if (dependencies.length > 0) { + const nextDependency = dependencies[0]; + await Loader.loadFileForModule(nextDependency, this); + dependencies = dependencies.slice(1); + await loadNextDependency(); + } else { + return Promise.resolve(); + } + }; + + await loadNextDependency(); + }, + + /** + * Load all translations. + */ + loadTranslations: async function () { + const translations = this.getTranslations() || {}; + const language = config.language.toLowerCase(); + + const languages = Object.keys(translations); + const fallbackLanguage = languages[0]; + + if (languages.length === 0) { + return; + } + + const translationFile = translations[language]; + const translationsFallbackFile = translations[fallbackLanguage]; + + if (!translationFile) { + return Translator.load(this, translationsFallbackFile, true); + } + + await Translator.load(this, translationFile, false); + + if (translationFile !== translationsFallbackFile) { + return Translator.load(this, translationsFallbackFile, true); + } + }, + + /** + * Request the translation for a given key with optional variables and default value. + * + * @param {string} key The key of the string to translate + * @param {string|object} [defaultValueOrVariables] The default value or variables for translating. + * @param {string} [defaultValue] The default value with variables. + * @returns {string} the translated key + */ + translate: function (key, defaultValueOrVariables, defaultValue) { + if (typeof defaultValueOrVariables === "object") { + return Translator.translate(this, key, defaultValueOrVariables) || defaultValue || ""; + } + return Translator.translate(this, key) || defaultValueOrVariables || ""; + }, + + /** + * Request an (animated) update of the module. + * + * @param {number} [speed] The speed of the animation. + */ + updateDom: function (speed) { + MM.updateDom(this, speed); + }, + + /** + * Send a notification to all modules. + * + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. + */ + sendNotification: function (notification, payload) { + MM.sendNotification(notification, payload, this); + }, + + /** + * Send a socket notification to the node helper. + * + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. + */ + sendSocketNotification: function (notification, payload) { + this.socket().sendNotification(notification, payload); + }, + + /** + * Hide this module. + * + * @param {number} speed The speed of the hide animation. + * @param {Function} callback Called when the animation is done. + * @param {object} [options] Optional settings for the hide method. + */ + hide: function (speed, callback, options) { + if (typeof callback === "object") { + options = callback; + callback = function () {}; + } + + callback = callback || function () {}; + options = options || {}; + + MM.hideModule( + this, + speed, + () => { + this.suspend(); + callback(); + }, + options + ); + }, + + /** + * Show this module. + * + * @param {number} speed The speed of the show animation. + * @param {Function} callback Called when the animation is done. + * @param {object} [options] Optional settings for the show method. + */ + show: function (speed, callback, options) { + if (typeof callback === "object") { + options = callback; + callback = function () {}; + } + + callback = callback || function () {}; + options = options || {}; + + MM.showModule( + this, + speed, + () => { + this.resume(); + callback(); + }, + options + ); + } +}); + +/** + * Merging MagicMirror² (or other) default/config script by @bugsounet + * Merge 2 objects or/with array + * + * Usage: + * ------- + * this.config = configMerge({}, this.defaults, this.config) + * ------- + * arg1: initial object + * arg2: config model + * arg3: config to merge + * ------- + * why using it ? + * Object.assign() function don't to all job + * it don't merge all thing in deep + * -> object in object and array is not merging + * ------- + * + * Todo: idea of Mich determinate what do you want to merge or not + * + * @param {object} result the initial object + * @returns {object} the merged config + */ +function configMerge(result) { + const stack = Array.prototype.slice.call(arguments, 1); + let item, key; + + while (stack.length) { + item = stack.shift(); + for (key in item) { + if (item.hasOwnProperty(key)) { + if (typeof result[key] === "object" && result[key] && Object.prototype.toString.call(result[key]) !== "[object Array]") { + if (typeof item[key] === "object" && item[key] !== null) { + result[key] = configMerge({}, result[key], item[key]); + } else { + result[key] = item[key]; + } + } else { + result[key] = item[key]; + } + } + } + } + return result; +} + +Module.definitions = {}; + +Module.create = function (name) { + // Make sure module definition is available. + if (!Module.definitions[name]) { + return; + } + + const moduleDefinition = Module.definitions[name]; + const clonedDefinition = cloneObject(moduleDefinition); + + // Note that we clone the definition. Otherwise the objects are shared, which gives problems. + const ModuleClass = Module.extend(clonedDefinition); + + return new ModuleClass(); +}; + +Module.register = function (name, moduleDefinition) { + if (moduleDefinition.requiresVersion) { + Log.log(`Check MagicMirror² version for module '${name}' - Minimum version: ${moduleDefinition.requiresVersion} - Current version: ${window.mmVersion}`); + if (cmpVersions(window.mmVersion, moduleDefinition.requiresVersion) >= 0) { + Log.log("Version is ok!"); + } else { + Log.warn(`Version is incorrect. Skip module: '${name}'`); + return; + } + } + Log.log(`Module registered: ${name}`); + Module.definitions[name] = moduleDefinition; +}; + +window.Module = Module; + +/** + * Compare two semantic version numbers and return the difference. + * + * @param {string} a Version number a. + * @param {string} b Version number b. + * @returns {number} A positive number if a is larger than b, a negative + * number if a is smaller and 0 if they are the same + */ +function cmpVersions(a, b) { + const regExStrip0 = /(\.0+)+$/; + const segmentsA = a.replace(regExStrip0, "").split("."); + const segmentsB = b.replace(regExStrip0, "").split("."); + const l = Math.min(segmentsA.length, segmentsB.length); + + for (let i = 0; i < l; i++) { + let diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10); + if (diff) { + return diff; + } + } + return segmentsA.length - segmentsB.length; +} diff --git a/MagicMirror/js/node_helper.js b/MagicMirror/js/node_helper.js new file mode 100644 index 0000000000000000000000000000000000000000..03c6dca79604560c8063d21a27fe0f038569bc56 --- /dev/null +++ b/MagicMirror/js/node_helper.js @@ -0,0 +1,144 @@ +/* MagicMirror² + * Node Helper Superclass + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +const express = require("express"); +const Log = require("logger"); +const Class = require("./class"); + +const NodeHelper = Class.extend({ + init() { + Log.log("Initializing new module helper ..."); + }, + + loaded() { + Log.log(`Module helper loaded: ${this.name}`); + }, + + start() { + Log.log(`Starting module helper: ${this.name}`); + }, + + /** + * Called when the MagicMirror² server receives a `SIGINT` + * Close any open connections, stop any sub-processes and + * gracefully exit the module. + */ + stop() { + Log.log(`Stopping module helper: ${this.name}`); + }, + + /** + * This method is called when a socket notification arrives. + * + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. + */ + socketNotificationReceived(notification, payload) { + Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`); + }, + + /** + * Set the module name. + * + * @param {string} name Module name. + */ + setName(name) { + this.name = name; + }, + + /** + * Set the module path. + * + * @param {string} path Module path. + */ + setPath(path) { + this.path = path; + }, + + /* sendSocketNotification(notification, payload) + * Send a socket notification to the node helper. + * + * argument notification string - The identifier of the notification. + * argument payload mixed - The payload of the notification. + */ + sendSocketNotification(notification, payload) { + this.io.of(this.name).emit(notification, payload); + }, + + /* setExpressApp(app) + * Sets the express app object for this module. + * This allows you to host files from the created webserver. + * + * argument app Express app - The Express app object. + */ + setExpressApp(app) { + this.expressApp = app; + + app.use(`/${this.name}`, express.static(`${this.path}/public`)); + }, + + /* setSocketIO(io) + * Sets the socket io object for this module. + * Binds message receiver. + * + * argument io Socket.io - The Socket io object. + */ + setSocketIO(io) { + this.io = io; + + Log.log(`Connecting socket for: ${this.name}`); + + io.of(this.name).on("connection", (socket) => { + // add a catch all event. + const onevent = socket.onevent; + socket.onevent = function (packet) { + 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("*", (notification, payload) => { + if (notification !== "*") { + this.socketNotificationReceived(notification, payload); + } + }); + }); + } +}); + +NodeHelper.checkFetchStatus = function (response) { + // response.status >= 200 && response.status < 300 + if (response.ok) { + return response; + } else { + throw Error(response.statusText); + } +}; + +/** + * Look at the specified error and return an appropriate error type, that + * can be translated to a detailed error message + * + * @param {Error} error the error from fetching something + * @returns {string} the string of the detailed error message in the translations + */ +NodeHelper.checkFetchError = function (error) { + let error_type = "MODULE_ERROR_UNSPECIFIED"; + if (error.code === "EAI_AGAIN") { + error_type = "MODULE_ERROR_NO_CONNECTION"; + } else if (error.message === "Unauthorized") { + error_type = "MODULE_ERROR_UNAUTHORIZED"; + } + return error_type; +}; + +NodeHelper.create = function (moduleDefinition) { + return NodeHelper.extend(moduleDefinition); +}; + +module.exports = NodeHelper; diff --git a/MagicMirror/js/server.js b/MagicMirror/js/server.js new file mode 100644 index 0000000000000000000000000000000000000000..14546217032f54f93ece3a7237274ca8f79f5253 --- /dev/null +++ b/MagicMirror/js/server.js @@ -0,0 +1,122 @@ +/* MagicMirror² + * Server + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +const fs = require("fs"); +const http = require("http"); +const https = require("https"); +const path = require("path"); +const express = require("express"); +const ipfilter = require("express-ipfilter").IpFilter; +const helmet = require("helmet"); +const socketio = require("socket.io"); + +const Log = require("logger"); +const Utils = require("./utils"); +const { cors, getConfig, getHtml, getVersion } = require("./server_functions"); + +/** + * Server + * + * @param {object} config The MM config + * @class + */ +function Server(config) { + const app = express(); + const port = process.env.MM_PORT || config.port; + const serverSockets = new Set(); + let server = null; + + /** + * Opens the server for incoming connections + * + * @returns {Promise} A promise that is resolved when the server listens to connections + */ + this.open = function () { + return new Promise((resolve) => { + if (config.useHttps) { + const options = { + key: fs.readFileSync(config.httpsPrivateKey), + cert: fs.readFileSync(config.httpsCertificate) + }; + server = https.Server(options, app); + } else { + server = http.Server(app); + } + const io = socketio(server, { + cors: { + origin: /.*$/, + credentials: true + }, + allowEIO3: true + }); + + server.on("connection", (socket) => { + serverSockets.add(socket); + socket.on("close", () => { + serverSockets.delete(socket); + }); + }); + + Log.log(`Starting server on port ${port} ... `); + 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) { + ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) { + if (err === undefined) { + res.header("Access-Control-Allow-Origin", "*"); + return next(); + } + Log.log(err.message); + res.status(403).send("This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this."); + }); + }); + + app.use(helmet(config.httpHeaders)); + app.use("/js", express.static(__dirname)); + + // TODO add tests directory only when running tests? + const directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs", "/tests/mocks"]; + for (const directory of directories) { + app.use(directory, express.static(path.resolve(global.root_path + directory))); + } + + app.get("/cors", async (req, res) => await cors(req, res)); + + app.get("/version", (req, res) => getVersion(req, res)); + + app.get("/config", (req, res) => getConfig(req, res)); + + app.get("/", (req, res) => getHtml(req, res)); + + server.on("listening", () => { + resolve({ + app, + io + }); + }); + }); + }; + + /** + * Closes the server and destroys all lingering connections to it. + * + * @returns {Promise} A promise that resolves when server has successfully shut down + */ + this.close = function () { + return new Promise((resolve) => { + for (const socket of serverSockets.values()) { + socket.destroy(); + } + server.close(resolve); + }); + }; +} + +module.exports = Server; diff --git a/MagicMirror/js/server_functions.js b/MagicMirror/js/server_functions.js new file mode 100644 index 0000000000000000000000000000000000000000..6ecc6cae2936ff8340dbdffeec3f837e91e2f000 --- /dev/null +++ b/MagicMirror/js/server_functions.js @@ -0,0 +1,127 @@ +const fs = require("fs"); +const path = require("path"); +const Log = require("logger"); +const fetch = require("./fetch"); + +/** + * Gets the config. + * + * @param {Request} req - the request + * @param {Response} res - the result + */ +function getConfig(req, res) { + res.send(config); +} + +/** + * A method that forwards HTTP Get-methods to the internet to avoid CORS-errors. + * + * Example input request url: /cors?sendheaders=header1:value1,header2:value2&expectedheaders=header1,header2&url=http://www.test.com/path?param1=value1 + * + * Only the url-param of the input request url is required. It must be the last parameter. + * + * @param {Request} req - the request + * @param {Response} res - the result + */ +async function cors(req, res) { + try { + const urlRegEx = "url=(.+?)$"; + let url; + + const match = new RegExp(urlRegEx, "g").exec(req.url); + if (!match) { + url = `invalid url: ${req.url}`; + Log.error(url); + res.send(url); + } else { + url = match[1]; + + const headersToSend = getHeadersToSend(req.url); + const expectedRecievedHeaders = geExpectedRecievedHeaders(req.url); + + Log.log(`cors url: ${url}`); + const response = await fetch(url, { headers: headersToSend }); + + for (const header of expectedRecievedHeaders) { + const headerValue = response.headers.get(header); + if (header) res.set(header, headerValue); + } + const data = await response.text(); + res.send(data); + } + } catch (error) { + Log.error(error); + res.send(error); + } +} + +/** + * Gets headers and values to attach to the web request. + * + * @param {string} url - The url containing the headers and values to send. + * @returns {object} An object specifying name and value of the headers. + */ +function getHeadersToSend(url) { + const headersToSend = { "User-Agent": `Mozilla/5.0 MagicMirror/${global.version}` }; + const headersToSendMatch = new RegExp("sendheaders=(.+?)(&|$)", "g").exec(url); + if (headersToSendMatch) { + const headers = headersToSendMatch[1].split(","); + for (const header of headers) { + const keyValue = header.split(":"); + if (keyValue.length !== 2) { + throw new Error(`Invalid format for header ${header}`); + } + headersToSend[keyValue[0]] = decodeURIComponent(keyValue[1]); + } + } + return headersToSend; +} + +/** + * Gets the headers expected from the response. + * + * @param {string} url - The url containing the expected headers from the response. + * @returns {string[]} headers - The name of the expected headers. + */ +function geExpectedRecievedHeaders(url) { + const expectedRecievedHeaders = ["Content-Type"]; + const expectedRecievedHeadersMatch = new RegExp("expectedheaders=(.+?)(&|$)", "g").exec(url); + if (expectedRecievedHeadersMatch) { + const headers = expectedRecievedHeadersMatch[1].split(","); + for (const header of headers) { + expectedRecievedHeaders.push(header); + } + } + return expectedRecievedHeaders; +} + +/** + * Gets the HTML to display the magic mirror. + * + * @param {Request} req - the request + * @param {Response} res - the result + */ +function getHtml(req, res) { + let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" }); + html = html.replace("#VERSION#", global.version); + + let configFile = "config/config.js"; + if (typeof global.configuration_file !== "undefined") { + configFile = global.configuration_file; + } + html = html.replace("#CONFIG_FILE#", configFile); + + res.send(html); +} + +/** + * Gets the MagicMirror version. + * + * @param {Request} req - the request + * @param {Response} res - the result + */ +function getVersion(req, res) { + res.send(global.version); +} + +module.exports = { cors, getConfig, getHtml, getVersion }; diff --git a/MagicMirror/js/socketclient.js b/MagicMirror/js/socketclient.js new file mode 100644 index 0000000000000000000000000000000000000000..fce49d301d35e94d4ee583b266b1bf1dd1f462ee --- /dev/null +++ b/MagicMirror/js/socketclient.js @@ -0,0 +1,53 @@ +/* global io */ + +/* MagicMirror² + * TODO add description + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +const MMSocket = function (moduleName) { + if (typeof moduleName !== "string") { + throw new Error("Please set the module name for the MMSocket."); + } + + this.moduleName = moduleName; + + // Private Methods + let base = "/"; + if (typeof config !== "undefined" && typeof config.basePath !== "undefined") { + base = config.basePath; + } + this.socket = io(`/${this.moduleName}`, { + path: `${base}socket.io` + }); + + let notificationCallback = function () {}; + + const onevent = this.socket.onevent; + this.socket.onevent = (packet) => { + const args = packet.data || []; + onevent.call(this.socket, packet); // original call + packet.data = ["*"].concat(args); + onevent.call(this.socket, packet); // additional call to catch-all + }; + + // register catch all. + this.socket.on("*", (notification, payload) => { + if (notification !== "*") { + notificationCallback(notification, payload); + } + }); + + // Public Methods + this.setNotificationCallback = (callback) => { + notificationCallback = callback; + }; + + this.sendNotification = (notification, payload) => { + if (typeof payload === "undefined") { + payload = {}; + } + this.socket.emit(notification, payload); + }; +}; diff --git a/MagicMirror/js/translator.js b/MagicMirror/js/translator.js new file mode 100644 index 0000000000000000000000000000000000000000..dd004d54140945bd797de3bef665d5741f77e66e --- /dev/null +++ b/MagicMirror/js/translator.js @@ -0,0 +1,149 @@ +/* global translations */ + +/* MagicMirror² + * Translator (l10n) + * + * By Christopher Fenner https://github.com/CFenner + * MIT Licensed. + */ +const Translator = (function () { + /** + * Load a JSON file via XHR. + * + * @param {string} file Path of the file we want to load. + * @returns {Promise<object>} the translations in the specified file + */ + async function loadJSON(file) { + const xhr = new XMLHttpRequest(); + return new Promise(function (resolve, reject) { + xhr.overrideMimeType("application/json"); + xhr.open("GET", file, true); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4 && xhr.status === 200) { + // needs error handler try/catch at least + let fileinfo = null; + try { + fileinfo = JSON.parse(xhr.responseText); + } catch (exception) { + // nothing here, but don't die + Log.error(` loading json file =${file} failed`); + } + resolve(fileinfo); + } + }; + xhr.send(null); + }); + } + + return { + coreTranslations: {}, + coreTranslationsFallback: {}, + translations: {}, + translationsFallback: {}, + + /** + * Load a translation for a given key for a given module. + * + * @param {Module} module The module to load the translation for. + * @param {string} key The key of the text to translate. + * @param {object} variables The variables to use within the translation template (optional) + * @returns {string} the translated key + */ + translate: function (module, key, variables) { + variables = variables || {}; // Empty object by default + + /** + * Combines template and variables like: + * template: "Please wait for {timeToWait} before continuing with {work}." + * variables: {timeToWait: "2 hours", work: "painting"} + * to: "Please wait for 2 hours before continuing with painting." + * + * @param {string} template Text with placeholder + * @param {object} variables Variables for the placeholder + * @returns {string} the template filled with the variables + */ + function createStringFromTemplate(template, variables) { + if (Object.prototype.toString.call(template) !== "[object String]") { + return template; + } + if (variables.fallback && !template.match(new RegExp("{.+}"))) { + template = variables.fallback; + } + return template.replace(new RegExp("{([^}]+)}", "g"), function (_unused, varName) { + return varName in variables ? variables[varName] : `{${varName}}`; + }); + } + + if (this.translations[module.name] && key in this.translations[module.name]) { + // Log.log("Got translation for " + key + " from module translation: "); + return createStringFromTemplate(this.translations[module.name][key], variables); + } + + if (key in this.coreTranslations) { + // Log.log("Got translation for " + key + " from core translation."); + return createStringFromTemplate(this.coreTranslations[key], variables); + } + + if (this.translationsFallback[module.name] && key in this.translationsFallback[module.name]) { + // Log.log("Got translation for " + key + " from module translation fallback."); + return createStringFromTemplate(this.translationsFallback[module.name][key], variables); + } + + if (key in this.coreTranslationsFallback) { + // Log.log("Got translation for " + key + " from core translation fallback."); + return createStringFromTemplate(this.coreTranslationsFallback[key], variables); + } + + return key; + }, + + /** + * Load a translation file (json) and remember the data. + * + * @param {Module} module The module to load the translation file for. + * @param {string} file Path of the file we want to load. + * @param {boolean} isFallback Flag to indicate fallback translations. + */ + async load(module, file, isFallback) { + Log.log(`${module.name} - Load translation${isFallback ? " fallback" : ""}: ${file}`); + + if (this.translationsFallback[module.name]) { + return; + } + + const json = await loadJSON(module.file(file)); + const property = isFallback ? "translationsFallback" : "translations"; + this[property][module.name] = json; + }, + + /** + * Load the core translations. + * + * @param {string} lang The language identifier of the core language. + */ + loadCoreTranslations: async function (lang) { + if (lang in translations) { + Log.log(`Loading core translation file: ${translations[lang]}`); + this.coreTranslations = await loadJSON(translations[lang]); + } else { + Log.log("Configured language not found in core translations."); + } + + await this.loadCoreTranslationsFallback(); + }, + + /** + * Load the core translations' fallback. + * The first language defined in translations.js will be used. + */ + loadCoreTranslationsFallback: async function () { + let first = Object.keys(translations)[0]; + if (first) { + Log.log(`Loading core translation fallback file: ${translations[first]}`); + this.coreTranslationsFallback = await loadJSON(translations[first]); + } + } + }; +})(); + +window.Translator = Translator; diff --git a/MagicMirror/js/utils.js b/MagicMirror/js/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..d111afca230cea0516def1aad5de05f2d3ee6ccd --- /dev/null +++ b/MagicMirror/js/utils.js @@ -0,0 +1,16 @@ +/* MagicMirror² + * Utils + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +const colors = require("colors/safe"); + +module.exports = { + colors: { + warn: colors.yellow, + error: colors.red, + info: colors.blue, + pass: colors.green + } +}; diff --git a/MagicMirror/jsconfig.json b/MagicMirror/jsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..6ae848fc38661c8f8b5c5383e3e637adcb99d464 --- /dev/null +++ b/MagicMirror/jsconfig.json @@ -0,0 +1,10 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=759670 + // for the documentation about the jsconfig.json format + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "allowSyntheticDefaultImports": true + }, + "exclude": ["modules", "node_modules"] +} diff --git a/MagicMirror/module-types.ts b/MagicMirror/module-types.ts new file mode 100644 index 0000000000000000000000000000000000000000..be1a2061ddb5cd615f0cf004661f2b658b574444 --- /dev/null +++ b/MagicMirror/module-types.ts @@ -0,0 +1,31 @@ +type ModuleProperties = { + defaults?: object; + start?(): void; + getHeader?(): string; + getTemplate?(): string; + getTemplateData?(): object; + notificationReceived?(notification: string, payload: any, sender: object): void; + socketNotificationReceived?(notification: string, payload: any): void; + suspend?(): void; + resume?(): void; + getDom?(): HTMLElement; + getStyles?(): string[]; + [key: string]: any; +}; + +export declare const Module: { + register(moduleName: string, moduleProperties: ModuleProperties): void; +}; + +export declare const Log: { + info(message?: any, ...optionalParams: any[]): void; + log(message?: any, ...optionalParams: any[]): void; + error(message?: any, ...optionalParams: any[]): void; + warn(message?: any, ...optionalParams: any[]): void; + group(groupTitle?: string, ...optionalParams: any[]): void; + groupCollapsed(groupTitle?: string, ...optionalParams: any[]): void; + groupEnd(): void; + time(timerName?: string): void; + timeEnd(timerName?: string): void; + timeStamp(timerName?: string): void; +}; diff --git a/MagicMirror/modules/.DS_Store b/MagicMirror/modules/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b41ff0cef104bad7a0032f281c11a3e1988f6fc7 Binary files /dev/null and b/MagicMirror/modules/.DS_Store differ diff --git a/MagicMirror/modules/default/alert/README.md b/MagicMirror/modules/default/alert/README.md new file mode 100644 index 0000000000000000000000000000000000000000..720adae32ca72b6719af80403325b28ab69dd445 --- /dev/null +++ b/MagicMirror/modules/default/alert/README.md @@ -0,0 +1,5 @@ +# Module: Alert + +The alert module is one of the default modules of the MagicMirror². This module displays notifications from other modules. + +For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/alert.html). diff --git a/MagicMirror/modules/default/alert/alert.js b/MagicMirror/modules/default/alert/alert.js new file mode 100644 index 0000000000000000000000000000000000000000..1bd11d02f6de6b2fc483b5338d9d066a419bbacb --- /dev/null +++ b/MagicMirror/modules/default/alert/alert.js @@ -0,0 +1,147 @@ +/* global NotificationFx */ + +/* MagicMirror² + * Module: alert + * + * By Paul-Vincent Roll https://paulvincentroll.com/ + * MIT Licensed. + */ +Module.register("alert", { + alerts: {}, + + defaults: { + effect: "slide", // scale|slide|genie|jelly|flip|bouncyflip|exploader + alert_effect: "jelly", // scale|slide|genie|jelly|flip|bouncyflip|exploader + display_time: 3500, // time a notification is displayed in seconds + position: "center", + welcome_message: false // shown at startup + }, + + getScripts() { + return ["notificationFx.js"]; + }, + + getStyles() { + return ["font-awesome.css", this.file(`./styles/notificationFx.css`), this.file(`./styles/${this.config.position}.css`)]; + }, + + getTranslations() { + return { + bg: "translations/bg.json", + da: "translations/da.json", + de: "translations/de.json", + en: "translations/en.json", + es: "translations/es.json", + fr: "translations/fr.json", + hu: "translations/hu.json", + nl: "translations/nl.json", + ru: "translations/ru.json", + th: "translations/th.json" + }; + }, + + getTemplate(type) { + return `templates/${type}.njk`; + }, + + async start() { + Log.info(`Starting module: ${this.name}`); + + if (this.config.effect === "slide") { + this.config.effect = `${this.config.effect}-${this.config.position}`; + } + + if (this.config.welcome_message) { + const message = this.config.welcome_message === true ? this.translate("welcome") : this.config.welcome_message; + await this.showNotification({ title: this.translate("sysTitle"), message }); + } + }, + + notificationReceived(notification, payload, sender) { + if (notification === "SHOW_ALERT") { + if (payload.type === "notification") { + this.showNotification(payload); + } else { + this.showAlert(payload, sender); + } + } else if (notification === "HIDE_ALERT") { + this.hideAlert(sender); + } + }, + + async showNotification(notification) { + const message = await this.renderMessage(notification.templateName || "notification", notification); + + new NotificationFx({ + message, + layout: "growl", + effect: this.config.effect, + ttl: notification.timer || this.config.display_time + }).show(); + }, + + async showAlert(alert, sender) { + // If module already has an open alert close it + if (this.alerts[sender.name]) { + this.hideAlert(sender, false); + } + + // Add overlay + if (!Object.keys(this.alerts).length) { + this.toggleBlur(true); + } + + const message = await this.renderMessage(alert.templateName || "alert", alert); + + // Store alert in this.alerts + this.alerts[sender.name] = new NotificationFx({ + message, + effect: this.config.alert_effect, + ttl: alert.timer, + onClose: () => this.hideAlert(sender), + al_no: "ns-alert" + }); + + // Show alert + this.alerts[sender.name].show(); + + // Add timer to dismiss alert and overlay + if (alert.timer) { + setTimeout(() => { + this.hideAlert(sender); + }, alert.timer); + } + }, + + hideAlert(sender, close = true) { + // Dismiss alert and remove from this.alerts + if (this.alerts[sender.name]) { + this.alerts[sender.name].dismiss(close); + delete this.alerts[sender.name]; + // Remove overlay + if (!Object.keys(this.alerts).length) { + this.toggleBlur(false); + } + } + }, + + renderMessage(type, data) { + return new Promise((resolve) => { + this.nunjucksEnvironment().render(this.getTemplate(type), data, function (err, res) { + if (err) { + Log.error("Failed to render alert", err); + } + + resolve(res); + }); + }); + }, + + toggleBlur(add = false) { + const method = add ? "add" : "remove"; + const modules = document.querySelectorAll(".module"); + for (const module of modules) { + module.classList[method]("alert-blur"); + } + } +}); diff --git a/MagicMirror/modules/default/alert/notificationFx.js b/MagicMirror/modules/default/alert/notificationFx.js new file mode 100644 index 0000000000000000000000000000000000000000..8f8a84ccaa808ba3a22ee7110e118664e72b0a54 --- /dev/null +++ b/MagicMirror/modules/default/alert/notificationFx.js @@ -0,0 +1,160 @@ +/** + * Based on work by + * + * notificationFx.js v1.0.0 + * https://tympanus.net/codrops/ + * + * Licensed under the MIT license. + * https://opensource.org/licenses/mit-license.php + * + * Copyright 2014, Codrops + * https://tympanus.net/codrops/ + * + * @param {object} window The window object + */ +(function (window) { + /** + * Extend one object with another one + * + * @param {object} a The object to extend + * @param {object} b The object which extends the other, overwrites existing keys + * @returns {object} The merged object + */ + function extend(a, b) { + for (let key in b) { + if (b.hasOwnProperty(key)) { + a[key] = b[key]; + } + } + return a; + } + + /** + * NotificationFx constructor + * + * @param {object} options The configuration options + * @class + */ + function NotificationFx(options) { + this.options = extend({}, this.options); + extend(this.options, options); + this._init(); + } + + /** + * NotificationFx options + */ + NotificationFx.prototype.options = { + // element to which the notification will be appended + // defaults to the document.body + wrapper: document.body, + // the message + message: "yo!", + // layout type: growl|attached|bar|other + layout: "growl", + // effects for the specified layout: + // for growl layout: scale|slide|genie|jelly + // for attached layout: flip|bouncyflip + // for other layout: boxspinner|cornerexpand|loadingcircle|thumbslider + // ... + effect: "slide", + // notice, warning, error, success + // will add class ns-type-warning, ns-type-error or ns-type-success + type: "notice", + // if the user doesn´t close the notification then we remove it + // after the following time + ttl: 6000, + al_no: "ns-box", + // callbacks + onClose: function () { + return false; + }, + onOpen: function () { + return false; + } + }; + + /** + * Initialize and cache some vars + */ + NotificationFx.prototype._init = function () { + // create HTML structure + this.ntf = document.createElement("div"); + this.ntf.className = `${this.options.al_no} ns-${this.options.layout} ns-effect-${this.options.effect} ns-type-${this.options.type}`; + let strinner = '<div class="ns-box-inner">'; + strinner += this.options.message; + strinner += "</div>"; + this.ntf.innerHTML = strinner; + + // append to body or the element specified in options.wrapper + this.options.wrapper.insertBefore(this.ntf, this.options.wrapper.nextSibling); + + // dismiss after [options.ttl]ms + if (this.options.ttl) { + this.dismissttl = setTimeout(() => { + if (this.active) { + this.dismiss(); + } + }, this.options.ttl); + } + + // init events + this._initEvents(); + }; + + /** + * Init events + */ + NotificationFx.prototype._initEvents = function () { + // dismiss notification by tapping on it if someone has a touchscreen + this.ntf.querySelector(".ns-box-inner").addEventListener("click", () => { + this.dismiss(); + }); + }; + + /** + * Show the notification + */ + NotificationFx.prototype.show = function () { + this.active = true; + this.ntf.classList.remove("ns-hide"); + this.ntf.classList.add("ns-show"); + this.options.onOpen(); + }; + + /** + * Dismiss the notification + * + * @param {boolean} [close] call the onClose callback at the end + */ + NotificationFx.prototype.dismiss = function (close = true) { + this.active = false; + clearTimeout(this.dismissttl); + this.ntf.classList.remove("ns-show"); + setTimeout(() => { + this.ntf.classList.add("ns-hide"); + + // callback + if (close) this.options.onClose(); + }, 25); + + // after animation ends remove ntf from the DOM + const onEndAnimationFn = (ev) => { + if (ev.target !== this.ntf) { + return false; + } + this.ntf.removeEventListener("animationend", onEndAnimationFn); + + if (ev.target.parentNode === this.options.wrapper) { + this.options.wrapper.removeChild(this.ntf); + } + }; + + this.ntf.addEventListener("animationend", onEndAnimationFn); + }; + + /** + * Add to global namespace + */ + window.NotificationFx = NotificationFx; +})(window); diff --git a/MagicMirror/modules/default/alert/styles/center.css b/MagicMirror/modules/default/alert/styles/center.css new file mode 100644 index 0000000000000000000000000000000000000000..4e8f5e1d6b6e80a85b19e2b0531025437ad26750 --- /dev/null +++ b/MagicMirror/modules/default/alert/styles/center.css @@ -0,0 +1,5 @@ +.ns-box { + margin-left: auto; + margin-right: auto; + text-align: center; +} diff --git a/MagicMirror/modules/default/alert/styles/left.css b/MagicMirror/modules/default/alert/styles/left.css new file mode 100644 index 0000000000000000000000000000000000000000..86d2746c09c81c31986175764ced7db1ff853f83 --- /dev/null +++ b/MagicMirror/modules/default/alert/styles/left.css @@ -0,0 +1,4 @@ +.ns-box { + margin-right: auto; + text-align: left; +} diff --git a/MagicMirror/modules/default/alert/styles/notificationFx.css b/MagicMirror/modules/default/alert/styles/notificationFx.css new file mode 100644 index 0000000000000000000000000000000000000000..df3407514c310c446d57fe460173a18b7bda40a2 --- /dev/null +++ b/MagicMirror/modules/default/alert/styles/notificationFx.css @@ -0,0 +1,929 @@ +/* Based on work by https://tympanus.net/codrops/licensing/ */ + +.ns-box { + background-color: rgb(0 0 0 / 93%); + padding: 17px; + line-height: 1.4; + margin-bottom: 10px; + z-index: 1; + font-size: 70%; + position: relative; + display: table; + word-wrap: break-word; + max-width: 100%; + border-width: 1px; + border-radius: 5px; + border-style: solid; + border-color: var(--color-text-dimmed); +} + +.ns-alert { + border-style: solid; + border-color: var(--color-text-bright); + padding: 17px; + line-height: 1.4; + margin-bottom: 10px; + z-index: 3; + color: var(--color-text-bright); + font-size: 70%; + position: fixed; + text-align: center; + right: 0; + left: 0; + margin-right: auto; + margin-left: auto; + top: 40%; + width: 40%; + height: auto; + word-wrap: break-word; + border-radius: 20px; +} + +.alert-blur { + filter: blur(2px) brightness(50%); +} + +[class^="ns-effect-"].ns-growl.ns-hide, +[class*=" ns-effect-"].ns-growl.ns-hide { + animation-direction: reverse; +} + +.ns-effect-flip { + transform-origin: 50% 100%; + backface-visibility: hidden; +} + +.ns-effect-flip.ns-show, +.ns-effect-flip.ns-hide { + animation-name: anim-flip-front; + animation-duration: 0.3s; +} + +.ns-effect-flip.ns-hide { + animation-name: anim-flip-back; +} + +@keyframes anim-flip-front { + 0% { + transform: perspective(1000px) rotate3d(1, 0, 0, -90deg); + } + + 100% { + transform: perspective(1000px); + } +} + +@keyframes anim-flip-back { + 0% { + transform: perspective(1000px) rotate3d(1, 0, 0, 90deg); + } + + 100% { + transform: perspective(1000px); + } +} + +.ns-effect-bouncyflip.ns-show, +.ns-effect-bouncyflip.ns-hide { + animation-name: flip-in-x; + animation-duration: 0.8s; +} + +@keyframes flip-in-x { + 0% { + transform: perspective(400px) rotate3d(1, 0, 0, -90deg); + transition-timing-function: ease-in; + } + + 40% { + transform: perspective(400px) rotate3d(1, 0, 0, 20deg); + transition-timing-function: ease-out; + } + + 60% { + transform: perspective(400px) rotate3d(1, 0, 0, -10deg); + transition-timing-function: ease-in; + opacity: 1; + } + + 80% { + transform: perspective(400px) rotate3d(1, 0, 0, 5deg); + transition-timing-function: ease-out; + } + + 100% { + transform: perspective(400px); + } +} + +.ns-effect-bouncyflip.ns-hide { + animation-name: flip-in-x-simple; + animation-duration: 0.3s; +} + +@keyframes flip-in-x-simple { + 0% { + transform: perspective(400px) rotate3d(1, 0, 0, -90deg); + transition-timing-function: ease-in; + } + + 100% { + transform: perspective(400px); + } +} + +.ns-effect-exploader { + transform-origin: 0 0; +} + +.ns-effect-exploader p { + padding: 0.25em 2em 0.25em 3em; +} + +.ns-effect-exploader.ns-show { + animation-name: anim-load; + animation-duration: 1s; +} + +@keyframes anim-load { + 0% { + opacity: 1; + transform: scale3d(0, 0.3, 1); + } + + 100% { + opacity: 1; + transform: scale3d(1, 1, 1); + } +} + +.ns-effect-exploader.ns-hide { + animation-name: anim-fade; + animation-duration: 0.3s; +} + +.ns-effect-exploader.ns-show .ns-box-inner, +.ns-effect-exploader.ns-show .ns-close { + animation-fill-mode: both; + animation-duration: 0.3s; + animation-delay: 0.6s; +} + +.ns-effect-exploader.ns-show .ns-close { + animation-name: anim-fade; +} + +.ns-effect-exploader.ns-show .ns-box-inner { + animation-name: anim-fade-move; + animation-timing-function: ease-out; +} + +@keyframes anim-fade-move { + 0% { + opacity: 0; + transform: translate3d(0, 10px, 0); + } + + 100% { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes anim-fade { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +.ns-effect-scale.ns-show, +.ns-effect-scale.ns-hide { + animation-name: anim-scale; + animation-duration: 0.25s; +} + +@keyframes anim-scale { + 0% { + opacity: 0; + transform: translate3d(0, 40px, 0) scale3d(0.1, 0.6, 1); + } + + 100% { + opacity: 1; + transform: translate3d(0, 0, 0) scale3d(1, 1, 1); + } +} + +.ns-effect-jelly.ns-show { + animation-name: anim-jelly; + animation-duration: 1s; + animation-timing-function: linear; +} + +.ns-effect-jelly.ns-hide { + animation-name: anim-fade; + animation-duration: 0.3s; +} + +@keyframes anim-fade { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes anim-jelly { + 0% { + transform: matrix3d(0.7, 0, 0, 0, 0, 0.7, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 2.083333% { + transform: matrix3d(0.7527, 0, 0, 0, 0, 0.7634, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 4.166667% { + transform: matrix3d(0.8107, 0, 0, 0, 0, 0.8454, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 6.25% { + transform: matrix3d(0.8681, 0, 0, 0, 0, 0.929, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 8.333333% { + transform: matrix3d(0.9204, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 10.416667% { + transform: matrix3d(0.9648, 0, 0, 0, 0, 1.052, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 12.5% { + transform: matrix3d(1, 0, 0, 0, 0, 1.082, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 14.583333% { + transform: matrix3d(1.0256, 0, 0, 0, 0, 1.0915, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 16.666667% { + transform: matrix3d(1.0423, 0, 0, 0, 0, 1.0845, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 18.75% { + transform: matrix3d(1.051, 0, 0, 0, 0, 1.0667, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 20.833333% { + transform: matrix3d(1.0533, 0, 0, 0, 0, 1.0436, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 22.916667% { + transform: matrix3d(1.0508, 0, 0, 0, 0, 1.0201, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 25% { + transform: matrix3d(1.0449, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 27.083333% { + transform: matrix3d(1.037, 0, 0, 0, 0, 0.9853, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 29.166667% { + transform: matrix3d(1.0283, 0, 0, 0, 0, 0.9769, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 31.25% { + transform: matrix3d(1.0197, 0, 0, 0, 0, 0.9742, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 33.333333% { + transform: matrix3d(1.0119, 0, 0, 0, 0, 0.9762, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 35.416667% { + transform: matrix3d(1.0053, 0, 0, 0, 0, 0.9812, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 37.5% { + transform: matrix3d(1, 0, 0, 0, 0, 0.9877, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 39.583333% { + transform: matrix3d(0.9962, 0, 0, 0, 0, 0.9943, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 41.666667% { + transform: matrix3d(0.9937, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 43.75% { + transform: matrix3d(0.9924, 0, 0, 0, 0, 1.0041, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 45.833333% { + transform: matrix3d(0.992, 0, 0, 0, 0, 1.0065, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 47.916667% { + transform: matrix3d(0.9924, 0, 0, 0, 0, 1.0073, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 50% { + transform: matrix3d(0.9933, 0, 0, 0, 0, 1.0067, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 52.083333% { + transform: matrix3d(0.9945, 0, 0, 0, 0, 1.0053, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 54.166667% { + transform: matrix3d(0.9958, 0, 0, 0, 0, 1.0035, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 56.25% { + transform: matrix3d(0.997, 0, 0, 0, 0, 1.002, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 58.333333% { + transform: matrix3d(0.9982, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 60.416667% { + transform: matrix3d(0.9992, 0, 0, 0, 0, 0.9989, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 62.5% { + transform: matrix3d(1, 0, 0, 0, 0, 0.9982, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 64.583333% { + transform: matrix3d(1.0006, 0, 0, 0, 0, 0.998, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 66.666667% { + transform: matrix3d(1.001, 0, 0, 0, 0, 0.9981, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 68.75% { + transform: matrix3d(1.0011, 0, 0, 0, 0, 0.9985, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 70.833333% { + transform: matrix3d(1.0012, 0, 0, 0, 0, 0.999, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 72.916667% { + transform: matrix3d(1.0011, 0, 0, 0, 0, 0.9996, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 75% { + transform: matrix3d(1.001, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 77.083333% { + transform: matrix3d(1.0008, 0, 0, 0, 0, 1.0003, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 79.166667% { + transform: matrix3d(1.0006, 0, 0, 0, 0, 1.0005, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 81.25% { + transform: matrix3d(1.0004, 0, 0, 0, 0, 1.0006, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 83.333333% { + transform: matrix3d(1.0003, 0, 0, 0, 0, 1.0005, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 85.416667% { + transform: matrix3d(1.0001, 0, 0, 0, 0, 1.0004, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 87.5% { + transform: matrix3d(1, 0, 0, 0, 0, 1.0003, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 89.583333% { + transform: matrix3d(0.9999, 0, 0, 0, 0, 1.0001, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 91.666667% { + transform: matrix3d(0.9999, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 93.75% { + transform: matrix3d(0.9998, 0, 0, 0, 0, 0.9999, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 95.833333% { + transform: matrix3d(0.9998, 0, 0, 0, 0, 0.9999, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 97.916667% { + transform: matrix3d(0.9998, 0, 0, 0, 0, 0.9998, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 100% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } +} + +.ns-effect-slide-left.ns-show { + animation-name: anim-slide-elastic-left; + animation-duration: 1s; + animation-timing-function: linear; +} + +@keyframes anim-slide-elastic-left { + 0% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1000, 0, 0, 1); + } + + 1.666667% { + transform: matrix3d(1.9293, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -739.2681, 0, 0, 1); + } + + 3.333333% { + transform: matrix3d(1.9699, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -521.8255, 0, 0, 1); + } + + 5% { + transform: matrix3d(1.709, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -349.2612, 0, 0, 1); + } + + 6.666667% { + transform: matrix3d(1.424, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -218.324, 0, 0, 1); + } + + 8.333333% { + transform: matrix3d(1.2107, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -123.2985, 0, 0, 1); + } + + 10% { + transform: matrix3d(1.0817, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -57.5927, 0, 0, 1); + } + + 11.666667% { + transform: matrix3d(1.017, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -14.7237, 0, 0, 1); + } + + 13.333333% { + transform: matrix3d(0.9906, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 11.1279, 0, 0, 1); + } + + 15% { + transform: matrix3d(0.9848, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 24.8634, 0, 0, 1); + } + + 16.666667% { + transform: matrix3d(0.9872, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 30.405, 0, 0, 1); + } + + 18.333333% { + transform: matrix3d(0.992, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 30.7528, 0, 0, 1); + } + + 20% { + transform: matrix3d(0.9954, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 28.1014, 0, 0, 1); + } + + 21.666667% { + transform: matrix3d(0.998, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 23.9827, 0, 0, 1); + } + + 23.333333% { + transform: matrix3d(0.9994, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 19.4075, 0, 0, 1); + } + + 25% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 14.9956, 0, 0, 1); + } + + 26.666667% { + transform: matrix3d(1.0002, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 11.0858, 0, 0, 1); + } + + 28.333333% { + transform: matrix3d(1.0002, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7.8251, 0, 0, 1); + } + + 30% { + transform: matrix3d(1.0002, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.2374, 0, 0, 1); + } + + 31.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.2739, 0, 0, 1); + } + + 33.333333% { + transform: matrix3d(1.0001, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.8489, 0, 0, 1); + } + + 35% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.8636, 0, 0, 1); + } + + 36.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.2208, 0, 0, 1); + } + + 38.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.1669, 0, 0, 1); + } + + 40% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.3728, 0, 0, 1); + } + + 41.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.4559, 0, 0, 1); + } + + 43.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.4612, 0, 0, 1); + } + + 45% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.421, 0, 0, 1); + } + + 46.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.3596, 0, 0, 1); + } + + 48.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.291, 0, 0, 1); + } + + 50% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.2249, 0, 0, 1); + } + + 51.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.1662, 0, 0, 1); + } + + 53.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.1173, 0, 0, 1); + } + + 55% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0785, 0, 0, 1); + } + + 56.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0491, 0, 0, 1); + } + + 58.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0277, 0, 0, 1); + } + + 60% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.013, 0, 0, 1); + } + + 61.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0033, 0, 0, 1); + } + + 63.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0025, 0, 0, 1); + } + + 65% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0056, 0, 0, 1); + } + + 66.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0068, 0, 0, 1); + } + + 68.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0069, 0, 0, 1); + } + + 70% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0063, 0, 0, 1); + } + + 71.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0054, 0, 0, 1); + } + + 73.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0044, 0, 0, 1); + } + + 75% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0034, 0, 0, 1); + } + + 76.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0025, 0, 0, 1); + } + + 78.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0018, 0, 0, 1); + } + + 80% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0012, 0, 0, 1); + } + + 81.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0007, 0, 0, 1); + } + + 83.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0004, 0, 0, 1); + } + + 85% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0002, 0, 0, 1); + } + + 86.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0001, 0, 0, 1); + } + + 88.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1); + } + + 90% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1); + } + + 91.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1); + } + + 93.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1); + } + + 95% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1); + } + + 96.666667% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1); + } + + 98.333333% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1); + } + + 100% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } +} + +.ns-effect-slide-left.ns-hide { + animation-name: anim-slide-left; + animation-duration: 0.25s; +} + +@keyframes anim-slide-left { + 0% { + transform: translate3d(-30px, 0, 0) translate3d(-100%, 0, 0); + } + + 100% { + transform: translate3d(0, 0, 0); + } +} + +.ns-effect-slide-right.ns-show { + animation: anim-slide-elastic-right 2000ms linear both; +} + +@keyframes anim-slide-elastic-right { + 0% { + transform: matrix3d(2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1000, 0, 0, 1); + } + + 2.15% { + transform: matrix3d(1.486, 0, 0, 0, 0, 0.514, 0, 0, 0, 0, 1, 0, 664.594, 0, 0, 1); + } + + 4.1% { + transform: matrix3d(1.147, 0, 0, 0, 0, 0.853, 0, 0, 0, 0, 1, 0, 419.708, 0, 0, 1); + } + + 4.3% { + transform: matrix3d(1.121, 0, 0, 0, 0, 0.879, 0, 0, 0, 0, 1, 0, 398.136, 0, 0, 1); + } + + 6.46% { + transform: matrix3d(0.948, 0, 0, 0, 0, 1.052, 0, 0, 0, 0, 1, 0, 206.714, 0, 0, 1); + } + + 8.11% { + transform: matrix3d(0.908, 0, 0, 0, 0, 1.092, 0, 0, 0, 0, 1, 0, 105.491, 0, 0, 1); + } + + 8.61% { + transform: matrix3d(0.907, 0, 0, 0, 0, 1.093, 0, 0, 0, 0, 1, 0, 81.572, 0, 0, 1); + } + + 12.11% { + transform: matrix3d(0.95, 0, 0, 0, 0, 1.05, 0, 0, 0, 0, 1, 0, -18.434, 0, 0, 1); + } + + 14.16% { + transform: matrix3d(0.979, 0, 0, 0, 0, 1.021, 0, 0, 0, 0, 1, 0, -38.734, 0, 0, 1); + } + + 16.12% { + transform: matrix3d(0.997, 0, 0, 0, 0, 1.003, 0, 0, 0, 0, 1, 0, -43.356, 0, 0, 1); + } + + 19.72% { + transform: matrix3d(1.006, 0, 0, 0, 0, 0.994, 0, 0, 0, 0, 1, 0, -34.155, 0, 0, 1); + } + + 27.23% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -7.839, 0, 0, 1); + } + + 30.83% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1.951, 0, 0, 1); + } + + 38.34% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.037, 0, 0, 1); + } + + 41.99% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.812, 0, 0, 1); + } + + 50% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.159, 0, 0, 1); + } + + 60.56% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.025, 0, 0, 1); + } + + 82.78% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.001, 0, 0, 1); + } + + 100% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } +} + +.ns-effect-slide-right.ns-hide { + animation-name: anim-slide-right; + animation-duration: 0.25s; +} + +@keyframes anim-slide-right { + 0% { + transform: translate3d(30px, 0, 0) translate3d(100%, 0, 0); + } + + 100% { + transform: translate3d(0, 0, 0); + } +} + +.ns-effect-slide-center.ns-show { + animation: anim-slide-elastic-center 2000ms linear both; +} + +@keyframes anim-slide-elastic-center { + 0% { + transform: matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, -300, 0, 1); + } + + 2.15% { + transform: matrix3d(1, 0, 0, 0, 0, 1.971, 0, 0, 0, 0, 1, 0, 0, -199.378, 0, 1); + } + + 4.1% { + transform: matrix3d(1, 0, 0, 0, 0, 1.294, 0, 0, 0, 0, 1, 0, 0, -125.912, 0, 1); + } + + 4.3% { + transform: matrix3d(1, 0, 0, 0, 0, 1.243, 0, 0, 0, 0, 1, 0, 0, -119.441, 0, 1); + } + + 6.46% { + transform: matrix3d(1, 0, 0, 0, 0, 0.895, 0, 0, 0, 0, 1, 0, 0, -62.014, 0, 1); + } + + 8.11% { + transform: matrix3d(1, 0, 0, 0, 0, 0.817, 0, 0, 0, 0, 1, 0, 0, -31.647, 0, 1); + } + + 8.61% { + transform: matrix3d(1, 0, 0, 0, 0, 0.813, 0, 0, 0, 0, 1, 0, 0, -24.472, 0, 1); + } + + 12.11% { + transform: matrix3d(1, 0, 0, 0, 0, 0.9, 0, 0, 0, 0, 1, 0, 0, 5.53, 0, 1); + } + + 14.16% { + transform: matrix3d(1, 0, 0, 0, 0, 0.959, 0, 0, 0, 0, 1, 0, 0, 11.62, 0, 1); + } + + 16.12% { + transform: matrix3d(1, 0, 0, 0, 0, 0.994, 0, 0, 0, 0, 1, 0, 0, 13.007, 0, 1); + } + + 19.72% { + transform: matrix3d(1, 0, 0, 0, 0, 1.012, 0, 0, 0, 0, 1, 0, 0, 10.247, 0, 1); + } + + 27.23% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 2.352, 0, 1); + } + + 30.83% { + transform: matrix3d(1, 0, 0, 0, 0, 0.999, 0, 0, 0, 0, 1, 0, 0, 0.585, 0, 1); + } + + 38.34% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -0.311, 0, 1); + } + + 41.99% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -0.244, 0, 1); + } + + 50% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -0.048, 0, 1); + } + + 60.56% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0.007, 0, 1); + } + + 82.78% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } + + 100% { + transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + } +} + +.ns-effect-slide-center.ns-hide { + animation-name: anim-slide-center; + animation-duration: 0.25s; +} + +@keyframes anim-slide-center { + 0% { + transform: translate3d(0, -30px, 0) translate3d(0, -100%, 0); + } + + 100% { + transform: translate3d(0, 0, 0); + } +} + +.ns-effect-genie.ns-show, +.ns-effect-genie.ns-hide { + animation-name: anim-genie; + animation-duration: 0.4s; +} + +@keyframes anim-genie { + 0% { + opacity: 0; + transform: translate3d(0, calc(200% + 30px), 0) scale3d(0, 1, 1); + animation-timing-function: ease-in; + } + + 40% { + opacity: 0.5; + transform: translate3d(0, 0, 0) scale3d(0.02, 1.1, 1); + animation-timing-function: ease-out; + } + + 70% { + opacity: 0.6; + transform: translate3d(0, -40px, 0) scale3d(0.8, 1.1, 1); + } + + 100% { + opacity: 1; + transform: translate3d(0, 0, 0) scale3d(1, 1, 1); + } +} diff --git a/MagicMirror/modules/default/alert/styles/right.css b/MagicMirror/modules/default/alert/styles/right.css new file mode 100644 index 0000000000000000000000000000000000000000..add9b6f1f5e4cfaf7667d1092354d26a3ea8e6f5 --- /dev/null +++ b/MagicMirror/modules/default/alert/styles/right.css @@ -0,0 +1,4 @@ +.ns-box { + margin-left: auto; + text-align: right; +} diff --git a/MagicMirror/modules/default/alert/templates/alert.njk b/MagicMirror/modules/default/alert/templates/alert.njk new file mode 100644 index 0000000000000000000000000000000000000000..7349a7ae3db6150e8b02f3f6461bb62afd1963d3 --- /dev/null +++ b/MagicMirror/modules/default/alert/templates/alert.njk @@ -0,0 +1,18 @@ +{% if imageUrl or imageFA %} + {% set imageHeight = imageHeight if imageHeight else "80px" %} + {% if imageUrl %} + <img src="{{ imageUrl }}" height="{{ imageHeight }}" style="margin-bottom: 10px;"/> + {% else %} + <span class="bright fas fa-{{ imageFA }}" style='margin-bottom: 10px; font-size: {{ imageHeight }};'/></span> + {% endif %} + <br/> +{% endif %} +{% if title %} + <span class="thin dimmed medium">{{ title if titleType == 'text' else title | safe }}</span> +{% endif %} +{% if message %} + {% if title %} + <br/> + {% endif %} + <span class="light bright small">{{ message if messageType == 'text' else message | safe }}</span> +{% endif %} diff --git a/MagicMirror/modules/default/alert/templates/notification.njk b/MagicMirror/modules/default/alert/templates/notification.njk new file mode 100644 index 0000000000000000000000000000000000000000..1594ad4866bee65cbe64d63e2e7a85931689fc0c --- /dev/null +++ b/MagicMirror/modules/default/alert/templates/notification.njk @@ -0,0 +1,9 @@ +{% if title %} + <span class="thin dimmed medium">{{ title if titleType == 'text' else title | safe }}</span> +{% endif %} +{% if message %} + {% if title %} + <br/> + {% endif %} + <span class="light bright small">{{ message if messageType == 'text' else message | safe }}</span> +{% endif %} diff --git a/MagicMirror/modules/default/alert/translations/bg.json b/MagicMirror/modules/default/alert/translations/bg.json new file mode 100644 index 0000000000000000000000000000000000000000..102a0b19c28c1cda2fb73533218ff2c8e041f72e --- /dev/null +++ b/MagicMirror/modules/default/alert/translations/bg.json @@ -0,0 +1,4 @@ +{ + "sysTitle": "MagicMirror² нотификациÑ", + "welcome": "Добре дошли, Ñтартирането беше уÑпешно" +} diff --git a/MagicMirror/modules/default/alert/translations/da.json b/MagicMirror/modules/default/alert/translations/da.json new file mode 100644 index 0000000000000000000000000000000000000000..406d2ff6f3ccc47e45032e721f3c449420e9ff55 --- /dev/null +++ b/MagicMirror/modules/default/alert/translations/da.json @@ -0,0 +1,4 @@ +{ + "sysTitle": "MagicMirror² Notifikation", + "welcome": "Velkommen, modulet er succesfuldt startet!" +} diff --git a/MagicMirror/modules/default/alert/translations/de.json b/MagicMirror/modules/default/alert/translations/de.json new file mode 100644 index 0000000000000000000000000000000000000000..3ae5238c0760f3fcf92eff142a13d78edd9ba33f --- /dev/null +++ b/MagicMirror/modules/default/alert/translations/de.json @@ -0,0 +1,4 @@ +{ + "sysTitle": "MagicMirror² Benachrichtigung", + "welcome": "Willkommen, Start war erfolgreich!" +} diff --git a/MagicMirror/modules/default/alert/translations/en.json b/MagicMirror/modules/default/alert/translations/en.json new file mode 100644 index 0000000000000000000000000000000000000000..ea3319b90cf732ea9e788cb184cea7505bc41f89 --- /dev/null +++ b/MagicMirror/modules/default/alert/translations/en.json @@ -0,0 +1,4 @@ +{ + "sysTitle": "MagicMirror² Notification", + "welcome": "Welcome, start was successful!" +} diff --git a/MagicMirror/modules/default/alert/translations/es.json b/MagicMirror/modules/default/alert/translations/es.json new file mode 100644 index 0000000000000000000000000000000000000000..a2f74722ffcf1eb4d069b4a89c4b3551adc91bd6 --- /dev/null +++ b/MagicMirror/modules/default/alert/translations/es.json @@ -0,0 +1,4 @@ +{ + "sysTitle": "MagicMirror² Notificaciones", + "welcome": "Bienvenido, ¡se iniciado correctamente!" +} diff --git a/MagicMirror/modules/default/alert/translations/fr.json b/MagicMirror/modules/default/alert/translations/fr.json new file mode 100644 index 0000000000000000000000000000000000000000..a6d3c10513b5fe88f883d83df520a2b4abc7dafe --- /dev/null +++ b/MagicMirror/modules/default/alert/translations/fr.json @@ -0,0 +1,4 @@ +{ + "sysTitle": "MagicMirror² Notification", + "welcome": "Bienvenue, le démarrage a été un succès!" +} diff --git a/MagicMirror/modules/default/alert/translations/hu.json b/MagicMirror/modules/default/alert/translations/hu.json new file mode 100644 index 0000000000000000000000000000000000000000..90ab35daff3a4597ab671659d56c28b3c124982b --- /dev/null +++ b/MagicMirror/modules/default/alert/translations/hu.json @@ -0,0 +1,4 @@ +{ + "sysTitle": "MagicMirror² értesÃtés", + "welcome": "Üdvözöljük, indulás sikeres!" +} diff --git a/MagicMirror/modules/default/alert/translations/nl.json b/MagicMirror/modules/default/alert/translations/nl.json new file mode 100644 index 0000000000000000000000000000000000000000..59a480bdc40a040e33e348512558e8d548326d85 --- /dev/null +++ b/MagicMirror/modules/default/alert/translations/nl.json @@ -0,0 +1,4 @@ +{ + "sysTitle": "MagicMirror² Notificatie", + "welcome": "Welkom, Succesvol gestart!" +} diff --git a/MagicMirror/modules/default/alert/translations/ru.json b/MagicMirror/modules/default/alert/translations/ru.json new file mode 100644 index 0000000000000000000000000000000000000000..4ddc86cb7db90b6eace95a4b713e6765d5aa8858 --- /dev/null +++ b/MagicMirror/modules/default/alert/translations/ru.json @@ -0,0 +1,4 @@ +{ + "sysTitle": "MagicMirror² Уведомление", + "welcome": "Добро пожаловать, Ñтарт был уÑпешным!" +} diff --git a/MagicMirror/modules/default/alert/translations/th.json b/MagicMirror/modules/default/alert/translations/th.json new file mode 100644 index 0000000000000000000000000000000000000000..a1894bf3f114db99ed76a03c73e8f36795a0547c --- /dev/null +++ b/MagicMirror/modules/default/alert/translations/th.json @@ -0,0 +1,4 @@ +{ + "sysTitle": "à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™ MagicMirror²", + "welcome": "ยินดีต้à¸à¸™à¸£à¸±à¸š à¸à¸²à¸£à¹€à¸£à¸´à¹ˆà¸¡à¸•้นสำเร็จà¹à¸¥à¹‰à¸§!" +} diff --git a/MagicMirror/modules/default/calendar/README.md b/MagicMirror/modules/default/calendar/README.md new file mode 100644 index 0000000000000000000000000000000000000000..152759582885f337a4c63a17d82f7dd09ad5998b --- /dev/null +++ b/MagicMirror/modules/default/calendar/README.md @@ -0,0 +1,6 @@ +# Module: Calendar + +The `calendar` module is one of the default modules of the MagicMirror². +This module displays events from a public .ical calendar. It can combine multiple calendars. + +For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/calendar.html). diff --git a/MagicMirror/modules/default/calendar/calendar.css b/MagicMirror/modules/default/calendar/calendar.css new file mode 100644 index 0000000000000000000000000000000000000000..f8e3bd7929157484991af63bd2400642dc991a13 --- /dev/null +++ b/MagicMirror/modules/default/calendar/calendar.css @@ -0,0 +1,24 @@ +.calendar .symbol { + display: flex; + flex-direction: row; + justify-content: flex-end; + padding-left: 0; + padding-right: 10px; + font-size: var(--font-size-small); +} + +.calendar .symbol span { + padding-top: 4px; +} + +.calendar .title { + padding-left: 0; + padding-right: 0; + vertical-align: top; +} + +.calendar .time { + padding-left: 30px; + text-align: right; + vertical-align: top; +} diff --git a/MagicMirror/modules/default/calendar/calendar.js b/MagicMirror/modules/default/calendar/calendar.js new file mode 100644 index 0000000000000000000000000000000000000000..2ef9ca9c893d135a27d302f09fa2e23e7ef8ff80 --- /dev/null +++ b/MagicMirror/modules/default/calendar/calendar.js @@ -0,0 +1,980 @@ +/* global cloneObject */ + +/* MagicMirror² + * Module: Calendar + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +Module.register("calendar", { + // Define module defaults + defaults: { + maximumEntries: 10, // Total Maximum Entries + maximumNumberOfDays: 365, + limitDays: 0, // Limit the number of days shown, 0 = no limit + pastDaysCount: 0, + displaySymbol: true, + defaultSymbol: "calendar-alt", // Fontawesome Symbol see https://fontawesome.com/cheatsheet?from=io + defaultSymbolClassName: "fas fa-fw fa-", + showLocation: false, + displayRepeatingCountTitle: false, + defaultRepeatingCountTitle: "", + maxTitleLength: 25, + maxLocationTitleLength: 25, + wrapEvents: false, // wrap events to multiple lines breaking at maxTitleLength + wrapLocationEvents: false, + maxTitleLines: 3, + maxEventTitleLines: 3, + fetchInterval: 5 * 60 * 1000, // Update every 5 minutes. + animationSpeed: 2000, + fade: true, + urgency: 7, + timeFormat: "relative", + dateFormat: "MMM Do", + dateEndFormat: "LT", + fullDayEventDateFormat: "MMM Do", + showEnd: false, + getRelative: 6, + fadePoint: 0.25, // Start on 1/4th of the list. + hidePrivate: false, + hideOngoing: false, + hideTime: false, + showTimeToday: false, + colored: false, + customEvents: [], // Array of {keyword: "", symbol: "", color: ""} where Keyword is a regexp and symbol/color are to be applied for matched + tableClass: "small", + calendars: [ + { + symbol: "calendar-alt", + url: "https://www.calendarlabs.com/templates/ical/US-Holidays.ics" + } + ], + titleReplace: { + "De verjaardag van ": "", + "'s birthday": "" + }, + locationTitleReplace: { + "street ": "" + }, + broadcastEvents: true, + excludedEvents: [], + sliceMultiDayEvents: false, + broadcastPastEvents: false, + nextDaysRelative: false, + selfSignedCert: false, + coloredText: false, + coloredBorder: false, + coloredSymbol: false, + coloredBackground: false, + limitDaysNeverSkip: false, + flipDateHeaderTitle: false + }, + + requiresVersion: "2.1.0", + + // Define required scripts. + getStyles: function () { + return ["calendar.css", "font-awesome.css"]; + }, + + // Define required scripts. + getScripts: function () { + return ["moment.js"]; + }, + + // Define required translations. + getTranslations: function () { + // The translations for the default modules are defined in the core translation files. + // 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; + }, + + // Override start method. + start: function () { + const ONE_MINUTE = 60 * 1000; + + Log.info(`Starting module: ${this.name}`); + + if (this.config.colored) { + Log.warn("Your are using the deprecated config values 'colored'. Please switch to 'coloredSymbol' & 'coloredText'!"); + this.config.coloredText = true; + this.config.coloredSymbol = true; + } + if (this.config.coloredSymbolOnly) { + Log.warn("Your are using the deprecated config values 'coloredSymbolOnly'. Please switch to 'coloredSymbol' & 'coloredText'!"); + this.config.coloredText = false; + this.config.coloredSymbol = true; + } + + // Set locale. + moment.updateLocale(config.language, this.getLocaleSpecification(config.timeFormat)); + + // clear data holder before start + this.calendarData = {}; + + // indicate no data available yet + this.loaded = false; + + this.config.calendars.forEach((calendar) => { + calendar.url = calendar.url.replace("webcal://", "http://"); + + const calendarConfig = { + maximumEntries: calendar.maximumEntries, + maximumNumberOfDays: calendar.maximumNumberOfDays, + pastDaysCount: calendar.pastDaysCount, + broadcastPastEvents: calendar.broadcastPastEvents, + selfSignedCert: calendar.selfSignedCert + }; + + if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) { + calendarConfig.symbolClass = ""; + } + if (calendar.titleClass === "undefined" || calendar.titleClass === null) { + calendarConfig.titleClass = ""; + } + if (calendar.timeClass === "undefined" || calendar.timeClass === null) { + calendarConfig.timeClass = ""; + } + + // we check user and password here for backwards compatibility with old configs + if (calendar.user && calendar.pass) { + Log.warn("Deprecation warning: Please update your calendar authentication configuration."); + Log.warn("https://github.com/MichMich/MagicMirror/tree/v2.1.2/modules/default/calendar#calendar-authentication-options"); + calendar.auth = { + user: calendar.user, + pass: calendar.pass + }; + } + + // tell helper to start a fetcher for this calendar + // fetcher till cycle + this.addCalendar(calendar.url, calendar.auth, calendarConfig); + }); + + // Refresh the DOM every minute if needed: When using relative date format for events that start + // or end in less than an hour, the date shows minute granularity and we want to keep that accurate. + setTimeout(() => { + setInterval(() => { + this.updateDom(1); + }, ONE_MINUTE); + }, ONE_MINUTE - (new Date() % ONE_MINUTE)); + }, + + // Override socket notification handler. + socketNotificationReceived: function (notification, payload) { + if (notification === "FETCH_CALENDAR") { + this.sendSocketNotification(notification, { url: payload.url, id: this.identifier }); + } + + if (this.identifier !== payload.id) { + return; + } + + if (notification === "CALENDAR_EVENTS") { + if (this.hasCalendarURL(payload.url)) { + this.calendarData[payload.url] = payload.events; + this.error = null; + this.loaded = true; + + if (this.config.broadcastEvents) { + this.broadcastEvents(); + } + } + } else if (notification === "CALENDAR_ERROR") { + let error_message = this.translate(payload.error_type); + this.error = this.translate("MODULE_CONFIG_ERROR", { MODULE_NAME: this.name, ERROR: error_message }); + this.loaded = true; + } + + this.updateDom(this.config.animationSpeed); + }, + + // Override dom generator. + getDom: function () { + const ONE_SECOND = 1000; // 1,000 milliseconds + const ONE_MINUTE = ONE_SECOND * 60; + const ONE_HOUR = ONE_MINUTE * 60; + const ONE_DAY = ONE_HOUR * 24; + + const events = this.createEventList(true); + const wrapper = document.createElement("table"); + wrapper.className = this.config.tableClass; + + if (this.error) { + wrapper.innerHTML = this.error; + wrapper.className = `${this.config.tableClass} dimmed`; + return wrapper; + } + + if (events.length === 0) { + wrapper.innerHTML = this.loaded ? this.translate("EMPTY") : this.translate("LOADING"); + wrapper.className = `${this.config.tableClass} dimmed`; + 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; + } + startFade = events.length * this.config.fadePoint; + fadeSteps = events.length - startFade; + } + + let lastSeenDate = ""; + + events.forEach((event, index) => { + const dateAsString = moment(event.startDate, "x").format(this.config.dateFormat); + if (this.config.timeFormat === "dateheaders") { + if (lastSeenDate !== dateAsString) { + const dateRow = document.createElement("tr"); + dateRow.className = "dateheader normal"; + if (event.today) dateRow.className += " today"; + else if (event.dayBeforeYesterday) dateRow.className += " dayBeforeYesterday"; + else if (event.yesterday) dateRow.className += " yesterday"; + else if (event.tomorrow) dateRow.className += " tomorrow"; + else if (event.dayAfterTomorrow) dateRow.className += " dayAfterTomorrow"; + + const dateCell = document.createElement("td"); + dateCell.colSpan = "3"; + dateCell.innerHTML = dateAsString; + dateCell.style.paddingTop = "10px"; + dateRow.appendChild(dateCell); + wrapper.appendChild(dateRow); + + if (this.config.fade && index >= startFade) { + //fading + currentFadeStep = index - startFade; + dateRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep; + } + + lastSeenDate = dateAsString; + } + } + + const eventWrapper = document.createElement("tr"); + + if (this.config.coloredText) { + eventWrapper.style.cssText = `color:${this.colorForUrl(event.url, false)}`; + } + + if (this.config.coloredBackground) { + eventWrapper.style.backgroundColor = this.colorForUrl(event.url, true); + } + + if (this.config.coloredBorder) { + eventWrapper.style.borderColor = this.colorForUrl(event.url, false); + } + + eventWrapper.className = "event-wrapper normal event"; + if (event.today) eventWrapper.className += " today"; + else if (event.dayBeforeYesterday) eventWrapper.className += " dayBeforeYesterday"; + else if (event.yesterday) eventWrapper.className += " yesterday"; + else if (event.tomorrow) eventWrapper.className += " tomorrow"; + else if (event.dayAfterTomorrow) eventWrapper.className += " dayAfterTomorrow"; + + const symbolWrapper = document.createElement("td"); + + if (this.config.displaySymbol) { + if (this.config.coloredSymbol) { + symbolWrapper.style.cssText = `color:${this.colorForUrl(event.url, false)}`; + } + + const symbolClass = this.symbolClassForUrl(event.url); + symbolWrapper.className = `symbol align-right ${symbolClass}`; + + const symbols = this.symbolsForEvent(event); + symbols.forEach((s, index) => { + const symbol = document.createElement("span"); + symbol.className = s; + if (index > 0) { + symbol.style.paddingLeft = "5px"; + } + symbolWrapper.appendChild(symbol); + }); + eventWrapper.appendChild(symbolWrapper); + } else if (this.config.timeFormat === "dateheaders") { + const blankCell = document.createElement("td"); + blankCell.innerHTML = " "; + eventWrapper.appendChild(blankCell); + } + + const titleWrapper = document.createElement("td"); + let repeatingCountTitle = ""; + + if (this.config.displayRepeatingCountTitle && event.firstYear !== undefined) { + repeatingCountTitle = this.countTitleForUrl(event.url); + + if (repeatingCountTitle !== "") { + const thisYear = new Date(parseInt(event.startDate)).getFullYear(), + yearDiff = thisYear - event.firstYear; + + repeatingCountTitle = `, ${yearDiff}. ${repeatingCountTitle}`; + } + } + + // Color events if custom color is specified + if (this.config.customEvents.length > 0) { + for (let ev in this.config.customEvents) { + if (typeof this.config.customEvents[ev].color !== "undefined" && this.config.customEvents[ev].color !== "") { + let needle = new RegExp(this.config.customEvents[ev].keyword, "gi"); + if (needle.test(event.title)) { + // Respect parameter ColoredSymbolOnly also for custom events + if (this.config.coloredText) { + eventWrapper.style.cssText = `color:${this.config.customEvents[ev].color}`; + titleWrapper.style.cssText = `color:${this.config.customEvents[ev].color}`; + } + if (this.config.displaySymbol && this.config.coloredSymbol) { + symbolWrapper.style.cssText = `color:${this.config.customEvents[ev].color}`; + } + break; + } + } + } + } + + titleWrapper.innerHTML = this.titleTransform(event.title, this.config.titleReplace, this.config.wrapEvents, this.config.maxTitleLength, this.config.maxTitleLines) + repeatingCountTitle; + + const titleClass = this.titleClassForUrl(event.url); + + if (!this.config.coloredText) { + titleWrapper.className = `title bright ${titleClass}`; + } else { + titleWrapper.className = `title ${titleClass}`; + } + + if (this.config.timeFormat === "dateheaders") { + if (this.config.flipDateHeaderTitle) eventWrapper.appendChild(titleWrapper); + + if (event.fullDayEvent) { + titleWrapper.colSpan = "2"; + titleWrapper.classList.add("align-left"); + } else { + const timeWrapper = document.createElement("td"); + timeWrapper.className = `time light ${this.config.flipDateHeaderTitle ? "align-right " : "align-left "}${this.timeClassForUrl(event.url)}`; + timeWrapper.style.paddingLeft = "2px"; + timeWrapper.style.textAlign = this.config.flipDateHeaderTitle ? "right" : "left"; + timeWrapper.innerHTML = moment(event.startDate, "x").format("LT"); + + // Add endDate to dataheaders if showEnd is enabled + if (this.config.showEnd) { + timeWrapper.innerHTML += ` - ${this.capFirst(moment(event.endDate, "x").format("LT"))}`; + } + + eventWrapper.appendChild(timeWrapper); + + if (!this.config.flipDateHeaderTitle) titleWrapper.classList.add("align-right"); + } + if (!this.config.flipDateHeaderTitle) eventWrapper.appendChild(titleWrapper); + } else { + const timeWrapper = document.createElement("td"); + + eventWrapper.appendChild(titleWrapper); + const now = new Date(); + + if (this.config.timeFormat === "absolute") { + // Use dateFormat + timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat)); + // Add end time if showEnd + if (this.config.showEnd) { + timeWrapper.innerHTML += "-"; + timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat)); + } + // For full day events we use the fullDayEventDateFormat + if (event.fullDayEvent) { + //subtract one second so that fullDayEvents end at 23:59:59, and not at 0:00:00 one the next day + event.endDate -= ONE_SECOND; + timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat)); + } else if (this.config.getRelative > 0 && event.startDate < now) { + // Ongoing and getRelative is set + timeWrapper.innerHTML = this.capFirst( + this.translate("RUNNING", { + fallback: `${this.translate("RUNNING")} {timeUntilEnd}`, + timeUntilEnd: moment(event.endDate, "x").fromNow(true) + }) + ); + } else if (this.config.urgency > 0 && event.startDate - now < this.config.urgency * ONE_DAY) { + // Within urgency days + timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow()); + } + if (event.fullDayEvent && this.config.nextDaysRelative) { + // Full days events within the next two days + if (event.today) { + timeWrapper.innerHTML = this.capFirst(this.translate("TODAY")); + } else if (event.yesterday) { + timeWrapper.innerHTML = this.capFirst(this.translate("YESTERDAY")); + } else if (event.startDate - now < ONE_DAY && event.startDate - now > 0) { + timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW")); + } else if (event.startDate - now < 2 * ONE_DAY && event.startDate - now > 0) { + if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") { + timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW")); + } + } + } + } else { + // Show relative times + if (event.startDate >= now || (event.fullDayEvent && event.today)) { + // Use relative time + if (!this.config.hideTime && !event.fullDayEvent) { + timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat })); + } else { + timeWrapper.innerHTML = this.capFirst( + moment(event.startDate, "x").calendar(null, { + sameDay: this.config.showTimeToday ? "LT" : `[${this.translate("TODAY")}]`, + nextDay: `[${this.translate("TOMORROW")}]`, + nextWeek: "dddd", + sameElse: event.fullDayEvent ? this.config.fullDayEventDateFormat : this.config.dateFormat + }) + ); + } + if (event.fullDayEvent) { + // Full days events within the next two days + if (event.today) { + timeWrapper.innerHTML = this.capFirst(this.translate("TODAY")); + } else if (event.dayBeforeYesterday) { + if (this.translate("DAYBEFOREYESTERDAY") !== "DAYBEFOREYESTERDAY") { + timeWrapper.innerHTML = this.capFirst(this.translate("DAYBEFOREYESTERDAY")); + } + } else if (event.yesterday) { + timeWrapper.innerHTML = this.capFirst(this.translate("YESTERDAY")); + } else if (event.startDate - now < ONE_DAY && event.startDate - now > 0) { + timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW")); + } else if (event.startDate - now < 2 * ONE_DAY && event.startDate - now > 0) { + if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") { + timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW")); + } + } + } else if (event.startDate - now < this.config.getRelative * ONE_HOUR) { + // If event is within getRelative hours, display 'in xxx' time format or moment.fromNow() + timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow()); + } + } else { + // Ongoing event + timeWrapper.innerHTML = this.capFirst( + this.translate("RUNNING", { + fallback: `${this.translate("RUNNING")} {timeUntilEnd}`, + timeUntilEnd: moment(event.endDate, "x").fromNow(true) + }) + ); + } + } + timeWrapper.className = `time light ${this.timeClassForUrl(event.url)}`; + eventWrapper.appendChild(timeWrapper); + } + + // Create fade effect. + if (index >= startFade) { + currentFadeStep = index - startFade; + eventWrapper.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep; + } + wrapper.appendChild(eventWrapper); + + if (this.config.showLocation) { + if (event.location !== false) { + const locationRow = document.createElement("tr"); + locationRow.className = "event-wrapper-location normal xsmall light"; + if (event.today) locationRow.className += " today"; + else if (event.dayBeforeYesterday) locationRow.className += " dayBeforeYesterday"; + else if (event.yesterday) locationRow.className += " yesterday"; + else if (event.tomorrow) locationRow.className += " tomorrow"; + else if (event.dayAfterTomorrow) locationRow.className += " dayAfterTomorrow"; + + if (this.config.displaySymbol) { + const symbolCell = document.createElement("td"); + locationRow.appendChild(symbolCell); + } + + if (this.config.coloredText) { + locationRow.style.cssText = `color:${this.colorForUrl(event.url, false)}`; + } + + if (this.config.coloredBackground) { + locationRow.style.backgroundColor = this.colorForUrl(event.url, true); + } + + if (this.config.coloredBorder) { + locationRow.style.borderColor = this.colorForUrl(event.url, false); + } + + 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); + locationRow.appendChild(descCell); + + wrapper.appendChild(locationRow); + + if (index >= startFade) { + currentFadeStep = index - startFade; + locationRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep; + } + } + } + }); + + return wrapper; + }, + + /** + * This function accepts a number (either 12 or 24) and returns a moment.js LocaleSpecification with the + * corresponding timeformat to be used in the calendar display. If no number is given (or otherwise invalid input) + * it will a localeSpecification object with the system locale time format. + * + * @param {number} timeFormat Specifies either 12 or 24 hour time format + * @returns {moment.LocaleSpecification} formatted time + */ + getLocaleSpecification: function (timeFormat) { + switch (timeFormat) { + case 12: { + return { longDateFormat: { LT: "h:mm A" } }; + } + case 24: { + return { longDateFormat: { LT: "HH:mm" } }; + } + default: { + return { longDateFormat: { LT: moment.localeData().longDateFormat("LT") } }; + } + } + }, + + /** + * Checks if this config contains the calendar url. + * + * @param {string} url The calendar url + * @returns {boolean} True if the calendar config contains the url, False otherwise + */ + hasCalendarURL: function (url) { + for (const calendar of this.config.calendars) { + if (calendar.url === url) { + return true; + } + } + + return false; + }, + + /** + * Creates the sorted list of all events. + * + * @param {boolean} limitNumberOfEntries Whether to filter returned events for display. + * @returns {object[]} Array with events. + */ + createEventList: function (limitNumberOfEntries) { + const ONE_SECOND = 1000; // 1,000 milliseconds + const ONE_MINUTE = ONE_SECOND * 60; + const ONE_HOUR = ONE_MINUTE * 60; + const ONE_DAY = ONE_HOUR * 24; + + const now = new Date(); + const today = moment().startOf("day"); + const future = moment().startOf("day").add(this.config.maximumNumberOfDays, "days").toDate(); + let events = []; + + for (const calendarUrl in this.calendarData) { + const calendar = this.calendarData[calendarUrl]; + let remainingEntries = this.maximumEntriesForUrl(calendarUrl); + let maxPastDaysCompare = now - this.maximumPastDaysForUrl(calendarUrl) * ONE_DAY; + for (const e in calendar) { + const event = JSON.parse(JSON.stringify(calendar[e])); // clone object + + if (this.config.hidePrivate && event.class === "PRIVATE") { + // do not add the current event, skip it + continue; + } + if (limitNumberOfEntries) { + if (event.endDate < maxPastDaysCompare) { + continue; + } + if (this.config.hideOngoing && event.startDate < now) { + continue; + } + if (this.listContainsEvent(events, event)) { + continue; + } + if (--remainingEntries < 0) { + break; + } + } + event.url = calendarUrl; + event.today = event.startDate >= today && event.startDate < today + ONE_DAY; + event.dayBeforeYesterday = event.startDate >= today - ONE_DAY * 2 && event.startDate < today - ONE_DAY; + event.yesterday = event.startDate >= today - ONE_DAY && event.startDate < today; + event.tomorrow = !event.today && event.startDate >= today + ONE_DAY && event.startDate < today + 2 * ONE_DAY; + event.dayAfterTomorrow = !event.tomorrow && event.startDate >= today + ONE_DAY * 2 && event.startDate < today + 3 * ONE_DAY; + + /* 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. + */ + const maxCount = Math.ceil((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / ONE_DAY) + 1; + if (this.config.sliceMultiDayEvents && maxCount > 1) { + const splitEvents = []; + let midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x"); + let count = 1; + while (event.endDate > midnight) { + const thisEvent = JSON.parse(JSON.stringify(event)); // clone object + thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < today + ONE_DAY; + thisEvent.tomorrow = !thisEvent.today && thisEvent.startDate >= today + ONE_DAY && thisEvent.startDate < today + 2 * ONE_DAY; + thisEvent.endDate = midnight; + thisEvent.title += ` (${count}/${maxCount})`; + splitEvents.push(thisEvent); + + event.startDate = midnight; + count += 1; + midnight = moment(midnight, "x").add(1, "day").format("x"); // next day + } + // Last day + event.title += ` (${count}/${maxCount})`; + event.today += event.startDate >= today && event.startDate < today + ONE_DAY; + event.tomorrow = !event.today && event.startDate >= today + ONE_DAY && event.startDate < today + 2 * ONE_DAY; + splitEvents.push(event); + + for (let splitEvent of splitEvents) { + if (splitEvent.endDate > now && splitEvent.endDate <= future) { + events.push(splitEvent); + } + } + } else { + events.push(event); + } + } + } + + events.sort(function (a, b) { + return a.startDate - b.startDate; + }); + + if (!limitNumberOfEntries) { + return events; + } + + // Limit the number of days displayed + // If limitDays is set > 0, limit display to that number of days + if (this.config.limitDays > 0) { + 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) { + // if the only entry in the first day is a full day event that day is not counted as unique + if (!this.config.limitDaysNeverSkip && newEvents.length === 1 && days === 1 && newEvents[0].fullDayEvent) { + days--; + } + days++; + if (days > this.config.limitDays) { + continue; + } else { + lastDate = eventDate; + } + } + newEvents.push(ev); + } + events = newEvents; + } + + return events.slice(0, this.config.maximumEntries); + }, + + listContainsEvent: function (eventList, event) { + for (const evt of eventList) { + if (evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)) { + return true; + } + } + return false; + }, + + /** + * Requests node helper to add calendar url. + * + * @param {string} url The calendar url to add + * @param {object} auth The authentication method and credentials + * @param {object} calendarConfig The config of the specific calendar + */ + addCalendar: function (url, auth, calendarConfig) { + this.sendSocketNotification("ADD_CALENDAR", { + id: this.identifier, + url: url, + excludedEvents: calendarConfig.excludedEvents || this.config.excludedEvents, + maximumEntries: calendarConfig.maximumEntries || this.config.maximumEntries, + maximumNumberOfDays: calendarConfig.maximumNumberOfDays || this.config.maximumNumberOfDays, + pastDaysCount: calendarConfig.pastDaysCount || this.config.pastDaysCount, + fetchInterval: this.config.fetchInterval, + symbolClass: calendarConfig.symbolClass, + titleClass: calendarConfig.titleClass, + timeClass: calendarConfig.timeClass, + auth: auth, + broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents, + selfSignedCert: calendarConfig.selfSignedCert || this.config.selfSignedCert + }); + }, + + /** + * Retrieves the symbols for a specific event. + * + * @param {object} event Event to look for. + * @returns {string[]} The symbols + */ + symbolsForEvent: function (event) { + let symbols = this.getCalendarPropertyAsArray(event.url, "symbol", this.config.defaultSymbol); + + if (event.recurringEvent === true && this.hasCalendarProperty(event.url, "recurringSymbol")) { + symbols = this.mergeUnique(this.getCalendarPropertyAsArray(event.url, "recurringSymbol", this.config.defaultSymbol), symbols); + } + + if (event.fullDayEvent === true && this.hasCalendarProperty(event.url, "fullDaySymbol")) { + symbols = this.mergeUnique(this.getCalendarPropertyAsArray(event.url, "fullDaySymbol", this.config.defaultSymbol), symbols); + } + + // If custom symbol is set, replace event symbol + for (let ev of this.config.customEvents) { + if (typeof ev.symbol !== "undefined" && ev.symbol !== "") { + let needle = new RegExp(ev.keyword, "gi"); + if (needle.test(event.title)) { + // Get the default prefix for this class name and add to the custom symbol provided + const className = this.getCalendarProperty(event.url, "symbolClassName", this.config.defaultSymbolClassName); + symbols[0] = className + ev.symbol; + break; + } + } + } + + return symbols; + }, + + mergeUnique: function (arr1, arr2) { + return arr1.concat( + arr2.filter(function (item) { + return arr1.indexOf(item) === -1; + }) + ); + }, + + /** + * Retrieves the symbolClass for a specific calendar url. + * + * @param {string} url The calendar url + * @returns {string} The class to be used for the symbols of the calendar + */ + symbolClassForUrl: function (url) { + return this.getCalendarProperty(url, "symbolClass", ""); + }, + + /** + * Retrieves the titleClass for a specific calendar url. + * + * @param {string} url The calendar url + * @returns {string} The class to be used for the title of the calendar + */ + titleClassForUrl: function (url) { + return this.getCalendarProperty(url, "titleClass", ""); + }, + + /** + * Retrieves the timeClass for a specific calendar url. + * + * @param {string} url The calendar url + * @returns {string} The class to be used for the time of the calendar + */ + timeClassForUrl: function (url) { + return this.getCalendarProperty(url, "timeClass", ""); + }, + + /** + * Retrieves the calendar name for a specific calendar url. + * + * @param {string} url The calendar url + * @returns {string} The name of the calendar + */ + calendarNameForUrl: function (url) { + return this.getCalendarProperty(url, "name", ""); + }, + + /** + * Retrieves the color for a specific calendar url. + * + * @param {string} url The calendar url + * @param {boolean} isBg Determines if we fetch the bgColor or not + * @returns {string} The color + */ + colorForUrl: function (url, isBg) { + return this.getCalendarProperty(url, isBg ? "bgColor" : "color", "#fff"); + }, + + /** + * Retrieves the count title for a specific calendar url. + * + * @param {string} url The calendar url + * @returns {string} The title + */ + countTitleForUrl: function (url) { + return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle); + }, + + /** + * Retrieves the maximum entry count for a specific calendar url. + * + * @param {string} url The calendar url + * @returns {number} The maximum entry count + */ + maximumEntriesForUrl: function (url) { + return this.getCalendarProperty(url, "maximumEntries", this.config.maximumEntries); + }, + + /** + * Retrieves the maximum count of past days which events of should be displayed for a specific calendar url. + * + * @param {string} url The calendar url + * @returns {number} The maximum past days count + */ + maximumPastDaysForUrl: function (url) { + return this.getCalendarProperty(url, "pastDaysCount", this.config.pastDaysCount); + }, + + /** + * Helper method to retrieve the property for a specific calendar url. + * + * @param {string} url The calendar url + * @param {string} property The property to look for + * @param {string} defaultValue The value if the property is not found + * @returns {*} The property + */ + getCalendarProperty: function (url, property, defaultValue) { + for (const calendar of this.config.calendars) { + if (calendar.url === url && calendar.hasOwnProperty(property)) { + return calendar[property]; + } + } + + return defaultValue; + }, + + getCalendarPropertyAsArray: function (url, property, defaultValue) { + let p = this.getCalendarProperty(url, property, defaultValue); + if (property === "symbol" || property === "recurringSymbol" || property === "fullDaySymbol") { + const className = this.getCalendarProperty(url, "symbolClassName", this.config.defaultSymbolClassName); + p = className + p; + } + + if (!(p instanceof Array)) p = [p]; + return p; + }, + + hasCalendarProperty: function (url, property) { + return !!this.getCalendarProperty(url, property, undefined); + }, + + /** + * Shortens a string if it's longer than maxLength and add a ellipsis to the end + * + * @param {string} string Text string to shorten + * @param {number} maxLength The max length of the string + * @param {boolean} wrapEvents Wrap the text after the line has reached maxLength + * @param {number} maxTitleLines The max number of vertical lines before cutting event title + * @returns {string} The shortened string + */ + shorten: function (string, maxLength, wrapEvents, maxTitleLines) { + if (typeof string !== "string") { + return ""; + } + + if (wrapEvents === true) { + const words = string.split(" "); + let temp = ""; + let currentLine = ""; + let line = 0; + + 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} `; + } else { + line++; + if (line > maxTitleLines - 1) { + if (i < words.length) { + currentLine += "…"; + } + break; + } + + if (currentLine.length > 0) { + temp += `${currentLine}<br>${word} `; + } else { + temp += `${word}<br>`; + } + currentLine = ""; + } + } + + return (temp + currentLine).trim(); + } else { + if (maxLength && typeof maxLength === "number" && string.length > maxLength) { + return `${string.trim().slice(0, maxLength)}…`; + } else { + return string.trim(); + } + } + }, + + /** + * Capitalize the first letter of a string + * + * @param {string} string The string to capitalize + * @returns {string} The capitalized string + */ + capFirst: function (string) { + return string.charAt(0).toUpperCase() + string.slice(1); + }, + + /** + * Transforms the title of an event for usage. + * Replaces parts of the text as defined in config.titleReplace. + * Shortens title based on config.maxTitleLength and config.wrapEvents + * + * @param {string} title The title to transform. + * @param {object} titleReplace Pairs of strings to be replaced in the title + * @param {boolean} wrapEvents Wrap the text after the line has reached maxLength + * @param {number} maxTitleLength The max length of the string + * @param {number} maxTitleLines The max number of vertical lines before cutting event title + * @returns {string} The transformed title. + */ + titleTransform: function (title, titleReplace, wrapEvents, maxTitleLength, maxTitleLines) { + for (let needle in titleReplace) { + const replacement = titleReplace[needle]; + + const regParts = needle.match(/^\/(.+)\/([gim]*)$/); + if (regParts) { + // the parsed pattern is a regexp. + needle = new RegExp(regParts[1], regParts[2]); + } + + title = title.replace(needle, replacement); + } + + title = this.shorten(title, maxTitleLength, wrapEvents, maxTitleLines); + return title; + }, + + /** + * Broadcasts the events to all other modules for reuse. + * The all events available in one array, sorted on startdate. + */ + broadcastEvents: function () { + const eventList = this.createEventList(false); + for (const event of eventList) { + event.symbol = this.symbolsForEvent(event); + event.calendarName = this.calendarNameForUrl(event.url); + event.color = this.colorForUrl(event.url, false); + delete event.url; + } + + this.sendNotification("CALENDAR_EVENTS", eventList); + } +}); diff --git a/MagicMirror/modules/default/calendar/calendarfetcher.js b/MagicMirror/modules/default/calendar/calendarfetcher.js new file mode 100644 index 0000000000000000000000000000000000000000..00688ee2bb602e0606e82cfe93be02150ddca112 --- /dev/null +++ b/MagicMirror/modules/default/calendar/calendarfetcher.js @@ -0,0 +1,159 @@ +/* MagicMirror² + * Node Helper: Calendar - CalendarFetcher + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ + +const https = require("https"); +const digest = require("digest-fetch"); +const ical = require("node-ical"); +const fetch = require("fetch"); +const Log = require("logger"); +const NodeHelper = require("node_helper"); +const CalendarUtils = require("./calendarutils"); + +/** + * + * @param {string} url The url of the calendar to fetch + * @param {number} reloadInterval Time in ms the calendar is fetched again + * @param {string[]} excludedEvents An array of words / phrases from event titles that will be excluded from being shown. + * @param {number} maximumEntries The maximum number of events fetched. + * @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, selfSignedCert) { + let reloadTimer = null; + let events = []; + + let fetchFailedCallback = function () {}; + let eventsReceivedCallback = function () {}; + + /** + * Initiates calendar fetch. + */ + const fetchCalendar = () => { + clearTimeout(reloadTimer); + reloadTimer = null; + const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); + let fetcher = null; + let httpsAgent = null; + let headers = { + "User-Agent": `Mozilla/5.0 (Node.js ${nodeVersion}) MagicMirror/${global.version}` + }; + + if (selfSignedCert) { + httpsAgent = new https.Agent({ + rejectUnauthorized: false + }); + } + if (auth) { + if (auth.method === "bearer") { + headers.Authorization = `Bearer ${auth.pass}`; + } else if (auth.method === "digest") { + fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, agent: httpsAgent }); + } else { + headers.Authorization = `Basic ${Buffer.from(`${auth.user}:${auth.pass}`).toString("base64")}`; + } + } + if (fetcher === null) { + fetcher = fetch(url, { headers: headers, agent: httpsAgent }); + } + + fetcher + .then(NodeHelper.checkFetchStatus) + .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); + scheduleTimer(); + return; + } + this.broadcastEvents(); + scheduleTimer(); + }) + .catch((error) => { + fetchFailedCallback(this, error); + scheduleTimer(); + }); + }; + + /** + * Schedule the timer for the next update. + */ + const scheduleTimer = function () { + clearTimeout(reloadTimer); + reloadTimer = setTimeout(function () { + fetchCalendar(); + }, reloadInterval); + }; + + /* public methods */ + + /** + * Initiate fetchCalendar(); + */ + this.startFetch = function () { + fetchCalendar(); + }; + + /** + * Broadcast the existing events. + */ + this.broadcastEvents = function () { + Log.info(`Calendar-Fetcher: Broadcasting ${events.length} events.`); + eventsReceivedCallback(this); + }; + + /** + * Sets the on success callback + * + * @param {Function} callback The on success callback. + */ + this.onReceive = function (callback) { + eventsReceivedCallback = callback; + }; + + /** + * Sets the on error callback + * + * @param {Function} callback The on error callback. + */ + this.onError = function (callback) { + fetchFailedCallback = callback; + }; + + /** + * Returns the url of this fetcher. + * + * @returns {string} The url of this fetcher. + */ + this.url = function () { + return url; + }; + + /** + * Returns current available events for this fetcher. + * + * @returns {object[]} The current available events for this fetcher. + */ + this.events = function () { + return events; + }; +}; + +module.exports = CalendarFetcher; diff --git a/MagicMirror/modules/default/calendar/calendarutils.js b/MagicMirror/modules/default/calendar/calendarutils.js new file mode 100644 index 0000000000000000000000000000000000000000..64a7b9f461b012df6d8cc00822d25837c33fff78 --- /dev/null +++ b/MagicMirror/modules/default/calendar/calendarutils.js @@ -0,0 +1,613 @@ +/* MagicMirror² + * Calendar Util Methods + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ + +/** + * @external Moment + */ +const path = require("path"); +const moment = require("moment"); +const zoneTable = require(path.join(__dirname, "windowsZones.json")); +const Log = require("../../../js/logger"); + +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 the event which needs adjustement + * @param {Date} date the date on which this event happens + * @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; + }, + + /** + * Filter the events from ical according to the given config + * + * @param {object} data the calendar data from ical + * @param {object} config The configuration object + * @returns {string[]} the filtered events + */ + 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]) => { + Log.debug("Processing entry..."); + const now = new Date(); + const today = moment().startOf("day").toDate(); + const future = moment().startOf("day").add(config.maximumNumberOfDays, "days").subtract(1, "seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat. + let past = today; + + 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") { + Log.debug(`Event:\n${JSON.stringify(event)}`); + let startDate = eventDate(event, "start"); + let endDate; + + 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()}`); + Log.debug(`end:: ${endDate.toDate()}`); + + // Calculate the duration of the event for use with recurring events. + let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x")); + Log.debug(`duration: ${duration}`); + + // FIXME: Since the parsed json object from node-ical comes with time information + // this check could be removed (?) + if (event.start.length === 8) { + startDate = startDate.startOf("day"); + } + + const title = CalendarUtils.getTitleFromEvent(event); + Log.debug(`title: ${title}`); + + 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)) { + Log.debug("fullday"); + // if full day event, only use the date part of the ranges + pastLocal = pastMoment.toDate(); + futureLocal = futureMoment.toDate(); + + Log.debug(`pastLocal: ${pastLocal}`); + Log.debug(`futureLocal: ${futureLocal}`); + } 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(`Search for recurring events between: ${pastLocal} and ${futureLocal}`); + const dates = rule.between(pastLocal, futureLocal, true, limitFunction); + Log.debug(`Title: ${event.summary}, with 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. + Log.debug(`event.recurrences: ${event.recurrences}`); + 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]; + // Remove the time information of each date by using its substring, using the following method: + // .toISOString().substring(0,10). + // since the date is given as ISOString with YYYY-MM-DDTHH:MM:SS.SSSZ + // (see https://momentjs.com/docs/#/displaying/as-iso-string/). + 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 following offset. + Log.debug(` recurring date is ${date} offset is ${dateoffset}`); + + let dh = moment(date).format("HH"); + Log.debug(` recurring date is ${date} offset is ${dateoffset / 60} Hour is ${dh}`); + + if (CalendarUtils.isFullDayEvent(event)) { + Log.debug("Fullday"); + // If the offset is negative (east of GMT), where the problem is + if (dateoffset < 0) { + if (dh < Math.abs(dateoffset / 60)) { + // if the rrule byweekday WAS explicitly set , correct it + // reduce the time by the offset + if (curEvent.rrule.origOptions.byweekday !== undefined) { + // Apply the correction to the date/time to get it UTC relative + date = new Date(date.getTime() - Math.abs(24 * 60) * 60000); + } + // the duration was calculated way back at the top before we could correct the start time.. + // fix it for this event entry + //duration = 24 * 60 * 60 * 1000; + Log.debug(`new recurring date1 fulldate is ${date}`); + } + } else { + // if the timezones are the same, correct date if needed + //if (event.start.tz === moment.tz.guess()) { + // if the date hour is less than the offset + if (24 - dh <= Math.abs(dateoffset / 60)) { + // if the rrule byweekday WAS explicitly set , correct it + if (curEvent.rrule.origOptions.byweekday !== undefined) { + // apply the correction to the date/time back to right day + date = new Date(date.getTime() + Math.abs(24 * 60) * 60000); + } + // the duration was calculated way back at the top before we could correct the start time.. + // fix it for this event entry + //duration = 24 * 60 * 60 * 1000; + Log.debug(`new recurring date2 fulldate is ${date}`); + } + //} + } + } else { + // not full day, but luxon can still screw up the date on the rule processing + // we need to correct the date to get back to the right event for + if (dateoffset < 0) { + // if the date hour is less than the offset + if (dh <= Math.abs(dateoffset / 60)) { + // if the rrule byweekday WAS explicitly set , correct it + if (curEvent.rrule.origOptions.byweekday !== undefined) { + // Reduce the time by t: + // Apply the correction to the date/time to get it UTC relative + date = new Date(date.getTime() - Math.abs(24 * 60) * 60000); + } + // the duration was calculated way back at the top before we could correct the start time.. + // fix it for this event entry + //duration = 24 * 60 * 60 * 1000; + Log.debug(`new recurring date1 is ${date}`); + } + } else { + // if the timezones are the same, correct date if needed + //if (event.start.tz === moment.tz.guess()) { + // if the date hour is less than the offset + if (24 - dh <= Math.abs(dateoffset / 60)) { + // if the rrule byweekday WAS explicitly set , correct it + if (curEvent.rrule.origOptions.byweekday !== undefined) { + // apply the correction to the date/time back to right day + date = new Date(date.getTime() + Math.abs(24 * 60) * 60000); + } + // the duration was calculated way back at the top before we could correct the start time.. + // fix it for this event entry + //duration = 24 * 60 * 60 * 1000; + Log.debug(`new recurring date2 is ${date}`); + } + //} + } + } + startDate = moment(date); + Log.debug(`Corrected startDate: ${startDate.toDate()}`); + + let adjustDays = 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; + } + + // 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; + }); + + return newEvents; + }, + + /** + * Lookup iana tz from windows + * + * @param {string} msTZName the timezone name to lookup + * @returns {string|null} the iana name or null of none is found + */ + 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; + }, + + /** + * Determines if the user defined title filter should apply + * + * @param {string} title the title of the event + * @param {string} filter the string to look for, can be a regex also + * @param {boolean} useRegex true if a regex should be used, otherwise it just looks for the filter as a string + * @param {string} regexFlags flags that should be applied to the regex + * @returns {boolean} True if the title should be filtered out, false otherwise + */ + 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; +} diff --git a/MagicMirror/modules/default/calendar/debug.js b/MagicMirror/modules/default/calendar/debug.js new file mode 100644 index 0000000000000000000000000000000000000000..5e19e131340996bbabb5fa9e0b1777958085e4fa --- /dev/null +++ b/MagicMirror/modules/default/calendar/debug.js @@ -0,0 +1,43 @@ +/* CalendarFetcher Tester + * use this script with `node debug.js` to test the fetcher without the need + * of starting the MagicMirror² core. Adjust the values below to your desire. + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +// Alias modules mentioned in package.js under _moduleAliases. +require("module-alias/register"); + +const CalendarFetcher = require("./calendarfetcher"); + +const url = "https://calendar.google.com/calendar/ical/pkm1t2uedjbp0uvq1o7oj1jouo%40group.calendar.google.com/private-08ba559f89eec70dd74bbd887d0a3598/basic.ics"; // Standard test URL +//const url = "https://www.googleapis.com/calendar/v3/calendars/primary/events/"; // URL for Bearer auth (must be configured in Google OAuth2 first) +const fetchInterval = 60 * 60 * 1000; +const maximumEntries = 10; +const maximumNumberOfDays = 365; +const user = "magicmirror"; +const pass = "MyStrongPass"; +const auth = { + user: user, + pass: pass +}; + +console.log("Create fetcher ..."); + +const fetcher = new CalendarFetcher(url, fetchInterval, [], maximumEntries, maximumNumberOfDays, auth); + +fetcher.onReceive(function (fetcher) { + console.log(fetcher.events()); + console.log("------------------------------------------------------------"); + process.exit(0); +}); + +fetcher.onError(function (fetcher, error) { + console.log("Fetcher error:"); + console.log(error); + process.exit(1); +}); + +fetcher.startFetch(); + +console.log("Create fetcher done! "); diff --git a/MagicMirror/modules/default/calendar/node_helper.js b/MagicMirror/modules/default/calendar/node_helper.js new file mode 100644 index 0000000000000000000000000000000000000000..08e6158bdadefbf438d09b23ee5265037dc8508c --- /dev/null +++ b/MagicMirror/modules/default/calendar/node_helper.js @@ -0,0 +1,96 @@ +/* MagicMirror² + * Node Helper: Calendar + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +const NodeHelper = require("node_helper"); +const Log = require("logger"); +const CalendarFetcher = require("./calendarfetcher"); + +module.exports = NodeHelper.create({ + // Override start method. + start: function () { + Log.log(`Starting node helper for: ${this.name}`); + this.fetchers = []; + }, + + // 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.selfSignedCert, payload.id); + } else if (notification === "FETCH_CALENDAR") { + const key = payload.id + payload.url; + if (typeof this.fetchers[key] === "undefined") { + Log.error("Calendar Error. No fetcher exists with key: ", key); + this.sendSocketNotification("CALENDAR_ERROR", { error_type: "MODULE_ERROR_UNSPECIFIED" }); + return; + } + this.fetchers[key].startFetch(); + } + }, + + /** + * Creates a fetcher for a new url if it doesn't exist yet. + * Otherwise it reuses the existing one. + * + * @param {string} url The url of the calendar + * @param {number} fetchInterval How often does the calendar needs to be fetched in ms + * @param {string[]} excludedEvents An array of words / phrases from event titles that will be excluded from being shown. + * @param {number} maximumEntries The maximum number of events fetched. + * @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, selfSignedCert, identifier) { + try { + new URL(url); + } catch (error) { + Log.error("Calendar Error. Malformed calendar url: ", url, error); + this.sendSocketNotification("CALENDAR_ERROR", { error_type: "MODULE_ERROR_MALFORMED_URL" }); + return; + } + + let fetcher; + if (typeof this.fetchers[identifier + url] === "undefined") { + Log.log(`Create new calendarfetcher for url: ${url} - Interval: ${fetchInterval}`); + fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert); + + fetcher.onReceive((fetcher) => { + this.broadcastEvents(fetcher, identifier); + }); + + fetcher.onError((fetcher, error) => { + Log.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error); + let error_type = NodeHelper.checkFetchError(error); + this.sendSocketNotification("CALENDAR_ERROR", { + id: identifier, + error_type + }); + }); + + this.fetchers[identifier + url] = fetcher; + } else { + Log.log(`Use existing calendarfetcher for url: ${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() + }); + } +}); diff --git a/MagicMirror/modules/default/calendar/windowsZones.json b/MagicMirror/modules/default/calendar/windowsZones.json new file mode 100644 index 0000000000000000000000000000000000000000..cad82bb9ee2f82423fe01ccc40452642926db7a5 --- /dev/null +++ b/MagicMirror/modules/default/calendar/windowsZones.json @@ -0,0 +1,237 @@ +{ + "Dateline Standard Time": { "iana": ["Etc/GMT+12"] }, + "UTC-11": { "iana": ["Etc/GMT+11"] }, + "Aleutian Standard Time": { "iana": ["America/Adak"] }, + "Hawaiian Standard Time": { "iana": ["Pacific/Honolulu"] }, + "Marquesas Standard Time": { "iana": ["Pacific/Marquesas"] }, + "Alaskan Standard Time": { "iana": ["America/Anchorage"] }, + "UTC-09": { "iana": ["Etc/GMT+9"] }, + "Pacific Standard Time (Mexico)": { "iana": ["America/Tijuana"] }, + "UTC-08": { "iana": ["Etc/GMT+8"] }, + "Pacific Standard Time": { "iana": ["America/Los_Angeles"] }, + "US Mountain Standard Time": { "iana": ["America/Phoenix"] }, + "Mountain Standard Time (Mexico)": { "iana": ["America/Chihuahua"] }, + "Mountain Standard Time": { "iana": ["America/Denver"] }, + "Central America Standard Time": { "iana": ["America/Guatemala"] }, + "Central Standard Time": { "iana": ["America/Chicago"] }, + "Easter Island Standard Time": { "iana": ["Pacific/Easter"] }, + "Central Standard Time (Mexico)": { "iana": ["America/Mexico_City"] }, + "Canada Central Standard Time": { "iana": ["America/Regina"] }, + "SA Pacific Standard Time": { "iana": ["America/Bogota"] }, + "Eastern Standard Time (Mexico)": { "iana": ["America/Cancun"] }, + "Eastern Standard Time": { "iana": ["America/New_York"] }, + "Haiti Standard Time": { "iana": ["America/Port-au-Prince"] }, + "Cuba Standard Time": { "iana": ["America/Havana"] }, + "US Eastern Standard Time": { "iana": ["America/Indianapolis"] }, + "Turks And Caicos Standard Time": { "iana": ["America/Grand_Turk"] }, + "Paraguay Standard Time": { "iana": ["America/Asuncion"] }, + "Atlantic Standard Time": { "iana": ["America/Halifax"] }, + "Venezuela Standard Time": { "iana": ["America/Caracas"] }, + "Central Brazilian Standard Time": { "iana": ["America/Cuiaba"] }, + "SA Western Standard Time": { "iana": ["America/La_Paz"] }, + "Pacific SA Standard Time": { "iana": ["America/Santiago"] }, + "Newfoundland Standard Time": { "iana": ["America/St_Johns"] }, + "Tocantins Standard Time": { "iana": ["America/Araguaina"] }, + "E. South America Standard Time": { "iana": ["America/Sao_Paulo"] }, + "SA Eastern Standard Time": { "iana": ["America/Cayenne"] }, + "Argentina Standard Time": { "iana": ["America/Buenos_Aires"] }, + "Greenland Standard Time": { "iana": ["America/Godthab"] }, + "Montevideo Standard Time": { "iana": ["America/Montevideo"] }, + "Magallanes Standard Time": { "iana": ["America/Punta_Arenas"] }, + "Saint Pierre Standard Time": { "iana": ["America/Miquelon"] }, + "Bahia Standard Time": { "iana": ["America/Bahia"] }, + "UTC-02": { "iana": ["Etc/GMT+2"] }, + "Azores Standard Time": { "iana": ["Atlantic/Azores"] }, + "Cape Verde Standard Time": { "iana": ["Atlantic/Cape_Verde"] }, + "UTC": { "iana": ["Etc/GMT"] }, + "GMT Standard Time": { "iana": ["Europe/London"] }, + "Greenwich Standard Time": { "iana": ["Atlantic/Reykjavik"] }, + "Sao Tome Standard Time": { "iana": ["Africa/Sao_Tome"] }, + "Morocco Standard Time": { "iana": ["Africa/Casablanca"] }, + "W. Europe Standard Time": { "iana": ["Europe/Berlin"] }, + "Central Europe Standard Time": { "iana": ["Europe/Budapest"] }, + "Romance Standard Time": { "iana": ["Europe/Paris"] }, + "Central European Standard Time": { "iana": ["Europe/Warsaw"] }, + "W. Central Africa Standard Time": { "iana": ["Africa/Lagos"] }, + "Jordan Standard Time": { "iana": ["Asia/Amman"] }, + "GTB Standard Time": { "iana": ["Europe/Bucharest"] }, + "Middle East Standard Time": { "iana": ["Asia/Beirut"] }, + "Egypt Standard Time": { "iana": ["Africa/Cairo"] }, + "E. Europe Standard Time": { "iana": ["Europe/Chisinau"] }, + "Syria Standard Time": { "iana": ["Asia/Damascus"] }, + "West Bank Standard Time": { "iana": ["Asia/Hebron"] }, + "South Africa Standard Time": { "iana": ["Africa/Johannesburg"] }, + "FLE Standard Time": { "iana": ["Europe/Kiev"] }, + "Israel Standard Time": { "iana": ["Asia/Jerusalem"] }, + "Kaliningrad Standard Time": { "iana": ["Europe/Kaliningrad"] }, + "Sudan Standard Time": { "iana": ["Africa/Khartoum"] }, + "Libya Standard Time": { "iana": ["Africa/Tripoli"] }, + "Namibia Standard Time": { "iana": ["Africa/Windhoek"] }, + "Arabic Standard Time": { "iana": ["Asia/Baghdad"] }, + "Turkey Standard Time": { "iana": ["Europe/Istanbul"] }, + "Arab Standard Time": { "iana": ["Asia/Riyadh"] }, + "Belarus Standard Time": { "iana": ["Europe/Minsk"] }, + "Russian Standard Time": { "iana": ["Europe/Moscow"] }, + "E. Africa Standard Time": { "iana": ["Africa/Nairobi"] }, + "Iran Standard Time": { "iana": ["Asia/Tehran"] }, + "Arabian Standard Time": { "iana": ["Asia/Dubai"] }, + "Astrakhan Standard Time": { "iana": ["Europe/Astrakhan"] }, + "Azerbaijan Standard Time": { "iana": ["Asia/Baku"] }, + "Russia Time Zone 3": { "iana": ["Europe/Samara"] }, + "Mauritius Standard Time": { "iana": ["Indian/Mauritius"] }, + "Saratov Standard Time": { "iana": ["Europe/Saratov"] }, + "Georgian Standard Time": { "iana": ["Asia/Tbilisi"] }, + "Volgograd Standard Time": { "iana": ["Europe/Volgograd"] }, + "Caucasus Standard Time": { "iana": ["Asia/Yerevan"] }, + "Afghanistan Standard Time": { "iana": ["Asia/Kabul"] }, + "West Asia Standard Time": { "iana": ["Asia/Tashkent"] }, + "Ekaterinburg Standard Time": { "iana": ["Asia/Yekaterinburg"] }, + "Pakistan Standard Time": { "iana": ["Asia/Karachi"] }, + "Qyzylorda Standard Time": { "iana": ["Asia/Qyzylorda"] }, + "India Standard Time": { "iana": ["Asia/Calcutta"] }, + "Sri Lanka Standard Time": { "iana": ["Asia/Colombo"] }, + "Nepal Standard Time": { "iana": ["Asia/Katmandu"] }, + "Central Asia Standard Time": { "iana": ["Asia/Almaty"] }, + "Bangladesh Standard Time": { "iana": ["Asia/Dhaka"] }, + "Omsk Standard Time": { "iana": ["Asia/Omsk"] }, + "Myanmar Standard Time": { "iana": ["Asia/Rangoon"] }, + "SE Asia Standard Time": { "iana": ["Asia/Bangkok"] }, + "Altai Standard Time": { "iana": ["Asia/Barnaul"] }, + "W. Mongolia Standard Time": { "iana": ["Asia/Hovd"] }, + "North Asia Standard Time": { "iana": ["Asia/Krasnoyarsk"] }, + "N. Central Asia Standard Time": { "iana": ["Asia/Novosibirsk"] }, + "Tomsk Standard Time": { "iana": ["Asia/Tomsk"] }, + "China Standard Time": { "iana": ["Asia/Shanghai"] }, + "North Asia East Standard Time": { "iana": ["Asia/Irkutsk"] }, + "Singapore Standard Time": { "iana": ["Asia/Singapore"] }, + "W. Australia Standard Time": { "iana": ["Australia/Perth"] }, + "Taipei Standard Time": { "iana": ["Asia/Taipei"] }, + "Ulaanbaatar Standard Time": { "iana": ["Asia/Ulaanbaatar"] }, + "Aus Central W. Standard Time": { "iana": ["Australia/Eucla"] }, + "Transbaikal Standard Time": { "iana": ["Asia/Chita"] }, + "Tokyo Standard Time": { "iana": ["Asia/Tokyo"] }, + "North Korea Standard Time": { "iana": ["Asia/Pyongyang"] }, + "Korea Standard Time": { "iana": ["Asia/Seoul"] }, + "Yakutsk Standard Time": { "iana": ["Asia/Yakutsk"] }, + "Cen. Australia Standard Time": { "iana": ["Australia/Adelaide"] }, + "AUS Central Standard Time": { "iana": ["Australia/Darwin"] }, + "E. Australia Standard Time": { "iana": ["Australia/Brisbane"] }, + "AUS Eastern Standard Time": { "iana": ["Australia/Sydney"] }, + "West Pacific Standard Time": { "iana": ["Pacific/Port_Moresby"] }, + "Tasmania Standard Time": { "iana": ["Australia/Hobart"] }, + "Vladivostok Standard Time": { "iana": ["Asia/Vladivostok"] }, + "Lord Howe Standard Time": { "iana": ["Australia/Lord_Howe"] }, + "Bougainville Standard Time": { "iana": ["Pacific/Bougainville"] }, + "Russia Time Zone 10": { "iana": ["Asia/Srednekolymsk"] }, + "Magadan Standard Time": { "iana": ["Asia/Magadan"] }, + "Norfolk Standard Time": { "iana": ["Pacific/Norfolk"] }, + "Sakhalin Standard Time": { "iana": ["Asia/Sakhalin"] }, + "Central Pacific Standard Time": { "iana": ["Pacific/Guadalcanal"] }, + "Russia Time Zone 11": { "iana": ["Asia/Kamchatka"] }, + "New Zealand Standard Time": { "iana": ["Pacific/Auckland"] }, + "UTC+12": { "iana": ["Etc/GMT-12"] }, + "Fiji Standard Time": { "iana": ["Pacific/Fiji"] }, + "Chatham Islands Standard Time": { "iana": ["Pacific/Chatham"] }, + "UTC+13": { "iana": ["Etc/GMT-13"] }, + "Tonga Standard Time": { "iana": ["Pacific/Tongatapu"] }, + "Samoa Standard Time": { "iana": ["Pacific/Apia"] }, + "Line Islands Standard Time": { "iana": ["Pacific/Kiritimati"] }, + "(UTC-12:00) International Date Line West": { "iana": ["Etc/GMT+12"] }, + "(UTC-11:00) Midway Island, Samoa": { "iana": ["Pacific/Apia"] }, + "(UTC-10:00) Hawaii": { "iana": ["Pacific/Honolulu"] }, + "(UTC-09:00) Alaska": { "iana": ["America/Anchorage"] }, + "(UTC-08:00) Pacific Time (US & Canada); Tijuana": { "iana": ["America/Los_Angeles"] }, + "(UTC-08:00) Pacific Time (US and Canada); Tijuana": { "iana": ["America/Los_Angeles"] }, + "(UTC-07:00) Mountain Time (US & Canada)": { "iana": ["America/Denver"] }, + "(UTC-07:00) Mountain Time (US and Canada)": { "iana": ["America/Denver"] }, + "(UTC-07:00) Chihuahua, La Paz, Mazatlan": { "iana": [null] }, + "(UTC-07:00) Arizona": { "iana": ["America/Phoenix"] }, + "(UTC-06:00) Central Time (US & Canada)": { "iana": ["America/Chicago"] }, + "(UTC-06:00) Central Time (US and Canada)": { "iana": ["America/Chicago"] }, + "(UTC-06:00) Saskatchewan": { "iana": ["America/Regina"] }, + "(UTC-06:00) Guadalajara, Mexico City, Monterrey": { "iana": [null] }, + "(UTC-06:00) Central America": { "iana": ["America/Guatemala"] }, + "(UTC-05:00) Eastern Time (US & Canada)": { "iana": ["America/New_York"] }, + "(UTC-05:00) Eastern Time (US and Canada)": { "iana": ["America/New_York"] }, + "(UTC-05:00) Indiana (East)": { "iana": ["America/Indianapolis"] }, + "(UTC-05:00) Bogota, Lima, Quito": { "iana": ["America/Bogota"] }, + "(UTC-04:00) Atlantic Time (Canada)": { "iana": ["America/Halifax"] }, + "(UTC-04:00) Georgetown, La Paz, San Juan": { "iana": ["America/La_Paz"] }, + "(UTC-04:00) Santiago": { "iana": ["America/Santiago"] }, + "(UTC-03:30) Newfoundland": { "iana": [null] }, + "(UTC-03:00) Brasilia": { "iana": ["America/Sao_Paulo"] }, + "(UTC-03:00) Georgetown": { "iana": ["America/Cayenne"] }, + "(UTC-03:00) Greenland": { "iana": ["America/Godthab"] }, + "(UTC-02:00) Mid-Atlantic": { "iana": [null] }, + "(UTC-01:00) Azores": { "iana": ["Atlantic/Azores"] }, + "(UTC-01:00) Cape Verde Islands": { "iana": ["Atlantic/Cape_Verde"] }, + "(UTC) Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London": { "iana": [null] }, + "(UTC) Monrovia, Reykjavik": { "iana": ["Atlantic/Reykjavik"] }, + "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague": { "iana": ["Europe/Budapest"] }, + "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb": { "iana": ["Europe/Warsaw"] }, + "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris": { "iana": ["Europe/Paris"] }, + "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna": { "iana": ["Europe/Berlin"] }, + "(UTC+01:00) West Central Africa": { "iana": ["Africa/Lagos"] }, + "(UTC+02:00) Minsk": { "iana": ["Europe/Chisinau"] }, + "(UTC+02:00) Cairo": { "iana": ["Africa/Cairo"] }, + "(UTC+02:00) Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius": { "iana": ["Europe/Kiev"] }, + "(UTC+02:00) Athens, Bucharest, Istanbul": { "iana": ["Europe/Bucharest"] }, + "(UTC+02:00) Jerusalem": { "iana": ["Asia/Jerusalem"] }, + "(UTC+02:00) Harare, Pretoria": { "iana": ["Africa/Johannesburg"] }, + "(UTC+03:00) Moscow, St. Petersburg, Volgograd": { "iana": ["Europe/Moscow"] }, + "(UTC+03:00) Kuwait, Riyadh": { "iana": ["Asia/Riyadh"] }, + "(UTC+03:00) Nairobi": { "iana": ["Africa/Nairobi"] }, + "(UTC+03:00) Baghdad": { "iana": ["Asia/Baghdad"] }, + "(UTC+03:30) Tehran": { "iana": ["Asia/Tehran"] }, + "(UTC+04:00) Abu Dhabi, Muscat": { "iana": ["Asia/Dubai"] }, + "(UTC+04:00) Baku, Tbilisi, Yerevan": { "iana": ["Asia/Yerevan"] }, + "(UTC+04:30) Kabul": { "iana": [null] }, + "(UTC+05:00) Ekaterinburg": { "iana": ["Asia/Yekaterinburg"] }, + "(UTC+05:00) Tashkent": { "iana": ["Asia/Tashkent"] }, + "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi": { "iana": ["Asia/Calcutta"] }, + "(UTC+05:45) Kathmandu": { "iana": ["Asia/Katmandu"] }, + "(UTC+06:00) Astana, Dhaka": { "iana": ["Asia/Almaty"] }, + "(UTC+06:00) Sri Jayawardenepura": { "iana": ["Asia/Colombo"] }, + "(UTC+06:00) Almaty, Novosibirsk": { "iana": ["Asia/Novosibirsk"] }, + "(UTC+06:30) Yangon (Rangoon)": { "iana": ["Asia/Rangoon"] }, + "(UTC+07:00) Bangkok, Hanoi, Jakarta": { "iana": ["Asia/Bangkok"] }, + "(UTC+07:00) Krasnoyarsk": { "iana": ["Asia/Krasnoyarsk"] }, + "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi": { "iana": ["Asia/Shanghai"] }, + "(UTC+08:00) Kuala Lumpur, Singapore": { "iana": ["Asia/Singapore"] }, + "(UTC+08:00) Taipei": { "iana": ["Asia/Taipei"] }, + "(UTC+08:00) Perth": { "iana": ["Australia/Perth"] }, + "(UTC+08:00) Irkutsk, Ulaanbaatar": { "iana": ["Asia/Irkutsk"] }, + "(UTC+09:00) Seoul": { "iana": ["Asia/Seoul"] }, + "(UTC+09:00) Osaka, Sapporo, Tokyo": { "iana": ["Asia/Tokyo"] }, + "(UTC+09:00) Yakutsk": { "iana": ["Asia/Yakutsk"] }, + "(UTC+09:30) Darwin": { "iana": ["Australia/Darwin"] }, + "(UTC+09:30) Adelaide": { "iana": ["Australia/Adelaide"] }, + "(UTC+10:00) Canberra, Melbourne, Sydney": { "iana": ["Australia/Sydney"] }, + "(GMT+10:00) Canberra, Melbourne, Sydney": { "iana": ["Australia/Sydney"] }, + "(UTC+10:00) Brisbane": { "iana": ["Australia/Brisbane"] }, + "(UTC+10:00) Hobart": { "iana": ["Australia/Hobart"] }, + "(UTC+10:00) Vladivostok": { "iana": ["Asia/Vladivostok"] }, + "(UTC+10:00) Guam, Port Moresby": { "iana": ["Pacific/Port_Moresby"] }, + "(UTC+11:00) Magadan, Solomon Islands, New Caledonia": { "iana": ["Pacific/Guadalcanal"] }, + "(UTC+12:00) Fiji, Kamchatka, Marshall Is.": { "iana": [null] }, + "(UTC+12:00) Auckland, Wellington": { "iana": ["Pacific/Auckland"] }, + "(UTC+13:00) Nuku'alofa": { "iana": ["Pacific/Tongatapu"] }, + "(UTC-03:00) Buenos Aires": { "iana": ["America/Buenos_Aires"] }, + "(UTC+02:00) Beirut": { "iana": ["Asia/Beirut"] }, + "(UTC+02:00) Amman": { "iana": ["Asia/Amman"] }, + "(UTC-06:00) Guadalajara, Mexico City, Monterrey - New": { "iana": ["America/Mexico_City"] }, + "(UTC-07:00) Chihuahua, La Paz, Mazatlan - New": { "iana": ["America/Chihuahua"] }, + "(UTC-08:00) Tijuana, Baja California": { "iana": ["America/Tijuana"] }, + "(UTC+02:00) Windhoek": { "iana": ["Africa/Windhoek"] }, + "(UTC+03:00) Tbilisi": { "iana": ["Asia/Tbilisi"] }, + "(UTC-04:00) Manaus": { "iana": ["America/Cuiaba"] }, + "(UTC-03:00) Montevideo": { "iana": ["America/Montevideo"] }, + "(UTC+04:00) Yerevan": { "iana": [null] }, + "(UTC-04:30) Caracas": { "iana": ["America/Caracas"] }, + "(UTC) Casablanca": { "iana": ["Africa/Casablanca"] }, + "(UTC+05:00) Islamabad, Karachi": { "iana": ["Asia/Karachi"] }, + "(UTC+04:00) Port Louis": { "iana": ["Indian/Mauritius"] }, + "(UTC) Coordinated Universal Time": { "iana": ["Etc/GMT"] }, + "(UTC-04:00) Asuncion": { "iana": ["America/Asuncion"] }, + "(UTC+12:00) Petropavlovsk-Kamchatsky": { "iana": [null] } +} diff --git a/MagicMirror/modules/default/clock/README.md b/MagicMirror/modules/default/clock/README.md new file mode 100644 index 0000000000000000000000000000000000000000..16703eb060576d81c130eda15bee6474f8b90710 --- /dev/null +++ b/MagicMirror/modules/default/clock/README.md @@ -0,0 +1,6 @@ +# Module: Clock + +The `clock` module is one of the default modules of the MagicMirror². +This module displays the current date and time. The information will be updated realtime. + +For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/clock.html). diff --git a/MagicMirror/modules/default/clock/clock.js b/MagicMirror/modules/default/clock/clock.js new file mode 100644 index 0000000000000000000000000000000000000000..595057c4919e18faa8aff7e2c53ae9a7077fcfd2 --- /dev/null +++ b/MagicMirror/modules/default/clock/clock.js @@ -0,0 +1,316 @@ +/* global SunCalc */ + +/* MagicMirror² + * Module: Clock + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +Module.register("clock", { + // Module config defaults. + defaults: { + displayType: "digital", // options: digital, analog, both + + timeFormat: config.timeFormat, + timezone: null, + + displaySeconds: true, + showPeriod: true, + showPeriodUpper: false, + clockBold: false, + showDate: true, + showTime: true, + showWeek: false, + dateFormat: "dddd, LL", + sendNotifications: false, + + /* specific to the analog clock */ + analogSize: "200px", + analogFace: "simple", // options: 'none', 'simple', 'face-###' (where ### is 001 to 012 inclusive) + analogPlacement: "bottom", // options: 'top', 'bottom', 'left', 'right' + analogShowDate: "top", // OBSOLETE, can be replaced with analogPlacement and showTime, options: false, 'top', or 'bottom' + secondsColor: "#888888", + + showSunTimes: false, + showMoonTimes: false, + lat: 47.630539, + lon: -122.344147 + }, + // Define required scripts. + getScripts: function () { + return ["moment.js", "moment-timezone.js", "suncalc.js"]; + }, + // Define styles. + getStyles: function () { + return ["clock_styles.css"]; + }, + // Define start sequence. + start: function () { + Log.info(`Starting module: ${this.name}`); + + // Schedule update interval. + this.second = moment().second(); + this.minute = moment().minute(); + + // Calculate how many ms should pass until next update depending on if seconds is displayed or not + const delayCalculator = (reducedSeconds) => { + const EXTRA_DELAY = 50; // Deliberate imperceptible delay to prevent off-by-one timekeeping errors + + if (this.config.displaySeconds) { + return 1000 - moment().milliseconds() + EXTRA_DELAY; + } else { + return (60 - reducedSeconds) * 1000 - moment().milliseconds() + EXTRA_DELAY; + } + }; + + // A recursive timeout function instead of interval to avoid drifting + const notificationTimer = () => { + this.updateDom(); + + if (this.config.sendNotifications) { + // If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent) + if (this.config.displaySeconds) { + this.second = moment().second(); + if (this.second !== 0) { + this.sendNotification("CLOCK_SECOND", this.second); + setTimeout(notificationTimer, delayCalculator(0)); + return; + } + } + + // If minute changed or seconds isn't displayed send CLOCK_MINUTE-notification + this.minute = moment().minute(); + this.sendNotification("CLOCK_MINUTE", this.minute); + } + + setTimeout(notificationTimer, delayCalculator(0)); + }; + + // Set the initial timeout with the amount of seconds elapsed as + // reducedSeconds, so it will trigger when the minute changes + setTimeout(notificationTimer, delayCalculator(this.second)); + + // Set locale. + moment.locale(config.language); + }, + // Override dom generator. + getDom: function () { + const wrapper = document.createElement("div"); + wrapper.classList.add("clock-grid"); + + /************************************ + * Create wrappers for analog and digital clock + */ + const analogWrapper = document.createElement("div"); + analogWrapper.className = "clock-circle"; + const digitalWrapper = document.createElement("div"); + digitalWrapper.className = "digital"; + digitalWrapper.style.gridArea = "center"; + + /************************************ + * Create wrappers for DIGITAL clock + */ + const dateWrapper = document.createElement("div"); + const timeWrapper = document.createElement("div"); + const secondsWrapper = document.createElement("sup"); + const periodWrapper = document.createElement("span"); + const sunWrapper = document.createElement("div"); + const moonWrapper = document.createElement("div"); + const weekWrapper = document.createElement("div"); + + // Style Wrappers + dateWrapper.className = "date normal medium"; + timeWrapper.className = "time bright large light"; + secondsWrapper.className = "seconds dimmed"; + sunWrapper.className = "sun dimmed small"; + moonWrapper.className = "moon dimmed small"; + weekWrapper.className = "week dimmed medium"; + + // Set content of wrappers. + // The moment().format("h") method has a bug on the Raspberry Pi. + // So we need to generate the timestring manually. + // See issue: https://github.com/MichMich/MagicMirror/issues/181 + let timeString; + const now = moment(); + if (this.config.timezone) { + now.tz(this.config.timezone); + } + + let hourSymbol = "HH"; + if (this.config.timeFormat !== 24) { + hourSymbol = "h"; + } + + if (this.config.clockBold) { + timeString = now.format(`${hourSymbol}[<span class="bold">]mm[</span>]`); + } else { + timeString = now.format(`${hourSymbol}:mm`); + } + + if (this.config.showDate) { + dateWrapper.innerHTML = now.format(this.config.dateFormat); + digitalWrapper.appendChild(dateWrapper); + } + + if (this.config.displayType !== "analog" && this.config.showTime) { + timeWrapper.innerHTML = timeString; + secondsWrapper.innerHTML = now.format("ss"); + if (this.config.showPeriodUpper) { + periodWrapper.innerHTML = now.format("A"); + } else { + periodWrapper.innerHTML = now.format("a"); + } + if (this.config.displaySeconds) { + timeWrapper.appendChild(secondsWrapper); + } + if (this.config.showPeriod && this.config.timeFormat !== 24) { + timeWrapper.appendChild(periodWrapper); + } + digitalWrapper.appendChild(timeWrapper); + } + + /** + * Format the time according to the config + * + * @param {object} config The config of the module + * @param {object} time time to format + * @returns {string} The formatted time string + */ + function formatTime(config, time) { + let formatString = `${hourSymbol}:mm`; + if (config.showPeriod && config.timeFormat !== 24) { + formatString += config.showPeriodUpper ? "A" : "a"; + } + return moment(time).format(formatString); + } + + /**************************************************************** + * Create wrappers for Sun Times, only if specified in config + */ + if (this.config.showSunTimes) { + const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon); + const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset); + let nextEvent; + if (now.isBefore(sunTimes.sunrise)) { + nextEvent = sunTimes.sunrise; + } else if (now.isBefore(sunTimes.sunset)) { + nextEvent = sunTimes.sunset; + } else { + const tomorrowSunTimes = SunCalc.getTimes(now.clone().add(1, "day"), this.config.lat, this.config.lon); + nextEvent = tomorrowSunTimes.sunrise; + } + const untilNextEvent = moment.duration(moment(nextEvent).diff(now)); + const untilNextEventString = `${untilNextEvent.hours()}h ${untilNextEvent.minutes()}m`; + sunWrapper.innerHTML = + `<span class="${isVisible ? "bright" : ""}"><i class="fas fa-sun" aria-hidden="true"></i> ${untilNextEventString}</span>` + + `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunrise)}</span>` + + `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunset)}</span>`; + digitalWrapper.appendChild(sunWrapper); + } + + /**************************************************************** + * Create wrappers for Moon Times, only if specified in config + */ + if (this.config.showMoonTimes) { + const moonIllumination = SunCalc.getMoonIllumination(now.toDate()); + const moonTimes = SunCalc.getMoonTimes(now, this.config.lat, this.config.lon); + const moonRise = moonTimes.rise; + let moonSet; + if (moment(moonTimes.set).isAfter(moonTimes.rise)) { + moonSet = moonTimes.set; + } else { + const nextMoonTimes = SunCalc.getMoonTimes(now.clone().add(1, "day"), this.config.lat, this.config.lon); + moonSet = nextMoonTimes.set; + } + const isVisible = now.isBetween(moonRise, moonSet) || moonTimes.alwaysUp === true; + const illuminatedFractionString = `${Math.round(moonIllumination.fraction * 100)}%`; + moonWrapper.innerHTML = + `<span class="${isVisible ? "bright" : ""}"><i class="fas fa-moon" aria-hidden="true"></i> ${illuminatedFractionString}</span>` + + `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${moonRise ? formatTime(this.config, moonRise) : "..."}</span>` + + `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${moonSet ? formatTime(this.config, moonSet) : "..."}</span>`; + digitalWrapper.appendChild(moonWrapper); + } + + if (this.config.showWeek) { + weekWrapper.innerHTML = this.translate("WEEK", { weekNumber: now.week() }); + digitalWrapper.appendChild(weekWrapper); + } + + /**************************************************************** + * Create wrappers for ANALOG clock, only if specified in config + */ + if (this.config.displayType !== "digital") { + // If it isn't 'digital', then an 'analog' clock was also requested + + // Calculate the degree offset for each hand of the clock + if (this.config.timezone) { + now.tz(this.config.timezone); + } + const second = now.seconds() * 6, + minute = now.minute() * 6 + second / 60, + hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12; + + // Create wrappers + analogWrapper.style.width = this.config.analogSize; + analogWrapper.style.height = this.config.analogSize; + + if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") { + analogWrapper.style.background = `url(${this.data.path}faces/${this.config.analogFace}.svg)`; + analogWrapper.style.backgroundSize = "100%"; + + // The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611 + // analogWrapper.style.border = "1px solid black"; + analogWrapper.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used + } else if (this.config.analogFace !== "none") { + analogWrapper.style.border = "2px solid white"; + } + const clockFace = document.createElement("div"); + clockFace.className = "clock-face"; + + const clockHour = document.createElement("div"); + clockHour.id = "clock-hour"; + clockHour.style.transform = `rotate(${hour}deg)`; + clockHour.className = "clock-hour"; + const clockMinute = document.createElement("div"); + clockMinute.id = "clock-minute"; + clockMinute.style.transform = `rotate(${minute}deg)`; + clockMinute.className = "clock-minute"; + + // Combine analog wrappers + clockFace.appendChild(clockHour); + clockFace.appendChild(clockMinute); + + if (this.config.displaySeconds) { + const clockSecond = document.createElement("div"); + clockSecond.id = "clock-second"; + clockSecond.style.transform = `rotate(${second}deg)`; + clockSecond.className = "clock-second"; + clockSecond.style.backgroundColor = this.config.secondsColor; + clockFace.appendChild(clockSecond); + } + analogWrapper.appendChild(clockFace); + } + + /******************************************* + * Update placement, respect old analogShowDate even if it's not needed anymore + */ + if (this.config.displayType === "analog") { + // Display only an analog clock + if (this.config.analogShowDate === "top") { + wrapper.classList.add("clock-grid-bottom"); + } else if (this.config.analogShowDate === "bottom") { + wrapper.classList.add("clock-grid-top"); + } + wrapper.appendChild(analogWrapper); + } else if (this.config.displayType === "digital") { + wrapper.appendChild(digitalWrapper); + } else if (this.config.displayType === "both") { + wrapper.classList.add(`clock-grid-${this.config.analogPlacement}`); + wrapper.appendChild(analogWrapper); + wrapper.appendChild(digitalWrapper); + } + + // Return the wrapper to the dom. + return wrapper; + } +}); diff --git a/MagicMirror/modules/default/clock/clock_styles.css b/MagicMirror/modules/default/clock/clock_styles.css new file mode 100644 index 0000000000000000000000000000000000000000..e938dd2e8e0fd6a58c85cc1e50187d1377225253 --- /dev/null +++ b/MagicMirror/modules/default/clock/clock_styles.css @@ -0,0 +1,93 @@ +.clock-grid { + display: inline-flex; + gap: 15px; +} + +.clock-grid-left { + flex-direction: row; +} + +.clock-grid-right { + flex-direction: row-reverse; +} + +.clock-grid-top { + flex-direction: column; +} + +.clock-grid-bottom { + flex-direction: column-reverse; +} + +.clock-circle { + place-self: center; + position: relative; + border-radius: 50%; + background-size: 100%; +} + +.clock-face { + width: 100%; + height: 100%; +} + +.clock-face::after { + position: absolute; + top: 50%; + left: 50%; + width: 6px; + height: 6px; + margin: -3px 0 0 -3px; + background: var(--color-text-bright); + border-radius: 3px; + content: ""; + display: block; +} + +.clock-hour { + width: 0; + height: 0; + position: absolute; + top: 50%; + left: 50%; + margin: -2px 0 -2px -25%; /* numbers must match negative length & thickness */ + padding: 2px 0 2px 25%; /* indicator length & thickness */ + background: var(--color-text-bright); + transform-origin: 100% 50%; + border-radius: 3px 0 0 3px; +} + +.clock-minute { + width: 0; + height: 0; + position: absolute; + top: 50%; + left: 50%; + margin: -35% -2px 0; /* numbers must match negative length & thickness */ + padding: 35% 2px 0; /* indicator length & thickness */ + background: var(--color-text-bright); + transform-origin: 50% 100%; + border-radius: 3px 0 0 3px; +} + +.clock-second { + width: 0; + height: 0; + position: absolute; + top: 50%; + left: 50%; + margin: -38% -1px 0 0; /* numbers must match negative length & thickness */ + padding: 38% 1px 0 0; /* indicator length & thickness */ + background: var(--color-text); + transform-origin: 50% 100%; +} + +.module.clock .sun, +.module.clock .moon { + display: flex; +} + +.module.clock .sun > *, +.module.clock .moon > * { + flex: 1; +} diff --git a/MagicMirror/modules/default/clock/faces/face-001.svg b/MagicMirror/modules/default/clock/faces/face-001.svg new file mode 100644 index 0000000000000000000000000000000000000000..abd08ce7c506d44538e15d5ea6a204328e484326 --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-001.svg @@ -0,0 +1 @@ +<svg id="Hour_Markers_-_Singlets" data-name="Hour Markers - Singlets" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;}.cls-2{stroke-width:0.5px;}</style></defs><title>face-001</title><line class="cls-1" x1="125" y1="1.25" x2="125" y2="16.23"/><line class="cls-1" x1="186.87" y1="17.83" x2="179.39" y2="30.8"/><line class="cls-1" x1="232.17" y1="63.12" x2="219.2" y2="70.61"/><line class="cls-1" x1="248.75" y1="125" x2="233.77" y2="125"/><line class="cls-1" x1="232.17" y1="186.87" x2="219.2" y2="179.39"/><line class="cls-1" x1="186.88" y1="232.17" x2="179.39" y2="219.2"/><line class="cls-1" x1="125" y1="248.75" x2="125" y2="233.77"/><line class="cls-1" x1="63.13" y1="232.17" x2="70.61" y2="219.2"/><line class="cls-1" x1="17.83" y1="186.88" x2="30.8" y2="179.39"/><line class="cls-1" x1="1.25" y1="125" x2="16.23" y2="125"/><line class="cls-1" x1="17.83" y1="63.13" x2="30.8" y2="70.61"/><line class="cls-1" x1="63.12" y1="17.83" x2="70.61" y2="30.8"/><line class="cls-2" x1="138.01" y1="1.25" x2="136.96" y2="11.23"/><line class="cls-2" x1="150.87" y1="3.29" x2="148.78" y2="13.11"/><line class="cls-2" x1="163.45" y1="6.66" x2="160.35" y2="16.21"/><line class="cls-2" x1="175.61" y1="11.33" x2="171.53" y2="20.5"/><line class="cls-2" x1="198.14" y1="24.33" x2="192.24" y2="32.45"/><line class="cls-2" x1="208.26" y1="32.53" x2="201.54" y2="39.99"/><line class="cls-2" x1="217.47" y1="41.74" x2="210.01" y2="48.46"/><line class="cls-2" x1="225.67" y1="51.86" x2="217.55" y2="57.76"/><line class="cls-2" x1="238.67" y1="74.39" x2="229.5" y2="78.47"/><line class="cls-2" x1="243.34" y1="86.55" x2="233.79" y2="89.65"/><line class="cls-2" x1="246.71" y1="99.13" x2="236.89" y2="101.22"/><line class="cls-2" x1="248.75" y1="111.99" x2="238.77" y2="113.04"/><line class="cls-2" x1="248.75" y1="138.01" x2="238.77" y2="136.96"/><line class="cls-2" x1="246.71" y1="150.87" x2="236.89" y2="148.78"/><line class="cls-2" x1="243.34" y1="163.45" x2="233.79" y2="160.35"/><line class="cls-2" x1="238.67" y1="175.61" x2="229.5" y2="171.53"/><line class="cls-2" x1="225.67" y1="198.14" x2="217.55" y2="192.24"/><line class="cls-2" x1="217.47" y1="208.26" x2="210.01" y2="201.54"/><line class="cls-2" x1="208.26" y1="217.47" x2="201.54" y2="210.01"/><line class="cls-2" x1="198.14" y1="225.67" x2="192.24" y2="217.55"/><line class="cls-2" x1="175.61" y1="238.67" x2="171.53" y2="229.5"/><line class="cls-2" x1="163.45" y1="243.34" x2="160.35" y2="233.79"/><line class="cls-2" x1="150.87" y1="246.71" x2="148.78" y2="236.89"/><line class="cls-2" x1="138.01" y1="248.75" x2="136.96" y2="238.77"/><line class="cls-2" x1="111.99" y1="248.75" x2="113.04" y2="238.77"/><line class="cls-2" x1="99.13" y1="246.71" x2="101.22" y2="236.89"/><line class="cls-2" x1="86.55" y1="243.34" x2="89.65" y2="233.79"/><line class="cls-2" x1="74.39" y1="238.67" x2="78.47" y2="229.5"/><line class="cls-2" x1="51.86" y1="225.67" x2="57.76" y2="217.55"/><line class="cls-2" x1="41.74" y1="217.47" x2="48.46" y2="210.01"/><line class="cls-2" x1="32.53" y1="208.26" x2="39.99" y2="201.54"/><line class="cls-2" x1="24.33" y1="198.14" x2="32.45" y2="192.24"/><line class="cls-2" x1="11.33" y1="175.61" x2="20.5" y2="171.53"/><line class="cls-2" x1="6.66" y1="163.45" x2="16.21" y2="160.35"/><line class="cls-2" x1="3.29" y1="150.87" x2="13.11" y2="148.78"/><line class="cls-2" x1="1.25" y1="138.01" x2="11.23" y2="136.96"/><line class="cls-2" x1="1.25" y1="111.99" x2="11.23" y2="113.04"/><line class="cls-2" x1="3.29" y1="99.13" x2="13.11" y2="101.22"/><line class="cls-2" x1="6.66" y1="86.55" x2="16.21" y2="89.65"/><line class="cls-2" x1="11.33" y1="74.39" x2="20.5" y2="78.47"/><line class="cls-2" x1="24.33" y1="51.86" x2="32.45" y2="57.76"/><line class="cls-2" x1="32.53" y1="41.74" x2="39.99" y2="48.46"/><line class="cls-2" x1="41.74" y1="32.53" x2="48.46" y2="39.99"/><line class="cls-2" x1="51.86" y1="24.33" x2="57.76" y2="32.45"/><line class="cls-2" x1="74.39" y1="11.33" x2="78.47" y2="20.5"/><line class="cls-2" x1="86.55" y1="6.66" x2="89.65" y2="16.21"/><line class="cls-2" x1="99.13" y1="3.29" x2="101.22" y2="13.11"/><line class="cls-2" x1="111.99" y1="1.25" x2="113.04" y2="11.23"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-002.svg b/MagicMirror/modules/default/clock/faces/face-002.svg new file mode 100644 index 0000000000000000000000000000000000000000..1ec3104959dbab1b9c83558cba68f07af8cfd706 --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-002.svg @@ -0,0 +1 @@ +<svg id="Hour_Markers_-_Doubles" data-name="Hour Markers - Doubles" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;stroke-width:2.98px;}</style></defs><title>face-002</title><line class="cls-1" x1="122.01" y1="1.75" x2="122.01" y2="16.67"/><line class="cls-1" x1="186.62" y1="18.26" x2="179.17" y2="31.18"/><line class="cls-1" x1="231.74" y1="63.37" x2="218.82" y2="70.83"/><line class="cls-1" x1="248.25" y1="127.99" x2="233.33" y2="127.99"/><line class="cls-1" x1="231.74" y1="186.62" x2="218.82" y2="179.17"/><line class="cls-1" x1="186.63" y1="231.74" x2="179.17" y2="218.82"/><line class="cls-1" x1="127.99" y1="248.25" x2="127.99" y2="233.33"/><line class="cls-1" x1="63.38" y1="231.74" x2="70.83" y2="218.82"/><line class="cls-1" x1="18.26" y1="186.63" x2="31.18" y2="179.17"/><line class="cls-1" x1="1.75" y1="122.01" x2="16.67" y2="122.01"/><line class="cls-1" x1="18.26" y1="63.38" x2="31.18" y2="70.83"/><line class="cls-1" x1="63.37" y1="18.26" x2="70.83" y2="31.18"/><line class="cls-1" x1="127.99" y1="1.75" x2="127.99" y2="16.67"/><line class="cls-1" x1="248.25" y1="122.01" x2="233.33" y2="122.01"/><line class="cls-1" x1="122.01" y1="248.25" x2="122.01" y2="233.33"/><line class="cls-1" x1="1.75" y1="127.99" x2="16.67" y2="127.99"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-003.svg b/MagicMirror/modules/default/clock/faces/face-003.svg new file mode 100644 index 0000000000000000000000000000000000000000..7cfeebace0a0a84ba78b69dfcc726dcbfc0346fd --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-003.svg @@ -0,0 +1 @@ +<svg id="Hour_Markers_-_Digits" data-name="Hour Markers - Digits" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:#fff;stroke-width:1.15px;}.cls-1,.cls-2{stroke:#fff;stroke-miterlimit:10;}.cls-2{fill:none;stroke-linecap:round;stroke-width:0.47px;}</style></defs><title>face-003</title><path class="cls-1" d="M117.49,5l-2.84,1.1-0.49-1.42,4.9-1.87V17.3h3.33v1.4h-8.81V17.3h3.92V5Z"/><path class="cls-1" d="M127.64,17.3l5.76-7.44A4.44,4.44,0,0,0,134.7,7a2.4,2.4,0,0,0-2.5-2.54c-1.84,0-2.77,1.45-3,3.12l-1.54-.21c0.37-2.59,1.79-4.38,4.55-4.38a3.79,3.79,0,0,1,4.13,4,6.17,6.17,0,0,1-1.63,3.8l-5.08,6.6h6.69v1.4h-8.67V17.3Z"/><path class="cls-1" d="M182.09,20.46l-2.84,1.1-0.49-1.42,4.9-1.87V32.73H187v1.4h-8.81v-1.4h3.92V20.46Z"/><path class="cls-1" d="M220.42,74.88l5.76-7.44a4.44,4.44,0,0,0,1.31-2.91A2.4,2.4,0,0,0,225,62c-1.84,0-2.77,1.45-3,3.12l-1.54-.21c0.37-2.59,1.79-4.38,4.55-4.38a3.79,3.79,0,0,1,4.13,4,6.17,6.17,0,0,1-1.63,3.8l-5.08,6.6h6.69v1.4h-8.67v-1.4Z"/><path class="cls-1" d="M237.64,124.46l4.59-4.73h-6.48v-1.4h8.63v1.4l-4.31,4.41c2.42,0,4.5,1.38,4.5,4a5.42,5.42,0,0,1-2.82,4.73c-1.28.79-2.8,1.26-5.78,1.26v-1.47c2.56,0,3.66-.28,4.48-0.75a4.41,4.41,0,0,0,2.5-3.73c0-2-1.75-2.61-3.47-2.61a16,16,0,0,0-1.82.19v-1.26Z"/><path class="cls-1" d="M220.35,187.58l2.31-12,1.56,0.42-2,10.14h4.22v-7.9H228v7.9h1.24v1.4H228v3.89h-1.56v-3.89h-6.09Z"/><path class="cls-1" d="M178.82,218.09h8v1.4h-6.41v4.78a6.61,6.61,0,0,1,2.31-.49,4,4,0,0,1,4.22,4.13,5.61,5.61,0,0,1-2.75,4.78c-1.21.77-2.8,1.17-5.83,1.17v-1.47a10.26,10.26,0,0,0,4.55-.68,4.38,4.38,0,0,0,2.4-3.8,2.53,2.53,0,0,0-2.75-2.66,5.36,5.36,0,0,0-2.59.82h-1.12v-8Z"/><path class="cls-1" d="M127.83,235.93a3.8,3.8,0,0,0-2.49-1.17c-2.82,0-3,3.59-3.08,5.67a4.56,4.56,0,0,1,3.1-1.26c2.94,0,4,2.42,4,5,0,2.77-1.49,5.11-4.48,5.11-4,0-4.27-4.85-4.27-7.81,0-3.17.4-8.16,4.64-8.16a5.09,5.09,0,0,1,3.45,1.35ZM125,247.8c1.91,0,2.73-1.68,2.73-3.57s-0.54-3.59-2.61-3.59a4.47,4.47,0,0,0-2.87,1.52C122.28,247,123.58,247.8,125,247.8Z"/><path class="cls-1" d="M70.2,218.51H63.79v-1.4h8.67L65.4,233l-1.4-.63Z"/><path class="cls-1" d="M29.71,187.55a4.17,4.17,0,0,1-4.45,4.17,4.17,4.17,0,0,1-4.45-4.17c0-2.17,1.35-3.19,3.12-4.08-1.42-.89-2.61-2-2.61-3.75a3.94,3.94,0,1,1,7.88,0c0,1.8-1.17,2.87-2.59,3.75C28.36,184.35,29.71,185.38,29.71,187.55Zm-7.27.07a2.64,2.64,0,0,0,2.82,2.63,2.64,2.64,0,0,0,2.82-2.63c0-1.84-1.45-2.5-2.82-3.36C23.88,185.12,22.43,185.78,22.43,187.62Zm5.13-7.9a2.32,2.32,0,1,0-4.62,0c0,1.56,1.17,2.17,2.31,2.94C26.4,181.88,27.56,181.28,27.56,179.71Z"/><path class="cls-1" d="M6.93,131.49a3.75,3.75,0,0,0,2.52,1.17c2.82,0,3-3.59,3.05-5.67a4.5,4.5,0,0,1-3.08,1.26c-2.94,0-4-2.4-4-5,0-2.75,1.47-5.11,4.45-5.11,4,0,4.27,4.85,4.27,7.81,0,3.17-.4,8.16-4.64,8.16A5.13,5.13,0,0,1,6,132.77Zm2.84-11.87c-1.91,0-2.73,1.68-2.73,3.59s0.51,3.57,2.61,3.57a4.49,4.49,0,0,0,2.84-1.49C12.5,120.51,11.24,119.62,9.78,119.62Z"/><path class="cls-1" d="M21.75,62.66l-2.84,1.1-0.49-1.42,4.9-1.87V74.92h3.33v1.4H17.84v-1.4h3.92V62.66Z"/><path class="cls-1" d="M40.57,68.56c0,2.5.42,8-4.34,8s-4.34-5.29-4.34-8c0-3.66-.19-8,4.34-8C41.18,60.56,40.57,66.81,40.57,68.56Zm-7,0c0,6.11,1.12,6.5,2.7,6.5,1.8,0,2.7-.72,2.7-6.5,0-6-1-6.53-2.7-6.53C33.3,62,33.53,65.46,33.53,68.56Z"/><path class="cls-1" d="M61.91,20.49l-2.84,1.1-0.49-1.42,4.9-1.87V32.75H66.8v1.4H58v-1.4h3.92V20.49Z"/><path class="cls-1" d="M75.9,20.49l-2.84,1.1-0.49-1.42,4.9-1.87V32.75h3.33v1.4H72v-1.4H75.9V20.49Z"/><line class="cls-2" x1="134.41" y1="35.49" x2="133.65" y2="42.71"/><line class="cls-2" x1="143.71" y1="36.97" x2="142.2" y2="44.07"/><line class="cls-2" x1="152.81" y1="39.4" x2="150.57" y2="46.31"/><line class="cls-2" x1="161.61" y1="42.78" x2="158.65" y2="49.41"/><line class="cls-2" x1="170" y1="47.06" x2="166.37" y2="53.35"/><line class="cls-2" x1="177.9" y1="52.19" x2="173.63" y2="58.06"/><line class="cls-2" x1="185.22" y1="58.12" x2="180.36" y2="63.51"/><line class="cls-2" x1="191.88" y1="64.78" x2="186.49" y2="69.64"/><line class="cls-2" x1="197.81" y1="72.1" x2="191.94" y2="76.37"/><line class="cls-2" x1="202.94" y1="80" x2="196.65" y2="83.63"/><line class="cls-2" x1="207.22" y1="88.39" x2="200.59" y2="91.35"/><line class="cls-2" x1="210.6" y1="97.19" x2="203.69" y2="99.43"/><line class="cls-2" x1="213.03" y1="106.29" x2="205.93" y2="107.8"/><line class="cls-2" x1="214.51" y1="115.59" x2="207.29" y2="116.35"/><line class="cls-2" x1="215" y1="125" x2="207.74" y2="125"/><line class="cls-2" x1="214.51" y1="134.41" x2="207.29" y2="133.65"/><line class="cls-2" x1="213.03" y1="143.71" x2="205.93" y2="142.2"/><line class="cls-2" x1="210.6" y1="152.81" x2="203.69" y2="150.57"/><line class="cls-2" x1="207.22" y1="161.61" x2="200.59" y2="158.65"/><line class="cls-2" x1="202.94" y1="170" x2="196.65" y2="166.37"/><line class="cls-2" x1="197.81" y1="177.9" x2="191.94" y2="173.63"/><line class="cls-2" x1="191.88" y1="185.22" x2="186.49" y2="180.36"/><line class="cls-2" x1="185.22" y1="191.88" x2="180.36" y2="186.49"/><line class="cls-2" x1="177.9" y1="197.81" x2="173.63" y2="191.94"/><line class="cls-2" x1="170" y1="202.94" x2="166.37" y2="196.65"/><line class="cls-2" x1="161.61" y1="207.22" x2="158.65" y2="200.59"/><line class="cls-2" x1="152.81" y1="210.6" x2="150.57" y2="203.69"/><line class="cls-2" x1="143.71" y1="213.03" x2="142.2" y2="205.93"/><line class="cls-2" x1="134.41" y1="214.51" x2="133.65" y2="207.29"/><line class="cls-2" x1="125" y1="215" x2="125" y2="207.74"/><line class="cls-2" x1="115.59" y1="214.51" x2="116.35" y2="207.29"/><line class="cls-2" x1="106.29" y1="213.03" x2="107.8" y2="205.93"/><line class="cls-2" x1="97.19" y1="210.6" x2="99.43" y2="203.69"/><line class="cls-2" x1="88.39" y1="207.22" x2="91.35" y2="200.59"/><line class="cls-2" x1="80" y1="202.94" x2="83.63" y2="196.65"/><line class="cls-2" x1="72.1" y1="197.81" x2="76.37" y2="191.94"/><line class="cls-2" x1="64.78" y1="191.88" x2="69.64" y2="186.49"/><line class="cls-2" x1="58.12" y1="185.22" x2="63.51" y2="180.36"/><line class="cls-2" x1="52.19" y1="177.9" x2="58.06" y2="173.63"/><line class="cls-2" x1="47.06" y1="170" x2="53.35" y2="166.37"/><line class="cls-2" x1="42.78" y1="161.61" x2="49.41" y2="158.65"/><line class="cls-2" x1="39.4" y1="152.81" x2="46.31" y2="150.57"/><line class="cls-2" x1="36.97" y1="143.71" x2="44.07" y2="142.2"/><line class="cls-2" x1="35.49" y1="134.41" x2="42.71" y2="133.65"/><line class="cls-2" x1="35" y1="125" x2="42.26" y2="125"/><line class="cls-2" x1="35.49" y1="115.59" x2="42.71" y2="116.35"/><line class="cls-2" x1="36.97" y1="106.29" x2="44.07" y2="107.8"/><line class="cls-2" x1="39.4" y1="97.19" x2="46.31" y2="99.43"/><line class="cls-2" x1="42.78" y1="88.39" x2="49.41" y2="91.35"/><line class="cls-2" x1="47.06" y1="80" x2="53.35" y2="83.63"/><line class="cls-2" x1="52.19" y1="72.1" x2="58.06" y2="76.37"/><line class="cls-2" x1="58.12" y1="64.78" x2="63.51" y2="69.64"/><line class="cls-2" x1="64.78" y1="58.12" x2="69.64" y2="63.51"/><line class="cls-2" x1="72.1" y1="52.19" x2="76.37" y2="58.06"/><line class="cls-2" x1="80" y1="47.06" x2="83.63" y2="53.35"/><line class="cls-2" x1="88.39" y1="42.78" x2="91.35" y2="49.41"/><line class="cls-2" x1="97.19" y1="39.4" x2="99.43" y2="46.31"/><line class="cls-2" x1="106.29" y1="36.97" x2="107.8" y2="44.07"/><line class="cls-2" x1="115.59" y1="35.49" x2="116.35" y2="42.71"/><line class="cls-2" x1="125" y1="35" x2="125" y2="42.26"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-004.svg b/MagicMirror/modules/default/clock/faces/face-004.svg new file mode 100644 index 0000000000000000000000000000000000000000..bc97588c430b08aadb08cc5e029308f9ab97b2bc --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-004.svg @@ -0,0 +1 @@ +<svg id="Hour_Markers_-_Doubles" data-name="Hour Markers - Doubles" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;}.cls-1{stroke-width:2.98px;}.cls-2{stroke-width:0.47px;}</style></defs><title>face-004</title><line class="cls-1" x1="122.01" y1="1.75" x2="122.01" y2="16.67"/><line class="cls-1" x1="186.62" y1="18.26" x2="179.17" y2="31.18"/><line class="cls-1" x1="231.74" y1="63.37" x2="218.82" y2="70.83"/><line class="cls-1" x1="248.25" y1="127.99" x2="233.33" y2="127.99"/><line class="cls-1" x1="231.74" y1="186.62" x2="218.82" y2="179.17"/><line class="cls-1" x1="186.63" y1="231.74" x2="179.17" y2="218.82"/><line class="cls-1" x1="127.99" y1="248.25" x2="127.99" y2="233.33"/><line class="cls-1" x1="63.38" y1="231.74" x2="70.83" y2="218.82"/><line class="cls-1" x1="18.26" y1="186.63" x2="31.18" y2="179.17"/><line class="cls-1" x1="1.75" y1="122.01" x2="16.67" y2="122.01"/><line class="cls-1" x1="18.26" y1="63.38" x2="31.18" y2="70.83"/><line class="cls-1" x1="63.37" y1="18.26" x2="70.83" y2="31.18"/><line class="cls-1" x1="127.99" y1="1.75" x2="127.99" y2="16.67"/><line class="cls-1" x1="248.25" y1="122.01" x2="233.33" y2="122.01"/><line class="cls-1" x1="122.01" y1="248.25" x2="122.01" y2="233.33"/><line class="cls-1" x1="1.75" y1="127.99" x2="16.67" y2="127.99"/><line class="cls-2" x1="134.41" y1="35.49" x2="133.65" y2="42.71"/><line class="cls-2" x1="143.71" y1="36.97" x2="142.2" y2="44.07"/><line class="cls-2" x1="152.81" y1="39.4" x2="150.57" y2="46.31"/><line class="cls-2" x1="161.61" y1="42.78" x2="158.65" y2="49.41"/><line class="cls-2" x1="170" y1="47.06" x2="166.37" y2="53.35"/><line class="cls-2" x1="177.9" y1="52.19" x2="173.63" y2="58.06"/><line class="cls-2" x1="185.22" y1="58.12" x2="180.36" y2="63.51"/><line class="cls-2" x1="191.88" y1="64.78" x2="186.49" y2="69.64"/><line class="cls-2" x1="197.81" y1="72.1" x2="191.94" y2="76.37"/><line class="cls-2" x1="202.94" y1="80" x2="196.65" y2="83.63"/><line class="cls-2" x1="207.22" y1="88.39" x2="200.59" y2="91.35"/><line class="cls-2" x1="210.6" y1="97.19" x2="203.69" y2="99.43"/><line class="cls-2" x1="213.03" y1="106.29" x2="205.93" y2="107.8"/><line class="cls-2" x1="214.51" y1="115.59" x2="207.29" y2="116.35"/><line class="cls-2" x1="215" y1="125" x2="207.74" y2="125"/><line class="cls-2" x1="214.51" y1="134.41" x2="207.29" y2="133.65"/><line class="cls-2" x1="213.03" y1="143.71" x2="205.93" y2="142.2"/><line class="cls-2" x1="210.6" y1="152.81" x2="203.69" y2="150.57"/><line class="cls-2" x1="207.22" y1="161.61" x2="200.59" y2="158.65"/><line class="cls-2" x1="202.94" y1="170" x2="196.65" y2="166.37"/><line class="cls-2" x1="197.81" y1="177.9" x2="191.94" y2="173.63"/><line class="cls-2" x1="191.88" y1="185.22" x2="186.49" y2="180.36"/><line class="cls-2" x1="185.22" y1="191.88" x2="180.36" y2="186.49"/><line class="cls-2" x1="177.9" y1="197.81" x2="173.63" y2="191.94"/><line class="cls-2" x1="170" y1="202.94" x2="166.37" y2="196.65"/><line class="cls-2" x1="161.61" y1="207.22" x2="158.65" y2="200.59"/><line class="cls-2" x1="152.81" y1="210.6" x2="150.57" y2="203.69"/><line class="cls-2" x1="143.71" y1="213.03" x2="142.2" y2="205.93"/><line class="cls-2" x1="134.41" y1="214.51" x2="133.65" y2="207.29"/><line class="cls-2" x1="125" y1="215" x2="125" y2="207.74"/><line class="cls-2" x1="115.59" y1="214.51" x2="116.35" y2="207.29"/><line class="cls-2" x1="106.29" y1="213.03" x2="107.8" y2="205.93"/><line class="cls-2" x1="97.19" y1="210.6" x2="99.43" y2="203.69"/><line class="cls-2" x1="88.39" y1="207.22" x2="91.35" y2="200.59"/><line class="cls-2" x1="80" y1="202.94" x2="83.63" y2="196.65"/><line class="cls-2" x1="72.1" y1="197.81" x2="76.37" y2="191.94"/><line class="cls-2" x1="64.78" y1="191.88" x2="69.64" y2="186.49"/><line class="cls-2" x1="58.12" y1="185.22" x2="63.51" y2="180.36"/><line class="cls-2" x1="52.19" y1="177.9" x2="58.06" y2="173.63"/><line class="cls-2" x1="47.06" y1="170" x2="53.35" y2="166.37"/><line class="cls-2" x1="42.78" y1="161.61" x2="49.41" y2="158.65"/><line class="cls-2" x1="39.4" y1="152.81" x2="46.31" y2="150.57"/><line class="cls-2" x1="36.97" y1="143.71" x2="44.07" y2="142.2"/><line class="cls-2" x1="35.49" y1="134.41" x2="42.71" y2="133.65"/><line class="cls-2" x1="35" y1="125" x2="42.26" y2="125"/><line class="cls-2" x1="35.49" y1="115.59" x2="42.71" y2="116.35"/><line class="cls-2" x1="36.97" y1="106.29" x2="44.07" y2="107.8"/><line class="cls-2" x1="39.4" y1="97.19" x2="46.31" y2="99.43"/><line class="cls-2" x1="42.78" y1="88.39" x2="49.41" y2="91.35"/><line class="cls-2" x1="47.06" y1="80" x2="53.35" y2="83.63"/><line class="cls-2" x1="52.19" y1="72.1" x2="58.06" y2="76.37"/><line class="cls-2" x1="58.12" y1="64.78" x2="63.51" y2="69.64"/><line class="cls-2" x1="64.78" y1="58.12" x2="69.64" y2="63.51"/><line class="cls-2" x1="72.1" y1="52.19" x2="76.37" y2="58.06"/><line class="cls-2" x1="80" y1="47.06" x2="83.63" y2="53.35"/><line class="cls-2" x1="88.39" y1="42.78" x2="91.35" y2="49.41"/><line class="cls-2" x1="97.19" y1="39.4" x2="99.43" y2="46.31"/><line class="cls-2" x1="106.29" y1="36.97" x2="107.8" y2="44.07"/><line class="cls-2" x1="115.59" y1="35.49" x2="116.35" y2="42.71"/><line class="cls-2" x1="125" y1="35" x2="125" y2="42.26"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-005.svg b/MagicMirror/modules/default/clock/faces/face-005.svg new file mode 100644 index 0000000000000000000000000000000000000000..0bc1b4301694aab395a348758e566035c5d46748 --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-005.svg @@ -0,0 +1 @@ +<svg id="Hour_Markers_-_Roman" data-name="Hour Markers - Roman" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;stroke-width:0.5px;}</style></defs><title>faces</title><path class="cls-1" d="M64.35,4.45V3.88Q65.5,4,66.45,4t2.44-.11V4.45a3.43,3.43,0,0,0-1.07.43,0.27,0.27,0,0,0-.1.21,2.31,2.31,0,0,0,.24.68q1,2.15,1.74,3.42,1.19-2,2.11-3.81A1.37,1.37,0,0,0,72,5a0.25,0.25,0,0,0-.1-0.19,4.22,4.22,0,0,0-1-.33V3.88Q72.16,4,72.86,4a16.81,16.81,0,0,0,1.77-.11V4.45A3.49,3.49,0,0,0,73.38,5a6.62,6.62,0,0,0-1,1.42q-1.3,2.24-2.13,3.84,0.62,1.33,1.62,3.25l1.49,2.85a1.32,1.32,0,0,0,.29.31,4.78,4.78,0,0,0,1.15.51v0.58q-1.3-.11-2.08-0.11t-2.28.11V17.15a6.18,6.18,0,0,0,1-.42,0.3,0.3,0,0,0,.08-0.22,1.84,1.84,0,0,0-.21-0.67q-1-2.22-2-4-0.88,1.43-2.19,3.95a2.28,2.28,0,0,0-.27.69,0.39,0.39,0,0,0,.11.27,2.31,2.31,0,0,0,.77.31L68,17.15v0.58a20.49,20.49,0,0,0-2.05-.11,11.91,11.91,0,0,0-1.67.11V17.15a3,3,0,0,0,1.14-.51,10.4,10.4,0,0,0,1.19-1.76q1.54-2.7,2.24-4Q68.12,9.38,66.62,6.5a6.71,6.71,0,0,0-.89-1.4A4.91,4.91,0,0,0,64.35,4.45Z"/><path class="cls-1" d="M80.28,3.88V4.45l-0.38.11A2.47,2.47,0,0,0,78.77,5a1.65,1.65,0,0,0-.15.89V6.22l0,3.92,0,5.7a1.6,1.6,0,0,0,.15.87,2,2,0,0,0,1,.32l0.49,0.12v0.58q-1.18-.11-2.27-0.11t-2.47.11V17.15l0.32-.1A2.74,2.74,0,0,0,77,16.63a1.88,1.88,0,0,0,.15-1V15.18l0-3.58q0-5.52,0-6.11A0.84,0.84,0,0,0,77,5a2.77,2.77,0,0,0-1.1-.42l-0.39-.13V3.88Q77.12,4,78,4T80.28,3.88Z"/><path class="cls-1" d="M85.75,3.88V4.45l-0.38.11A2.48,2.48,0,0,0,84.24,5a1.67,1.67,0,0,0-.15.89V6.22l0,3.92,0,5.7a1.59,1.59,0,0,0,.15.87,2,2,0,0,0,1,.32l0.49,0.12v0.58q-1.18-.11-2.27-0.11T81,17.72V17.15l0.32-.1a2.74,2.74,0,0,0,1.19-.41,1.87,1.87,0,0,0,.15-1V15.18l0-3.58q0-5.52,0-6.11A0.83,0.83,0,0,0,82.49,5a2.76,2.76,0,0,0-1.1-.42L81,4.45V3.88Q82.59,4,83.49,4T85.75,3.88Z"/><path class="cls-1" d="M112.66,14.61l-0.29.5L112,15a2.49,2.49,0,0,0-1.19-.2,1.66,1.66,0,0,0-.58.7l-0.17.3-2,3.38-2.83,4.95a1.58,1.58,0,0,0-.3.83,2,2,0,0,0,.73.79L106,26.1l-0.29.5q-1-.68-1.91-1.23t-2.2-1.14l0.29-.5,0.33,0.08a2.74,2.74,0,0,0,1.24.24,1.88,1.88,0,0,0,.61-0.76l0.23-.43,1.81-3.09q2.76-4.78,3-5.3a0.84,0.84,0,0,0,.11-0.51,2.77,2.77,0,0,0-.75-0.92l-0.27-.3,0.29-.5q1.32,0.89,2.1,1.33T112.66,14.61Z"/><path class="cls-1" d="M136.43,39.17l-0.5.29-0.28-.28a2.49,2.49,0,0,0-.93-0.76,1.67,1.67,0,0,0-.85.31l-0.29.17-3.42,1.93-4.92,2.87a1.59,1.59,0,0,0-.68.56,2,2,0,0,0,.24,1.05q0.06,0.16.14,0.48l-0.5.29q-0.5-1.07-1-2T122.07,42l0.5-.29a2.24,2.24,0,0,1,.25.23,2.75,2.75,0,0,0,1,.82,1.87,1.87,0,0,0,.91-0.35l0.42-.26,3.11-1.77q4.78-2.76,5.28-3.07a0.84,0.84,0,0,0,.35-0.38,2.77,2.77,0,0,0-.19-1.17c0-.09,0-0.22-0.08-0.4l0.5-.29q0.7,1.43,1.15,2.21T136.43,39.17Z"/><path class="cls-1" d="M139.17,43.91l-0.5.29-0.28-.28a2.48,2.48,0,0,0-.93-0.76,1.66,1.66,0,0,0-.85.31l-0.3.17-3.42,1.93L128,48.44a1.6,1.6,0,0,0-.68.56,2,2,0,0,0,.24,1.05c0,0.11.09,0.27,0.14,0.48l-0.5.29q-0.5-1.08-1-2t-1.33-2.09l0.5-.29a2.13,2.13,0,0,1,.25.23,2.76,2.76,0,0,0,1,.82,1.88,1.88,0,0,0,.91-0.35l0.42-.26,3.11-1.77q4.78-2.76,5.28-3.07a0.83,0.83,0,0,0,.35-0.38,2.77,2.77,0,0,0-.19-1.17c0-.09,0-0.22-0.08-0.4l0.5-.29q0.7,1.43,1.15,2.21T139.17,43.91Z"/><path class="cls-1" d="M146.12,71.92h-0.58c-0.05-.17-0.09-0.3-0.11-0.38a2.49,2.49,0,0,0-.42-1.13,1.67,1.67,0,0,0-.89-0.15h-0.34l-3.92,0-5.7,0a1.59,1.59,0,0,0-.87.15,2,2,0,0,0-.32,1q0,0.17-.12.49h-0.58q0.11-1.18.11-2.27t-0.11-2.47h0.58a2.06,2.06,0,0,1,.1.32,2.75,2.75,0,0,0,.42,1.19,1.87,1.87,0,0,0,1,.15h0.49l3.58,0q5.52,0,6.11,0a0.84,0.84,0,0,0,.5-0.16,2.75,2.75,0,0,0,.42-1.1l0.13-.39h0.58Q146,68.76,146,69.66T146.12,71.92Z"/><path class="cls-1" d="M146.12,77.39h-0.58c-0.05-.17-0.09-0.3-0.11-0.38a2.48,2.48,0,0,0-.42-1.13,1.66,1.66,0,0,0-.89-0.15h-0.34l-3.92,0-5.7,0a1.6,1.6,0,0,0-.87.15,2,2,0,0,0-.32,1c0,0.11-.06.28-0.12,0.49h-0.58c0.07-.79.11-1.54,0.11-2.27s0-1.58-.11-2.47h0.58a2.06,2.06,0,0,1,.1.32,2.75,2.75,0,0,0,.42,1.19,1.87,1.87,0,0,0,1,.15h0.49l3.58,0q5.52,0,6.11,0a0.84,0.84,0,0,0,.5-0.16,2.75,2.75,0,0,0,.42-1.1l0.13-.39h0.58Q146,74.23,146,75.13C146,75.79,146.05,76.55,146.12,77.39Z"/><path class="cls-1" d="M146.12,82.87h-0.58c-0.05-.17-0.09-0.3-0.11-0.38a2.49,2.49,0,0,0-.42-1.13,1.67,1.67,0,0,0-.89-0.15h-0.34l-3.92,0-5.7,0a1.59,1.59,0,0,0-.87.15,2,2,0,0,0-.32,1q0,0.17-.12.49h-0.58q0.11-1.18.11-2.27t-0.11-2.47h0.58a2,2,0,0,1,.1.32,2.75,2.75,0,0,0,.42,1.19,1.87,1.87,0,0,0,1,.15h0.49l3.58,0q5.52,0,6.11,0a0.84,0.84,0,0,0,.5-0.16,2.75,2.75,0,0,0,.42-1.1l0.13-.39h0.58Q146,79.71,146,80.6T146.12,82.87Z"/><path class="cls-1" d="M138,108.11l-0.5-.29c0-.17.07-0.3,0.1-0.38a2.46,2.46,0,0,0,.2-1.19,1.66,1.66,0,0,0-.7-0.58l-0.3-.17-3.38-2-4.95-2.83a1.59,1.59,0,0,0-.83-0.3,2,2,0,0,0-.79.73l-0.35.37-0.5-.29q0.68-1,1.23-1.91t1.14-2.2l0.5,0.29a1.9,1.9,0,0,1-.08.33,2.77,2.77,0,0,0-.23,1.24,1.86,1.86,0,0,0,.76.61l0.43,0.23,3.09,1.81q4.78,2.76,5.3,3a0.84,0.84,0,0,0,.51.11,2.75,2.75,0,0,0,.92-0.75l0.3-.27,0.5,0.29q-0.89,1.32-1.33,2.1T138,108.11Z"/><path class="cls-1" d="M122.85,106.16l0.24-.42q3.7,1,8.42,2.18,3.93,1,4.36,1a1.11,1.11,0,0,0,.48,0,4.85,4.85,0,0,0,1-.7l0.5,0.29q-0.88,1.32-1.13,1.75-0.35.62-.95,1.85l-0.5-.29c0-.09,0-0.16,0-0.21a2.57,2.57,0,0,0,.1-0.92,0.48,0.48,0,0,0-.22-0.28l-0.2-.09q-0.68-.24-4.21-1.16l-5.12-1.35q2.24,2.33,4.19,4.24,2.44,2.39,2.72,2.62l0.11,0.08a0.42,0.42,0,0,0,.32.06,3.73,3.73,0,0,0,1-.68l0.47,0.27-0.29.44q-0.63.94-.79,1.22a12.23,12.23,0,0,0-.65,1.34l-0.47-.27a3,3,0,0,0,.08-1.07,2.35,2.35,0,0,0-.54-0.8q-0.94-1.06-5.75-5.92Z"/><path class="cls-1" d="M103.21,124.55l0.42-.24q2.68,2.75,6.2,6.1,2.93,2.79,3.27,3.06a1.14,1.14,0,0,0,.43.22,4.92,4.92,0,0,0,1.19-.12l0.29,0.5q-1.42.7-1.85,0.95-0.62.35-1.75,1.13l-0.29-.5,0.14-.16a2.55,2.55,0,0,0,.54-0.75,0.49,0.49,0,0,0-.05-0.35,1.17,1.17,0,0,0-.13-0.17q-0.47-.55-3.06-3.11l-3.75-3.73q0.77,3.14,1.51,5.77,0.92,3.29,1,3.63a0.78,0.78,0,0,0,.06.13,0.43,0.43,0,0,0,.25.21,3.81,3.81,0,0,0,1.17-.11l0.27,0.47-0.47.24q-1,.5-1.29.66a12,12,0,0,0-1.23.84l-0.27-.47a3,3,0,0,0,.61-0.88,2.36,2.36,0,0,0-.07-1q-0.29-1.39-2-8Z"/><path class="cls-1" d="M77.4,132h0.48q1,3.72,2.32,8.38,1.14,3.88,1.3,4.28a1.14,1.14,0,0,0,.26.41,4.91,4.91,0,0,0,1.09.49v0.58Q81.28,146,80.78,146q-0.71,0-2.08.11v-0.58l0.2-.07a2.53,2.53,0,0,0,.84-0.37,0.47,0.47,0,0,0,.13-0.33,1.17,1.17,0,0,0,0-.21q-0.13-.71-1.1-4.23l-1.39-5.1q-0.9,3.1-1.58,5.75-0.85,3.31-.91,3.67a0.75,0.75,0,0,0,0,.14,0.43,0.43,0,0,0,.11.3,3.74,3.74,0,0,0,1.07.49v0.55l-0.53,0Q74.39,146,74.07,146a12.15,12.15,0,0,0-1.49.11v-0.55a3,3,0,0,0,1-.46,2.34,2.34,0,0,0,.42-0.87q0.45-1.35,2.25-7.94Z"/><path class="cls-1" d="M67.38,146.12v-0.58l0.38-.11a2.49,2.49,0,0,0,1.13-.42,1.67,1.67,0,0,0,.15-0.89v-0.34l0-3.92,0-5.7a1.59,1.59,0,0,0-.15-0.87,2,2,0,0,0-1-.32l-0.49-.12v-0.58q1.18,0.11,2.27.11t2.47-.11v0.58l-0.32.1a2.75,2.75,0,0,0-1.19.42,1.87,1.87,0,0,0-.15,1v0.49l0,3.58q0,5.52,0,6.11a0.84,0.84,0,0,0,.16.5,2.75,2.75,0,0,0,1.1.42l0.39,0.13v0.58Q70.53,146,69.64,146T67.38,146.12Z"/><path class="cls-1" d="M51,126.93l0.42,0.24q-1,3.7-2.18,8.42-1,3.93-1,4.36a1.14,1.14,0,0,0,0,.48,4.87,4.87,0,0,0,.7,1l-0.29.5q-1.31-.88-1.75-1.13-0.62-.36-1.85-0.95l0.29-.5,0.21,0a2.52,2.52,0,0,0,.92.1,0.47,0.47,0,0,0,.28-0.22,1.13,1.13,0,0,0,.09-0.2q0.24-.68,1.16-4.21l1.35-5.12Q47,132,45.1,133.91q-2.39,2.44-2.62,2.72l-0.08.11a0.43,0.43,0,0,0-.06.32,3.74,3.74,0,0,0,.68,1l-0.27.47-0.44-.29q-0.94-.63-1.21-0.79a12.38,12.38,0,0,0-1.34-.65l0.27-.47a3,3,0,0,0,1.07.08,2.33,2.33,0,0,0,.8-0.54q1.06-.94,5.92-5.75Z"/><path class="cls-1" d="M35.24,134.16l0.29-.5,0.38,0.1a2.49,2.49,0,0,0,1.19.2,1.68,1.68,0,0,0,.58-0.7l0.17-.3,2-3.38,2.83-4.95a1.6,1.6,0,0,0,.3-0.83,2,2,0,0,0-.73-0.79l-0.37-.35,0.29-.5q1,0.68,1.91,1.23t2.2,1.14L46,125,45.66,125a2.75,2.75,0,0,0-1.24-.24,1.88,1.88,0,0,0-.61.76l-0.23.43L41.77,129q-2.76,4.78-3,5.3a0.83,0.83,0,0,0-.11.51,2.77,2.77,0,0,0,.75.92l0.27,0.3-0.29.5q-1.32-.89-2.1-1.33T35.24,134.16Z"/><path class="cls-1" d="M30.5,131.42l0.29-.5,0.38,0.1a2.49,2.49,0,0,0,1.19.2,1.67,1.67,0,0,0,.58-0.7l0.17-.3,2-3.38,2.83-4.95a1.6,1.6,0,0,0,.31-0.83,2,2,0,0,0-.73-0.79l-0.37-.35,0.29-.5q1,0.68,1.91,1.23t2.2,1.14l-0.29.5-0.33-.08a2.75,2.75,0,0,0-1.24-.24,1.88,1.88,0,0,0-.61.76l-0.23.43L37,126.27q-2.76,4.78-3,5.3a0.83,0.83,0,0,0-.11.51,2.77,2.77,0,0,0,.75.92l0.27,0.3-0.29.5q-1.32-.89-2.1-1.33T30.5,131.42Z"/><path class="cls-1" d="M29.59,110.34l0.24,0.42q-2.74,2.68-6.1,6.2-2.79,2.93-3.06,3.27a1.14,1.14,0,0,0-.22.43,4.88,4.88,0,0,0,.12,1.19l-0.5.29q-0.7-1.42-.95-1.85-0.36-.62-1.13-1.75l0.5-.29,0.16,0.14a2.49,2.49,0,0,0,.75.54,0.47,0.47,0,0,0,.35-0.05l0.17-.13q0.55-.47,3.11-3.06l3.73-3.75q-3.14.77-5.77,1.51-3.29.92-3.63,1l-0.13.06a0.42,0.42,0,0,0-.21.25,3.72,3.72,0,0,0,.11,1.17l-0.47.27-0.24-.47q-0.5-1-.66-1.29a12.13,12.13,0,0,0-.83-1.23l0.47-.27a3,3,0,0,0,.88.61,2.36,2.36,0,0,0,1-.07q1.39-.29,8-2Z"/><path class="cls-1" d="M12.34,108.73l0.5-.29,0.28,0.28a2.49,2.49,0,0,0,.93.76,1.68,1.68,0,0,0,.85-0.31l0.3-.17,3.42-1.93,4.92-2.87a1.6,1.6,0,0,0,.68-0.56,2,2,0,0,0-.24-1.05c0-.11-0.09-0.27-0.14-0.48l0.5-.29q0.5,1.07,1,2t1.33,2.09l-0.5.29A2.08,2.08,0,0,1,26,106a2.75,2.75,0,0,0-1-.82,1.87,1.87,0,0,0-.91.35l-0.42.26-3.11,1.77q-4.78,2.76-5.28,3.07a0.83,0.83,0,0,0-.35.38,2.76,2.76,0,0,0,.19,1.17c0,0.09,0,.22.08,0.4l-0.5.29q-0.7-1.43-1.15-2.2T12.34,108.73Z"/><path class="cls-1" d="M9.6,104l0.5-.29L10.38,104a2.49,2.49,0,0,0,.93.76,1.67,1.67,0,0,0,.85-0.31l0.3-.17,3.42-1.93,4.92-2.87a1.6,1.6,0,0,0,.68-0.56,2,2,0,0,0-.24-1.05c0-.11-0.09-0.27-0.14-0.48l0.5-.29q0.5,1.07,1,2T24,101.18l-0.5.29a2.08,2.08,0,0,1-.25-0.23,2.73,2.73,0,0,0-1-.82,1.86,1.86,0,0,0-.91.35l-0.42.26-3.11,1.77q-4.78,2.76-5.28,3.07a0.84,0.84,0,0,0-.35.39,2.76,2.76,0,0,0,.19,1.17c0,0.09,0,.22.08,0.4l-0.5.29q-0.7-1.43-1.15-2.21T9.6,104Z"/><path class="cls-1" d="M6.87,99.25L7.36,99l0.28,0.28a2.49,2.49,0,0,0,.93.76,1.67,1.67,0,0,0,.85-0.31l0.3-.17,3.42-1.93,4.92-2.87a1.6,1.6,0,0,0,.68-0.56,2,2,0,0,0-.24-1.05q-0.06-.16-0.14-0.48l0.5-.29q0.5,1.07,1,2t1.33,2.09l-0.5.29a2.11,2.11,0,0,1-.25-0.23,2.73,2.73,0,0,0-1-.82,1.86,1.86,0,0,0-.91.35l-0.42.26L15.1,98.05q-4.78,2.76-5.28,3.07a0.84,0.84,0,0,0-.35.39,2.76,2.76,0,0,0,.19,1.17c0,0.09,0,.22.08,0.4l-0.5.29q-0.7-1.43-1.15-2.2T6.87,99.25Z"/><path class="cls-1" d="M3.88,78.23H4.45c0.05,0.17.09,0.3,0.11,0.38A2.48,2.48,0,0,0,5,79.74a1.67,1.67,0,0,0,.89.15H6.22l3.92,0,5.7,0a1.59,1.59,0,0,0,.87-0.15,2,2,0,0,0,.32-1q0-.17.12-0.49h0.58q-0.11,1.18-.11,2.27T17.72,83H17.15a2.06,2.06,0,0,1-.1-0.32,2.74,2.74,0,0,0-.41-1.19,1.87,1.87,0,0,0-1-.15H15.18l-3.58,0q-5.52,0-6.11,0a0.83,0.83,0,0,0-.5.16,2.76,2.76,0,0,0-.42,1.1L4.45,83H3.88Q4,81.39,4,80.49T3.88,78.23Z"/><path class="cls-1" d="M4.45,77.44H3.88Q4,76.3,4,75.34T3.88,72.9H4.45A3.43,3.43,0,0,0,4.88,74a0.27,0.27,0,0,0,.21.1,2.31,2.31,0,0,0,.68-0.24q2.15-1,3.42-1.74Q7.15,70.9,5.38,70A1.37,1.37,0,0,0,5,69.81a0.25,0.25,0,0,0-.19.1,4.22,4.22,0,0,0-.33,1H3.88Q4,69.63,4,68.94a16.81,16.81,0,0,0-.11-1.77H4.45A3.49,3.49,0,0,0,5,68.41a6.62,6.62,0,0,0,1.42,1q2.24,1.3,3.84,2.13,1.33-.62,3.25-1.62l2.85-1.49a1.32,1.32,0,0,0,.31-0.29A4.78,4.78,0,0,0,17.15,67h0.58q-0.11,1.3-.11,2.08t0.11,2.28H17.15a6.18,6.18,0,0,0-.42-1,0.3,0.3,0,0,0-.22-0.08,1.84,1.84,0,0,0-.67.21q-2.22,1-4,2,1.43,0.88,3.95,2.19a2.29,2.29,0,0,0,.69.27,0.39,0.39,0,0,0,.27-0.11A2.31,2.31,0,0,0,17.08,74l0.07-.21h0.58a20.5,20.5,0,0,0-.11,2.05,11.92,11.92,0,0,0,.11,1.67H17.15a3,3,0,0,0-.51-1.14,10.37,10.37,0,0,0-1.76-1.19q-2.7-1.55-4-2.24-1.45.68-4.32,2.19a6.74,6.74,0,0,0-1.4.89A4.93,4.93,0,0,0,4.45,77.44Z"/><path class="cls-1" d="M11.35,44.22l-0.5-.29Q11.51,43,12,42.16T13.12,40l0.5,0.29a3.45,3.45,0,0,0-.16,1.14,0.27,0.27,0,0,0,.13.19,2.32,2.32,0,0,0,.71.13q2.37,0.19,3.83.2-1.18-2-2.25-3.73a1.4,1.4,0,0,0-.27-0.34,0.25,0.25,0,0,0-.21,0,4.16,4.16,0,0,0-.76.66l-0.5-.29q0.71-1,1.06-1.61A16.76,16.76,0,0,0,16,35l0.5,0.29a3.49,3.49,0,0,0-.18,1.33A6.61,6.61,0,0,0,17,38.24q1.29,2.25,2.26,3.77,1.46,0.13,3.63.23l3.21,0.14a1.34,1.34,0,0,0,.42-0.1,4.76,4.76,0,0,0,1-.74l0.5,0.29q-0.74,1.07-1.13,1.75t-1,2l-0.5-.29a6.31,6.31,0,0,0,.16-1.11A0.3,0.3,0,0,0,25.38,44a1.83,1.83,0,0,0-.69-0.16q-2.45-.2-4.44-0.25,0.8,1.48,2.32,3.87a2.28,2.28,0,0,0,.46.58,0.39,0.39,0,0,0,.29,0A2.26,2.26,0,0,0,24,47.59l0.17-.15,0.5,0.29a20.38,20.38,0,0,0-1.12,1.73,11.85,11.85,0,0,0-.74,1.5l-0.5-.29a3,3,0,0,0,.13-1.24,10.4,10.4,0,0,0-.93-1.91q-1.57-2.69-2.39-4-1.59-.13-4.84-0.27a6.73,6.73,0,0,0-1.66.07A4.91,4.91,0,0,0,11.35,44.22Z"/><path class="cls-1" d="M32.88,17.89l-0.29-.5q1-.48,1.87-1t2.06-1.31l0.29,0.5a3.43,3.43,0,0,0-.71.91,0.27,0.27,0,0,0,0,.22,2.3,2.3,0,0,0,.55.47q2,1.35,3.22,2.09,0-2.36-.08-4.35a1.37,1.37,0,0,0-.07-0.44,0.25,0.25,0,0,0-.17-0.11,4.15,4.15,0,0,0-1,.19l-0.29-.5q1.12-.52,1.72-0.87a16.59,16.59,0,0,0,1.48-1l0.29,0.5A3.5,3.5,0,0,0,41,13.82a6.63,6.63,0,0,0-.17,1.73q0,2.59.07,4.4,1.2,0.84,3,2l2.71,1.73a1.36,1.36,0,0,0,.41.12,4.78,4.78,0,0,0,1.25-.14l0.29,0.5q-1.18.56-1.85,0.95t-1.92,1.23l-0.29-.5A6.32,6.32,0,0,0,45.17,25a0.3,0.3,0,0,0,0-.24,1.84,1.84,0,0,0-.51-0.48q-2-1.4-3.72-2.44,0,1.68.07,4.51a2.27,2.27,0,0,0,.11.73,0.38,0.38,0,0,0,.23.18,2.23,2.23,0,0,0,.82-0.11l0.22,0,0.29,0.5q-1,.44-1.83.93a11.92,11.92,0,0,0-1.39.93l-0.29-.5a3,3,0,0,0,.73-1A10.41,10.41,0,0,0,40,25.81q0-3.11-.09-4.62-1.31-.91-4.05-2.65a6.84,6.84,0,0,0-1.47-.77A5,5,0,0,0,32.88,17.89Z"/><path class="cls-1" d="M46.39,9.43l0.29,0.5-0.28.28a2.48,2.48,0,0,0-.76.93,1.66,1.66,0,0,0,.31.85l0.17,0.3L48,15.7l2.87,4.92a1.59,1.59,0,0,0,.56.68,2,2,0,0,0,1.05-.23L53,20.92l0.29,0.5q-1.07.5-2,1T49.2,23.79l-0.29-.5a2,2,0,0,1,.23-0.24,2.75,2.75,0,0,0,.82-1,1.88,1.88,0,0,0-.35-0.91l-0.26-.42-1.77-3.11q-2.76-4.78-3.07-5.28A0.84,0.84,0,0,0,44.12,12a2.78,2.78,0,0,0-1.17.19l-0.4.08-0.29-.5q1.43-.7,2.21-1.15T46.39,9.43Z"/><line class="cls-2" x1="80.64" y1="21.3" x2="80.19" y2="25.63"/><line class="cls-2" x1="86.23" y1="22.18" x2="85.32" y2="26.44"/><line class="cls-2" x1="91.69" y1="23.64" x2="90.34" y2="27.79"/><line class="cls-2" x1="96.96" y1="25.67" x2="95.19" y2="29.65"/><line class="cls-2" x1="102" y1="28.23" x2="99.82" y2="32.01"/><line class="cls-2" x1="106.74" y1="31.31" x2="104.18" y2="34.84"/><line class="cls-2" x1="111.13" y1="34.87" x2="108.22" y2="38.11"/><line class="cls-2" x1="115.13" y1="38.87" x2="111.89" y2="41.78"/><line class="cls-2" x1="118.69" y1="43.26" x2="115.16" y2="45.82"/><line class="cls-2" x1="121.77" y1="48" x2="117.99" y2="50.18"/><line class="cls-2" x1="124.33" y1="53.04" x2="120.35" y2="54.81"/><line class="cls-2" x1="126.36" y1="58.31" x2="122.21" y2="59.66"/><line class="cls-2" x1="127.82" y1="63.77" x2="123.56" y2="64.68"/><line class="cls-2" x1="128.7" y1="69.36" x2="124.37" y2="69.81"/><line class="cls-2" x1="129" y1="75" x2="124.64" y2="75"/><line class="cls-2" x1="128.7" y1="80.64" x2="124.37" y2="80.19"/><line class="cls-2" x1="127.82" y1="86.23" x2="123.56" y2="85.32"/><line class="cls-2" x1="126.36" y1="91.69" x2="122.21" y2="90.34"/><line class="cls-2" x1="124.33" y1="96.96" x2="120.35" y2="95.19"/><line class="cls-2" x1="121.77" y1="102" x2="117.99" y2="99.82"/><line class="cls-2" x1="118.69" y1="106.74" x2="115.16" y2="104.18"/><line class="cls-2" x1="115.13" y1="111.13" x2="111.89" y2="108.22"/><line class="cls-2" x1="111.13" y1="115.13" x2="108.22" y2="111.89"/><line class="cls-2" x1="106.74" y1="118.69" x2="104.18" y2="115.16"/><line class="cls-2" x1="102" y1="121.77" x2="99.82" y2="117.99"/><line class="cls-2" x1="96.96" y1="124.33" x2="95.19" y2="120.35"/><line class="cls-2" x1="91.69" y1="126.36" x2="90.34" y2="122.21"/><line class="cls-2" x1="86.23" y1="127.82" x2="85.32" y2="123.56"/><line class="cls-2" x1="80.64" y1="128.7" x2="80.19" y2="124.37"/><line class="cls-2" x1="75" y1="129" x2="75" y2="124.64"/><line class="cls-2" x1="69.36" y1="128.7" x2="69.81" y2="124.37"/><line class="cls-2" x1="63.77" y1="127.82" x2="64.68" y2="123.56"/><line class="cls-2" x1="58.31" y1="126.36" x2="59.66" y2="122.21"/><line class="cls-2" x1="53.04" y1="124.33" x2="54.81" y2="120.35"/><line class="cls-2" x1="48" y1="121.77" x2="50.18" y2="117.99"/><line class="cls-2" x1="43.26" y1="118.69" x2="45.82" y2="115.16"/><line class="cls-2" x1="38.87" y1="115.13" x2="41.78" y2="111.89"/><line class="cls-2" x1="34.87" y1="111.13" x2="38.11" y2="108.22"/><line class="cls-2" x1="31.31" y1="106.74" x2="34.84" y2="104.18"/><line class="cls-2" x1="28.23" y1="102" x2="32.01" y2="99.82"/><line class="cls-2" x1="25.67" y1="96.96" x2="29.65" y2="95.19"/><line class="cls-2" x1="23.64" y1="91.69" x2="27.79" y2="90.34"/><line class="cls-2" x1="22.18" y1="86.23" x2="26.44" y2="85.32"/><line class="cls-2" x1="21.3" y1="80.64" x2="25.63" y2="80.19"/><line class="cls-2" x1="21" y1="75" x2="25.36" y2="75"/><line class="cls-2" x1="21.3" y1="69.36" x2="25.63" y2="69.81"/><line class="cls-2" x1="22.18" y1="63.77" x2="26.44" y2="64.68"/><line class="cls-2" x1="23.64" y1="58.31" x2="27.79" y2="59.66"/><line class="cls-2" x1="25.67" y1="53.04" x2="29.65" y2="54.81"/><line class="cls-2" x1="28.23" y1="48" x2="32.01" y2="50.18"/><line class="cls-2" x1="31.31" y1="43.26" x2="34.84" y2="45.82"/><line class="cls-2" x1="34.87" y1="38.87" x2="38.11" y2="41.78"/><line class="cls-2" x1="38.87" y1="34.87" x2="41.78" y2="38.11"/><line class="cls-2" x1="43.26" y1="31.31" x2="45.82" y2="34.84"/><line class="cls-2" x1="48" y1="28.23" x2="50.18" y2="32.01"/><line class="cls-2" x1="53.04" y1="25.67" x2="54.81" y2="29.65"/><line class="cls-2" x1="58.31" y1="23.64" x2="59.66" y2="27.79"/><line class="cls-2" x1="63.77" y1="22.18" x2="64.68" y2="26.44"/><line class="cls-2" x1="69.36" y1="21.3" x2="69.81" y2="25.63"/><line class="cls-2" x1="75" y1="21" x2="75" y2="25.36"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-006.svg b/MagicMirror/modules/default/clock/faces/face-006.svg new file mode 100644 index 0000000000000000000000000000000000000000..63d1c935b322a64a5d5456a488d9d69cd19e2238 --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-006.svg @@ -0,0 +1 @@ +<svg id="Hour_Markers_-_Singlets_Gradient" data-name="Hour Markers - Singlets Gradient" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-10,.cls-11,.cls-12,.cls-13,.cls-14,.cls-15,.cls-16,.cls-17,.cls-18,.cls-19,.cls-2,.cls-20,.cls-21,.cls-22,.cls-23,.cls-24,.cls-25,.cls-26,.cls-27,.cls-28,.cls-29,.cls-3,.cls-30,.cls-31,.cls-32,.cls-33,.cls-34,.cls-35,.cls-36,.cls-37,.cls-38,.cls-39,.cls-4,.cls-40,.cls-41,.cls-42,.cls-43,.cls-44,.cls-45,.cls-46,.cls-47,.cls-48,.cls-49,.cls-5,.cls-50,.cls-51,.cls-52,.cls-53,.cls-54,.cls-55,.cls-56,.cls-57,.cls-58,.cls-59,.cls-6,.cls-60,.cls-7,.cls-8,.cls-9{fill:none;stroke-linecap:round;stroke-miterlimit:10;}.cls-1,.cls-10,.cls-11,.cls-12,.cls-2,.cls-3,.cls-4,.cls-5,.cls-6,.cls-7,.cls-8,.cls-9{stroke-width:2.99px;}.cls-1{stroke:url(#Gray_Gradient_2);}.cls-2{stroke:url(#Gray_Gradient_2-2);}.cls-3{stroke:url(#Gray_Gradient_2-3);}.cls-4{stroke:url(#Gray_Gradient_2-4);}.cls-5{stroke:url(#Gray_Gradient_2-5);}.cls-6{stroke:url(#Gray_Gradient_2-6);}.cls-7{stroke:url(#Gray_Gradient_2-7);}.cls-8{stroke:url(#Gray_Gradient_2-8);}.cls-9{stroke:url(#Gray_Gradient_2-9);}.cls-10{stroke:url(#Gray_Gradient_2-10);}.cls-11{stroke:url(#Gray_Gradient_2-11);}.cls-12{stroke:url(#Gray_Gradient_2-12);}.cls-13,.cls-14,.cls-15,.cls-16,.cls-17,.cls-18,.cls-19,.cls-20,.cls-21,.cls-22,.cls-23,.cls-24,.cls-25,.cls-26,.cls-27,.cls-28,.cls-29,.cls-30,.cls-31,.cls-32,.cls-33,.cls-34,.cls-35,.cls-36,.cls-37,.cls-38,.cls-39,.cls-40,.cls-41,.cls-42,.cls-43,.cls-44,.cls-45,.cls-46,.cls-47,.cls-48,.cls-49,.cls-50,.cls-51,.cls-52,.cls-53,.cls-54,.cls-55,.cls-56,.cls-57,.cls-58,.cls-59,.cls-60{stroke-width:0.5px;}.cls-13{stroke:url(#Gray_Gradient_2-13);}.cls-14{stroke:url(#Gray_Gradient_2-14);}.cls-15{stroke:url(#Gray_Gradient_2-15);}.cls-16{stroke:url(#Gray_Gradient_2-16);}.cls-17{stroke:url(#Gray_Gradient_2-17);}.cls-18{stroke:url(#Gray_Gradient_2-18);}.cls-19{stroke:url(#Gray_Gradient_2-19);}.cls-20{stroke:url(#Gray_Gradient_2-20);}.cls-21{stroke:url(#Gray_Gradient_2-21);}.cls-22{stroke:url(#Gray_Gradient_2-22);}.cls-23{stroke:url(#Gray_Gradient_2-23);}.cls-24{stroke:url(#Gray_Gradient_2-24);}.cls-25{stroke:url(#Gray_Gradient_2-25);}.cls-26{stroke:url(#Gray_Gradient_2-26);}.cls-27{stroke:url(#Gray_Gradient_2-27);}.cls-28{stroke:url(#Gray_Gradient_2-28);}.cls-29{stroke:url(#Gray_Gradient_2-29);}.cls-30{stroke:url(#Gray_Gradient_2-30);}.cls-31{stroke:url(#Gray_Gradient_2-31);}.cls-32{stroke:url(#Gray_Gradient_2-32);}.cls-33{stroke:url(#Gray_Gradient_2-33);}.cls-34{stroke:url(#Gray_Gradient_2-34);}.cls-35{stroke:url(#Gray_Gradient_2-35);}.cls-36{stroke:url(#Gray_Gradient_2-36);}.cls-37{stroke:url(#Gray_Gradient_2-37);}.cls-38{stroke:url(#Gray_Gradient_2-38);}.cls-39{stroke:url(#Gray_Gradient_2-39);}.cls-40{stroke:url(#Gray_Gradient_2-40);}.cls-41{stroke:url(#Gray_Gradient_2-41);}.cls-42{stroke:url(#Gray_Gradient_2-42);}.cls-43{stroke:url(#Gray_Gradient_2-43);}.cls-44{stroke:url(#Gray_Gradient_2-44);}.cls-45{stroke:url(#Gray_Gradient_2-45);}.cls-46{stroke:url(#Gray_Gradient_2-46);}.cls-47{stroke:url(#Gray_Gradient_2-47);}.cls-48{stroke:url(#Gray_Gradient_2-48);}.cls-49{stroke:url(#Gray_Gradient_2-49);}.cls-50{stroke:url(#Gray_Gradient_2-50);}.cls-51{stroke:url(#Gray_Gradient_2-51);}.cls-52{stroke:url(#Gray_Gradient_2-52);}.cls-53{stroke:url(#Gray_Gradient_2-53);}.cls-54{stroke:url(#Gray_Gradient_2-54);}.cls-55{stroke:url(#Gray_Gradient_2-55);}.cls-56{stroke:url(#Gray_Gradient_2-56);}.cls-57{stroke:url(#Gray_Gradient_2-57);}.cls-58{stroke:url(#Gray_Gradient_2-58);}.cls-59{stroke:url(#Gray_Gradient_2-59);}.cls-60{stroke:url(#Gray_Gradient_2-60);}</style><linearGradient id="Gray_Gradient_2" x1="125" y1="0.01" x2="125" y2="17.94" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset="0.03" stop-color="#e2e2e2"/><stop offset="0.1" stop-color="#b3b3b3"/><stop offset="0.17" stop-color="#888"/><stop offset="0.24" stop-color="#636363"/><stop offset="0.32" stop-color="#444"/><stop offset="0.4" stop-color="#2b2b2b"/><stop offset="0.5" stop-color="#181818"/><stop offset="0.61" stop-color="#0a0a0a"/><stop offset="0.75" stop-color="#020202"/><stop offset="1"/></linearGradient><linearGradient id="Gray_Gradient_2-2" x1="187.5" y1="16.75" x2="178.53" y2="32.28" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-3" x1="233.25" y1="62.5" x2="217.72" y2="71.47" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-4" x1="249.99" y1="125" x2="232.06" y2="125" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-5" x1="233.25" y1="187.5" x2="217.72" y2="178.53" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-6" x1="187.5" y1="233.25" x2="178.53" y2="217.72" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-7" x1="125" y1="249.99" x2="125" y2="232.06" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-8" x1="62.5" y1="233.25" x2="71.47" y2="217.72" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-9" x1="16.75" y1="187.5" x2="32.28" y2="178.53" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-10" x1="0.01" y1="125" x2="17.94" y2="125" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-11" x1="16.75" y1="62.5" x2="32.28" y2="71.47" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-12" x1="62.5" y1="16.75" x2="71.47" y2="32.28" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-13" x1="138.01" y1="1.25" x2="136.91" y2="11.71" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-14" x1="150.87" y1="3.29" x2="148.68" y2="13.58" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-15" x1="163.45" y1="6.66" x2="160.2" y2="16.66" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-16" x1="175.61" y1="11.33" x2="171.33" y2="20.94" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-17" x1="198.14" y1="24.33" x2="191.96" y2="32.84" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-18" x1="208.26" y1="32.53" x2="201.22" y2="40.35" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-19" x1="217.47" y1="41.74" x2="209.65" y2="48.78" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-20" x1="225.67" y1="51.86" x2="217.16" y2="58.04" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-21" x1="238.67" y1="74.39" x2="229.06" y2="78.67" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-22" x1="243.34" y1="86.55" x2="233.34" y2="89.8" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-23" x1="246.71" y1="99.13" x2="236.42" y2="101.32" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-24" x1="248.75" y1="111.99" x2="238.29" y2="113.09" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-25" x1="248.75" y1="138.01" x2="238.29" y2="136.91" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-26" x1="246.71" y1="150.87" x2="236.42" y2="148.68" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-27" x1="243.34" y1="163.45" x2="233.34" y2="160.2" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-28" x1="238.67" y1="175.61" x2="229.06" y2="171.33" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-29" x1="225.67" y1="198.14" x2="217.16" y2="191.96" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-30" x1="217.47" y1="208.26" x2="209.65" y2="201.22" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-31" x1="208.26" y1="217.47" x2="201.22" y2="209.65" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-32" x1="198.14" y1="225.67" x2="191.96" y2="217.16" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-33" x1="175.61" y1="238.67" x2="171.33" y2="229.06" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-34" x1="163.45" y1="243.34" x2="160.2" y2="233.34" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-35" x1="150.87" y1="246.71" x2="148.68" y2="236.42" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-36" x1="138.01" y1="248.75" x2="136.91" y2="238.29" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-37" x1="111.99" y1="248.75" x2="113.09" y2="238.29" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-38" x1="99.13" y1="246.71" x2="101.32" y2="236.42" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-39" x1="86.55" y1="243.34" x2="89.8" y2="233.34" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-40" x1="74.39" y1="238.67" x2="78.67" y2="229.06" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-41" x1="51.86" y1="225.67" x2="58.04" y2="217.16" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-42" x1="41.74" y1="217.47" x2="48.78" y2="209.65" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-43" x1="32.53" y1="208.26" x2="40.35" y2="201.22" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-44" x1="24.33" y1="198.14" x2="32.84" y2="191.96" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-45" x1="11.33" y1="175.61" x2="20.94" y2="171.33" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-46" x1="6.66" y1="163.45" x2="16.66" y2="160.2" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-47" x1="3.29" y1="150.87" x2="13.58" y2="148.68" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-48" x1="1.25" y1="138.01" x2="11.71" y2="136.91" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-49" x1="1.25" y1="111.99" x2="11.71" y2="113.09" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-50" x1="3.29" y1="99.13" x2="13.58" y2="101.32" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-51" x1="6.66" y1="86.55" x2="16.66" y2="89.8" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-52" x1="11.33" y1="74.39" x2="20.94" y2="78.67" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-53" x1="24.33" y1="51.86" x2="32.84" y2="58.04" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-54" x1="32.53" y1="41.74" x2="40.35" y2="48.78" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-55" x1="41.74" y1="32.53" x2="48.78" y2="40.35" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-56" x1="51.86" y1="24.33" x2="58.04" y2="32.84" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-57" x1="74.39" y1="11.33" x2="78.67" y2="20.94" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-58" x1="86.55" y1="6.66" x2="89.8" y2="16.66" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-59" x1="99.13" y1="3.29" x2="101.32" y2="13.58" xlink:href="#Gray_Gradient_2"/><linearGradient id="Gray_Gradient_2-60" x1="111.99" y1="1.25" x2="113.09" y2="11.71" xlink:href="#Gray_Gradient_2"/></defs><title>face-006</title><line class="cls-1" x1="125" y1="1.5" x2="125" y2="16.45"/><line class="cls-2" x1="186.75" y1="18.05" x2="179.28" y2="30.99"/><line class="cls-3" x1="231.95" y1="63.25" x2="219.01" y2="70.72"/><line class="cls-4" x1="248.5" y1="125" x2="233.55" y2="125"/><line class="cls-5" x1="231.95" y1="186.75" x2="219.01" y2="179.28"/><line class="cls-6" x1="186.75" y1="231.95" x2="179.28" y2="219.01"/><line class="cls-7" x1="125" y1="248.5" x2="125" y2="233.55"/><line class="cls-8" x1="63.25" y1="231.95" x2="70.72" y2="219.01"/><line class="cls-9" x1="18.05" y1="186.75" x2="30.99" y2="179.28"/><line class="cls-10" x1="1.5" y1="125" x2="16.45" y2="125"/><line class="cls-11" x1="18.05" y1="63.25" x2="30.99" y2="70.72"/><line class="cls-12" x1="63.25" y1="18.05" x2="70.72" y2="30.99"/><line class="cls-13" x1="137.98" y1="1.5" x2="136.93" y2="11.46"/><line class="cls-14" x1="150.82" y1="3.53" x2="148.74" y2="13.33"/><line class="cls-15" x1="163.37" y1="6.9" x2="160.28" y2="16.43"/><line class="cls-16" x1="175.51" y1="11.56" x2="171.43" y2="20.71"/><line class="cls-17" x1="197.99" y1="24.54" x2="192.1" y2="32.64"/><line class="cls-18" x1="208.09" y1="32.72" x2="201.39" y2="40.16"/><line class="cls-19" x1="217.28" y1="41.91" x2="209.84" y2="48.61"/><line class="cls-20" x1="225.46" y1="52.01" x2="217.36" y2="57.9"/><line class="cls-21" x1="238.44" y1="74.49" x2="229.29" y2="78.57"/><line class="cls-22" x1="243.1" y1="86.63" x2="233.57" y2="89.72"/><line class="cls-23" x1="246.47" y1="99.18" x2="236.67" y2="101.26"/><line class="cls-24" x1="248.5" y1="112.02" x2="238.54" y2="113.07"/><line class="cls-25" x1="248.5" y1="137.98" x2="238.54" y2="136.93"/><line class="cls-26" x1="246.47" y1="150.82" x2="236.67" y2="148.74"/><line class="cls-27" x1="243.1" y1="163.37" x2="233.57" y2="160.28"/><line class="cls-28" x1="238.44" y1="175.51" x2="229.29" y2="171.43"/><line class="cls-29" x1="225.46" y1="197.99" x2="217.36" y2="192.1"/><line class="cls-30" x1="217.28" y1="208.09" x2="209.84" y2="201.39"/><line class="cls-31" x1="208.09" y1="217.28" x2="201.39" y2="209.84"/><line class="cls-32" x1="197.99" y1="225.46" x2="192.1" y2="217.36"/><line class="cls-33" x1="175.51" y1="238.44" x2="171.43" y2="229.29"/><line class="cls-34" x1="163.37" y1="243.1" x2="160.28" y2="233.57"/><line class="cls-35" x1="150.82" y1="246.47" x2="148.74" y2="236.67"/><line class="cls-36" x1="137.98" y1="248.5" x2="136.93" y2="238.54"/><line class="cls-37" x1="112.02" y1="248.5" x2="113.07" y2="238.54"/><line class="cls-38" x1="99.18" y1="246.47" x2="101.26" y2="236.67"/><line class="cls-39" x1="86.63" y1="243.1" x2="89.72" y2="233.57"/><line class="cls-40" x1="74.49" y1="238.44" x2="78.57" y2="229.29"/><line class="cls-41" x1="52.01" y1="225.46" x2="57.9" y2="217.36"/><line class="cls-42" x1="41.91" y1="217.28" x2="48.61" y2="209.84"/><line class="cls-43" x1="32.72" y1="208.09" x2="40.16" y2="201.39"/><line class="cls-44" x1="24.54" y1="197.99" x2="32.64" y2="192.1"/><line class="cls-45" x1="11.56" y1="175.51" x2="20.71" y2="171.43"/><line class="cls-46" x1="6.9" y1="163.37" x2="16.43" y2="160.28"/><line class="cls-47" x1="3.53" y1="150.82" x2="13.33" y2="148.74"/><line class="cls-48" x1="1.5" y1="137.98" x2="11.46" y2="136.93"/><line class="cls-49" x1="1.5" y1="112.02" x2="11.46" y2="113.07"/><line class="cls-50" x1="3.53" y1="99.18" x2="13.33" y2="101.26"/><line class="cls-51" x1="6.9" y1="86.63" x2="16.43" y2="89.72"/><line class="cls-52" x1="11.56" y1="74.49" x2="20.71" y2="78.57"/><line class="cls-53" x1="24.54" y1="52.01" x2="32.64" y2="57.9"/><line class="cls-54" x1="32.72" y1="41.91" x2="40.16" y2="48.61"/><line class="cls-55" x1="41.91" y1="32.72" x2="48.61" y2="40.16"/><line class="cls-56" x1="52.01" y1="24.54" x2="57.9" y2="32.64"/><line class="cls-57" x1="74.49" y1="11.56" x2="78.57" y2="20.71"/><line class="cls-58" x1="86.63" y1="6.9" x2="89.72" y2="16.43"/><line class="cls-59" x1="99.18" y1="3.53" x2="101.26" y2="13.33"/><line class="cls-60" x1="112.02" y1="1.5" x2="113.07" y2="11.46"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-007.svg b/MagicMirror/modules/default/clock/faces/face-007.svg new file mode 100644 index 0000000000000000000000000000000000000000..e557f55fbf19419f0b03e22cb8c672b32793466e --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-007.svg @@ -0,0 +1 @@ +<svg id="Hour_Markers_-_Singlets" data-name="Hour Markers - Singlets" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-2,.cls-3{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;}.cls-2{stroke-width:0.37px;}.cls-3{stroke-width:0.5px;}.cls-4{fill:#fff;}</style></defs><title>face-007</title><line class="cls-1" x1="125" y1="1.25" x2="125" y2="16.23"/><line class="cls-1" x1="186.87" y1="17.83" x2="179.39" y2="30.8"/><line class="cls-1" x1="232.17" y1="63.12" x2="219.2" y2="70.61"/><line class="cls-1" x1="248.75" y1="125" x2="233.77" y2="125"/><line class="cls-1" x1="232.17" y1="186.87" x2="219.2" y2="179.39"/><line class="cls-1" x1="186.88" y1="232.17" x2="179.39" y2="219.2"/><line class="cls-1" x1="125" y1="248.75" x2="125" y2="233.77"/><line class="cls-1" x1="63.13" y1="232.17" x2="70.61" y2="219.2"/><line class="cls-1" x1="17.83" y1="186.88" x2="30.8" y2="179.39"/><line class="cls-1" x1="1.25" y1="125" x2="16.23" y2="125"/><line class="cls-1" x1="17.83" y1="63.13" x2="30.8" y2="70.61"/><line class="cls-1" x1="63.12" y1="17.83" x2="70.61" y2="30.8"/><line class="cls-2" x1="132.32" y1="55.38" x2="131.73" y2="61"/><line class="cls-2" x1="139.55" y1="56.53" x2="138.38" y2="62.05"/><line class="cls-2" x1="146.63" y1="58.43" x2="144.89" y2="63.8"/><line class="cls-2" x1="153.47" y1="61.05" x2="151.17" y2="66.21"/><line class="cls-2" x1="160" y1="64.38" x2="157.18" y2="69.27"/><line class="cls-2" x1="166.14" y1="68.37" x2="162.83" y2="72.94"/><line class="cls-2" x1="171.84" y1="72.98" x2="168.06" y2="77.18"/><line class="cls-2" x1="177.02" y1="78.16" x2="172.82" y2="81.94"/><line class="cls-2" x1="181.63" y1="83.86" x2="177.06" y2="87.17"/><line class="cls-2" x1="185.62" y1="90" x2="180.73" y2="92.82"/><line class="cls-2" x1="188.95" y1="96.53" x2="183.79" y2="98.83"/><line class="cls-2" x1="191.57" y1="103.37" x2="186.2" y2="105.11"/><line class="cls-2" x1="193.47" y1="110.45" x2="187.95" y2="111.62"/><line class="cls-2" x1="194.62" y1="117.68" x2="189" y2="118.27"/><line class="cls-2" x1="195" y1="125" x2="189.35" y2="125"/><line class="cls-2" x1="194.62" y1="132.32" x2="189" y2="131.73"/><line class="cls-2" x1="193.47" y1="139.55" x2="187.95" y2="138.38"/><line class="cls-2" x1="191.57" y1="146.63" x2="186.2" y2="144.89"/><line class="cls-2" x1="188.95" y1="153.47" x2="183.79" y2="151.17"/><line class="cls-2" x1="185.62" y1="160" x2="180.73" y2="157.18"/><line class="cls-2" x1="181.63" y1="166.14" x2="177.06" y2="162.83"/><line class="cls-2" x1="177.02" y1="171.84" x2="172.82" y2="168.06"/><line class="cls-2" x1="171.84" y1="177.02" x2="168.06" y2="172.82"/><line class="cls-2" x1="166.14" y1="181.63" x2="162.83" y2="177.06"/><line class="cls-2" x1="160" y1="185.62" x2="157.18" y2="180.73"/><line class="cls-2" x1="153.47" y1="188.95" x2="151.17" y2="183.79"/><line class="cls-2" x1="146.63" y1="191.57" x2="144.89" y2="186.2"/><line class="cls-2" x1="139.55" y1="193.47" x2="138.38" y2="187.95"/><line class="cls-2" x1="132.32" y1="194.62" x2="131.73" y2="189"/><line class="cls-2" x1="125" y1="195" x2="125" y2="189.35"/><line class="cls-2" x1="117.68" y1="194.62" x2="118.27" y2="189"/><line class="cls-2" x1="110.45" y1="193.47" x2="111.62" y2="187.95"/><line class="cls-2" x1="103.37" y1="191.57" x2="105.11" y2="186.2"/><line class="cls-2" x1="96.53" y1="188.95" x2="98.83" y2="183.79"/><line class="cls-2" x1="90" y1="185.62" x2="92.82" y2="180.73"/><line class="cls-2" x1="83.86" y1="181.63" x2="87.17" y2="177.06"/><line class="cls-2" x1="78.16" y1="177.02" x2="81.94" y2="172.82"/><line class="cls-2" x1="72.98" y1="171.84" x2="77.18" y2="168.06"/><line class="cls-2" x1="68.37" y1="166.14" x2="72.94" y2="162.83"/><line class="cls-2" x1="64.38" y1="160" x2="69.27" y2="157.18"/><line class="cls-2" x1="61.05" y1="153.47" x2="66.21" y2="151.17"/><line class="cls-2" x1="58.43" y1="146.63" x2="63.8" y2="144.89"/><line class="cls-2" x1="56.53" y1="139.55" x2="62.05" y2="138.38"/><line class="cls-2" x1="55.38" y1="132.32" x2="61" y2="131.73"/><line class="cls-2" x1="55" y1="125" x2="60.65" y2="125"/><line class="cls-2" x1="55.38" y1="117.68" x2="61" y2="118.27"/><line class="cls-2" x1="56.53" y1="110.45" x2="62.05" y2="111.62"/><line class="cls-2" x1="58.43" y1="103.37" x2="63.8" y2="105.11"/><line class="cls-2" x1="61.05" y1="96.53" x2="66.21" y2="98.83"/><line class="cls-2" x1="64.38" y1="90" x2="69.27" y2="92.82"/><line class="cls-2" x1="68.37" y1="83.86" x2="72.94" y2="87.17"/><line class="cls-2" x1="72.98" y1="78.16" x2="77.18" y2="81.94"/><line class="cls-2" x1="78.16" y1="72.98" x2="81.94" y2="77.18"/><line class="cls-2" x1="83.86" y1="68.37" x2="87.17" y2="72.94"/><line class="cls-2" x1="90" y1="64.38" x2="92.82" y2="69.27"/><line class="cls-2" x1="96.53" y1="61.05" x2="98.83" y2="66.21"/><line class="cls-2" x1="103.37" y1="58.43" x2="105.11" y2="63.8"/><line class="cls-2" x1="110.45" y1="56.53" x2="111.62" y2="62.05"/><line class="cls-2" x1="117.68" y1="55.38" x2="118.27" y2="61"/><line class="cls-2" x1="125" y1="50.64" x2="125" y2="60.65"/><line class="cls-2" x1="162.18" y1="60.6" x2="157.18" y2="69.27"/><line class="cls-2" x1="189.4" y1="87.82" x2="180.73" y2="92.82"/><line class="cls-2" x1="199.36" y1="125" x2="189.35" y2="125"/><line class="cls-2" x1="189.4" y1="162.18" x2="180.73" y2="157.18"/><line class="cls-2" x1="162.18" y1="189.4" x2="157.18" y2="180.73"/><line class="cls-2" x1="125" y1="199.36" x2="125" y2="189.35"/><line class="cls-2" x1="87.82" y1="189.4" x2="92.82" y2="180.73"/><line class="cls-2" x1="60.6" y1="162.18" x2="69.27" y2="157.18"/><line class="cls-2" x1="50.64" y1="125" x2="60.65" y2="125"/><line class="cls-2" x1="60.6" y1="87.82" x2="69.27" y2="92.82"/><line class="cls-2" x1="87.82" y1="60.6" x2="92.82" y2="69.27"/><line class="cls-3" x1="138.01" y1="1.25" x2="136.96" y2="11.23"/><line class="cls-3" x1="150.87" y1="3.29" x2="148.78" y2="13.11"/><line class="cls-3" x1="163.45" y1="6.66" x2="160.35" y2="16.21"/><line class="cls-3" x1="175.61" y1="11.33" x2="171.53" y2="20.5"/><line class="cls-3" x1="198.14" y1="24.33" x2="192.24" y2="32.45"/><line class="cls-3" x1="208.26" y1="32.53" x2="201.54" y2="39.99"/><line class="cls-3" x1="217.47" y1="41.74" x2="210.01" y2="48.46"/><line class="cls-3" x1="225.67" y1="51.86" x2="217.55" y2="57.76"/><line class="cls-3" x1="238.67" y1="74.39" x2="229.5" y2="78.47"/><line class="cls-3" x1="243.34" y1="86.55" x2="233.79" y2="89.65"/><line class="cls-3" x1="246.71" y1="99.13" x2="236.89" y2="101.22"/><line class="cls-3" x1="248.75" y1="111.99" x2="238.77" y2="113.04"/><line class="cls-3" x1="248.75" y1="138.01" x2="238.77" y2="136.96"/><line class="cls-3" x1="246.71" y1="150.87" x2="236.89" y2="148.78"/><line class="cls-3" x1="243.34" y1="163.45" x2="233.79" y2="160.35"/><line class="cls-3" x1="238.67" y1="175.61" x2="229.5" y2="171.53"/><line class="cls-3" x1="225.67" y1="198.14" x2="217.55" y2="192.24"/><line class="cls-3" x1="217.47" y1="208.26" x2="210.01" y2="201.54"/><line class="cls-3" x1="208.26" y1="217.47" x2="201.54" y2="210.01"/><line class="cls-3" x1="198.14" y1="225.67" x2="192.24" y2="217.55"/><line class="cls-3" x1="175.61" y1="238.67" x2="171.53" y2="229.5"/><line class="cls-3" x1="163.45" y1="243.34" x2="160.35" y2="233.79"/><line class="cls-3" x1="150.87" y1="246.71" x2="148.78" y2="236.89"/><line class="cls-3" x1="138.01" y1="248.75" x2="136.96" y2="238.77"/><line class="cls-3" x1="111.99" y1="248.75" x2="113.04" y2="238.77"/><line class="cls-3" x1="99.13" y1="246.71" x2="101.22" y2="236.89"/><line class="cls-3" x1="86.55" y1="243.34" x2="89.65" y2="233.79"/><line class="cls-3" x1="74.39" y1="238.67" x2="78.47" y2="229.5"/><line class="cls-3" x1="51.86" y1="225.67" x2="57.76" y2="217.55"/><line class="cls-3" x1="41.74" y1="217.47" x2="48.46" y2="210.01"/><line class="cls-3" x1="32.53" y1="208.26" x2="39.99" y2="201.54"/><line class="cls-3" x1="24.33" y1="198.14" x2="32.45" y2="192.24"/><line class="cls-3" x1="11.33" y1="175.61" x2="20.5" y2="171.53"/><line class="cls-3" x1="6.66" y1="163.45" x2="16.21" y2="160.35"/><line class="cls-3" x1="3.29" y1="150.87" x2="13.11" y2="148.78"/><line class="cls-3" x1="1.25" y1="138.01" x2="11.23" y2="136.96"/><line class="cls-3" x1="1.25" y1="111.99" x2="11.23" y2="113.04"/><line class="cls-3" x1="3.29" y1="99.13" x2="13.11" y2="101.22"/><line class="cls-3" x1="6.66" y1="86.55" x2="16.21" y2="89.65"/><line class="cls-3" x1="11.33" y1="74.39" x2="20.5" y2="78.47"/><line class="cls-3" x1="24.33" y1="51.86" x2="32.45" y2="57.76"/><line class="cls-3" x1="32.53" y1="41.74" x2="39.99" y2="48.46"/><line class="cls-3" x1="41.74" y1="32.53" x2="48.46" y2="39.99"/><line class="cls-3" x1="51.86" y1="24.33" x2="57.76" y2="32.45"/><line class="cls-3" x1="74.39" y1="11.33" x2="78.47" y2="20.5"/><line class="cls-3" x1="86.55" y1="6.66" x2="89.65" y2="16.21"/><line class="cls-3" x1="99.13" y1="3.29" x2="101.22" y2="13.11"/><line class="cls-3" x1="111.99" y1="1.25" x2="113.04" y2="11.23"/><path class="cls-4" d="M119.08,21.34V20.86q1.3-.71,1.86-1.05a6.4,6.4,0,0,0,.65-0.47l0.19,0.19q-0.15,2.61-.15,6.5,0,3.36,0,3.81a4.28,4.28,0,0,0,.11,1.13,0.39,0.39,0,0,0,.13.18l1,0.37V32q-1.36-.1-2-0.1t-1.68.1V31.52a8.28,8.28,0,0,0,1.05-.4,0.43,0.43,0,0,0,.14-0.18,6,6,0,0,0,.09-1.13q0-.33,0-3.52V21.94a1.57,1.57,0,0,0-.1-0.66,0.32,0.32,0,0,0-.3-0.16A5.51,5.51,0,0,0,119.08,21.34Z"/><path class="cls-4" d="M125.73,22.72h-0.47q0.2-1.05.32-2.17a4.2,4.2,0,0,1,1.1-.92,2.33,2.33,0,0,1,1.13-.3,2.14,2.14,0,0,1,1.27.43A2.6,2.6,0,0,1,130,21a4.63,4.63,0,0,1,.3,1.71,5.43,5.43,0,0,1-.23,1.6,9.12,9.12,0,0,1-1,2.11q-0.68,1.13-2.85,4.25H129a1.36,1.36,0,0,0,.79-0.16,2.32,2.32,0,0,0,.46-0.88l0.08-.19h0.5Q130.28,31.1,130,32q-1.32-.1-2.39-0.1t-2.91.1V31.5q2-2.79,2.85-4.16a11.48,11.48,0,0,0,1.21-2.53,5.33,5.33,0,0,0,.25-1.65,3.08,3.08,0,0,0-.42-1.77,1.28,1.28,0,0,0-1.09-.61,1.43,1.43,0,0,0-1.08.52A4.21,4.21,0,0,0,125.73,22.72Z"/><path class="cls-4" d="M222,120.89h-0.44q0.21-1,.35-1.94a3.54,3.54,0,0,1,1-.67,2.38,2.38,0,0,1,1-.22,1.94,1.94,0,0,1,1.11.33,2,2,0,0,1,.73.93,3.24,3.24,0,0,1,.25,1.24,3.73,3.73,0,0,1-1.29,2.78,2.25,2.25,0,0,1,1.11.81,3.87,3.87,0,0,1,.63,2.31,5.05,5.05,0,0,1-1,3.2,3.19,3.19,0,0,1-2.61,1.34,3.65,3.65,0,0,1-1.72-.45q0.19-1.12.28-2.29h0.4a3.15,3.15,0,0,0,.63,1.43,1.29,1.29,0,0,0,1,.41,1.67,1.67,0,0,0,1.4-.77,3.47,3.47,0,0,0,.56-2.07,3.29,3.29,0,0,0-.59-2.06,2,2,0,0,0-1.68-.77h-0.22v-0.62a3.26,3.26,0,0,0,1.61-1.15,2.72,2.72,0,0,0,.46-1.52,2,2,0,0,0-.36-1.26,1.17,1.17,0,0,0-1-.47,1.28,1.28,0,0,0-.94.4A3.06,3.06,0,0,0,222,120.89Z"/><path class="cls-4" d="M127.19,216.79l0.15,0.69a8.47,8.47,0,0,0-2,1.49,6.72,6.72,0,0,0-1.25,1.84,9.3,9.3,0,0,0-.59,1.92,11,11,0,0,1,1.3-1.2,1.26,1.26,0,0,1,.69-0.19,2.09,2.09,0,0,1,1.79,1,4.8,4.8,0,0,1,.7,2.75,6.51,6.51,0,0,1-.8,3.2,2.42,2.42,0,0,1-4.34.19A6.2,6.2,0,0,1,122,225a9.92,9.92,0,0,1,1.13-4.71A7.59,7.59,0,0,1,127.19,216.79ZM126.84,226a7,7,0,0,0-.2-1.46,3.24,3.24,0,0,0-.63-1.4,1.29,1.29,0,0,0-1-.52,1.35,1.35,0,0,0-.88.35,2,2,0,0,0-.6.86,4,4,0,0,0-.18,1.32,6,6,0,0,0,.67,3,1.46,1.46,0,0,0,1.21.88,1.31,1.31,0,0,0,1.21-.92A5.25,5.25,0,0,0,126.84,226Z"/><path class="cls-4" d="M24.23,131.07l-0.12-.71A6.63,6.63,0,0,0,26,129a7.33,7.33,0,0,0,1.35-2,10.86,10.86,0,0,0,.69-2.22,6.66,6.66,0,0,1-1.21,1.3,1.6,1.6,0,0,1-.92.34,2.05,2.05,0,0,1-1.69-1,4.81,4.81,0,0,1-.72-2.79,5.49,5.49,0,0,1,.9-3.26,2.63,2.63,0,0,1,2.19-1.26,2.41,2.41,0,0,1,2.06,1.15,5.9,5.9,0,0,1,.79,3.36,11.58,11.58,0,0,1-.59,3.6,7.8,7.8,0,0,1-1.68,3A8.42,8.42,0,0,1,24.23,131.07Zm0.4-9.45a5.06,5.06,0,0,0,.54,2.62,1.54,1.54,0,0,0,1.3.87,1.43,1.43,0,0,0,1.17-.73,3.35,3.35,0,0,0,.51-1.94,5.74,5.74,0,0,0-.54-2.58,1.59,1.59,0,0,0-1.37-1.08,1.36,1.36,0,0,0-1.13.74A3.87,3.87,0,0,0,24.63,121.62Z"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-008.svg b/MagicMirror/modules/default/clock/faces/face-008.svg new file mode 100644 index 0000000000000000000000000000000000000000..6fadb3966ce022b25f5b8ba7cf7d896779d6f306 --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-008.svg @@ -0,0 +1 @@ +<svg id="Minute_Markers_-_No_Hours" data-name="Minute Markers - No Hours" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;}.cls-1{stroke-width:0.5px;}.cls-3{fill:#fff;}</style></defs><title>face-008</title><line class="cls-1" x1="138.01" y1="1.25" x2="136.96" y2="11.23"/><line class="cls-1" x1="150.87" y1="3.29" x2="148.78" y2="13.11"/><line class="cls-1" x1="163.45" y1="6.66" x2="160.35" y2="16.21"/><line class="cls-1" x1="175.61" y1="11.33" x2="171.53" y2="20.5"/><line class="cls-1" x1="198.14" y1="24.33" x2="192.24" y2="32.45"/><line class="cls-1" x1="208.26" y1="32.53" x2="201.54" y2="39.99"/><line class="cls-1" x1="217.47" y1="41.74" x2="210.01" y2="48.46"/><line class="cls-1" x1="225.67" y1="51.86" x2="217.55" y2="57.76"/><line class="cls-1" x1="238.67" y1="74.39" x2="229.5" y2="78.47"/><line class="cls-1" x1="243.34" y1="86.55" x2="233.79" y2="89.65"/><line class="cls-1" x1="246.71" y1="99.13" x2="236.89" y2="101.22"/><line class="cls-1" x1="248.75" y1="111.99" x2="238.77" y2="113.04"/><line class="cls-1" x1="248.75" y1="138.01" x2="238.77" y2="136.96"/><line class="cls-1" x1="246.71" y1="150.87" x2="236.89" y2="148.78"/><line class="cls-1" x1="243.34" y1="163.45" x2="233.79" y2="160.35"/><line class="cls-1" x1="238.67" y1="175.61" x2="229.5" y2="171.53"/><line class="cls-1" x1="225.67" y1="198.14" x2="217.55" y2="192.24"/><line class="cls-1" x1="217.47" y1="208.26" x2="210.01" y2="201.54"/><line class="cls-1" x1="208.26" y1="217.47" x2="201.54" y2="210.01"/><line class="cls-1" x1="198.14" y1="225.67" x2="192.24" y2="217.55"/><line class="cls-1" x1="175.61" y1="238.67" x2="171.53" y2="229.5"/><line class="cls-1" x1="163.45" y1="243.34" x2="160.35" y2="233.79"/><line class="cls-1" x1="150.87" y1="246.71" x2="148.78" y2="236.89"/><line class="cls-1" x1="138.01" y1="248.75" x2="136.96" y2="238.77"/><line class="cls-1" x1="111.99" y1="248.75" x2="113.04" y2="238.77"/><line class="cls-1" x1="99.13" y1="246.71" x2="101.22" y2="236.89"/><line class="cls-1" x1="86.55" y1="243.34" x2="89.65" y2="233.79"/><line class="cls-1" x1="74.39" y1="238.67" x2="78.47" y2="229.5"/><line class="cls-1" x1="51.86" y1="225.67" x2="57.76" y2="217.55"/><line class="cls-1" x1="41.74" y1="217.47" x2="48.46" y2="210.01"/><line class="cls-1" x1="32.53" y1="208.26" x2="39.99" y2="201.54"/><line class="cls-1" x1="24.33" y1="198.14" x2="32.45" y2="192.24"/><line class="cls-1" x1="11.33" y1="175.61" x2="20.5" y2="171.53"/><line class="cls-1" x1="6.66" y1="163.45" x2="16.21" y2="160.35"/><line class="cls-1" x1="3.29" y1="150.87" x2="13.11" y2="148.78"/><line class="cls-1" x1="1.25" y1="138.01" x2="11.23" y2="136.96"/><line class="cls-1" x1="1.25" y1="111.99" x2="11.23" y2="113.04"/><line class="cls-1" x1="3.29" y1="99.13" x2="13.11" y2="101.22"/><line class="cls-1" x1="6.66" y1="86.55" x2="16.21" y2="89.65"/><line class="cls-1" x1="11.33" y1="74.39" x2="20.5" y2="78.47"/><line class="cls-1" x1="24.33" y1="51.86" x2="32.45" y2="57.76"/><line class="cls-1" x1="32.53" y1="41.74" x2="39.99" y2="48.46"/><line class="cls-1" x1="41.74" y1="32.53" x2="48.46" y2="39.99"/><line class="cls-1" x1="51.86" y1="24.33" x2="57.76" y2="32.45"/><line class="cls-1" x1="74.39" y1="11.33" x2="78.47" y2="20.5"/><line class="cls-1" x1="86.55" y1="6.66" x2="89.65" y2="16.21"/><line class="cls-1" x1="99.13" y1="3.29" x2="101.22" y2="13.11"/><line class="cls-1" x1="111.99" y1="1.25" x2="113.04" y2="11.23"/><line class="cls-2" x1="125" y1="1.25" x2="125" y2="16.23"/><line class="cls-2" x1="186.87" y1="17.83" x2="179.39" y2="30.8"/><line class="cls-2" x1="232.17" y1="63.12" x2="219.2" y2="70.61"/><line class="cls-2" x1="248.75" y1="125" x2="233.77" y2="125"/><line class="cls-2" x1="232.17" y1="186.87" x2="219.2" y2="179.39"/><line class="cls-2" x1="186.88" y1="232.17" x2="179.39" y2="219.2"/><line class="cls-2" x1="125" y1="248.75" x2="125" y2="233.77"/><line class="cls-2" x1="63.13" y1="232.17" x2="70.61" y2="219.2"/><line class="cls-2" x1="17.83" y1="186.88" x2="30.8" y2="179.39"/><line class="cls-2" x1="1.25" y1="125" x2="16.23" y2="125"/><line class="cls-2" x1="17.83" y1="63.13" x2="30.8" y2="70.61"/><line class="cls-2" x1="63.12" y1="17.83" x2="70.61" y2="30.8"/><path class="cls-3" d="M119.08,21.34V20.86q1.3-.71,1.86-1.05a6.4,6.4,0,0,0,.65-0.47l0.19,0.19q-0.15,2.61-.15,6.5,0,3.36,0,3.81a4.28,4.28,0,0,0,.11,1.13,0.39,0.39,0,0,0,.13.18l1,0.37V32q-1.36-.1-2-0.1t-1.68.1V31.52a8.28,8.28,0,0,0,1.05-.4,0.43,0.43,0,0,0,.14-0.18,6,6,0,0,0,.09-1.13q0-.33,0-3.52V21.94a1.57,1.57,0,0,0-.1-0.66,0.32,0.32,0,0,0-.3-0.16A5.51,5.51,0,0,0,119.08,21.34Z"/><path class="cls-3" d="M125.73,22.72h-0.47q0.2-1.05.32-2.17a4.2,4.2,0,0,1,1.1-.92,2.33,2.33,0,0,1,1.13-.3,2.14,2.14,0,0,1,1.27.43A2.6,2.6,0,0,1,130,21a4.63,4.63,0,0,1,.3,1.71,5.43,5.43,0,0,1-.23,1.6,9.12,9.12,0,0,1-1,2.11q-0.68,1.13-2.85,4.25H129a1.36,1.36,0,0,0,.79-0.16,2.32,2.32,0,0,0,.46-0.88l0.08-.19h0.5Q130.28,31.1,130,32q-1.32-.1-2.39-0.1t-2.91.1V31.5q2-2.79,2.85-4.16a11.48,11.48,0,0,0,1.21-2.53,5.33,5.33,0,0,0,.25-1.65,3.08,3.08,0,0,0-.42-1.77,1.28,1.28,0,0,0-1.09-.61,1.43,1.43,0,0,0-1.08.52A4.21,4.21,0,0,0,125.73,22.72Z"/><path class="cls-3" d="M222,120.89h-0.44q0.21-1,.35-1.94a3.54,3.54,0,0,1,1-.67,2.38,2.38,0,0,1,1-.22,1.94,1.94,0,0,1,1.11.33,2,2,0,0,1,.73.93,3.24,3.24,0,0,1,.25,1.24,3.73,3.73,0,0,1-1.29,2.78,2.25,2.25,0,0,1,1.11.81,3.87,3.87,0,0,1,.63,2.31,5.05,5.05,0,0,1-1,3.2,3.19,3.19,0,0,1-2.61,1.34,3.65,3.65,0,0,1-1.72-.45q0.19-1.12.28-2.29h0.4a3.15,3.15,0,0,0,.63,1.43,1.29,1.29,0,0,0,1,.41,1.67,1.67,0,0,0,1.4-.77,3.47,3.47,0,0,0,.56-2.07,3.29,3.29,0,0,0-.59-2.06,2,2,0,0,0-1.68-.77h-0.22v-0.62a3.26,3.26,0,0,0,1.61-1.15,2.72,2.72,0,0,0,.46-1.52,2,2,0,0,0-.36-1.26,1.17,1.17,0,0,0-1-.47,1.28,1.28,0,0,0-.94.4A3.06,3.06,0,0,0,222,120.89Z"/><path class="cls-3" d="M127.19,216.79l0.15,0.69a8.47,8.47,0,0,0-2,1.49,6.72,6.72,0,0,0-1.25,1.84,9.3,9.3,0,0,0-.59,1.92,11,11,0,0,1,1.3-1.2,1.26,1.26,0,0,1,.69-0.19,2.09,2.09,0,0,1,1.79,1,4.8,4.8,0,0,1,.7,2.75,6.51,6.51,0,0,1-.8,3.2,2.42,2.42,0,0,1-4.34.19A6.2,6.2,0,0,1,122,225a9.92,9.92,0,0,1,1.13-4.71A7.59,7.59,0,0,1,127.19,216.79ZM126.84,226a7,7,0,0,0-.2-1.46,3.24,3.24,0,0,0-.63-1.4,1.29,1.29,0,0,0-1-.52,1.35,1.35,0,0,0-.88.35,2,2,0,0,0-.6.86,4,4,0,0,0-.18,1.32,6,6,0,0,0,.67,3,1.46,1.46,0,0,0,1.21.88,1.31,1.31,0,0,0,1.21-.92A5.25,5.25,0,0,0,126.84,226Z"/><path class="cls-3" d="M24.23,131.07l-0.12-.71A6.63,6.63,0,0,0,26,129a7.33,7.33,0,0,0,1.35-2,10.86,10.86,0,0,0,.69-2.22,6.66,6.66,0,0,1-1.21,1.3,1.6,1.6,0,0,1-.92.34,2.05,2.05,0,0,1-1.69-1,4.81,4.81,0,0,1-.72-2.79,5.49,5.49,0,0,1,.9-3.26,2.63,2.63,0,0,1,2.19-1.26,2.41,2.41,0,0,1,2.06,1.15,5.9,5.9,0,0,1,.79,3.36,11.58,11.58,0,0,1-.59,3.6,7.8,7.8,0,0,1-1.68,3A8.42,8.42,0,0,1,24.23,131.07Zm0.4-9.45a5.06,5.06,0,0,0,.54,2.62,1.54,1.54,0,0,0,1.3.87,1.43,1.43,0,0,0,1.17-.73,3.35,3.35,0,0,0,.51-1.94,5.74,5.74,0,0,0-.54-2.58,1.59,1.59,0,0,0-1.37-1.08,1.36,1.36,0,0,0-1.13.74A3.87,3.87,0,0,0,24.63,121.62Z"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-009.svg b/MagicMirror/modules/default/clock/faces/face-009.svg new file mode 100644 index 0000000000000000000000000000000000000000..bd207e090025c402a799fddd0899ab68e31b0f73 --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-009.svg @@ -0,0 +1 @@ +<svg id="Roman_Outer_Ring" data-name="Roman Outer Ring" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-3{fill:none;stroke:#fff;stroke-miterlimit:10;}.cls-2{fill:#fff;}.cls-3{stroke-linecap:round;stroke-width:0.47px;}</style></defs><title>face-009</title><circle class="cls-1" cx="125" cy="125" r="90"/><path class="cls-2" d="M125,1.25A123.75,123.75,0,0,1,212.5,212.5a123.75,123.75,0,0,1-175-175A122.94,122.94,0,0,1,125,1.25m0-1A124.75,124.75,0,1,0,249.75,125,124.75,124.75,0,0,0,125,.25h0Z"/><path class="cls-2" d="M107.26,7.42v-1q1.9,0.18,3.5.18t4.06-.18v1a5.71,5.71,0,0,0-1.78.72,0.45,0.45,0,0,0-.16.34,3.84,3.84,0,0,0,.4,1.14q1.7,3.58,2.9,5.7,2-3.4,3.51-6.35A2.29,2.29,0,0,0,120,8.28,0.42,0.42,0,0,0,119.81,8a7,7,0,0,0-1.59-.55v-1q2,0.18,3.21.18a28.13,28.13,0,0,0,3-.18v1a5.83,5.83,0,0,0-2.08.85,11,11,0,0,0-1.69,2.36q-2.17,3.74-3.56,6.4,1,2.22,2.69,5.42l2.48,4.75a2.21,2.21,0,0,0,.49.52,8,8,0,0,0,1.91.85v1q-2.16-.18-3.46-0.18t-3.8.18v-1a10.33,10.33,0,0,0,1.73-.7,0.5,0.5,0,0,0,.13-0.37,3.07,3.07,0,0,0-.34-1.12q-1.75-3.7-3.34-6.62-1.47,2.39-3.65,6.58a3.81,3.81,0,0,0-.45,1.14,0.65,0.65,0,0,0,.18.46,3.84,3.84,0,0,0,1.28.52l0.35,0.11v1q-1.78-.18-3.42-0.18a19.92,19.92,0,0,0-2.78.18v-1a5,5,0,0,0,1.9-.85,17.32,17.32,0,0,0,2-2.94q2.58-4.5,3.73-6.74-1.14-2.41-3.64-7.21a11.2,11.2,0,0,0-1.49-2.33A8.22,8.22,0,0,0,107.26,7.42Z"/><path class="cls-2" d="M133.79,6.46v1l-0.63.18a4.13,4.13,0,0,0-1.88.71A2.78,2.78,0,0,0,131,9.79v0.57L131,16.9l0,9.5a2.65,2.65,0,0,0,.25,1.45,3.37,3.37,0,0,0,1.71.54q0.28,0,.82.2v1q-2-.18-3.78-0.18t-4.12.18v-1a3.41,3.41,0,0,1,.54-0.16,4.56,4.56,0,0,0,2-.69,3.1,3.1,0,0,0,.25-1.6l0-.81,0-6q0-9.2,0-10.18a1.39,1.39,0,0,0-.26-0.83,4.61,4.61,0,0,0-1.84-.7l-0.64-.21v-1q2.65,0.18,4.14.18Q131.68,6.64,133.79,6.46Z"/><path class="cls-2" d="M142.92,6.46v1l-0.63.18a4.13,4.13,0,0,0-1.88.71,2.78,2.78,0,0,0-.26,1.49v0.57l-0.06,6.54,0,9.5a2.65,2.65,0,0,0,.25,1.45,3.37,3.37,0,0,0,1.71.54q0.28,0,.82.2v1q-2-.18-3.78-0.18t-4.12.18v-1a3.46,3.46,0,0,1,.54-0.16,4.56,4.56,0,0,0,2-.69,3.1,3.1,0,0,0,.25-1.6l0-.81,0-6q0-9.2,0-10.18a1.39,1.39,0,0,0-.26-0.83,4.61,4.61,0,0,0-1.84-.7L135,7.42v-1q2.65,0.18,4.14.18Q140.8,6.64,142.92,6.46Z"/><path class="cls-2" d="M187.77,24.35l-0.48.83L186.66,25a4.13,4.13,0,0,0-2-.33,2.77,2.77,0,0,0-1,1.16l-0.28.49L180.1,32l-4.72,8.24a2.66,2.66,0,0,0-.51,1.38,3.37,3.37,0,0,0,1.22,1.32q0.22,0.18.61,0.58l-0.48.83q-1.61-1.14-3.19-2t-3.66-1.91l0.48-.83a3.53,3.53,0,0,1,.55.13,4.57,4.57,0,0,0,2.06.39,3.11,3.11,0,0,0,1-1.26l0.38-.72,3-5.15q4.6-8,5.06-8.83a1.39,1.39,0,0,0,.19-0.85,4.6,4.6,0,0,0-1.24-1.53l-0.45-.5,0.48-.83q2.21,1.48,3.5,2.23T187.77,24.35Z"/><path class="cls-2" d="M227.38,65.29l-0.83.48-0.47-.46A4.13,4.13,0,0,0,224.53,64a2.78,2.78,0,0,0-1.42.52l-0.49.28-5.69,3.22-8.21,4.78a2.65,2.65,0,0,0-1.13.94,3.35,3.35,0,0,0,.39,1.75q0.1,0.27.24,0.81l-0.83.48q-0.83-1.79-1.74-3.36T203.45,70l0.83-.48a3.62,3.62,0,0,1,.41.38,4.57,4.57,0,0,0,1.59,1.37,3.1,3.1,0,0,0,1.51-.58l0.69-.43,5.19-3q8-4.6,8.8-5.12a1.4,1.4,0,0,0,.59-0.64,4.6,4.6,0,0,0-.31-1.94q-0.05-.22-0.14-0.66l0.83-.48q1.17,2.39,1.92,3.68T227.38,65.29Z"/><path class="cls-2" d="M231.95,73.19l-0.83.48-0.47-.46a4.12,4.12,0,0,0-1.55-1.27,2.77,2.77,0,0,0-1.42.52l-0.49.28L221.49,76l-8.21,4.78a2.65,2.65,0,0,0-1.13.94,3.37,3.37,0,0,0,.39,1.75q0.1,0.27.24,0.81l-0.83.48q-0.83-1.79-1.74-3.36T208,77.87l0.83-.48a3.6,3.6,0,0,1,.41.38,4.57,4.57,0,0,0,1.59,1.37,3.12,3.12,0,0,0,1.51-.58l0.69-.43,5.19-3q8-4.6,8.8-5.12a1.39,1.39,0,0,0,.59-0.64,4.6,4.6,0,0,0-.31-1.94c0-.15-0.08-0.37-0.14-0.66l0.83-.48q1.17,2.39,1.92,3.68T231.95,73.19Z"/><path class="cls-2" d="M243.54,119.87h-1q-0.13-.43-0.18-0.63a4.14,4.14,0,0,0-.71-1.88,2.78,2.78,0,0,0-1.49-.26h-0.57L233.1,117l-9.5,0a2.64,2.64,0,0,0-1.45.25,3.36,3.36,0,0,0-.54,1.71q0,0.28-.2.82h-1q0.18-2,.18-3.78T220.47,112h1a3.55,3.55,0,0,1,.16.54,4.57,4.57,0,0,0,.69,2,3.11,3.11,0,0,0,1.6.25l0.81,0,6,0q9.2,0,10.18,0a1.39,1.39,0,0,0,.83-0.26,4.58,4.58,0,0,0,.7-1.84q0.07-.22.21-0.64h1q-0.18,2.65-.18,4.14Q243.36,117.75,243.54,119.87Z"/><path class="cls-2" d="M243.54,129h-1c-0.09-.28-0.15-0.5-0.18-0.63a4.13,4.13,0,0,0-.71-1.88,2.77,2.77,0,0,0-1.49-.26h-0.57l-6.54-.06-9.5,0a2.65,2.65,0,0,0-1.45.25,3.36,3.36,0,0,0-.54,1.71q0,0.28-.2.82h-1q0.18-2,.18-3.78t-0.18-4.12h1a3.55,3.55,0,0,1,.16.54,4.57,4.57,0,0,0,.69,2,3.11,3.11,0,0,0,1.6.25l0.81,0,6,0q9.2,0,10.18,0a1.39,1.39,0,0,0,.83-0.26,4.58,4.58,0,0,0,.7-1.84q0.07-.22.21-0.64h1q-0.18,2.65-.18,4.14Q243.36,126.88,243.54,129Z"/><path class="cls-2" d="M243.54,138.11h-1c-0.09-.28-0.15-0.5-0.18-0.63a4.13,4.13,0,0,0-.71-1.88,2.77,2.77,0,0,0-1.49-.26h-0.57l-6.54-.06-9.5,0a2.65,2.65,0,0,0-1.45.25,3.36,3.36,0,0,0-.54,1.71q0,0.28-.2.82h-1q0.18-2,.18-3.78t-0.18-4.12h1a3.59,3.59,0,0,1,.16.54,4.57,4.57,0,0,0,.69,2,3.11,3.11,0,0,0,1.6.25l0.81,0,6,0q9.2,0,10.18,0a1.39,1.39,0,0,0,.83-0.26,4.59,4.59,0,0,0,.7-1.84q0.07-.22.21-0.64h1q-0.18,2.65-.18,4.14Q243.36,136,243.54,138.11Z"/><path class="cls-2" d="M230.08,180.18l-0.83-.48c0.07-.29.12-0.5,0.16-0.64a4.13,4.13,0,0,0,.33-2,2.78,2.78,0,0,0-1.16-1l-0.49-.29-5.63-3.32-8.24-4.72a2.63,2.63,0,0,0-1.38-.51,3.36,3.36,0,0,0-1.32,1.22q-0.18.22-.58,0.61l-0.83-.48q1.14-1.61,2-3.18t1.91-3.66l0.83,0.48a3.52,3.52,0,0,1-.13.55,4.57,4.57,0,0,0-.39,2.06,3.12,3.12,0,0,0,1.26,1l0.72,0.39,5.15,3q8,4.6,8.83,5.06a1.39,1.39,0,0,0,.85.19,4.6,4.6,0,0,0,1.52-1.24l0.51-.45,0.83,0.48q-1.48,2.21-2.22,3.5T230.08,180.18Z"/><path class="cls-2" d="M204.74,176.94l0.4-.7q6.16,1.72,14,3.64,6.56,1.59,7.27,1.69a1.9,1.9,0,0,0,.8,0,8.11,8.11,0,0,0,1.62-1.16l0.83,0.48q-1.47,2.19-1.89,2.91-0.59,1-1.58,3.09l-0.83-.48q0-.23.06-0.34a4.18,4.18,0,0,0,.16-1.53,0.79,0.79,0,0,0-.37-0.46,1.91,1.91,0,0,0-.33-0.15q-1.14-.4-7-1.94l-8.52-2.25q3.73,3.89,7,7.06,4.07,4,4.54,4.37l0.18,0.13a0.7,0.7,0,0,0,.53.09,6.23,6.23,0,0,0,1.59-1.13l0.79,0.46-0.48.74q-1.05,1.56-1.32,2a20.74,20.74,0,0,0-1.08,2.23l-0.79-.46a4.94,4.94,0,0,0,.14-1.78,3.89,3.89,0,0,0-.91-1.34q-1.57-1.77-9.59-9.86Z"/><path class="cls-2" d="M172,207.58l0.7-.4q4.47,4.57,10.33,10.16,4.88,4.65,5.45,5.1a1.89,1.89,0,0,0,.72.36,8.12,8.12,0,0,0,2-.2l0.48,0.83q-2.37,1.16-3.09,1.58-1,.59-2.91,1.89l-0.48-.83,0.23-.26a4.17,4.17,0,0,0,.91-1.25,0.79,0.79,0,0,0-.09-0.58,2,2,0,0,0-.21-0.29q-0.78-.92-5.1-5.19l-6.26-6.21q1.28,5.23,2.52,9.61,1.53,5.48,1.75,6.05a1.28,1.28,0,0,0,.09.21,0.71,0.71,0,0,0,.41.34,6.21,6.21,0,0,0,1.95-.19l0.46,0.79-0.78.4q-1.69.83-2.16,1.1a20.5,20.5,0,0,0-2.05,1.39l-0.46-.79a5,5,0,0,0,1-1.47,3.89,3.89,0,0,0-.12-1.61q-0.48-2.32-3.38-13.34Z"/><path class="cls-2" d="M129,220h0.8q1.58,6.2,3.87,14,1.9,6.47,2.17,7.14a1.89,1.89,0,0,0,.44.67,8.12,8.12,0,0,0,1.82.82v1q-2.64-.18-3.46-0.18-1.18,0-3.46.18v-1l0.33-.11a4.19,4.19,0,0,0,1.41-.63,0.79,0.79,0,0,0,.21-0.54,2,2,0,0,0,0-.36q-0.22-1.19-1.83-7l-2.31-8.51q-1.5,5.17-2.62,9.58-1.42,5.51-1.51,6.12a1.27,1.27,0,0,0,0,.23,0.71,0.71,0,0,0,.18.5,6.21,6.21,0,0,0,1.78.81v0.91l-0.88,0q-1.88-.13-2.41-0.13a20.51,20.51,0,0,0-2.48.18v-0.91a5,5,0,0,0,1.61-.77,3.9,3.9,0,0,0,.71-1.46q0.74-2.24,3.74-13.24Z"/><path class="cls-2" d="M112.29,243.54v-1l0.63-.18a4.14,4.14,0,0,0,1.88-.71,2.78,2.78,0,0,0,.26-1.49v-0.57l0.06-6.54,0-9.5a2.64,2.64,0,0,0-.25-1.45,3.36,3.36,0,0,0-1.71-.54q-0.28,0-.82-0.2v-1q2,0.18,3.78.18t4.12-.18v1a3.55,3.55,0,0,1-.54.16,4.57,4.57,0,0,0-2,.69,3.11,3.11,0,0,0-.25,1.6l0,0.81,0,6q0,9.2,0,10.18a1.39,1.39,0,0,0,.26.83,4.58,4.58,0,0,0,1.84.7l0.64,0.21v1q-2.65-.18-4.14-0.18Q114.41,243.36,112.29,243.54Z"/><path class="cls-2" d="M85,211.55l0.7,0.4Q84,218.11,82,226q-1.59,6.56-1.69,7.27a1.88,1.88,0,0,0,0,.8,8.06,8.06,0,0,0,1.16,1.62l-0.48.83q-2.19-1.47-2.91-1.89-1-.59-3.09-1.58l0.48-.83,0.34,0.07a4.22,4.22,0,0,0,1.53.16,0.79,0.79,0,0,0,.46-0.37,1.91,1.91,0,0,0,.15-0.33q0.4-1.14,1.94-7l2.25-8.53q-3.89,3.73-7.06,7-4,4.07-4.37,4.54a1.22,1.22,0,0,0-.13.18,0.71,0.71,0,0,0-.09.53A6.23,6.23,0,0,0,71.7,230l-0.46.79-0.74-.48q-1.56-1.05-2-1.32a20.6,20.6,0,0,0-2.24-1.08l0.46-.79a4.94,4.94,0,0,0,1.78.14,3.89,3.89,0,0,0,1.34-.91q1.77-1.57,9.86-9.59Z"/><path class="cls-2" d="M58.73,223.6l0.48-.83,0.64,0.16a4.13,4.13,0,0,0,2,.33,2.78,2.78,0,0,0,1-1.16l0.28-.49L66.4,216l4.72-8.24a2.66,2.66,0,0,0,.51-1.38A3.38,3.38,0,0,0,70.41,205q-0.22-.18-0.61-0.58l0.48-.83q1.61,1.14,3.18,2t3.66,1.91l-0.48.83a3.53,3.53,0,0,1-.55-0.13,4.57,4.57,0,0,0-2.06-.39,3.1,3.1,0,0,0-1,1.26l-0.39.72-3,5.15q-4.6,8-5.06,8.83a1.4,1.4,0,0,0-.19.85,4.62,4.62,0,0,0,1.24,1.53l0.45,0.5-0.48.83q-2.21-1.48-3.5-2.23T58.73,223.6Z"/><path class="cls-2" d="M50.83,219l0.48-.83,0.64,0.16a4.14,4.14,0,0,0,2,.33,2.78,2.78,0,0,0,1-1.16l0.28-.49,3.32-5.63,4.72-8.24a2.66,2.66,0,0,0,.51-1.38,3.36,3.36,0,0,0-1.22-1.32q-0.22-.18-0.61-0.58l0.48-.83q1.61,1.14,3.18,2T69.22,203l-0.48.83a3.48,3.48,0,0,1-.55-0.13,4.56,4.56,0,0,0-2.06-.39,3.1,3.1,0,0,0-1,1.26l-0.39.72-3,5.15q-4.6,8-5.06,8.83a1.39,1.39,0,0,0-.19.85,4.6,4.6,0,0,0,1.24,1.53l0.45,0.5-0.48.83q-2.21-1.48-3.5-2.23T50.83,219Z"/><path class="cls-2" d="M49.32,183.91l0.4,0.7q-4.57,4.47-10.16,10.33-4.65,4.88-5.1,5.45a1.89,1.89,0,0,0-.36.72,8.09,8.09,0,0,0,.2,2l-0.83.48q-1.16-2.37-1.58-3.09-0.59-1-1.89-2.91l0.83-.48,0.26,0.23a4.18,4.18,0,0,0,1.25.91,0.78,0.78,0,0,0,.58-0.09,1.88,1.88,0,0,0,.29-0.21q0.92-.78,5.19-5.1l6.21-6.26q-5.23,1.29-9.61,2.52-5.48,1.53-6.05,1.75l-0.21.09a0.71,0.71,0,0,0-.35.41,6.19,6.19,0,0,0,.19,1.95l-0.79.46-0.4-.79q-0.83-1.69-1.09-2.15a20.39,20.39,0,0,0-1.39-2.06l0.79-.46a5,5,0,0,0,1.47,1,3.9,3.9,0,0,0,1.61-.12q2.32-.48,13.33-3.37Z"/><path class="cls-2" d="M20.56,181.22l0.83-.48,0.47,0.46a4.13,4.13,0,0,0,1.55,1.27,2.77,2.77,0,0,0,1.42-.52l0.49-.29L31,178.45l8.21-4.78a2.64,2.64,0,0,0,1.13-.94A3.35,3.35,0,0,0,40,171c-0.06-.18-0.15-0.45-0.24-0.81l0.83-.48q0.83,1.79,1.74,3.36t2.22,3.48l-0.83.48a3.5,3.5,0,0,1-.41-0.38,4.58,4.58,0,0,0-1.59-1.37,3.09,3.09,0,0,0-1.51.58l-0.69.43-5.19,3q-8,4.6-8.8,5.12a1.4,1.4,0,0,0-.59.64,4.61,4.61,0,0,0,.31,1.94c0,0.15.08,0.37,0.14,0.66l-0.83.48q-1.17-2.39-1.92-3.67T20.56,181.22Z"/><path class="cls-2" d="M16,173.32l0.83-.48,0.47,0.46a4.11,4.11,0,0,0,1.55,1.27,2.76,2.76,0,0,0,1.42-.52l0.49-.29,5.69-3.21,8.21-4.78a2.66,2.66,0,0,0,1.13-.94,3.37,3.37,0,0,0-.39-1.75q-0.1-.27-0.24-0.81l0.83-.48q0.83,1.79,1.74,3.36t2.22,3.48l-0.83.48a3.51,3.51,0,0,1-.41-0.38,4.56,4.56,0,0,0-1.59-1.37,3.11,3.11,0,0,0-1.51.58l-0.69.43-5.19,3q-8,4.6-8.8,5.12a1.39,1.39,0,0,0-.59.64,4.6,4.6,0,0,0,.31,1.94c0,0.15.08,0.37,0.14,0.66l-0.83.48q-1.17-2.38-1.92-3.67T16,173.32Z"/><path class="cls-2" d="M11.44,165.42l0.83-.48,0.47,0.46a4.13,4.13,0,0,0,1.55,1.27,2.77,2.77,0,0,0,1.42-.52l0.49-.29,5.69-3.22,8.21-4.78a2.64,2.64,0,0,0,1.13-.94,3.35,3.35,0,0,0-.39-1.75c-0.06-.18-0.15-0.45-0.24-0.81l0.83-.48q0.83,1.79,1.74,3.36t2.22,3.48l-0.83.48a3.58,3.58,0,0,1-.41-0.38,4.58,4.58,0,0,0-1.59-1.37A3.1,3.1,0,0,0,31,160l-0.69.43-5.19,3q-8,4.6-8.8,5.12a1.4,1.4,0,0,0-.59.64,4.61,4.61,0,0,0,.31,1.94q0.05,0.22.14,0.66l-0.83.48q-1.17-2.39-1.92-3.67Q12.65,167.16,11.44,165.42Z"/><path class="cls-2" d="M6.46,130.38h1c0.09,0.28.15,0.5,0.18,0.63a4.13,4.13,0,0,0,.71,1.88,2.78,2.78,0,0,0,1.49.26h0.57l6.54,0.06,9.5,0a2.65,2.65,0,0,0,1.45-.25,3.37,3.37,0,0,0,.54-1.71q0-.28.2-0.82h1q-0.18,2-.18,3.78t0.18,4.12h-1a3.46,3.46,0,0,1-.16-0.54,4.56,4.56,0,0,0-.69-2,3.1,3.1,0,0,0-1.6-.25l-0.81,0-6,0q-9.2,0-10.18,0a1.39,1.39,0,0,0-.83.26,4.61,4.61,0,0,0-.7,1.84q-0.06.22-.21,0.64h-1q0.18-2.65.18-4.14Q6.64,132.49,6.46,130.38Z"/><path class="cls-2" d="M7.42,129.06h-1q0.18-1.9.18-3.5T6.46,121.5h1a5.71,5.71,0,0,0,.72,1.78,0.45,0.45,0,0,0,.34.16,3.84,3.84,0,0,0,1.14-.4q3.58-1.7,5.7-2.9-3.4-2-6.35-3.51a2.29,2.29,0,0,0-.68-0.27,0.42,0.42,0,0,0-.31.16,7,7,0,0,0-.55,1.59h-1q0.18-2,.18-3.21a28.13,28.13,0,0,0-.18-3h1A5.83,5.83,0,0,0,8.27,114a11,11,0,0,0,2.36,1.69q3.74,2.17,6.4,3.56,2.22-1,5.42-2.69l4.75-2.48a2.21,2.21,0,0,0,.52-0.49,8,8,0,0,0,.85-1.91h1q-0.18,2.16-.18,3.46t0.18,3.8h-1a10.33,10.33,0,0,0-.7-1.73,0.5,0.5,0,0,0-.37-0.13,3.07,3.07,0,0,0-1.12.34q-3.7,1.75-6.62,3.34,2.39,1.47,6.58,3.65a3.81,3.81,0,0,0,1.14.45,0.65,0.65,0,0,0,.46-0.18,3.84,3.84,0,0,0,.52-1.28c0-.08.06-0.2,0.11-0.35h1q-0.18,1.78-.18,3.42a19.92,19.92,0,0,0,.18,2.78h-1a5,5,0,0,0-.85-1.9,17.32,17.32,0,0,0-2.94-2q-4.5-2.58-6.74-3.73-2.41,1.14-7.21,3.64a11.27,11.27,0,0,0-2.33,1.49A8.25,8.25,0,0,0,7.42,129.06Z"/><path class="cls-2" d="M18.92,73.69l-0.83-.48q1.11-1.56,1.91-2.94t1.88-3.61l0.83,0.48a5.74,5.74,0,0,0-.27,1.9,0.45,0.45,0,0,0,.22.31,3.85,3.85,0,0,0,1.19.22q4,0.31,6.39.34-2-3.41-3.75-6.22A2.31,2.31,0,0,0,26,63.12a0.42,0.42,0,0,0-.35,0,7,7,0,0,0-1.27,1.1l-0.83-.48Q24.75,62,25.32,61a28.1,28.1,0,0,0,1.32-2.65l0.83,0.48a5.83,5.83,0,0,0-.3,2.22,11,11,0,0,0,1.2,2.64q2.15,3.75,3.77,6.28,2.44,0.21,6,.38l5.35,0.23a2.22,2.22,0,0,0,.69-0.16,8,8,0,0,0,1.69-1.24l0.83,0.48q-1.23,1.78-1.89,2.91T43.12,76l-0.83-.48a10.43,10.43,0,0,0,.26-1.85,0.5,0.5,0,0,0-.26-0.3,3.07,3.07,0,0,0-1.14-.27q-4.08-.34-7.4-0.41,1.33,2.47,3.87,6.45a3.78,3.78,0,0,0,.76,1,0.65,0.65,0,0,0,.49.07A3.83,3.83,0,0,0,40,79.32l0.28-.25,0.83,0.48Q40,81,39.2,82.43A19.91,19.91,0,0,0,38,84.92l-0.83-.48a5,5,0,0,0,.21-2.07,17.37,17.37,0,0,0-1.55-3.19q-2.61-4.48-4-6.6-2.66-.22-8.06-0.45a11.22,11.22,0,0,0-2.76.12A8.17,8.17,0,0,0,18.92,73.69Z"/><path class="cls-2" d="M54.81,29.82L54.33,29q1.74-.8,3.12-1.6t3.43-2.19L61.36,26a5.76,5.76,0,0,0-1.18,1.51,0.45,0.45,0,0,0,0,.38,3.84,3.84,0,0,0,.92.78q3.27,2.25,5.37,3.49,0-3.94-.14-7.26a2.27,2.27,0,0,0-.11-0.73A0.42,0.42,0,0,0,66,24a7,7,0,0,0-1.65.31l-0.48-.83q1.86-.87,2.87-1.45a28,28,0,0,0,2.47-1.63l0.48,0.83A5.82,5.82,0,0,0,68.26,23,11,11,0,0,0,68,25.92q0,4.32.12,7.32,2,1.41,5,3.35l4.52,2.87a2.22,2.22,0,0,0,.68.21,8,8,0,0,0,2.08-.22l0.48,0.83q-2,.92-3.09,1.58t-3.21,2.06l-0.48-.83a10.42,10.42,0,0,0,1.15-1.47,0.5,0.5,0,0,0-.07-0.39,3.06,3.06,0,0,0-.86-0.8q-3.36-2.33-6.2-4.06-0.08,2.8.12,7.52a3.83,3.83,0,0,0,.18,1.21,0.65,0.65,0,0,0,.39.31,3.83,3.83,0,0,0,1.37-.19l0.36-.08L71.06,46q-1.63.73-3.05,1.56a19.91,19.91,0,0,0-2.31,1.54l-0.48-.83a5,5,0,0,0,1.22-1.69A17.42,17.42,0,0,0,66.69,43q0-5.18-.14-7.7-2.19-1.52-6.76-4.42a11.2,11.2,0,0,0-2.46-1.28A8.18,8.18,0,0,0,54.81,29.82Z"/><path class="cls-2" d="M77.31,15.72l0.48,0.83L77.33,17a4.15,4.15,0,0,0-1.27,1.55A2.78,2.78,0,0,0,76.58,20l0.28,0.49,3.22,5.69,4.78,8.21a2.64,2.64,0,0,0,.94,1.13,3.35,3.35,0,0,0,1.75-.39q0.27-.1.8-0.24l0.48,0.83q-1.79.83-3.36,1.74T82,39.66l-0.48-.83a3.66,3.66,0,0,1,.38-0.41,4.57,4.57,0,0,0,1.37-1.59,3.11,3.11,0,0,0-.58-1.51l-0.43-.69-3-5.19q-4.6-8-5.12-8.8a1.38,1.38,0,0,0-.64-0.59,4.58,4.58,0,0,0-1.94.31l-0.66.14-0.48-.83q2.38-1.17,3.67-1.92T77.31,15.72Z"/><line class="cls-3" x1="134.41" y1="35.49" x2="133.65" y2="42.71"/><line class="cls-3" x1="143.71" y1="36.97" x2="142.2" y2="44.07"/><line class="cls-3" x1="152.81" y1="39.4" x2="150.57" y2="46.31"/><line class="cls-3" x1="161.61" y1="42.78" x2="158.65" y2="49.41"/><line class="cls-3" x1="170" y1="47.06" x2="166.37" y2="53.35"/><line class="cls-3" x1="177.9" y1="52.19" x2="173.63" y2="58.06"/><line class="cls-3" x1="185.22" y1="58.12" x2="180.36" y2="63.51"/><line class="cls-3" x1="191.88" y1="64.78" x2="186.49" y2="69.64"/><line class="cls-3" x1="197.81" y1="72.1" x2="191.94" y2="76.37"/><line class="cls-3" x1="202.94" y1="80" x2="196.65" y2="83.63"/><line class="cls-3" x1="207.22" y1="88.39" x2="200.59" y2="91.35"/><line class="cls-3" x1="210.6" y1="97.19" x2="203.69" y2="99.43"/><line class="cls-3" x1="213.03" y1="106.29" x2="205.93" y2="107.8"/><line class="cls-3" x1="214.51" y1="115.59" x2="207.29" y2="116.35"/><line class="cls-3" x1="215" y1="125" x2="207.74" y2="125"/><line class="cls-3" x1="214.51" y1="134.41" x2="207.29" y2="133.65"/><line class="cls-3" x1="213.03" y1="143.71" x2="205.93" y2="142.2"/><line class="cls-3" x1="210.6" y1="152.81" x2="203.69" y2="150.57"/><line class="cls-3" x1="207.22" y1="161.61" x2="200.59" y2="158.65"/><line class="cls-3" x1="202.94" y1="170" x2="196.65" y2="166.37"/><line class="cls-3" x1="197.81" y1="177.9" x2="191.94" y2="173.63"/><line class="cls-3" x1="191.88" y1="185.22" x2="186.49" y2="180.36"/><line class="cls-3" x1="185.22" y1="191.88" x2="180.36" y2="186.49"/><line class="cls-3" x1="177.9" y1="197.81" x2="173.63" y2="191.94"/><line class="cls-3" x1="170" y1="202.94" x2="166.37" y2="196.65"/><line class="cls-3" x1="161.61" y1="207.22" x2="158.65" y2="200.59"/><line class="cls-3" x1="152.81" y1="210.6" x2="150.57" y2="203.69"/><line class="cls-3" x1="143.71" y1="213.03" x2="142.2" y2="205.93"/><line class="cls-3" x1="134.41" y1="214.51" x2="133.65" y2="207.29"/><line class="cls-3" x1="125" y1="215" x2="125" y2="207.74"/><line class="cls-3" x1="115.59" y1="214.51" x2="116.35" y2="207.29"/><line class="cls-3" x1="106.29" y1="213.03" x2="107.8" y2="205.93"/><line class="cls-3" x1="97.19" y1="210.6" x2="99.43" y2="203.69"/><line class="cls-3" x1="88.39" y1="207.22" x2="91.35" y2="200.59"/><line class="cls-3" x1="80" y1="202.94" x2="83.63" y2="196.65"/><line class="cls-3" x1="72.1" y1="197.81" x2="76.37" y2="191.94"/><line class="cls-3" x1="64.78" y1="191.88" x2="69.64" y2="186.49"/><line class="cls-3" x1="58.12" y1="185.22" x2="63.51" y2="180.36"/><line class="cls-3" x1="52.19" y1="177.9" x2="58.06" y2="173.63"/><line class="cls-3" x1="47.06" y1="170" x2="53.35" y2="166.37"/><line class="cls-3" x1="42.78" y1="161.61" x2="49.41" y2="158.65"/><line class="cls-3" x1="39.4" y1="152.81" x2="46.31" y2="150.57"/><line class="cls-3" x1="36.97" y1="143.71" x2="44.07" y2="142.2"/><line class="cls-3" x1="35.49" y1="134.41" x2="42.71" y2="133.65"/><line class="cls-3" x1="35" y1="125" x2="42.26" y2="125"/><line class="cls-3" x1="35.49" y1="115.59" x2="42.71" y2="116.35"/><line class="cls-3" x1="36.97" y1="106.29" x2="44.07" y2="107.8"/><line class="cls-3" x1="39.4" y1="97.19" x2="46.31" y2="99.43"/><line class="cls-3" x1="42.78" y1="88.39" x2="49.41" y2="91.35"/><line class="cls-3" x1="47.06" y1="80" x2="53.35" y2="83.63"/><line class="cls-3" x1="52.19" y1="72.1" x2="58.06" y2="76.37"/><line class="cls-3" x1="58.12" y1="64.78" x2="63.51" y2="69.64"/><line class="cls-3" x1="64.78" y1="58.12" x2="69.64" y2="63.51"/><line class="cls-3" x1="72.1" y1="52.19" x2="76.37" y2="58.06"/><line class="cls-3" x1="80" y1="47.06" x2="83.63" y2="53.35"/><line class="cls-3" x1="88.39" y1="42.78" x2="91.35" y2="49.41"/><line class="cls-3" x1="97.19" y1="39.4" x2="99.43" y2="46.31"/><line class="cls-3" x1="106.29" y1="36.97" x2="107.8" y2="44.07"/><line class="cls-3" x1="115.59" y1="35.49" x2="116.35" y2="42.71"/><line class="cls-3" x1="125" y1="35" x2="125" y2="42.26"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-010.svg b/MagicMirror/modules/default/clock/faces/face-010.svg new file mode 100644 index 0000000000000000000000000000000000000000..8c5e5848d633fd31774d18245bbb4b6b1d1174f2 --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-010.svg @@ -0,0 +1 @@ +<svg id="Dots_Small" data-name="Dots Small" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:#fff;}</style></defs><title>face-010</title><circle class="cls-1" cx="134.86" cy="31.94" r="1" transform="translate(89 162.73) rotate(-84)"/><circle class="cls-1" cx="144.54" cy="33.48" r="1" transform="translate(81.74 167.9) rotate(-78)"/><circle class="cls-1" cx="154" cy="36.02" r="1" transform="translate(72.15 171.35) rotate(-72)"/><circle class="cls-1" cx="163.14" cy="39.54" r="1" transform="translate(60.66 172.49) rotate(-66)"/><circle class="cls-1" cx="171.86" cy="44" r="1" transform="translate(47.83 170.84) rotate(-60)"/><circle class="cls-1" cx="180.07" cy="49.34" r="1" transform="translate(34.31 166.02) rotate(-54)"/><circle class="cls-1" cx="187.68" cy="55.51" r="1" transform="translate(20.85 157.84) rotate(-48)"/><circle class="cls-1" cx="194.6" cy="62.44" r="1" transform="translate(8.2 146.25) rotate(-42)"/><circle class="cls-1" cx="200.76" cy="70.06" r="1" transform="matrix(0.81, -0.59, 0.59, 0.81, -2.84, 131.38)"/><circle class="cls-1" cx="206.09" cy="78.28" r="1" transform="translate(-11.53 113.53) rotate(-30)"/><circle class="cls-1" cx="210.53" cy="87.01" r="1" transform="translate(-17.19 93.15) rotate(-24)"/><circle class="cls-1" cx="214.03" cy="96.16" r="1" transform="translate(-19.24 70.84) rotate(-18)"/><circle class="cls-1" cx="216.56" cy="105.62" r="1" transform="translate(-17.23 47.33) rotate(-12)"/><circle class="cls-1" cx="218.08" cy="115.3" r="1" transform="translate(-10.86 23.43) rotate(-6)"/><circle class="cls-1" cx="218.58" cy="125.08" r="1"/><circle class="cls-1" cx="218.06" cy="134.86" r="1" transform="translate(61.14 337.64) rotate(-84)"/><circle class="cls-1" cx="216.52" cy="144.54" r="1" transform="translate(30.12 326.28) rotate(-78)"/><circle class="cls-1" cx="213.98" cy="154" r="1" transform="translate(1.39 309.91) rotate(-72)"/><circle class="cls-1" cx="210.46" cy="163.14" r="1" transform="translate(-24.18 289.05) rotate(-66)"/><circle class="cls-1" cx="206" cy="171.86" r="1" transform="translate(-45.84 264.34) rotate(-60)"/><circle class="cls-1" cx="200.66" cy="180.07" r="1" transform="translate(-62.97 236.57) rotate(-54)"/><circle class="cls-1" cx="194.49" cy="187.68" r="1" transform="translate(-75.12 206.63) rotate(-48)"/><circle class="cls-1" cx="187.56" cy="194.6" r="1" transform="translate(-82.04 175.48) rotate(-42)"/><circle class="cls-1" cx="179.94" cy="200.76" r="1" transform="translate(-83.64 144.11) rotate(-36)"/><circle class="cls-1" cx="171.72" cy="206.09" r="1" transform="translate(-80.04 113.47) rotate(-30)"/><circle class="cls-1" cx="162.99" cy="210.53" r="1" transform="translate(-71.54 84.49) rotate(-24)"/><circle class="cls-1" cx="153.84" cy="214.03" r="1" transform="translate(-58.61 58.01) rotate(-18)"/><circle class="cls-1" cx="144.38" cy="216.56" r="1" transform="translate(-41.87 34.75) rotate(-12)"/><circle class="cls-1" cx="134.7" cy="218.08" r="1" transform="translate(-22.06 15.27) rotate(-6)"/><circle class="cls-1" cx="124.92" cy="218.58" r="1"/><circle class="cls-1" cx="115.14" cy="218.06" r="1" transform="translate(-113.77 309.77) rotate(-84)"/><circle class="cls-1" cx="105.46" cy="216.52" r="1" transform="matrix(0.21, -0.98, 0.98, 0.21, -128.25, 274.66)"/><circle class="cls-1" cx="96" cy="213.98" r="1" transform="translate(-137.17 239.16) rotate(-72)"/><circle class="cls-1" cx="86.86" cy="210.46" r="1" transform="translate(-140.73 204.21) rotate(-66)"/><circle class="cls-1" cx="78.14" cy="206" r="1" transform="translate(-139.34 170.67) rotate(-60)"/><circle class="cls-1" cx="69.93" cy="200.66" r="1" transform="translate(-133.51 139.29) rotate(-54)"/><circle class="cls-1" cx="62.32" cy="194.49" r="1" transform="translate(-123.92 110.66) rotate(-48)"/><circle class="cls-1" cx="55.4" cy="187.56" r="1" transform="translate(-111.27 85.24) rotate(-42)"/><circle class="cls-1" cx="49.24" cy="179.94" r="1" transform="translate(-96.36 63.31) rotate(-36)"/><circle class="cls-1" cx="43.91" cy="171.72" r="1" transform="translate(-79.98 44.96) rotate(-30)"/><circle class="cls-1" cx="39.47" cy="162.99" r="1" transform="translate(-62.88 30.15) rotate(-24)"/><circle class="cls-1" cx="35.97" cy="153.84" r="1" transform="translate(-45.78 18.65) rotate(-18)"/><circle class="cls-1" cx="33.44" cy="144.38" r="1" transform="translate(-29.29 10.11) rotate(-12)"/><circle class="cls-1" cx="31.92" cy="134.7" r="1" transform="translate(-13.91 4.07) rotate(-6)"/><circle class="cls-1" cx="31.42" cy="124.92" r="1"/><circle class="cls-1" cx="31.94" cy="115.14" r="1" transform="translate(-85.9 134.86) rotate(-84)"/><circle class="cls-1" cx="33.48" cy="105.46" r="1" transform="translate(-76.64 116.28) rotate(-78)"/><circle class="cls-1" cx="36.02" cy="96" r="1" transform="translate(-66.41 100.6) rotate(-72)"/><circle class="cls-1" cx="39.54" cy="86.86" r="1" transform="translate(-55.89 87.65) rotate(-66)"/><circle class="cls-1" cx="44" cy="78.14" r="1" transform="translate(-45.67 77.17) rotate(-60)"/><circle class="cls-1" cx="49.34" cy="69.93" r="1" transform="translate(-36.23 68.74) rotate(-54)"/><circle class="cls-1" cx="55.51" cy="62.32" r="1" transform="translate(-27.95 61.87) rotate(-48)"/><circle class="cls-1" cx="62.44" cy="55.4" r="1" transform="translate(-21.03 56.01) rotate(-42)"/><circle class="cls-1" cx="70.06" cy="49.24" r="1" transform="translate(-15.56 50.58) rotate(-36)"/><circle class="cls-1" cx="78.28" cy="43.91" r="1" transform="matrix(0.87, -0.5, 0.5, 0.87, -11.47, 45.02)"/><circle class="cls-1" cx="87.01" cy="39.47" r="1" transform="translate(-8.53 38.8) rotate(-24)"/><circle class="cls-1" cx="96.16" cy="35.97" r="1" transform="translate(-6.41 31.48) rotate(-18)"/><circle class="cls-1" cx="105.62" cy="33.44" r="1" transform="translate(-4.65 22.69) rotate(-12)"/><circle class="cls-1" cx="115.3" cy="31.92" r="1" transform="translate(-2.7 12.23) rotate(-6)"/><circle class="cls-1" cx="125.08" cy="31.42" r="1"/><circle class="cls-1" cx="171.86" cy="44" r="2.5" transform="translate(47.83 170.84) rotate(-60)"/><circle class="cls-1" cx="206.09" cy="78.28" r="2.5" transform="translate(-11.53 113.53) rotate(-30)"/><circle class="cls-1" cx="218.58" cy="125.08" r="2.5"/><circle class="cls-1" cx="206" cy="171.86" r="2.5" transform="translate(-45.84 264.34) rotate(-60)"/><circle class="cls-1" cx="171.72" cy="206.09" r="2.5" transform="translate(-80.04 113.47) rotate(-30)"/><circle class="cls-1" cx="124.92" cy="218.58" r="2.5"/><circle class="cls-1" cx="78.14" cy="206" r="2.5" transform="translate(-139.34 170.67) rotate(-60)"/><circle class="cls-1" cx="43.91" cy="171.72" r="2.5" transform="translate(-79.98 44.96) rotate(-30)"/><circle class="cls-1" cx="31.42" cy="124.92" r="2.5"/><circle class="cls-1" cx="44" cy="78.14" r="2.5" transform="translate(-45.67 77.17) rotate(-60)"/><circle class="cls-1" cx="78.28" cy="43.91" r="2.5" transform="translate(-11.47 45.02) rotate(-30)"/><circle class="cls-1" cx="125.08" cy="31.42" r="2.5"/><path class="cls-1" d="M125,1.5a123.5,123.5,0,0,1,87.33,210.83A123.5,123.5,0,0,1,37.67,37.67,122.7,122.7,0,0,1,125,1.5m0-1A124.5,124.5,0,1,0,249.5,125,124.5,124.5,0,0,0,125,.5h0Z"/><path class="cls-1" d="M120.24,20.44h-2.1V6.5h-2.07v-1a2.51,2.51,0,0,0,2.43-1.64h1.74V20.44Z"/><path class="cls-1" d="M132.84,20.34h-9.67a14.87,14.87,0,0,1,.23-2.81,4.69,4.69,0,0,1,.73-1.79,6.14,6.14,0,0,1,1.5-1.42q0.66-.46,1.45-0.94l1.24-.75c0.3-.18.62-0.38,1-0.62a4.13,4.13,0,0,0,.77-0.67,4.08,4.08,0,0,0,.5-0.77,3.4,3.4,0,0,0,.33-1A9.47,9.47,0,0,0,131,8.14q0-.48,0-0.75c0-.18,0-0.38,0-0.62a2.16,2.16,0,0,0-.11-0.53,3.43,3.43,0,0,0-.2-0.4,0.84,0.84,0,0,0-.32-0.33,3.84,3.84,0,0,0-.48-0.21,2.62,2.62,0,0,0-.66-0.15,8.74,8.74,0,0,0-.87,0,9.89,9.89,0,0,0-1.46.08,2.19,2.19,0,0,0-.94.38,1.64,1.64,0,0,0-.59.82A6.16,6.16,0,0,0,125,7.84l-1.66-.39q0-.1,0-0.3a3.11,3.11,0,0,1,1.2-2.75,6.24,6.24,0,0,1,3.56-.83,14.76,14.76,0,0,1,1.58.07,6.19,6.19,0,0,1,1.24.26,2.75,2.75,0,0,1,1,.52,3,3,0,0,1,.66.84,4,4,0,0,1,.39,1.22A10.37,10.37,0,0,1,133,8.14a7.76,7.76,0,0,1-.37,2.59,4.45,4.45,0,0,1-1.07,1.72,8.75,8.75,0,0,1-1.92,1.33l-0.77.44-0.73.41-0.64.38a6.31,6.31,0,0,0-.59.39l-0.49.4a2.77,2.77,0,0,0-.44.45q-0.15.21-.33,0.5a2.52,2.52,0,0,0-.27.59,5.29,5.29,0,0,0-.15.67,5.71,5.71,0,0,0-.07.78h7.64v1.53Z"/><path class="cls-1" d="M183.06,35.62H181V21.68h-2.07v-1A2.51,2.51,0,0,0,181.32,19h1.74V35.62Z"/><path class="cls-1" d="M227.74,77h-9.67a14.87,14.87,0,0,1,.23-2.81,4.69,4.69,0,0,1,.73-1.79,6.16,6.16,0,0,1,1.5-1.42Q221.2,70.52,222,70l1.24-.75c0.3-.18.62-0.38,1-0.62a4.13,4.13,0,0,0,.77-0.67,4.08,4.08,0,0,0,.5-0.77,3.4,3.4,0,0,0,.33-1,9.34,9.34,0,0,0,.09-1.38q0-.48,0-0.75t0-.62a2.12,2.12,0,0,0-.11-0.53,3.41,3.41,0,0,0-.2-0.4,0.84,0.84,0,0,0-.33-0.33,3.89,3.89,0,0,0-.48-0.21,2.63,2.63,0,0,0-.66-0.15,8.72,8.72,0,0,0-.87,0,9.88,9.88,0,0,0-1.46.08,2.18,2.18,0,0,0-.94.38,1.64,1.64,0,0,0-.59.82,6.13,6.13,0,0,0-.26,1.44l-1.66-.39a2.86,2.86,0,0,1,0-.3,3.11,3.11,0,0,1,1.2-2.75,6.24,6.24,0,0,1,3.56-.83,14.77,14.77,0,0,1,1.58.07,6.19,6.19,0,0,1,1.24.26,2.76,2.76,0,0,1,1,.52,2.94,2.94,0,0,1,.66.84,3.94,3.94,0,0,1,.39,1.22,10.37,10.37,0,0,1,.12,1.66,7.76,7.76,0,0,1-.37,2.59,4.43,4.43,0,0,1-1.07,1.72,8.75,8.75,0,0,1-1.92,1.33l-0.77.44-0.73.41-0.64.38a6.44,6.44,0,0,0-.59.39l-0.49.4a2.73,2.73,0,0,0-.44.45q-0.15.21-.33,0.5a2.52,2.52,0,0,0-.27.59,5.38,5.38,0,0,0-.15.67,5.78,5.78,0,0,0-.07.78h7.64V77Z"/><path class="cls-1" d="M243.25,129.08a7.47,7.47,0,0,1-.25,2,4,4,0,0,1-.68,1.41,3,3,0,0,1-1.15.87,5.93,5.93,0,0,1-1.55.44,13.63,13.63,0,0,1-2,.12,13.37,13.37,0,0,1-2-.13,4.65,4.65,0,0,1-1.46-.47,2.15,2.15,0,0,1-1-1,3.58,3.58,0,0,1-.31-1.58q0-.44,0-0.71l1.79-.26c0,0.09,0,.21,0,0.38s0,0.31,0,.44,0,0.26,0,.38a1.25,1.25,0,0,0,.79,1.08,6.44,6.44,0,0,0,2.31.3q0.86,0,1.39,0a2.48,2.48,0,0,0,.92-0.21,2.53,2.53,0,0,0,.57-0.34,1.23,1.23,0,0,0,.28-0.64,4.85,4.85,0,0,0,.12-0.86q0-.37,0-1.27v0a5.86,5.86,0,0,0-.16-1.5,3.28,3.28,0,0,0-.37-0.9,1.29,1.29,0,0,0-.85-0.47,7.34,7.34,0,0,0-1.15-.17q-0.51,0-1.72,0h-0.07l-0.09-1.59h1.8a4.49,4.49,0,0,0,1.23-.18,2.52,2.52,0,0,0,.75-0.36,1.22,1.22,0,0,0,.38-0.73,5.8,5.8,0,0,0,.14-0.92q0-.4,0-1.33v-0.19a4.3,4.3,0,0,0-.1-1.05,1.28,1.28,0,0,0-.44-0.66,2,2,0,0,0-.94-0.38,10.57,10.57,0,0,0-1.65-.1,7,7,0,0,0-2.36.27,1.1,1.1,0,0,0-.71,1l-0.1,1.22-1.73-.34v-0.31a6.3,6.3,0,0,1,.11-1.31,2.54,2.54,0,0,1,.41-0.94,1.92,1.92,0,0,1,.87-0.66,6.33,6.33,0,0,1,1.45-.35,18.42,18.42,0,0,1,2.19-.15,5.94,5.94,0,0,1,3.81,1,3.9,3.9,0,0,1,1.2,3.18,7.4,7.4,0,0,1-.2,1.8,4,4,0,0,1-.45,1.18,2.37,2.37,0,0,1-.62.66,2.45,2.45,0,0,1-.58.33,3.65,3.65,0,0,1-.44.12h-0.08l0.3,0.06a2.43,2.43,0,0,1,.4.15,2.21,2.21,0,0,1,.48.28,3.39,3.39,0,0,1,.46.46,2.74,2.74,0,0,1,.43.69,4.85,4.85,0,0,1,.29,1A7.25,7.25,0,0,1,243.25,129.08Z"/><path class="cls-1" d="M228.34,186.08h-1.56v4.37h-2.07v-4.36h-7v-1.68l5.8-10.57h3.24v10.77h1.56v1.47ZM224.71,175l-0.57,1.15-0.52,1L221,182.46l-0.55,1.09-0.54,1.06h4.81V175Z"/><path class="cls-1" d="M187.18,226.8a13.7,13.7,0,0,1-.12,1.94,5,5,0,0,1-.4,1.42,2.76,2.76,0,0,1-1.77,1.57,6.63,6.63,0,0,1-1.46.31,18.57,18.57,0,0,1-1.91.08q-0.67,0-1.19,0t-1.05-.12a4.26,4.26,0,0,1-.92-0.25,3.71,3.71,0,0,1-.73-0.41,2,2,0,0,1-.57-0.61,3.15,3.15,0,0,1-.34-0.86,4.77,4.77,0,0,1-.12-1.14c0-.07,0-0.19,0-0.35s0-.29,0-0.38l1.69-.24q0.06,0.66.11,1a3.49,3.49,0,0,0,.19.71,1.17,1.17,0,0,0,.32.5,1.83,1.83,0,0,0,.55.28,3.24,3.24,0,0,0,.83.16q0.47,0,1.21,0a8.56,8.56,0,0,0,1.64-.13,3.74,3.74,0,0,0,1.07-.35,1.54,1.54,0,0,0,.6-0.59,2.37,2.37,0,0,0,.28-0.76,5.52,5.52,0,0,0,.07-0.94q0-.23,0-0.78t0-.85q0-.58,0-0.89a6.06,6.06,0,0,0-.06-0.68,2,2,0,0,0-.13-0.55,1.84,1.84,0,0,0-.25-0.38,0.88,0.88,0,0,0-.4-0.29,6.11,6.11,0,0,0-.6-0.16,4.46,4.46,0,0,0-.82-0.1l-1.09,0H181c-0.2,0-.43,0-0.67.06a1.93,1.93,0,0,0-.59.16,2.54,2.54,0,0,0-.45.3,1.31,1.31,0,0,0-.36.47,2.68,2.68,0,0,0-.2.69l-2-.15v-9.21h9.44V217H179l-0.36,6.38a5.87,5.87,0,0,1,.32-0.68,3.12,3.12,0,0,1,.43-0.57,2.14,2.14,0,0,1,.64-0.48,3.89,3.89,0,0,1,.93-0.29,6.89,6.89,0,0,1,1.31-.11,11.37,11.37,0,0,1,1.83.13,4,4,0,0,1,1.33.44,3.14,3.14,0,0,1,.89.72,3,3,0,0,1,.54,1.06,7.39,7.39,0,0,1,.27,1.38Q187.18,225.76,187.18,226.8Z"/><path class="cls-1" d="M130.75,241.65a18.38,18.38,0,0,1-.09,1.95,6.27,6.27,0,0,1-.33,1.47,3.14,3.14,0,0,1-.61,1.06,3,3,0,0,1-1,.68,5,5,0,0,1-1.39.38,14.37,14.37,0,0,1-1.88.11,19.56,19.56,0,0,1-2.35-.12,4.55,4.55,0,0,1-1.63-.49,3.45,3.45,0,0,1-1.06-.85,3.58,3.58,0,0,1-.59-1.42,11.49,11.49,0,0,1-.27-1.95q-0.06-1-.06-2.7,0-3.67.1-4.78a5.86,5.86,0,0,1,1-3.28,4.74,4.74,0,0,1,3.42-1.36q0.54,0,1.15,0c0.5,0,.93,0,1.28,0a10.78,10.78,0,0,1,1.1.12,3.68,3.68,0,0,1,1,.26,2.91,2.91,0,0,1,.7.45,1.62,1.62,0,0,1,.47.7,3,3,0,0,1,.15,1q0,0.21-.09,1.11-1,.1-1.79.24c0-.34,0-0.58,0-0.74a5.29,5.29,0,0,0,0-.55,1.58,1.58,0,0,0-.09-0.43,1.34,1.34,0,0,0-.21-0.29,0.63,0.63,0,0,0-.31-0.21l-0.48-.1a4.13,4.13,0,0,0-.65-0.06h-0.87a6.79,6.79,0,0,0-2.73.45,1.75,1.75,0,0,0-.82,1.53q-0.11,2.51-.16,4.54a3.62,3.62,0,0,1,.77-1,3.3,3.3,0,0,1,1.19-.63,5.87,5.87,0,0,1,1.78-.24c0.53,0,1,0,1.39,0a9.2,9.2,0,0,1,1.14.15,4.13,4.13,0,0,1,.94.29,3.33,3.33,0,0,1,.72.46,2.29,2.29,0,0,1,.54.66,4.64,4.64,0,0,1,.36.91,6.11,6.11,0,0,1,.22,1.18C130.72,240.61,130.75,241.1,130.75,241.65Zm-2,1.74q0-3.13,0-3.47a1.42,1.42,0,0,0-.77-1.33,7.52,7.52,0,0,0-2.72-.36,13.41,13.41,0,0,0-1.51.07,2.95,2.95,0,0,0-1.64.67,1.14,1.14,0,0,0-.32.62,4.77,4.77,0,0,0-.12.73q0,0.31,0,.94,0,0.35,0,.92c0,0.38,0,.65,0,0.81a6.72,6.72,0,0,0,.06,1,2.41,2.41,0,0,0,.26.76,1.33,1.33,0,0,0,.59.57,3.94,3.94,0,0,0,1.06.31,9.68,9.68,0,0,0,1.64.12q0.72,0,1.24,0a9,9,0,0,0,.91-0.12,1.84,1.84,0,0,0,.63-0.23,2.06,2.06,0,0,0,.4-0.31,1,1,0,0,0,.22-0.44,3.29,3.29,0,0,0,.1-0.54C128.71,243.9,128.71,243.67,128.71,243.39Z"/><path class="cls-1" d="M72.3,214.38v1.91q-3.37,8.73-5,14.7H65.23q2.07-7.06,5.18-14.9H64v-1.7H72.3Z"/><path class="cls-1" d="M27.08,190.67h0a16.53,16.53,0,0,1-2.1-.11,6.11,6.11,0,0,1-1.54-.4,2.54,2.54,0,0,1-1.08-.8,3.61,3.61,0,0,1-.59-1.28,7.55,7.55,0,0,1-.2-1.88,6.76,6.76,0,0,1,.51-2.91,4.45,4.45,0,0,1,1.53-1.74q-1.22-.9-1.22-3.74a4.1,4.1,0,0,1,1.09-3.27,5.56,5.56,0,0,1,3.58-.92,5.56,5.56,0,0,1,3.58.92,4.1,4.1,0,0,1,1.09,3.27q0,2.85-1.22,3.74a4.45,4.45,0,0,1,1.53,1.74,6.76,6.76,0,0,1,.51,2.91,7.57,7.57,0,0,1-.2,1.88,3.61,3.61,0,0,1-.59,1.28,2.54,2.54,0,0,1-1.08.8,6.11,6.11,0,0,1-1.54.4A16.53,16.53,0,0,1,27.08,190.67Zm0-8.15h-1l-0.81.05a1.88,1.88,0,0,0-.66.14l-0.44.26a0.78,0.78,0,0,0-.33.44,5.7,5.7,0,0,0-.15.64,5.1,5.1,0,0,0-.07.9q0,0.38,0,1t0,0.91a3.52,3.52,0,0,0,.07.75,1.64,1.64,0,0,0,.27.59,1.35,1.35,0,0,0,.59.45,4.43,4.43,0,0,0,1,.26,12.19,12.19,0,0,0,3.13,0,4.43,4.43,0,0,0,1-.26,1.35,1.35,0,0,0,.59-0.45,1.64,1.64,0,0,0,.27-0.59,3.52,3.52,0,0,0,.07-0.75q0-.27,0-0.91t0-1a5.1,5.1,0,0,0-.07-0.9,5.6,5.6,0,0,0-.15-0.64A0.78,0.78,0,0,0,30,183l-0.44-.26a1.89,1.89,0,0,0-.66-0.14l-0.81-.05h-1Zm0-7.25H26.22a4.57,4.57,0,0,0-.67.07,4.18,4.18,0,0,0-.5.11,0.81,0.81,0,0,0-.33.21,1,1,0,0,0-.22.3,1.84,1.84,0,0,0-.12.44,3.76,3.76,0,0,0-.06.56c0,0.18,0,.43,0,0.74q0,2.66.86,3.21H29q0.86-.55.86-3.21c0-.31,0-0.56,0-0.74a3.76,3.76,0,0,0-.06-0.56A1.84,1.84,0,0,0,29.7,176a1.05,1.05,0,0,0-.22-0.3,0.81,0.81,0,0,0-.33-0.21,4.22,4.22,0,0,0-.5-0.11,4.57,4.57,0,0,0-.67-0.07H27.08Z"/><path class="cls-1" d="M17.14,124.39q0,3.88-.1,5a5.65,5.65,0,0,1-1,3.11,4.21,4.21,0,0,1-3,1.24,14.62,14.62,0,0,1-1.49.07h-1.1q-0.44,0-1-.06a7.1,7.1,0,0,1-1-.14,4.78,4.78,0,0,1-.8-0.26,1.8,1.8,0,0,1-1-1,2.1,2.1,0,0,1-.16-0.83,7.59,7.59,0,0,1,.1-1.23q0.91-.1,1.78-0.24,0,0.44,0,.66a4.19,4.19,0,0,0,.05.52,1.39,1.39,0,0,0,.12.43,1.53,1.53,0,0,0,.23.3,0.8,0.8,0,0,0,.37.23l0.55,0.13a4.56,4.56,0,0,0,.76.09l1,0a14.51,14.51,0,0,0,1.64-.11,2.63,2.63,0,0,0,1-.31,1.19,1.19,0,0,0,.53-0.58,3.81,3.81,0,0,0,.21-1q0.11-2.58.16-4.67a3.77,3.77,0,0,1-3.64,1.86,17.84,17.84,0,0,1-1.81-.08,6.66,6.66,0,0,1-1.38-.29,2.88,2.88,0,0,1-1-.55,2.85,2.85,0,0,1-.67-0.92,4.63,4.63,0,0,1-.39-1.34A13.11,13.11,0,0,1,6,122.59a17.11,17.11,0,0,1,.1-2,6.07,6.07,0,0,1,.35-1.48,3.21,3.21,0,0,1,.63-1,2.82,2.82,0,0,1,1-.66A5.44,5.44,0,0,1,9.46,117a14,14,0,0,1,1.82-.1,18.67,18.67,0,0,1,2.31.12,4.43,4.43,0,0,1,1.6.49,3.42,3.42,0,0,1,1,.85,3.58,3.58,0,0,1,.58,1.41,11.65,11.65,0,0,1,.27,1.93Q17.14,122.73,17.14,124.39Zm-2.07-2.68q0-.75,0-1.09a5,5,0,0,0-.12-0.81,1.16,1.16,0,0,0-.31-0.64,3.09,3.09,0,0,0-.6-0.38,2.7,2.7,0,0,0-1-.26q-0.59-.06-1.48-0.06a9.51,9.51,0,0,0-2.21.19,1.69,1.69,0,0,0-1,.55,2.07,2.07,0,0,0-.29,1.09q0,1.68,0,2.54t0,1.09a3.76,3.76,0,0,0,.13.78,1,1,0,0,0,.32.58,3,3,0,0,0,.62.33,3.13,3.13,0,0,0,1,.22q0.58,0,1.47.06a6.1,6.1,0,0,0,2.64-.41,1.59,1.59,0,0,0,.83-1.43Q15.07,123.29,15.07,121.71Z"/><path class="cls-1" d="M25.11,77.14H23V63.2H20.94v-1a2.51,2.51,0,0,0,2.43-1.64h1.74V77.14Z"/><path class="cls-1" d="M34.06,77.32a14,14,0,0,1-1.84-.11,3.9,3.9,0,0,1-1.38-.43,9.06,9.06,0,0,1-1-.63,2.16,2.16,0,0,1-.67-1Q28.9,74.5,28.75,74a7.78,7.78,0,0,1-.22-1.56q-0.07-1-.08-1.8t0-2.17q0-.94,0-1.63t0.07-1.42a12.12,12.12,0,0,1,.15-1.25,10.09,10.09,0,0,1,.25-1.05,3.46,3.46,0,0,1,.39-0.89,5.11,5.11,0,0,1,.54-0.7,2.56,2.56,0,0,1,.72-0.55,6.29,6.29,0,0,1,.92-0.37,5.62,5.62,0,0,1,1.15-.24,13.53,13.53,0,0,1,1.41-.07,10.69,10.69,0,0,1,1.87.15,5.9,5.9,0,0,1,1.41.42,2.93,2.93,0,0,1,1,.76,4.63,4.63,0,0,1,.68,1,5.47,5.47,0,0,1,.41,1.4,15.35,15.35,0,0,1,.2,1.73q0.05,0.88.05,2.1,0,3.66-.13,4.91A5.61,5.61,0,0,1,38.37,76a5.32,5.32,0,0,1-3.82,1.27H34.06Zm0-15.38a5.24,5.24,0,0,0-2.53.48,1.74,1.74,0,0,0-.89,1.52q0,0.71-.07,1.51t0,1.83q0,1,0,1.57,0,3,.12,4.92a1.58,1.58,0,0,0,.87,1.39,5.66,5.66,0,0,0,2.55.44,5.66,5.66,0,0,0,2.55-.44,1.58,1.58,0,0,0,.87-1.39q0.12-1.93.12-4.92,0-.54-0.05-2.27t-0.08-2.64a1.76,1.76,0,0,0-.9-1.52A5.21,5.21,0,0,0,34.06,61.94Z"/><path class="cls-1" d="M68.4,35.64h-2.1V21.7H64.23v-1A2.51,2.51,0,0,0,66.67,19H68.4V35.64Z"/><path class="cls-1" d="M74.95,35.64h-2.1V21.7H70.78v-1A2.51,2.51,0,0,0,73.21,19h1.74V35.64Z"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-011.svg b/MagicMirror/modules/default/clock/faces/face-011.svg new file mode 100644 index 0000000000000000000000000000000000000000..9886fed546b95a824cb714b264e7a6e4fd602b82 --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-011.svg @@ -0,0 +1 @@ +<svg id="Dots_Small_Larger" data-name="Dots Small Larger" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:#fff;}</style></defs><title>face-011</title><circle class="cls-1" cx="136.81" cy="13.62" r="1.2" transform="translate(108.97 148.25) rotate(-84)"/><circle class="cls-1" cx="148.39" cy="15.46" r="1.2" transform="translate(102.41 157.39) rotate(-78)"/><circle class="cls-1" cx="159.71" cy="18.51" r="1.2" transform="translate(92.76 164.68) rotate(-72)"/><circle class="cls-1" cx="170.65" cy="22.72" r="1.2" transform="translate(80.49 169.37) rotate(-66)"/><circle class="cls-1" cx="181.09" cy="28.05" r="1.2" transform="translate(66.25 170.85) rotate(-60)"/><circle class="cls-1" cx="190.92" cy="34.44" r="1.2" transform="translate(50.83 168.65) rotate(-54)"/><circle class="cls-1" cx="200.02" cy="41.83" r="1.2" transform="translate(35.1 162.49) rotate(-48)"/><circle class="cls-1" cx="208.3" cy="50.13" r="1.2" transform="translate(19.96 152.26) rotate(-42)"/><circle class="cls-1" cx="215.67" cy="59.24" r="1.2" transform="translate(6.37 138.09) rotate(-36)"/><circle class="cls-1" cx="222.05" cy="69.08" r="1.2" transform="translate(-4.79 120.28) rotate(-30)"/><circle class="cls-1" cx="227.36" cy="79.53" r="1.2" transform="translate(-12.69 99.35) rotate(-24)"/><circle class="cls-1" cx="231.56" cy="90.48" r="1.2" transform="translate(-16.63 75.98) rotate(-18)"/><circle class="cls-1" cx="234.58" cy="101.81" r="1.2" transform="translate(-16.04 51) rotate(-12)"/><circle class="cls-1" cx="236.4" cy="113.39" r="1.2" transform="translate(-10.56 25.33) rotate(-6)"/><circle class="cls-1" cx="237.01" cy="125.1" r="1.2"/><circle class="cls-1" cx="236.38" cy="136.81" r="1.2" transform="translate(75.62 357.6) rotate(-84)"/><circle class="cls-1" cx="234.54" cy="148.39" r="1.2" transform="translate(40.63 346.95) rotate(-78)"/><circle class="cls-1" cx="231.49" cy="159.71" r="1.2" transform="translate(8.07 330.52) rotate(-72)"/><circle class="cls-1" cx="227.28" cy="170.65" r="1.2" transform="translate(-21.06 308.87) rotate(-66)"/><circle class="cls-1" cx="221.95" cy="181.09" r="1.2" transform="translate(-45.85 282.76) rotate(-60)"/><circle class="cls-1" cx="215.56" cy="190.92" r="1.2" transform="translate(-65.6 253.09) rotate(-54)"/><circle class="cls-1" cx="208.17" cy="200.02" r="1.2" transform="translate(-79.77 220.88) rotate(-48)"/><circle class="cls-1" cx="199.87" cy="208.3" r="1.2" transform="translate(-88.04 187.25) rotate(-42)"/><circle class="cls-1" cx="190.76" cy="215.67" r="1.2" transform="translate(-90.34 153.31) rotate(-36)"/><circle class="cls-1" cx="180.92" cy="222.05" r="1.2" transform="translate(-86.79 120.21) rotate(-30)"/><circle class="cls-1" cx="170.47" cy="227.36" r="1.2" transform="translate(-77.74 88.99) rotate(-24)"/><circle class="cls-1" cx="159.52" cy="231.56" r="1.2" transform="translate(-63.75 60.63) rotate(-18)"/><circle class="cls-1" cx="148.19" cy="234.58" r="1.2" transform="translate(-45.53 35.94) rotate(-12)"/><circle class="cls-1" cx="136.61" cy="236.4" r="1.2" transform="translate(-23.96 15.57) rotate(-6)"/><circle class="cls-1" cx="124.9" cy="237.01" r="1.2"/><circle class="cls-1" cx="113.19" cy="236.38" r="1.2" transform="translate(-133.73 324.25) rotate(-84)"/><circle class="cls-1" cx="101.61" cy="234.54" r="1.2" transform="translate(-148.93 285.17) rotate(-78)"/><circle class="cls-1" cx="90.29" cy="231.49" r="1.2" transform="translate(-157.77 245.83) rotate(-72)"/><circle class="cls-1" cx="79.35" cy="227.28" r="1.2" transform="matrix(0.41, -0.91, 0.91, 0.41, -160.56, 207.33)"/><circle class="cls-1" cx="68.91" cy="221.95" r="1.2" transform="translate(-157.76 170.65) rotate(-60)"/><circle class="cls-1" cx="59.08" cy="215.56" r="1.2" transform="translate(-150.03 136.65) rotate(-54)"/><circle class="cls-1" cx="49.98" cy="208.17" r="1.2" transform="translate(-138.17 106.02) rotate(-48)"/><circle class="cls-1" cx="41.7" cy="199.87" r="1.2" transform="translate(-123.03 79.24) rotate(-42)"/><circle class="cls-1" cx="34.33" cy="190.76" r="1.2" transform="translate(-105.57 56.61) rotate(-36)"/><circle class="cls-1" cx="27.95" cy="180.92" r="1.2" transform="matrix(0.87, -0.5, 0.5, 0.87, -86.71, 38.21)"/><circle class="cls-1" cx="22.64" cy="170.47" r="1.2" transform="translate(-67.38 23.94) rotate(-24)"/><circle class="cls-1" cx="18.44" cy="159.52" r="1.2" transform="translate(-48.39 13.51) rotate(-18)"/><circle class="cls-1" cx="15.42" cy="148.19" r="1.2" transform="translate(-30.47 6.44) rotate(-12)"/><circle class="cls-1" cx="13.6" cy="136.61" r="1.2" transform="translate(-14.21 2.17) rotate(-6)"/><circle class="cls-1" cx="12.99" cy="124.9" r="1.2"/><circle class="cls-1" cx="13.62" cy="113.19" r="1.2" transform="translate(-100.38 114.9) rotate(-84)"/><circle class="cls-1" cx="15.46" cy="101.61" r="1.2" transform="translate(-87.15 95.61) rotate(-78)"/><circle class="cls-1" cx="18.51" cy="90.29" r="1.2" transform="translate(-73.09 79.99) rotate(-72)"/><circle class="cls-1" cx="22.72" cy="79.35" r="1.2" transform="translate(-59.01 67.83) rotate(-66)"/><circle class="cls-1" cx="28.05" cy="68.91" r="1.2" transform="translate(-45.65 58.75) rotate(-60)"/><circle class="cls-1" cx="34.44" cy="59.08" r="1.2" transform="translate(-33.6 52.22) rotate(-54)"/><circle class="cls-1" cx="41.83" cy="49.98" r="1.2" transform="translate(-23.3 47.62) rotate(-48)"/><circle class="cls-1" cx="50.13" cy="41.7" r="1.2" transform="translate(-15.02 44.25) rotate(-42)"/><circle class="cls-1" cx="59.24" cy="34.33" r="1.2" transform="translate(-8.86 41.38) rotate(-36)"/><circle class="cls-1" cx="69.08" cy="27.95" r="1.2" transform="translate(-4.72 38.29) rotate(-30)"/><circle class="cls-1" cx="79.53" cy="22.64" r="1.2" transform="translate(-2.33 34.31) rotate(-24)"/><circle class="cls-1" cx="90.48" cy="18.44" r="1.2" transform="translate(-1.27 28.86) rotate(-18)"/><circle class="cls-1" cx="101.81" cy="15.42" r="1.2" transform="translate(-0.98 21.5) rotate(-12)"/><circle class="cls-1" cx="113.39" cy="13.6" r="1.2" transform="translate(-0.8 11.93) rotate(-6)"/><circle class="cls-1" cx="125.1" cy="12.99" r="1.2"/><circle class="cls-1" cx="181.09" cy="28.05" r="2.99" transform="translate(66.25 170.85) rotate(-60)"/><circle class="cls-1" cx="222.05" cy="69.08" r="2.99" transform="matrix(0.87, -0.5, 0.5, 0.87, -4.79, 120.28)"/><circle class="cls-1" cx="237.01" cy="125.1" r="2.99"/><circle class="cls-1" cx="221.95" cy="181.09" r="2.99" transform="translate(-45.85 282.76) rotate(-60)"/><circle class="cls-1" cx="180.92" cy="222.05" r="2.99" transform="matrix(0.87, -0.5, 0.5, 0.87, -86.79, 120.21)"/><circle class="cls-1" cx="124.9" cy="237.01" r="2.99"/><circle class="cls-1" cx="68.91" cy="221.95" r="2.99" transform="translate(-157.76 170.65) rotate(-60)"/><circle class="cls-1" cx="27.95" cy="180.92" r="2.99" transform="translate(-86.71 38.21) rotate(-30)"/><circle class="cls-1" cx="12.99" cy="124.9" r="2.99"/><circle class="cls-1" cx="28.05" cy="68.91" r="2.99" transform="translate(-45.65 58.75) rotate(-60)"/><circle class="cls-1" cx="69.08" cy="27.95" r="2.99" transform="translate(-4.72 38.29) rotate(-30)"/><circle class="cls-1" cx="125.1" cy="12.99" r="2.99"/><path class="cls-1" d="M125,1.5a123.5,123.5,0,0,1,87.33,210.83A123.5,123.5,0,0,1,37.67,37.67,122.7,122.7,0,0,1,125,1.5m0-1A124.5,124.5,0,1,0,249.5,125,124.5,124.5,0,0,0,125,.5h0Z"/></svg> diff --git a/MagicMirror/modules/default/clock/faces/face-012.svg b/MagicMirror/modules/default/clock/faces/face-012.svg new file mode 100644 index 0000000000000000000000000000000000000000..cfd806982cad7914f5f74508a3ec4a5ff5c5357a --- /dev/null +++ b/MagicMirror/modules/default/clock/faces/face-012.svg @@ -0,0 +1 @@ +<svg id="Dots_Small_Larger" data-name="Dots Small Larger" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:#fff;}</style></defs><title>face-012</title><circle class="cls-1" cx="135.52" cy="25.72" r="1.07" transform="translate(95.78 157.82) rotate(-84)"/><circle class="cls-1" cx="145.84" cy="27.37" r="1.07" transform="translate(88.75 164.33) rotate(-78)"/><circle class="cls-1" cx="155.93" cy="30.08" r="1.07" transform="translate(79.14 169.09) rotate(-72)"/><circle class="cls-1" cx="165.69" cy="33.83" r="1.07" transform="translate(67.39 171.44) rotate(-66)"/><circle class="cls-1" cx="174.99" cy="38.59" r="1.07" transform="translate(54.08 170.84) rotate(-60)"/><circle class="cls-1" cx="183.75" cy="44.29" r="1.07" transform="translate(39.92 166.91) rotate(-54)"/><circle class="cls-1" cx="191.87" cy="50.87" r="1.07" transform="translate(25.68 159.42) rotate(-48)"/><circle class="cls-1" cx="199.25" cy="58.26" r="1.07" transform="translate(12.19 148.29) rotate(-42)"/><circle class="cls-1" cx="205.82" cy="66.39" r="1.07" transform="translate(0.28 133.66) rotate(-36)"/><circle class="cls-1" cx="211.5" cy="75.16" r="1.07" transform="matrix(0.87, -0.5, 0.5, 0.87, -9.24, 115.82)"/><circle class="cls-1" cx="216.24" cy="84.48" r="1.07" transform="translate(-15.66 95.26) rotate(-24)"/><circle class="cls-1" cx="219.97" cy="94.23" r="1.07" transform="translate(-18.35 72.59) rotate(-18)"/><circle class="cls-1" cx="222.67" cy="104.33" r="1.07" transform="translate(-16.83 48.58) rotate(-12)"/><circle class="cls-1" cx="224.3" cy="114.65" r="1.07" transform="translate(-10.76 24.07) rotate(-6)"/><circle class="cls-1" cx="224.83" cy="125.09" r="1.07"/><circle class="cls-1" cx="224.28" cy="135.52" r="1.07" transform="translate(66.05 344.41) rotate(-84)"/><circle class="cls-1" cx="222.63" cy="145.84" r="1.07" transform="translate(33.69 333.29) rotate(-78)"/><circle class="cls-1" cx="219.92" cy="155.93" r="1.07" transform="translate(3.66 316.9) rotate(-72)"/><circle class="cls-1" cx="216.17" cy="165.69" r="1.07" transform="translate(-23.12 295.77) rotate(-66)"/><circle class="cls-1" cx="211.41" cy="174.99" r="1.07" transform="translate(-45.84 270.59) rotate(-60)"/><circle class="cls-1" cx="205.71" cy="183.75" r="1.07" transform="translate(-63.86 242.17) rotate(-54)"/><circle class="cls-1" cx="199.13" cy="191.87" r="1.07" transform="translate(-76.7 211.47) rotate(-48)"/><circle class="cls-1" cx="191.74" cy="199.25" r="1.07" transform="translate(-84.08 179.47) rotate(-42)"/><circle class="cls-1" cx="183.61" cy="205.82" r="1.07" transform="translate(-85.91 147.23) rotate(-36)"/><circle class="cls-1" cx="174.84" cy="211.5" r="1.07" transform="translate(-82.33 115.76) rotate(-30)"/><circle class="cls-1" cx="165.52" cy="216.24" r="1.07" transform="translate(-73.64 86.02) rotate(-24)"/><circle class="cls-1" cx="155.77" cy="219.97" r="1.07" transform="matrix(0.95, -0.31, 0.31, 0.95, -60.35, 58.9)"/><circle class="cls-1" cx="145.67" cy="222.67" r="1.07" transform="translate(-43.11 35.15) rotate(-12)"/><circle class="cls-1" cx="135.35" cy="224.3" r="1.07" transform="translate(-22.7 15.38) rotate(-6)"/><circle class="cls-1" cx="124.91" cy="224.83" r="1.07"/><circle class="cls-1" cx="114.48" cy="224.28" r="1.07" transform="translate(-120.54 314.68) rotate(-84)"/><circle class="cls-1" cx="104.16" cy="222.63" r="1.07" transform="translate(-135.27 278.23) rotate(-78)"/><circle class="cls-1" cx="94.07" cy="219.92" r="1.07" transform="translate(-144.16 241.42) rotate(-72)"/><circle class="cls-1" cx="84.31" cy="216.17" r="1.07" transform="translate(-147.46 205.27) rotate(-66)"/><circle class="cls-1" cx="75.01" cy="211.41" r="1.07" transform="translate(-145.59 170.66) rotate(-60)"/><circle class="cls-1" cx="66.25" cy="205.71" r="1.07" transform="matrix(0.59, -0.81, 0.81, 0.59, -139.12, 138.39)"/><circle class="cls-1" cx="58.13" cy="199.13" r="1.07" transform="translate(-128.75 109.09) rotate(-48)"/><circle class="cls-1" cx="50.75" cy="191.74" r="1.07" transform="translate(-115.26 83.21) rotate(-42)"/><circle class="cls-1" cx="44.18" cy="183.61" r="1.07" transform="translate(-99.48 61.04) rotate(-36)"/><circle class="cls-1" cx="38.5" cy="174.84" r="1.07" transform="translate(-82.26 42.67) rotate(-30)"/><circle class="cls-1" cx="33.76" cy="165.52" r="1.07" transform="translate(-64.41 28.04) rotate(-24)"/><circle class="cls-1" cx="30.03" cy="155.77" r="1.07" transform="matrix(0.95, -0.31, 0.31, 0.95, -46.66, 16.9)"/><circle class="cls-1" cx="27.33" cy="145.67" r="1.07" transform="translate(-29.69 8.87) rotate(-12)"/><circle class="cls-1" cx="25.7" cy="135.35" r="1.07" transform="translate(-14.01 3.43) rotate(-6)"/><circle class="cls-1" cx="25.17" cy="124.91" r="1.07"/><circle class="cls-1" cx="25.72" cy="114.48" r="1.07" transform="translate(-90.81 128.09) rotate(-84)"/><circle class="cls-1" cx="27.37" cy="104.16" r="1.07" transform="translate(-80.2 109.27) rotate(-78)"/><circle class="cls-1" cx="30.08" cy="94.07" r="1.07" transform="translate(-68.68 93.61) rotate(-72)"/><circle class="cls-1" cx="33.83" cy="84.31" r="1.07" transform="translate(-56.95 80.93) rotate(-66)"/><circle class="cls-1" cx="38.59" cy="75.01" r="1.07" transform="translate(-45.66 70.92) rotate(-60)"/><circle class="cls-1" cx="44.29" cy="66.25" r="1.07" transform="translate(-35.34 63.14) rotate(-54)"/><circle class="cls-1" cx="50.87" cy="58.13" r="1.07" transform="translate(-26.37 57.04) rotate(-48)"/><circle class="cls-1" cx="58.26" cy="50.75" r="1.07" transform="translate(-18.99 52.02) rotate(-42)"/><circle class="cls-1" cx="66.39" cy="44.18" r="1.07" transform="translate(-13.29 47.46) rotate(-36)"/><circle class="cls-1" cx="75.16" cy="38.5" r="1.07" transform="translate(-9.18 42.74) rotate(-30)"/><circle class="cls-1" cx="84.48" cy="33.76" r="1.07" transform="translate(-6.43 37.28) rotate(-24)"/><circle class="cls-1" cx="94.23" cy="30.03" r="1.07" transform="translate(-4.67 30.59) rotate(-18)"/><circle class="cls-1" cx="104.33" cy="27.33" r="1.07" transform="translate(-3.4 22.29) rotate(-12)"/><circle class="cls-1" cx="114.65" cy="25.7" r="1.07" transform="translate(-2.06 12.13) rotate(-6)"/><circle class="cls-1" cx="125.09" cy="25.17" r="1.07"/><circle class="cls-1" cx="174.99" cy="38.59" r="2.67" transform="translate(54.08 170.84) rotate(-60)"/><circle class="cls-1" cx="211.5" cy="75.16" r="2.67" transform="matrix(0.87, -0.5, 0.5, 0.87, -9.24, 115.82)"/><circle class="cls-1" cx="224.83" cy="125.09" r="2.67"/><circle class="cls-1" cx="211.41" cy="174.99" r="2.67" transform="translate(-45.84 270.59) rotate(-60)"/><circle class="cls-1" cx="174.84" cy="211.5" r="2.67" transform="matrix(0.87, -0.5, 0.5, 0.87, -82.33, 115.76)"/><circle class="cls-1" cx="124.91" cy="224.83" r="2.67"/><circle class="cls-1" cx="75.01" cy="211.41" r="2.67" transform="translate(-145.59 170.66) rotate(-60)"/><circle class="cls-1" cx="38.5" cy="174.84" r="2.67" transform="translate(-82.26 42.67) rotate(-30)"/><circle class="cls-1" cx="25.17" cy="124.91" r="2.67"/><circle class="cls-1" cx="38.59" cy="75.01" r="2.67" transform="translate(-45.66 70.92) rotate(-60)"/><circle class="cls-1" cx="75.16" cy="38.5" r="2.67" transform="translate(-9.18 42.74) rotate(-30)"/><circle class="cls-1" cx="125.09" cy="25.17" r="2.67"/></svg> diff --git a/MagicMirror/modules/default/compliments/README.md b/MagicMirror/modules/default/compliments/README.md new file mode 100644 index 0000000000000000000000000000000000000000..57e105ff3fbeb50dc4a0671c50826486b04a31a5 --- /dev/null +++ b/MagicMirror/modules/default/compliments/README.md @@ -0,0 +1,6 @@ +# Module: Compliments + +The `compliments` module is one of the default modules of the MagicMirror². +This module displays a random compliment. + +For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/compliments.html). diff --git a/MagicMirror/modules/default/compliments/compliments.js b/MagicMirror/modules/default/compliments/compliments.js new file mode 100644 index 0000000000000000000000000000000000000000..555bfc467867ef92497608705056eb92a710d8ca --- /dev/null +++ b/MagicMirror/modules/default/compliments/compliments.js @@ -0,0 +1,185 @@ +/* MagicMirror² + * Module: Compliments + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +Module.register("compliments", { + // Module config defaults. + defaults: { + compliments: { + anytime: ["Hey there sexy!"], + morning: ["Good morning, handsome!", "Enjoy your day!", "How was your sleep?"], + afternoon: ["Hello, beauty!", "You look sexy!", "Looking good today!"], + evening: ["Wow, you look hot!", "You look nice!", "Hi, sexy!"], + "....-01-01": ["Happy new year!"] + }, + updateInterval: 30000, + remoteFile: null, + fadeSpeed: 4000, + morningStartTime: 3, + morningEndTime: 12, + afternoonStartTime: 12, + afternoonEndTime: 17, + random: true + }, + lastIndexUsed: -1, + // Set currentweather from module + currentWeatherType: "", + + // Define required scripts. + getScripts: function () { + return ["moment.js"]; + }, + + // Define start sequence. + start: async function () { + Log.info(`Starting module: ${this.name}`); + + this.lastComplimentIndex = -1; + + if (this.config.remoteFile !== null) { + const response = await this.loadComplimentFile(); + this.config.compliments = JSON.parse(response); + this.updateDom(); + } + + // Schedule update timer. + setInterval(() => { + this.updateDom(this.config.fadeSpeed); + }, this.config.updateInterval); + }, + + /** + * Generate a random index for a list of compliments. + * + * @param {string[]} compliments Array with compliments. + * @returns {number} a random index of given array + */ + randomIndex: function (compliments) { + if (compliments.length === 1) { + return 0; + } + + const generate = function () { + return Math.floor(Math.random() * compliments.length); + }; + + let complimentIndex = generate(); + + while (complimentIndex === this.lastComplimentIndex) { + complimentIndex = generate(); + } + + this.lastComplimentIndex = complimentIndex; + + return complimentIndex; + }, + + /** + * Retrieve an array of compliments for the time of the day. + * + * @returns {string[]} array with compliments for the time of the day. + */ + complimentArray: function () { + const hour = moment().hour(); + const date = moment().format("YYYY-MM-DD"); + let compliments = []; + + // Add time of day compliments + if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime && this.config.compliments.hasOwnProperty("morning")) { + compliments = [...this.config.compliments.morning]; + } else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime && this.config.compliments.hasOwnProperty("afternoon")) { + compliments = [...this.config.compliments.afternoon]; + } else if (this.config.compliments.hasOwnProperty("evening")) { + compliments = [...this.config.compliments.evening]; + } + + // Add compliments based on weather + if (this.currentWeatherType in this.config.compliments) { + Array.prototype.push.apply(compliments, this.config.compliments[this.currentWeatherType]); + } + + // Add compliments for anytime + Array.prototype.push.apply(compliments, this.config.compliments.anytime); + + // Add compliments for special days + for (let entry in this.config.compliments) { + if (new RegExp(entry).test(date)) { + Array.prototype.push.apply(compliments, this.config.compliments[entry]); + } + } + + return compliments; + }, + + /** + * Retrieve a file from the local filesystem + * + * @returns {Promise} Resolved when the file is loaded + */ + loadComplimentFile: async function () { + const isRemote = this.config.remoteFile.indexOf("http://") === 0 || this.config.remoteFile.indexOf("https://") === 0, + url = isRemote ? this.config.remoteFile : this.file(this.config.remoteFile); + const response = await fetch(url); + return await response.text(); + }, + + /** + * Retrieve a random compliment. + * + * @returns {string} a compliment + */ + getRandomCompliment: function () { + // get the current time of day compliments list + const compliments = this.complimentArray(); + // variable for index to next message to display + let index; + // are we randomizing + if (this.config.random) { + // yes + index = this.randomIndex(compliments); + } else { + // no, sequential + // if doing sequential, don't fall off the end + index = this.lastIndexUsed >= compliments.length - 1 ? 0 : ++this.lastIndexUsed; + } + + return compliments[index] || ""; + }, + + // Override dom generator. + getDom: function () { + const wrapper = document.createElement("div"); + wrapper.className = this.config.classes ? this.config.classes : "thin xlarge bright pre-line"; + // get the compliment text + const complimentText = this.getRandomCompliment(); + // split it into parts on newline text + const parts = complimentText.split("\n"); + // create a span to hold the compliment + const compliment = document.createElement("span"); + // process all the parts of the compliment text + for (const part of parts) { + if (part !== "") { + // create a text element for each part + compliment.appendChild(document.createTextNode(part)); + // add a break + compliment.appendChild(document.createElement("BR")); + } + } + // only add compliment to wrapper if there is actual text in there + if (compliment.children.length > 0) { + // remove the last break + compliment.lastElementChild.remove(); + wrapper.appendChild(compliment); + } + return wrapper; + }, + + // Override notification handler. + notificationReceived: function (notification, payload, sender) { + if (notification === "CURRENTWEATHER_TYPE") { + this.currentWeatherType = payload.type; + } + } +}); diff --git a/MagicMirror/modules/default/defaultmodules.js b/MagicMirror/modules/default/defaultmodules.js new file mode 100644 index 0000000000000000000000000000000000000000..c74e94a45c17c487a3d412096a0e25e10a29d952 --- /dev/null +++ b/MagicMirror/modules/default/defaultmodules.js @@ -0,0 +1,12 @@ +/* MagicMirror² Default Modules List + * Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name. + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +const defaultModules = ["alert", "calendar", "clock", "compliments", "helloworld", "newsfeed", "updatenotification", "weather"]; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = defaultModules; +} diff --git a/MagicMirror/modules/default/helloworld/README.md b/MagicMirror/modules/default/helloworld/README.md new file mode 100644 index 0000000000000000000000000000000000000000..065d5f9842f0ebddd53323631579195a27073182 --- /dev/null +++ b/MagicMirror/modules/default/helloworld/README.md @@ -0,0 +1,5 @@ +# Module: Hello World + +The `helloworld` module is one of the default modules of the MagicMirror². It is a simple way to display a static text on the mirror. + +For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/helloworld.html). diff --git a/MagicMirror/modules/default/helloworld/helloworld.js b/MagicMirror/modules/default/helloworld/helloworld.js new file mode 100644 index 0000000000000000000000000000000000000000..53357d0fd2ea4b040cb8c9713621c0fb028c5dc3 --- /dev/null +++ b/MagicMirror/modules/default/helloworld/helloworld.js @@ -0,0 +1,20 @@ +/* MagicMirror² + * Module: HelloWorld + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +Module.register("helloworld", { + // Default module config. + defaults: { + text: "Hello World!" + }, + + getTemplate: function () { + return "helloworld.njk"; + }, + + getTemplateData: function () { + return this.config; + } +}); diff --git a/MagicMirror/modules/default/helloworld/helloworld.njk b/MagicMirror/modules/default/helloworld/helloworld.njk new file mode 100644 index 0000000000000000000000000000000000000000..005ca28e3ff5f205132592c20b63b3efb14a8ccc --- /dev/null +++ b/MagicMirror/modules/default/helloworld/helloworld.njk @@ -0,0 +1,5 @@ +<!-- + Use ` | safe` to allow html tages within the text string. + https://mozilla.github.io/nunjucks/templating.html#autoescaping +--> +<div>{{text | safe}}</div> diff --git a/MagicMirror/modules/default/newsfeed/README.md b/MagicMirror/modules/default/newsfeed/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0671f138ded881b11290be3de8a79310b963a3f9 --- /dev/null +++ b/MagicMirror/modules/default/newsfeed/README.md @@ -0,0 +1,6 @@ +# Module: News Feed + +The `newsfeed` module is one of the default modules of the MagicMirror². +This module displays news headlines based on an RSS feed. Scrolling through news headlines happens time-based (`updateInterval`), but can also be controlled by sending news feed specific notifications to the module. + +For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/newsfeed.html). diff --git a/MagicMirror/modules/default/newsfeed/fullarticle.njk b/MagicMirror/modules/default/newsfeed/fullarticle.njk new file mode 100644 index 0000000000000000000000000000000000000000..6570396e5e92a06271937cafe7c87652763c904a --- /dev/null +++ b/MagicMirror/modules/default/newsfeed/fullarticle.njk @@ -0,0 +1,3 @@ +<div> + <iframe class="newsfeed-fullarticle" src="{{ url }}"></iframe> +</div> \ No newline at end of file diff --git a/MagicMirror/modules/default/newsfeed/newsfeed.css b/MagicMirror/modules/default/newsfeed/newsfeed.css new file mode 100644 index 0000000000000000000000000000000000000000..2c690a48e27d460bc8750728f8cc2dd9456baebf --- /dev/null +++ b/MagicMirror/modules/default/newsfeed/newsfeed.css @@ -0,0 +1,24 @@ +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; +} + +.newsfeed-list { + list-style: none; +} + +.newsfeed-list li { + text-align: justify; + margin-bottom: 0.5em; +} diff --git a/MagicMirror/modules/default/newsfeed/newsfeed.js b/MagicMirror/modules/default/newsfeed/newsfeed.js new file mode 100644 index 0000000000000000000000000000000000000000..320c5a5dc86d639531f148c51b49c17ad791337e --- /dev/null +++ b/MagicMirror/modules/default/newsfeed/newsfeed.js @@ -0,0 +1,412 @@ +/* MagicMirror² + * Module: NewsFeed + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +Module.register("newsfeed", { + // Default module config. + defaults: { + feeds: [ + { + title: "New York Times", + url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml", + encoding: "UTF-8" //ISO-8859-1 + } + ], + showAsList: false, + showSourceTitle: true, + showPublishDate: true, + broadcastNewsFeeds: true, + broadcastNewsUpdates: true, + showDescription: false, + showTitleAsUrl: false, + wrapTitle: true, + wrapDescription: true, + truncDescription: true, + lengthDescription: 400, + hideLoading: false, + reloadInterval: 5 * 60 * 1000, // every 5 minutes + updateInterval: 10 * 1000, + animationSpeed: 2.5 * 1000, + maxNewsItems: 0, // 0 for unlimited + ignoreOldItems: false, + ignoreOlderThan: 24 * 60 * 60 * 1000, // 1 day + removeStartTags: "", + removeEndTags: "", + startTags: [], + endTags: [], + prohibitedWords: [], + scrollLength: 500, + logFeedWarnings: false, + dangerouslyDisableAutoEscaping: false + }, + + getUrlPrefix: function (item) { + if (item.useCorsProxy) { + return `${location.protocol}//${location.host}/cors?url=`; + } else { + return ""; + } + }, + + // Define required scripts. + getScripts: function () { + 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. + // Therefor 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; + }, + + // Define start sequence. + start: function () { + Log.info(`Starting module: ${this.name}`); + + // Set locale. + moment.locale(config.language); + + this.newsItems = []; + this.loaded = false; + this.error = null; + this.activeItem = 0; + this.scrollPosition = 0; + + this.registerFeeds(); + + this.isShowingDescription = this.config.showDescription; + }, + + // Override socket notification handler. + socketNotificationReceived: function (notification, payload) { + if (notification === "NEWS_ITEMS") { + this.generateFeed(payload); + + if (!this.loaded) { + if (this.config.hideLoading) { + this.show(); + } + this.scheduleUpdateInterval(); + } + + this.loaded = true; + this.error = null; + } else if (notification === "NEWSFEED_ERROR") { + this.error = this.translate(payload.error_type); + this.scheduleUpdateInterval(); + } + }, + + //Override fetching of template name + getTemplate: function () { + if (this.config.feedUrl) { + 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 { + empty: true + }; + } + if (this.activeItem >= this.newsItems.length) { + this.activeItem = 0; + } + + const item = this.newsItems[this.activeItem]; + const items = this.newsItems.map(function (item) { + item.publishDate = moment(new Date(item.pubdate)).fromNow(); + return item; + }); + + return { + loaded: true, + config: this.config, + sourceTitle: item.sourceTitle, + publishDate: moment(new Date(item.pubdate)).fromNow(), + title: item.title, + url: this.getUrlPrefix(item) + item.url, + description: item.description, + items: items + }; + }, + + getActiveItemURL: function () { + const item = this.newsItems[this.activeItem]; + if (item) { + return typeof item.url === "string" ? this.getUrlPrefix(item) + item.url : this.getUrlPrefix(item) + item.url.href; + } else { + return ""; + } + }, + + /** + * Registers the feeds to be used by the backend. + */ + registerFeeds: function () { + for (let feed of this.config.feeds) { + this.sendSocketNotification("ADD_FEED", { + feed: feed, + config: this.config + }); + } + }, + + /** + * Generate an ordered list of items for this configured module. + * + * @param {object} feeds An object with feeds returned by the node helper. + */ + generateFeed: function (feeds) { + let newsItems = []; + for (let feed in feeds) { + const feedItems = feeds[feed]; + if (this.subscribedToFeed(feed)) { + 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); + } + } + } + } + newsItems.sort(function (a, b) { + const dateA = new Date(a.pubdate); + const dateB = new Date(b.pubdate); + return dateB - dateA; + }); + + if (this.config.maxNewsItems > 0) { + newsItems = newsItems.slice(0, this.config.maxNewsItems); + } + + if (this.config.prohibitedWords.length > 0) { + newsItems = newsItems.filter(function (item) { + for (let word of this.config.prohibitedWords) { + if (item.title.toLowerCase().indexOf(word.toLowerCase()) > -1) { + return false; + } + } + return true; + }, 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 + const updatedItems = []; + newsItems.forEach((value) => { + if (this.newsItems.findIndex((value1) => value1 === value) === -1) { + // Add item to updated items list + updatedItems.push(value); + } + }); + + // check if updated items exist, if so and if we should broadcast these updates, then lets do so + if (this.config.broadcastNewsUpdates && updatedItems.length > 0) { + this.sendNotification("NEWS_FEED_UPDATE", { items: updatedItems }); + } + + this.newsItems = newsItems; + }, + + /** + * Check if this module is configured to show this feed. + * + * @param {string} feedUrl Url of the feed to check. + * @returns {boolean} True if it is subscribed, false otherwise + */ + subscribedToFeed: function (feedUrl) { + for (let feed of this.config.feeds) { + if (feed.url === feedUrl) { + return true; + } + } + return false; + }, + + /** + * Returns title for the specific feed url. + * + * @param {string} feedUrl Url of the feed + * @returns {string} The title of the feed + */ + titleForFeed: function (feedUrl) { + for (let feed of this.config.feeds) { + if (feed.url === feedUrl) { + return feed.title || ""; + } + } + return ""; + }, + + /** + * Schedule visual update. + */ + scheduleUpdateInterval: function () { + this.updateDom(this.config.animationSpeed); + + // Broadcast NewsFeed if needed + if (this.config.broadcastNewsFeeds) { + this.sendNotification("NEWS_FEED", { items: this.newsItems }); + } + + // #2638 Clear timer if it already exists + if (this.timer) clearInterval(this.timer); + + this.timer = setInterval(() => { + this.activeItem++; + this.updateDom(this.config.animationSpeed); + + // Broadcast NewsFeed if needed + if (this.config.broadcastNewsFeeds) { + this.sendNotification("NEWS_FEED", { items: this.newsItems }); + } + }, this.config.updateInterval); + }, + + resetDescrOrFullArticleAndTimer: function () { + this.isShowingDescription = this.config.showDescription; + this.config.showFullArticle = false; + this.scrollPosition = 0; + // reset bottom bar alignment + document.getElementsByClassName("region bottom bar")[0].classList.remove("newsfeed-fullarticle"); + if (!this.timer) { + this.scheduleUpdateInterval(); + } + }, + + notificationReceived: function (notification, payload, sender) { + const before = this.activeItem; + 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; + } + this.resetDescrOrFullArticleAndTimer(); + Log.debug(`${this.name} - going from article #${before} to #${this.activeItem} (of ${this.newsItems.length})`); + this.updateDom(100); + } else if (notification === "ARTICLE_PREVIOUS") { + this.activeItem--; + if (this.activeItem < 0) { + this.activeItem = this.newsItems.length - 1; + } + this.resetDescrOrFullArticleAndTimer(); + Log.debug(`${this.name} - going from article #${before} to #${this.activeItem} (of ${this.newsItems.length})`); + this.updateDom(100); + } + // if "more details" is received the first time: show article summary, on second time show full article + else if (notification === "ARTICLE_MORE_DETAILS") { + // full article is already showing, so scrolling down + if (this.config.showFullArticle === true) { + this.scrollPosition += this.config.scrollLength; + window.scrollTo(0, this.scrollPosition); + Log.debug(`${this.name} - scrolling down`); + Log.debug(`${this.name} - ARTICLE_MORE_DETAILS, scroll position: ${this.config.scrollLength}`); + } else { + this.showFullArticle(); + } + } else if (notification === "ARTICLE_SCROLL_UP") { + if (this.config.showFullArticle === true) { + this.scrollPosition -= this.config.scrollLength; + window.scrollTo(0, this.scrollPosition); + Log.debug(`${this.name} - scrolling up`); + Log.debug(`${this.name} - ARTICLE_SCROLL_UP, scroll position: ${this.config.scrollLength}`); + } + } else if (notification === "ARTICLE_LESS_DETAILS") { + this.resetDescrOrFullArticleAndTimer(); + Log.debug(`${this.name} - showing only article titles again`); + this.updateDom(100); + } else if (notification === "ARTICLE_TOGGLE_FULL") { + if (this.config.showFullArticle) { + this.activeItem++; + this.resetDescrOrFullArticleAndTimer(); + } else { + this.showFullArticle(); + } + } else if (notification === "ARTICLE_INFO_REQUEST") { + this.sendNotification("ARTICLE_INFO_RESPONSE", { + title: this.newsItems[this.activeItem].title, + source: this.newsItems[this.activeItem].sourceTitle, + date: this.newsItems[this.activeItem].pubdate, + desc: this.newsItems[this.activeItem].description, + url: this.getActiveItemURL() + }); + } + }, + + showFullArticle: function () { + this.isShowingDescription = !this.isShowingDescription; + 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].classList.add("newsfeed-fullarticle"); + } + clearInterval(this.timer); + this.timer = null; + Log.debug(`${this.name} - showing ${this.isShowingDescription ? "article description" : "full article"}`); + this.updateDom(100); + } +}); diff --git a/MagicMirror/modules/default/newsfeed/newsfeed.njk b/MagicMirror/modules/default/newsfeed/newsfeed.njk new file mode 100644 index 0000000000000000000000000000000000000000..9e7e9d78bb9eee055c9d112f202812876e736d45 --- /dev/null +++ b/MagicMirror/modules/default/newsfeed/newsfeed.njk @@ -0,0 +1,93 @@ +{% macro escapeText(text, dangerouslyDisableAutoEscaping=false) %} + {% if dangerouslyDisableAutoEscaping %} + {{ text | safe}} + {% else %} + {{ text }} + {% endif %} +{% endmacro %} + +{% macro escapeTitle(title, url, dangerouslyDisableAutoEscaping=false, showTitleAsUrl=false) %} + {% if dangerouslyDisableAutoEscaping %} + {% if showTitleAsUrl %} + <a href="{{ url }}" style="text-decoration:none;color:#ffffff" target="_blank">{{ title | safe }}</a> + {% else %} + {{ title | safe}} + {% endif %} + {% else %} + {% if showTitleAsUrl %} + <a href="{{ url }}" style="text-decoration:none;color:#ffffff" target="_blank">{{ title }}</a> + {% else %} + {{ title }} + {% endif %} + {% endif %} +{% endmacro %} + +{% if loaded %} + {% if config.showAsList %} + <ul class="newsfeed-list"> + {% for item in items %} + <li> + {% if (config.showSourceTitle and item.sourceTitle) or config.showPublishDate %} + <div class="newsfeed-source light small dimmed"> + {% if item.sourceTitle and config.showSourceTitle %} + {{ item.sourceTitle }}{% if config.showPublishDate %}, {% else %}: {% endif %} + {% endif %} + {% if config.showPublishDate %} + {{ item.publishDate }}: + {% endif %} + </div> + {% endif %} + <div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}"> + {{ escapeTitle(item.title, item.url, config.dangerouslyDisableAutoEscaping, config.showTitleAsUrl) }} + </div> + {% if config.showDescription %} + <div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}"> + {% if config.truncDescription %} + {{ escapeText(item.description | truncate(config.lengthDescription), config.dangerouslyDisableAutoEscaping) }} + {% else %} + {{ escapeText(item.description, config.dangerouslyDisableAutoEscaping) }} + {% endif %} + </div> + {% endif %} + </li> + {% endfor %} + </ul> + {% else %} + <div> + {% if (config.showSourceTitle and sourceTitle) or config.showPublishDate %} + <div class="newsfeed-source light small dimmed"> + {% if sourceTitle and config.showSourceTitle %} + {{ escapeText(sourceTitle, config.dangerouslyDisableAutoEscaping) }}{% 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 }}"> + {{ escapeTitle(title, url, config.dangerouslyDisableAutoEscaping, config.showTitleAsUrl) }} + </div> + {% if config.showDescription %} + <div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}"> + {% if config.truncDescription %} + {{ escapeText(description | truncate(config.lengthDescription), config.dangerouslyDisableAutoEscaping) }} + {% else %} + {{ escapeText(description, config.dangerouslyDisableAutoEscaping) }} + {% endif %} + </div> + {% endif %} + </div> + {% endif %} +{% elseif empty %} + <div class="small dimmed"> + {{ "NEWSFEED_NO_ITEMS" | translate | safe }} + </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 %} diff --git a/MagicMirror/modules/default/newsfeed/newsfeedfetcher.js b/MagicMirror/modules/default/newsfeed/newsfeedfetcher.js new file mode 100644 index 0000000000000000000000000000000000000000..039a3ea587e620d6a4e678b31d7b8317be449ef9 --- /dev/null +++ b/MagicMirror/modules/default/newsfeed/newsfeedfetcher.js @@ -0,0 +1,184 @@ +/* MagicMirror² + * Node Helper: Newsfeed - NewsfeedFetcher + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ + +const stream = require("stream"); +const FeedMe = require("feedme"); +const iconv = require("iconv-lite"); +const fetch = require("fetch"); +const Log = require("logger"); +const NodeHelper = require("node_helper"); + +/** + * Responsible for requesting an update on the set interval and broadcasting the data. + * + * @param {string} url URL of the news feed. + * @param {number} reloadInterval Reload interval in milliseconds. + * @param {string} encoding Encoding of the feed. + * @param {boolean} logFeedWarnings If true log warnings when there is an error parsing a news article. + * @param {boolean} useCorsProxy If true cors proxy is used for article url's. + * @class + */ +const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings, useCorsProxy) { + let reloadTimer = null; + let items = []; + + let fetchFailedCallback = function () {}; + let itemsReceivedCallback = function () {}; + + if (reloadInterval < 1000) { + reloadInterval = 1000; + } + + /* private methods */ + + /** + * Request the new items. + */ + const fetchNews = () => { + clearTimeout(reloadTimer); + reloadTimer = null; + items = []; + + const parser = new FeedMe(); + + 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"]; + const url = item.url || item.link || ""; + + if (title && pubdate) { + const regex = /(<([^>]+)>)/gi; + description = description.toString().replace(regex, ""); + + items.push({ + title: title, + description: description, + pubdate: pubdate, + url: url, + useCorsProxy: useCorsProxy + }); + } else if (logFeedWarnings) { + Log.warn("Can't parse feed item:"); + Log.warn(item); + Log.warn(`Title: ${title}`); + Log.warn(`Description: ${description}`); + Log.warn(`Pubdate: ${pubdate}`); + } + }); + + parser.on("end", () => { + this.broadcastItems(); + }); + + parser.on("error", (error) => { + fetchFailedCallback(this, error); + scheduleTimer(); + }); + + //"end" event is not broadcast if the feed is empty but "finish" is used for both + parser.on("finish", () => { + scheduleTimer(); + }); + + parser.on("ttl", (minutes) => { + try { + // 86400000 = 24 hours is mentioned in the docs as maximum value: + const ttlms = Math.min(minutes * 60 * 1000, 86400000); + if (ttlms > reloadInterval) { + reloadInterval = ttlms; + Log.info(`Newsfeed-Fetcher: reloadInterval set to ttl=${reloadInterval} for url ${url}`); + } + } catch (error) { + Log.warn(`Newsfeed-Fetcher: feed ttl is no valid integer=${minutes} for url ${url}`); + } + }); + + const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); + const headers = { + "User-Agent": `Mozilla/5.0 (Node.js ${nodeVersion}) MagicMirror/${global.version}`, + "Cache-Control": "max-age=0, no-cache, no-store, must-revalidate", + Pragma: "no-cache" + }; + + fetch(url, { headers: headers }) + .then(NodeHelper.checkFetchStatus) + .then((response) => { + let nodeStream; + if (response.body instanceof stream.Readable) { + nodeStream = response.body; + } else { + nodeStream = stream.Readable.fromWeb(response.body); + } + nodeStream.pipe(iconv.decodeStream(encoding)).pipe(parser); + }) + .catch((error) => { + fetchFailedCallback(this, error); + scheduleTimer(); + }); + }; + + /** + * Schedule the timer for the next update. + */ + const scheduleTimer = function () { + clearTimeout(reloadTimer); + reloadTimer = setTimeout(function () { + fetchNews(); + }, reloadInterval); + }; + + /* public methods */ + + /** + * Update the reload interval, but only if we need to increase the speed. + * + * @param {number} interval Interval for the update in milliseconds. + */ + this.setReloadInterval = function (interval) { + if (interval > 1000 && interval < reloadInterval) { + reloadInterval = interval; + } + }; + + /** + * Initiate fetchNews(); + */ + this.startFetch = function () { + fetchNews(); + }; + + /** + * Broadcast the existing items. + */ + this.broadcastItems = function () { + if (items.length <= 0) { + Log.info("Newsfeed-Fetcher: No items to broadcast yet."); + return; + } + Log.info(`Newsfeed-Fetcher: Broadcasting ${items.length} items.`); + itemsReceivedCallback(this); + }; + + this.onReceive = function (callback) { + itemsReceivedCallback = callback; + }; + + this.onError = function (callback) { + fetchFailedCallback = callback; + }; + + this.url = function () { + return url; + }; + + this.items = function () { + return items; + }; +}; + +module.exports = NewsfeedFetcher; diff --git a/MagicMirror/modules/default/newsfeed/node_helper.js b/MagicMirror/modules/default/newsfeed/node_helper.js new file mode 100644 index 0000000000000000000000000000000000000000..534b7020338613e763f0554803665f44faa24b2d --- /dev/null +++ b/MagicMirror/modules/default/newsfeed/node_helper.js @@ -0,0 +1,87 @@ +/* MagicMirror² + * Node Helper: Newsfeed + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ + +const NodeHelper = require("node_helper"); +const Log = require("logger"); +const NewsfeedFetcher = require("./newsfeedfetcher"); + +module.exports = NodeHelper.create({ + // Override start method. + start: function () { + Log.log(`Starting node helper for: ${this.name}`); + this.fetchers = []; + }, + + // Override socketNotificationReceived received. + socketNotificationReceived: function (notification, payload) { + if (notification === "ADD_FEED") { + this.createFetcher(payload.feed, payload.config); + } + }, + + /** + * Creates a fetcher for a new feed if it doesn't exist yet. + * Otherwise it reuses the existing one. + * + * @param {object} feed The feed object + * @param {object} config The configuration object + */ + createFetcher: function (feed, config) { + const url = feed.url || ""; + const encoding = feed.encoding || "UTF-8"; + const reloadInterval = feed.reloadInterval || config.reloadInterval || 5 * 60 * 1000; + let useCorsProxy = feed.useCorsProxy; + if (useCorsProxy === undefined) useCorsProxy = true; + + try { + new URL(url); + } catch (error) { + Log.error("Newsfeed Error. Malformed newsfeed url: ", url, error); + this.sendSocketNotification("NEWSFEED_ERROR", { error_type: "MODULE_ERROR_MALFORMED_URL" }); + return; + } + + let fetcher; + if (typeof this.fetchers[url] === "undefined") { + Log.log(`Create new newsfetcher for url: ${url} - Interval: ${reloadInterval}`); + fetcher = new NewsfeedFetcher(url, reloadInterval, encoding, config.logFeedWarnings, useCorsProxy); + + fetcher.onReceive(() => { + this.broadcastFeeds(); + }); + + fetcher.onError((fetcher, error) => { + Log.error("Newsfeed Error. Could not fetch newsfeed: ", url, error); + let error_type = NodeHelper.checkFetchError(error); + this.sendSocketNotification("NEWSFEED_ERROR", { + error_type + }); + }); + + this.fetchers[url] = fetcher; + } else { + Log.log(`Use existing newsfetcher for url: ${url}`); + fetcher = this.fetchers[url]; + fetcher.setReloadInterval(reloadInterval); + fetcher.broadcastItems(); + } + + fetcher.startFetch(); + }, + + /** + * Creates an object with all feed items of the different registered feeds, + * and broadcasts these using sendSocketNotification. + */ + broadcastFeeds: function () { + const feeds = {}; + for (let f in this.fetchers) { + feeds[f] = this.fetchers[f].items(); + } + this.sendSocketNotification("NEWS_ITEMS", feeds); + } +}); diff --git a/MagicMirror/modules/default/newsfeed/oldconfig.njk b/MagicMirror/modules/default/newsfeed/oldconfig.njk new file mode 100644 index 0000000000000000000000000000000000000000..db0f8d4b6388f2553b4a6d3ce60120879a0ba30e --- /dev/null +++ b/MagicMirror/modules/default/newsfeed/oldconfig.njk @@ -0,0 +1,3 @@ +<div class="small bright"> + {{ "MODULE_CONFIG_CHANGED" | translate({MODULE_NAME: "Newsfeed"}) | safe }} +</div> \ No newline at end of file diff --git a/MagicMirror/modules/default/updatenotification/README.md b/MagicMirror/modules/default/updatenotification/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d700376598053bf394e8c80b98eddc928d583ea0 --- /dev/null +++ b/MagicMirror/modules/default/updatenotification/README.md @@ -0,0 +1,6 @@ +# Module: Update Notification + +The `updatenotification` module is one of the default modules of the MagicMirror². +This will display a message whenever a new version of the MagicMirror² application is available. + +For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/updatenotification.html). diff --git a/MagicMirror/modules/default/updatenotification/git_helper.js b/MagicMirror/modules/default/updatenotification/git_helper.js new file mode 100644 index 0000000000000000000000000000000000000000..b4e0299c1803d6c0446534054e844142de960626 --- /dev/null +++ b/MagicMirror/modules/default/updatenotification/git_helper.js @@ -0,0 +1,192 @@ +const util = require("util"); +const exec = util.promisify(require("child_process").exec); +const fs = require("fs"); +const path = require("path"); +const Log = require("logger"); + +const BASE_DIR = path.normalize(`${__dirname}/../../../`); + +class GitHelper { + constructor() { + this.gitRepos = []; + } + + getRefRegex(branch) { + return new RegExp(`s*([a-z,0-9]+[.][.][a-z,0-9]+) ${branch}`, "g"); + } + + async execShell(command) { + const { stdout = "", stderr = "" } = await exec(command); + + return { stdout, stderr }; + } + + async isGitRepo(moduleFolder) { + const { stderr } = await this.execShell(`cd ${moduleFolder} && git remote -v`); + + if (stderr) { + Log.error(`Failed to fetch git data for ${moduleFolder}: ${stderr}`); + + return false; + } + + return true; + } + + async add(moduleName) { + let moduleFolder = BASE_DIR; + + if (moduleName !== "MagicMirror") { + moduleFolder = `${moduleFolder}modules/${moduleName}`; + } + + try { + Log.info(`Checking git for module: ${moduleName}`); + // Throws error if file doesn't exist + fs.statSync(path.join(moduleFolder, ".git")); + + // Fetch the git or throw error if no remotes + const isGitRepo = await this.isGitRepo(moduleFolder); + + if (isGitRepo) { + // Folder has .git and has at least one git remote, watch this folder + this.gitRepos.push({ module: moduleName, folder: moduleFolder }); + } + } catch (err) { + // Error when directory .git doesn't exist or doesn't have any remotes + // This module is not managed with git, skip + } + } + + async getStatusInfo(repo) { + let gitInfo = { + module: repo.module, + behind: 0, // commits behind + current: "", // branch name + hash: "", // current hash + tracking: "", // remote branch + isBehindInStatus: false + }; + + if (repo.module === "MagicMirror") { + // the hash is only needed for the mm repo + const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git rev-parse HEAD`); + + if (stderr) { + Log.error(`Failed to get current commit hash for ${repo.module}: ${stderr}`); + } + + gitInfo.hash = stdout; + } + + const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git status -sb`); + + if (stderr) { + Log.error(`Failed to get git status for ${repo.module}: ${stderr}`); + // exit without git status info + return; + } + + // only the first line of stdout is evaluated + let status = stdout.split("\n")[0]; + // examples for status: + // ## develop...origin/develop + // ## master...origin/master [behind 8] + // ## master...origin/master [ahead 8, behind 1] + status = status.match(/## (.*)\.\.\.([^ ]*)(?: .*behind (\d+))?/); + // examples for status: + // [ '## develop...origin/develop', 'develop', 'origin/develop' ] + // [ '## master...origin/master [behind 8]', 'master', 'origin/master', '8' ] + // [ '## master...origin/master [ahead 8, behind 1]', 'master', 'origin/master', '1' ] + gitInfo.current = status[1]; + gitInfo.tracking = status[2]; + + if (status[3]) { + // git fetch was already called before so `git status -sb` delivers already the behind number + gitInfo.behind = parseInt(status[3]); + gitInfo.isBehindInStatus = true; + } + + return gitInfo; + } + + async getRepoInfo(repo) { + const gitInfo = await this.getStatusInfo(repo); + + if (!gitInfo) { + return; + } + + if (gitInfo.isBehindInStatus && (gitInfo.module !== "MagicMirror" || gitInfo.current !== "master")) { + return gitInfo; + } + + const { stderr } = await this.execShell(`cd ${repo.folder} && git fetch -n --dry-run`); + + // example output: + // From https://github.com/MichMich/MagicMirror + // e40ddd4..06389e3 develop -> origin/develop + // here the result is in stderr (this is a git default, don't ask why ...) + const matches = stderr.match(this.getRefRegex(gitInfo.current)); + + // this is the default if there was no match from "git fetch -n --dry-run". + // Its a fallback because if there was a real "git fetch", the above "git fetch -n --dry-run" would deliver nothing. + let refDiff = `${gitInfo.current}..origin/${gitInfo.current}`; + if (matches && matches[0]) { + refDiff = matches[0]; + } + + // get behind with refs + try { + const { stdout } = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path --count ${refDiff}`); + gitInfo.behind = parseInt(stdout); + + // for MagicMirror-Repo and "master" branch avoid getting notified when no tag is in refDiff + // so only releases are reported and we can change e.g. the README.md without sending notifications + if (gitInfo.behind > 0 && gitInfo.module === "MagicMirror" && gitInfo.current === "master") { + let tagList = ""; + try { + const { stdout } = await this.execShell(`cd ${repo.folder} && git ls-remote -q --tags --refs`); + tagList = stdout.trim(); + } catch (err) { + Log.error(`Failed to get tag list for ${repo.module}: ${err}`); + } + // check if tag is between commits and only report behind > 0 if so + try { + const { stdout } = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path ${refDiff}`); + let cnt = 0; + for (const ref of stdout.trim().split("\n")) { + if (tagList.includes(ref)) cnt++; // tag found + } + if (cnt === 0) gitInfo.behind = 0; + } catch (err) { + Log.error(`Failed to get git revisions for ${repo.module}: ${err}`); + } + } + + return gitInfo; + } catch (err) { + Log.error(`Failed to get git revisions for ${repo.module}: ${err}`); + } + } + + async getRepos() { + const gitResultList = []; + + for (const repo of this.gitRepos) { + try { + const gitInfo = await this.getRepoInfo(repo); + + if (gitInfo) { + gitResultList.push(gitInfo); + } + } catch (e) { + Log.error(`Failed to retrieve repo info for ${repo.module}: ${e}`); + } + } + + return gitResultList; + } +} + +module.exports = GitHelper; diff --git a/MagicMirror/modules/default/updatenotification/node_helper.js b/MagicMirror/modules/default/updatenotification/node_helper.js new file mode 100644 index 0000000000000000000000000000000000000000..ae3e03751d427e24641cd3f96cd89c7849ee151e --- /dev/null +++ b/MagicMirror/modules/default/updatenotification/node_helper.js @@ -0,0 +1,72 @@ +const NodeHelper = require("node_helper"); +const defaultModules = require("../defaultmodules"); +const GitHelper = require("./git_helper"); + +const ONE_MINUTE = 60 * 1000; + +module.exports = NodeHelper.create({ + config: {}, + + updateTimer: null, + updateProcessStarted: false, + + gitHelper: new GitHelper(), + + async configureModules(modules) { + for (const moduleName of modules) { + if (!this.ignoreUpdateChecking(moduleName)) { + await this.gitHelper.add(moduleName); + } + } + + if (!this.ignoreUpdateChecking("MagicMirror")) { + await this.gitHelper.add("MagicMirror"); + } + }, + + async socketNotificationReceived(notification, payload) { + if (notification === "CONFIG") { + this.config = payload; + } else if (notification === "MODULES") { + // if this is the 1st time thru the update check process + if (!this.updateProcessStarted) { + this.updateProcessStarted = true; + await this.configureModules(payload); + await this.performFetch(); + } + } + }, + + async performFetch() { + const repos = await this.gitHelper.getRepos(); + + for (const repo of repos) { + this.sendSocketNotification("STATUS", repo); + } + + this.scheduleNextFetch(this.config.updateInterval); + }, + + scheduleNextFetch(delay) { + clearTimeout(this.updateTimer); + + this.updateTimer = setTimeout(() => { + this.performFetch(); + }, Math.max(delay, ONE_MINUTE)); + }, + + ignoreUpdateChecking(moduleName) { + // Should not check for updates for default modules + if (defaultModules.includes(moduleName)) { + return true; + } + + // Should not check for updates for ignored modules + if (this.config.ignoreModules.includes(moduleName)) { + return true; + } + + // The rest of the modules that passes should check for updates + return false; + } +}); diff --git a/MagicMirror/modules/default/updatenotification/updatenotification.css b/MagicMirror/modules/default/updatenotification/updatenotification.css new file mode 100644 index 0000000000000000000000000000000000000000..deed9e64ca0a94ef28e417362db3a4dba672ba5c --- /dev/null +++ b/MagicMirror/modules/default/updatenotification/updatenotification.css @@ -0,0 +1,3 @@ +.module.updatenotification a.difflink { + text-decoration: none; +} diff --git a/MagicMirror/modules/default/updatenotification/updatenotification.js b/MagicMirror/modules/default/updatenotification/updatenotification.js new file mode 100644 index 0000000000000000000000000000000000000000..602e76f464223ac1f0430275698b1277d67cd30d --- /dev/null +++ b/MagicMirror/modules/default/updatenotification/updatenotification.js @@ -0,0 +1,89 @@ +/* MagicMirror² + * Module: UpdateNotification + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +Module.register("updatenotification", { + defaults: { + updateInterval: 10 * 60 * 1000, // every 10 minutes + refreshInterval: 24 * 60 * 60 * 1000, // one day + ignoreModules: [] + }, + + suspended: false, + moduleList: {}, + + start() { + Log.info(`Starting module: ${this.name}`); + this.addFilters(); + setInterval(() => { + this.moduleList = {}; + this.updateDom(2); + }, this.config.refreshInterval); + }, + + suspend() { + this.suspended = true; + }, + + resume() { + this.suspended = false; + this.updateDom(2); + }, + + notificationReceived(notification) { + if (notification === "DOM_OBJECTS_CREATED") { + this.sendSocketNotification("CONFIG", this.config); + this.sendSocketNotification("MODULES", Object.keys(Module.definitions)); + } + }, + + socketNotificationReceived(notification, payload) { + if (notification === "STATUS") { + this.updateUI(payload); + } + }, + + getStyles() { + return [`${this.name}.css`]; + }, + + getTemplate() { + return `${this.name}.njk`; + }, + + getTemplateData() { + return { moduleList: this.moduleList, suspended: this.suspended }; + }, + + updateUI(payload) { + if (payload && payload.behind > 0) { + // if we haven't seen info for this module + if (this.moduleList[payload.module] === undefined) { + // save it + this.moduleList[payload.module] = payload; + this.updateDom(2); + } + } else if (payload && payload.behind === 0) { + // if the module WAS in the list, but shouldn't be + if (this.moduleList[payload.module] !== undefined) { + // remove it + delete this.moduleList[payload.module]; + this.updateDom(2); + } + } + }, + + addFilters() { + this.nunjucksEnvironment().addFilter("diffLink", (text, status) => { + if (status.module !== "MagicMirror") { + return text; + } + + const localRef = status.hash; + const remoteRef = status.tracking.replace(/.*\//, ""); + return `<a href="https://github.com/MichMich/MagicMirror/compare/${localRef}...${remoteRef}" class="xsmall dimmed difflink" target="_blank">${text}</a>`; + }); + } +}); diff --git a/MagicMirror/modules/default/updatenotification/updatenotification.njk b/MagicMirror/modules/default/updatenotification/updatenotification.njk new file mode 100644 index 0000000000000000000000000000000000000000..77d7975413bb2541ed0c01ed643cf7b89c26dccf --- /dev/null +++ b/MagicMirror/modules/default/updatenotification/updatenotification.njk @@ -0,0 +1,15 @@ +{% if not suspended %} + {% for name, status in moduleList %} + <div class="small bright"> + <i class="fas fa-exclamation-circle"></i> + <span> + {% set mainTextLabel = "UPDATE_NOTIFICATION" if name === "MagicMirror" else "UPDATE_NOTIFICATION_MODULE" %} + {{ mainTextLabel | translate({MODULE_NAME: name}) }} + </span> + </div> + <div class="xsmall dimmed"> + {% set subTextLabel = "UPDATE_INFO_SINGLE" if status.behind === 1 else "UPDATE_INFO_MULTIPLE" %} + {{ subTextLabel | translate({COMMIT_COUNT: status.behind, BRANCH_NAME: status.current}) | diffLink(status) | safe }} + </div> + {% endfor %} +{% endif %} diff --git a/MagicMirror/modules/default/utils.js b/MagicMirror/modules/default/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..604ab0431f103bfb1fe9bf8f7700d95b29f1cfe4 --- /dev/null +++ b/MagicMirror/modules/default/utils.js @@ -0,0 +1,147 @@ +/** + * A function to make HTTP requests via the server to avoid CORS-errors. + * + * @param {string} url the url to fetch from + * @param {string} type what contenttype to expect in the response, can be "json" or "xml" + * @param {boolean} useCorsProxy A flag to indicate + * @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send + * @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive + * @returns {Promise} resolved when the fetch is done. The response headers is placed in a headers-property (provided the response does not already contain a headers-property). + */ +async function performWebRequest(url, type = "json", useCorsProxy = false, requestHeaders = undefined, expectedResponseHeaders = undefined) { + const request = {}; + if (useCorsProxy) { + url = getCorsUrl(url, requestHeaders, expectedResponseHeaders); + } else { + request.headers = getHeadersToSend(requestHeaders); + } + const response = await fetch(url, request); + const data = await response.text(); + + if (type === "xml") { + return new DOMParser().parseFromString(data, "text/html"); + } else { + if (!data || !data.length > 0) return undefined; + + const dataResponse = JSON.parse(data); + if (!dataResponse.headers) { + dataResponse.headers = getHeadersFromResponse(expectedResponseHeaders, response); + } + return dataResponse; + } +} + +/** + * Gets a URL that will be used when calling the CORS-method on the server. + * + * @param {string} url the url to fetch from + * @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send + * @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive + * @returns {string} to be used as URL when calling CORS-method on server. + */ +const getCorsUrl = function (url, requestHeaders, expectedResponseHeaders) { + if (!url || url.length < 1) { + throw new Error(`Invalid URL: ${url}`); + } else { + let corsUrl = `${location.protocol}//${location.host}/cors?`; + + const requestHeaderString = getRequestHeaderString(requestHeaders); + if (requestHeaderString) corsUrl = `${corsUrl}sendheaders=${requestHeaderString}`; + + const expectedResponseHeadersString = getExpectedResponseHeadersString(expectedResponseHeaders); + if (requestHeaderString && expectedResponseHeadersString) { + corsUrl = `${corsUrl}&expectedheaders=${expectedResponseHeadersString}`; + } else if (expectedResponseHeadersString) { + corsUrl = `${corsUrl}expectedheaders=${expectedResponseHeadersString}`; + } + + if (requestHeaderString || expectedResponseHeadersString) { + return `${corsUrl}&url=${url}`; + } + return `${corsUrl}url=${url}`; + } +}; + +/** + * Gets the part of the CORS URL that represents the HTTP headers to send. + * + * @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send + * @returns {string} to be used as request-headers component in CORS URL. + */ +const getRequestHeaderString = function (requestHeaders) { + let requestHeaderString = ""; + if (requestHeaders) { + for (const header of requestHeaders) { + if (requestHeaderString.length === 0) { + requestHeaderString = `${header.name}:${encodeURIComponent(header.value)}`; + } else { + requestHeaderString = `${requestHeaderString},${header.name}:${encodeURIComponent(header.value)}`; + } + } + return requestHeaderString; + } + return undefined; +}; + +/** + * Gets headers and values to attach to the web request. + * + * @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send + * @returns {object} An object specifying name and value of the headers. + */ +const getHeadersToSend = (requestHeaders) => { + const headersToSend = {}; + if (requestHeaders) { + for (const header of requestHeaders) { + headersToSend[header.name] = header.value; + } + } + + return headersToSend; +}; + +/** + * Gets the part of the CORS URL that represents the expected HTTP headers to receive. + * + * @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive + * @returns {string} to be used as the expected HTTP-headers component in CORS URL. + */ +const getExpectedResponseHeadersString = function (expectedResponseHeaders) { + let expectedResponseHeadersString = ""; + if (expectedResponseHeaders) { + for (const header of expectedResponseHeaders) { + if (expectedResponseHeadersString.length === 0) { + expectedResponseHeadersString = `${header}`; + } else { + expectedResponseHeadersString = `${expectedResponseHeadersString},${header}`; + } + } + return expectedResponseHeaders; + } + return undefined; +}; + +/** + * Gets the values for the expected headers from the response. + * + * @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to receive + * @param {Response} response the HTTP response + * @returns {string} to be used as the expected HTTP-headers component in CORS URL. + */ +const getHeadersFromResponse = (expectedResponseHeaders, response) => { + const responseHeaders = []; + + if (expectedResponseHeaders) { + for (const header of expectedResponseHeaders) { + const headerValue = response.headers.get(header); + responseHeaders.push({ name: header, value: headerValue }); + } + } + + return responseHeaders; +}; + +if (typeof module !== "undefined") + module.exports = { + performWebRequest + }; diff --git a/MagicMirror/modules/default/weather/README.md b/MagicMirror/modules/default/weather/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7effbb1cf0f070a2d8a16dc58c1ddf810a5bc203 --- /dev/null +++ b/MagicMirror/modules/default/weather/README.md @@ -0,0 +1,5 @@ +# Weather Module + +This module will be configurable to be used as a current weather view, or to show the forecast. This way the module can be used twice to fulfill both purposes. + +For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html). diff --git a/MagicMirror/modules/default/weather/current.njk b/MagicMirror/modules/default/weather/current.njk new file mode 100644 index 0000000000000000000000000000000000000000..df673afeef7ffa1ec31bf8ab899c649bfa313a72 --- /dev/null +++ b/MagicMirror/modules/default/weather/current.njk @@ -0,0 +1,83 @@ +{% if current %} + {% if not config.onlyTemp %} + <div class="normal medium"> + <span class="wi wi-strong-wind dimmed"></span> + <span> + {{ current.windSpeed | unit("wind") | round }} + {% if config.showWindDirection %} + <sup> + {% if config.showWindDirectionAsArrow %} + <i class="fas fa-long-arrow-alt-down" style="transform:rotate({{ current.windFromDirection }}deg);"></i> + {% else %} + {{ current.cardinalWindDirection() | translate }} + {% endif %} + + </sup> + {% endif %} + </span> + {% if config.showHumidity and current.humidity %} + <span>{{ current.humidity | decimalSymbol }}</span><sup> <i class="wi wi-humidity humidity-icon"></i></sup> + {% endif %} + {% if config.showSun %} + <span class="wi dimmed wi-{{ current.nextSunAction() }}"></span> + <span> + {% if current.nextSunAction() === "sunset" %} + {{ current.sunset | formatTime }} + {% else %} + {{ current.sunrise | formatTime }} + {% endif %} + </span> + {% endif %} + </div> + {% endif %} + <div class="large light"> + <span class="wi weathericon wi-{{current.weatherType}}"></span> + <span class="bright"> + {{ current.temperature | roundValue | unit("temperature") | decimalSymbol }} + </span> + </div> + <div class="normal light indoor"> + {% if config.showIndoorTemperature and indoor.temperature %} + <div> + <span class="fas fa-home"></span> + <span class="bright"> + {{ indoor.temperature | roundValue | unit("temperature") | decimalSymbol }} + </span> + </div> + {% endif %} + {% if config.showIndoorHumidity and indoor.humidity %} + <div> + <span class="fas fa-tint"></span> + <span class="bright"> + {{ indoor.humidity | roundValue | unit("humidity") | decimalSymbol }} + </span> + </div> + {% endif %} + </div> + {% if (config.showFeelsLike or config.showPrecipitationAmount or config.showPrecipitationProbability) and not config.onlyTemp %} + <div class="normal medium feelslike"> + {% if config.showFeelsLike %} + <span class="dimmed"> + {{ "FEELS" | translate({DEGREE: current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }) }} + </span><br/> + {% endif %} + {% if config.showPrecipitationAmount and current.precipitationAmount %} + <span class="dimmed"> + <span class="precipitationLeadText">{{ "PRECIP_AMOUNT" | translate }}</span> {{ current.precipitationAmount | unit("precip", current.precipitationUnits) }} + </span><br/> + {% endif %} + {% if config.showPrecipitationProbability and current.precipitationProbability %} + <span class="dimmed"> + <span class="precipitationLeadText">{{ "PRECIP_POP" | translate }}</span> {{ current.precipitationProbability }}% + </span> + {% endif %} + </div> + {% endif %} +{% else %} + <div class="dimmed light small"> + {{ "LOADING" | translate }} + </div> +{% endif %} + +<!-- Uncomment the line below to see the contents of the `current` object. --> +<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{current | dump}}</div> --> diff --git a/MagicMirror/modules/default/weather/forecast.njk b/MagicMirror/modules/default/weather/forecast.njk new file mode 100644 index 0000000000000000000000000000000000000000..0ea390f0a5f48aee78699cac04b7fe4f08fea556 --- /dev/null +++ b/MagicMirror/modules/default/weather/forecast.njk @@ -0,0 +1,46 @@ +{% if forecast %} + {% set numSteps = forecast | calcNumSteps %} + {% set currentStep = 0 %} + <table class="{{ config.tableClass }}"> + {% if config.ignoreToday %} + {% set forecast = forecast.splice(1) %} + {% endif %} + {% set forecast = forecast.slice(0, numSteps) %} + {% for f in forecast %} + <tr {% if config.colored %}class="colored"{% endif %} {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}> + {% if (currentStep == 0) and config.ignoreToday == false and config.absoluteDates == false %} + <td class="day">{{ "TODAY" | translate }}</td> + {% elif (currentStep == 1) and config.ignoreToday == false and config.absoluteDates == false %} + <td class="day">{{ "TOMORROW" | translate }}</td> + {% else %} + <td class="day">{{ f.date.format('ddd') }}</td> + {% endif %} + <td class="bright weather-icon"><span class="wi weathericon wi-{{ f.weatherType }}"></span></td> + <td class="align-right bright max-temp"> + {{ f.maxTemperature | roundValue | unit("temperature") | decimalSymbol }} + </td> + <td class="align-right min-temp"> + {{ f.minTemperature | roundValue | unit("temperature") | decimalSymbol }} + </td> + {% if config.showPrecipitationAmount %} + <td class="align-right bright precipitation-amount"> + {{ f.precipitationAmount | unit("precip", f.precipitationUnits) }} + </td> + {% endif %} + {% if config.showPrecipitationProbability %} + <td class="align-right bright precipitation-prob"> + {{ f.precipitationProbability | unit("precip", "%") }} + </td> + {% endif %} + </tr> + {% set currentStep = currentStep + 1 %} + {% endfor %} + </table> +{% else %} + <div class="dimmed light small"> + {{ "LOADING" | translate }} + </div> +{% endif %} + +<!-- Uncomment the line below to see the contents of the `forecast` object. --> +<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{forecast | dump}}</div> --> diff --git a/MagicMirror/modules/default/weather/hourly.njk b/MagicMirror/modules/default/weather/hourly.njk new file mode 100644 index 0000000000000000000000000000000000000000..a0699fab39c955c041125137d392bea5d0f136fa --- /dev/null +++ b/MagicMirror/modules/default/weather/hourly.njk @@ -0,0 +1,34 @@ +{% if hourly %} + {% set numSteps = hourly | calcNumEntries %} + {% set currentStep = 0 %} + <table class="{{ config.tableClass }}"> + {% set hours = hourly.slice(0, numSteps) %} + {% for hour in hours %} + <tr {% if config.colored %}class="colored"{% endif %} {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}> + <td class="day">{{ hour.date | formatTime }}</td> + <td class="bright weather-icon"><span class="wi weathericon wi-{{ hour.weatherType }}"></span></td> + <td class="align-right bright"> + {{ hour.temperature | roundValue | unit("temperature") }} + </td> + {% if config.showPrecipitationAmount %} + <td class="align-right bright precipitation-amount"> + {{ hour.precipitationAmount | unit("precip", hour.precipitationUnits) }} + </td> + {% endif %} + {% if config.showPrecipitationProbability %} + <td class="align-right bright precipitation-prob"> + {{ hour.precipitationProbability | unit("precip", "%") }} + </td> + {% endif %} + </tr> + {% set currentStep = currentStep + 1 %} + {% endfor %} + </table> +{% else %} + <div class="dimmed light small"> + {{ "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">{{hourly | dump}}</div> --> diff --git a/MagicMirror/modules/default/weather/providers/README.md b/MagicMirror/modules/default/weather/providers/README.md new file mode 100644 index 0000000000000000000000000000000000000000..faa60a058a150b318004d2a0d538121ea45e99e1 --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/README.md @@ -0,0 +1,3 @@ +# Weather Module Weather Provider Development Documentation + +For how to develop your own weather provider, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/development/weather-provider.html). diff --git a/MagicMirror/modules/default/weather/providers/envcanada.js b/MagicMirror/modules/default/weather/providers/envcanada.js new file mode 100644 index 0000000000000000000000000000000000000000..39ddba2a1c7885757716bc056115aa0f97f5f990 --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/envcanada.js @@ -0,0 +1,572 @@ +/* global WeatherProvider, WeatherObject, WeatherUtils */ + +/* MagicMirror² + * Module: Weather + * Provider: Environment Canada (EC) + * + * This class is a provider for Environment Canada MSC Datamart + * Note that this is only for Canadian locations and does not require an API key (access is anonymous) + * + * EC Documentation at following links: + * https://dd.weather.gc.ca/citypage_weather/schema/ + * https://eccc-msc.github.io/open-data/msc-datamart/readme_en/ + * + * This module supports Canadian locations only and requires 2 additional config parameters: + * + * siteCode - the city/town unique identifier for which weather is to be displayed. Format is 's0000000'. + * + * provCode - the 2-character province code for the selected city/town. + * + * Example: for Toronto, Ontario, the following parameters would be used + * + * siteCode: 's0000458', + * provCode: 'ON' + * + * To determine the siteCode and provCode values for a Canadian city/town, look at the Environment Canada document + * at https://dd.weather.gc.ca/citypage_weather/docs/site_list_en.csv (or site_list_fr.csv). There you will find a table + * with locations you can search under column B (English Names), with the corresponding siteCode under + * column A (Codes) and provCode under column C (Province). + * + * Original by Kevin Godin + * + * License to use Environment Canada (EC) data is detailed here: + * https://eccc-msc.github.io/open-data/licence/readme_en/ + * + */ + +WeatherProvider.register("envcanada", { + // Set the name of the provider for debugging and alerting purposes (eg. provide eye-catcher) + providerName: "Environment Canada", + + // Set the default config properties that is specific to this provider + defaults: { + useCorsProxy: true, + siteCode: "s1234567", + provCode: "ON" + }, + + // + // Set config values (equates to weather module config values). Also set values pertaining to caching of + // Today's temperature forecast (for use in the Forecast functions below) + // + setConfig: function (config) { + this.config = config; + + this.todayTempCacheMin = 0; + this.todayTempCacheMax = 0; + this.todayCached = false; + this.cacheCurrentTemp = 999; + }, + + // + // Called when the weather provider is started + // + start: function () { + Log.info(`Weather provider: ${this.providerName} started.`); + this.setFetchedLocation(this.config.location); + }, + + // + // Override the fetchCurrentWeather method to query EC and construct a Current weather object + // + fetchCurrentWeather() { + this.fetchData(this.getUrl(), "xml") + .then((data) => { + if (!data) { + // Did not receive usable new data. + return; + } + const currentWeather = this.generateWeatherObjectFromCurrentWeather(data); + + this.setCurrentWeather(currentWeather); + }) + .catch(function (request) { + Log.error("Could not load EnvCanada site data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + // + // Override the fetchWeatherForecast method to query EC and construct Forecast weather objects + // + fetchWeatherForecast() { + this.fetchData(this.getUrl(), "xml") + .then((data) => { + if (!data) { + // Did not receive usable new data. + return; + } + const forecastWeather = this.generateWeatherObjectsFromForecast(data); + + this.setWeatherForecast(forecastWeather); + }) + .catch(function (request) { + Log.error("Could not load EnvCanada forecast data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + // + // Override the fetchWeatherHourly method to query EC and construct Forecast weather objects + // + fetchWeatherHourly() { + this.fetchData(this.getUrl(), "xml") + .then((data) => { + if (!data) { + // Did not receive usable new data. + return; + } + const hourlyWeather = this.generateWeatherObjectsFromHourly(data); + + this.setWeatherHourly(hourlyWeather); + }) + .catch(function (request) { + Log.error("Could not load EnvCanada hourly data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + ////////////////////////////////////////////////////////////////////////////////// + // + // Environment Canada methods - not part of the standard Provider methods + // + ////////////////////////////////////////////////////////////////////////////////// + + // + // Build the EC URL based on the Site Code and Province Code specified in the config params. Note that the + // URL defaults to the English version simply because there is no language dependency in the data + // being accessed. This is only pertinent when using the EC data elements that contain a textual forecast. + // + getUrl() { + return `https://dd.weather.gc.ca/citypage_weather/xml/${this.config.provCode}/${this.config.siteCode}_e.xml`; + }, + + // + // Generate a WeatherObject based on current EC weather conditions + // + + generateWeatherObjectFromCurrentWeather(ECdoc) { + const currentWeather = new WeatherObject(); + + // There are instances where EC will update weather data and current temperature will not be + // provided. While this is a defect in the EC systems, we need to accommodate to avoid a current temp + // of NaN being displayed. Therefore... whenever we get a valid current temp from EC, we will cache + // the value. Whenever EC data is missing current temp, we will provide the cached value + // instead. This is reasonable since the cached value will typically be accurate within the previous + // hour. The only time this does not work as expected is when MM is restarted and the first query to + // EC finds no current temp. In this scenario, MM will end up displaying a current temp of null; + + if (ECdoc.querySelector("siteData currentConditions temperature").textContent) { + currentWeather.temperature = ECdoc.querySelector("siteData currentConditions temperature").textContent; + this.cacheCurrentTemp = currentWeather.temperature; + } else { + currentWeather.temperature = this.cacheCurrentTemp; + } + + currentWeather.windSpeed = WeatherUtils.convertWindToMs(ECdoc.querySelector("siteData currentConditions wind speed").textContent); + + currentWeather.windFromDirection = ECdoc.querySelector("siteData currentConditions wind bearing").textContent; + + currentWeather.humidity = ECdoc.querySelector("siteData currentConditions relativeHumidity").textContent; + + // Ensure showPrecipitationAmount is forced to false. EC does not really provide POP for current day + // and this feature for the weather module (current only) is sort of broken in that it wants + // to say POP but will display precip as an accumulated amount vs. a percentage. + + this.config.showPrecipitationAmount = false; + + // + // If the module config wants to showFeelsLike... default to the current temperature. + // Check for EC wind chill and humidex values and overwrite the feelsLikeTemp value. + // This assumes that the EC current conditions will never contain both a wind chill + // and humidex temperature. + // + + if (this.config.showFeelsLike) { + currentWeather.feelsLikeTemp = currentWeather.temperature; + + if (ECdoc.querySelector("siteData currentConditions windChill")) { + currentWeather.feelsLikeTemp = ECdoc.querySelector("siteData currentConditions windChill").textContent; + } + + if (ECdoc.querySelector("siteData currentConditions humidex")) { + currentWeather.feelsLikeTemp = ECdoc.querySelector("siteData currentConditions humidex").textContent; + } + } + + // + // Need to map EC weather icon to MM weatherType values + // + + currentWeather.weatherType = this.convertWeatherType(ECdoc.querySelector("siteData currentConditions iconCode").textContent); + + // + // Capture the sunrise and sunset values from EC data + // + + const sunList = ECdoc.querySelectorAll("siteData riseSet dateTime"); + + currentWeather.sunrise = moment(sunList[1].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss"); + currentWeather.sunset = moment(sunList[3].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss"); + + return currentWeather; + }, + + // + // Generate an array of WeatherObjects based on EC weather forecast + // + + generateWeatherObjectsFromForecast(ECdoc) { + // Declare an array to hold each day's forecast object + + const days = []; + + const weather = new WeatherObject(); + + const foreBaseDates = ECdoc.querySelectorAll("siteData forecastGroup dateTime"); + const baseDate = foreBaseDates[1].querySelector("timeStamp").textContent; + + weather.date = moment(baseDate, "YYYYMMDDhhmmss"); + + const foreGroup = ECdoc.querySelectorAll("siteData forecastGroup forecast"); + + weather.precipitationAmount = null; + + // + // The EC forecast is held in a 12-element array - Elements 0 to 11 - with each day encompassing + // 2 elements. the first element for a day details the Today (daytime) forecast while the second + // element details the Tonight (nightime) forecast. Element 0 is always for the current day. + // + // However... the forecast is somewhat 'rolling'. + // + // If the EC forecast is queried in the morning, then Element 0 will contain Current + // Today and Element 1 will contain Current Tonight. From there, the next 5 days of forecast will be + // contained in Elements 2/3, 4/5, 6/7, 8/9, and 10/11. This module will create a 6-day forecast using + // all of these Elements. + // + // But, if the EC forecast is queried in late afternoon, the Current Today forecast will be rolled + // off and Element 0 will contain Current Tonight. From there, the next 5 days will be contained in + // Elements 1/2, 3/4, 5/6, 7/8, and 9/10. As well, Elelement 11 will contain a forecast for a 6th day, + // but only for the Today portion (not Tonight). This module will create a 6-day forecast using + // Elements 0 to 11, and will ignore the additional Todat forecast in Element 11. + // + // We need to determine if Element 0 is showing the forecast for Current Today or Current Tonight. + // This is required to understand how Min and Max temperature will be determined, and to understand + // where the next day's (aka Tomorrow's) forecast is located in the forecast array. + // + + let nextDay = 0; + let lastDay = 0; + const currentTemp = ECdoc.querySelector("siteData currentConditions temperature").textContent; + + // + // If the first Element is Current Today, look at Current Today and Current Tonight for the current day. + // + + if (foreGroup[0].querySelector("period[textForecastName='Today']")) { + this.todaytempCacheMin = 0; + this.todaytempCacheMax = 0; + this.todayCached = true; + + this.setMinMaxTemps(weather, foreGroup, 0, true, currentTemp); + + this.setPrecipitation(weather, foreGroup, 0); + + // + // Set the Element number that will reflect where the next day's forecast is located. Also set + // the Element number where the end of the forecast will be. This is important because of the + // rolling nature of the EC forecast. In the current scenario (Today and Tonight are present + // in elements 0 and 11, we know that we will have 6 full days of forecasts and we will use + // them. We will set lastDay such that we iterate through all 12 elements of the forecast. + // + + nextDay = 2; + lastDay = 12; + } + + // + // If the first Element is Current Tonight, look at Tonight only for the current day. + // + if (foreGroup[0].querySelector("period[textForecastName='Tonight']")) { + this.setMinMaxTemps(weather, foreGroup, 0, false, currentTemp); + + this.setPrecipitation(weather, foreGroup, 0); + + // + // Set the Element number that will reflect where the next day's forecast is located. Also set + // the Element number where the end of the forecast will be. This is important because of the + // rolling nature of the EC forecast. In the current scenario (only Current Tonight is present + // in Element 0, we know that we will have 6 full days of forecasts PLUS a half-day and + // forecast in the final element. Because we will only use full day forecasts, we set the + // lastDay number to ensure we ignore that final half-day (in forecast Element 11). + // + + nextDay = 1; + lastDay = 11; + } + + // + // Need to map EC weather icon to MM weatherType values. Always pick the first Element's icon to + // reflect either Today or Tonight depending on what the forecast is showing in Element 0. + // + + weather.weatherType = this.convertWeatherType(foreGroup[0].querySelector("abbreviatedForecast iconCode").textContent); + + // Push the weather object into the forecast array. + + days.push(weather); + + // + // Now do the rest of the forecast starting at nextDay. We will process each day using 2 EC + // forecast Elements. This will address the fact that the EC forecast always includes Today and + // Tonight for each day. This is why we iterate through the forecast by a a count of 2, with each + // iteration looking at the current Element and the next Element. + // + + let lastDate = moment(baseDate, "YYYYMMDDhhmmss"); + + for (let stepDay = nextDay; stepDay < lastDay; stepDay += 2) { + let weather = new WeatherObject(); + + // Add 1 to the date to reflect the current forecast day we are building + + lastDate = lastDate.add(1, "day"); + weather.date = moment(lastDate); + + // Capture the temperatures for the current Element and the next Element in order to set + // the Min and Max temperatures for the forecast + + this.setMinMaxTemps(weather, foreGroup, stepDay, true, currentTemp); + + weather.precipitationAmount = null; + + this.setPrecipitation(weather, foreGroup, stepDay); + + // + // Need to map EC weather icon to MM weatherType values. Always pick the first Element icon. + // + + weather.weatherType = this.convertWeatherType(foreGroup[stepDay].querySelector("abbreviatedForecast iconCode").textContent); + + // Push the weather object into the forecast array. + + days.push(weather); + } + + return days; + }, + + // + // Generate an array of WeatherObjects based on EC hourly weather forecast + // + + generateWeatherObjectsFromHourly(ECdoc) { + // Declare an array to hold each hour's forecast object + + const hours = []; + + // Get local timezone UTC offset so that each hourly time can be calculated properly + + const baseHours = ECdoc.querySelectorAll("siteData hourlyForecastGroup dateTime"); + const hourOffset = baseHours[1].getAttribute("UTCOffset"); + + // + // The EC hourly forecast is held in a 24-element array - Elements 0 to 23 - with Element 0 holding + // the forecast for the next 'on the hour' timeslot. This means the array is a rolling 24 hours. + // + + const hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast"); + + for (let stepHour = 0; stepHour < 24; stepHour += 1) { + const weather = new WeatherObject(); + + // Determine local time by applying UTC offset to the forecast timestamp + + const foreTime = moment(hourGroup[stepHour].getAttribute("dateTimeUTC"), "YYYYMMDDhhmmss"); + const currTime = foreTime.add(hourOffset, "hours"); + weather.date = moment.unix(currTime); + + // Capture the temperature + + weather.temperature = hourGroup[stepHour].querySelector("temperature").textContent; + + // Capture Likelihood of Precipitation (LOP) and unit-of-measure values + + const precipLOP = hourGroup[stepHour].querySelector("lop").textContent * 1.0; + + if (precipLOP > 0) { + weather.precipitationProbability = precipLOP; + } + + // + // Need to map EC weather icon to MM weatherType values. Always pick the first Element icon. + // + + weather.weatherType = this.convertWeatherType(hourGroup[stepHour].querySelector("iconCode").textContent); + + // Push the weather object into the forecast array. + + hours.push(weather); + } + + return hours; + }, + // + // Determine Min and Max temp based on a supplied Forecast Element index and a boolen that denotes if + // the next Forecast element should be considered - i.e. look at Today *and* Tonight vs.Tonight-only + // + + setMinMaxTemps(weather, foreGroup, today, fullDay, currentTemp) { + const todayTemp = foreGroup[today].querySelector("temperatures temperature").textContent; + + const todayClass = foreGroup[today].querySelector("temperatures temperature").getAttribute("class"); + + // + // The following logic is largely aimed at accommodating the Current day's forecast whereby we + // can have either Current Today+Current Tonight or only Current Tonight. + // + // If fullDay is false, then we only have Tonight for the current day's forecast - meaning we have + // lost a min or max temp value for the day. Therefore, we will see if we were able to cache the the + // Today forecast for the current day. If we have, we will use them. If we do not have the cached values, + // it means that MM or the Computer has been restarted since the time EC rolled off Today from the + // forecast. In this scenario, we will simply default to the Current Conditions temperature and then + // check the Tonight temperature. + // + + if (fullDay === false) { + if (this.todayCached === true) { + weather.minTemperature = this.todayTempCacheMin; + weather.maxTemperature = this.todayTempCacheMax; + } else { + weather.minTemperature = currentTemp; + weather.maxTemperature = weather.minTemperature; + } + } + + // + // We will check to see if the current Element's temperature is Low or High and set weather values + // accordingly. We will also check the condition where fullDay is true *and* we are looking at forecast + // element 0. This is a special case where we will cache temperature values so that we have them later + // in the current day when the Current Today element rolls off and we have Current Tonight only. + // + + if (todayClass === "low") { + weather.minTemperature = todayTemp; + if (today === 0 && fullDay === true) { + this.todayTempCacheMin = weather.minTemperature; + } + } + + if (todayClass === "high") { + weather.maxTemperature = todayTemp; + if (today === 0 && fullDay === true) { + this.todayTempCacheMax = weather.maxTemperature; + } + } + + const nextTemp = foreGroup[today + 1].querySelector("temperatures temperature").textContent; + + const nextClass = foreGroup[today + 1].querySelector("temperatures temperature").getAttribute("class"); + + if (fullDay === true) { + if (nextClass === "low") { + weather.minTemperature = nextTemp; + } + + if (nextClass === "high") { + weather.maxTemperature = nextTemp; + } + } + }, + + // + // Check for a Precipitation forecast. EC can provide a forecast in 2 ways: either an accumulation figure + // or a POP percentage. If there is a POP, then that is what the module will show. If there is an accumulation, + // then it will be displayed ONLY if no POP is present. + // + // POP Logic: By default, we want to show the POP for 'daytime' since we are presuming that is what + // people are more interested in seeing. While EC provides a separate POP for daytime and nightime portions + // of each day, the weather module does not really allow for that view of a daily forecast. There we will + // ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show + // the nightime forecast after a certain point in the afternoon. As such, we will be showing the nightime POP + // (if one exists) in that specific scenario. + // + // Accumulation Logic: Similar to POP, we want to show accumulation for 'daytime' since we presume that is what + // people are interested in seeing. While EC provides a separate accumulation for daytime and nightime portions + // of each day, the weather module does not really allow for that view of a daily forecast. There we will + // ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show + // the nightime forecast after a certain point in that specific scenario. + // + + setPrecipitation(weather, foreGroup, today) { + if (foreGroup[today].querySelector("precipitation accumulation")) { + weather.precipitationAmount = foreGroup[today].querySelector("precipitation accumulation amount").textContent * 1.0; + weather.precipitationUnits = foreGroup[today].querySelector("precipitation accumulation amount").getAttribute("units"); + } + + // Check Today element for POP + + if (foreGroup[today].querySelector("abbreviatedForecast pop").textContent > 0) { + weather.precipitationProbability = foreGroup[today].querySelector("abbreviatedForecast pop").textContent; + } + }, + + // + // Convert the icons to a more usable name. + // + convertWeatherType(weatherType) { + const weatherTypes = { + "00": "day-sunny", + "01": "day-sunny", + "02": "day-sunny-overcast", + "03": "day-cloudy", + "04": "day-cloudy", + "05": "day-cloudy", + "06": "day-sprinkle", + "07": "day-showers", + "08": "day-snow", + "09": "day-thunderstorm", + 10: "cloud", + 11: "showers", + 12: "rain", + 13: "rain", + 14: "sleet", + 15: "sleet", + 16: "snow", + 17: "snow", + 18: "snow", + 19: "thunderstorm", + 20: "cloudy", + 21: "cloudy", + 22: "day-cloudy", + 23: "day-haze", + 24: "fog", + 25: "snow-wind", + 26: "sleet", + 27: "sleet", + 28: "rain", + 29: "na", + 30: "night-clear", + 31: "night-clear", + 32: "night-partly-cloudy", + 33: "night-alt-cloudy", + 34: "night-alt-cloudy", + 35: "night-partly-cloudy", + 36: "night-alt-showers", + 37: "night-rain-mix", + 38: "night-alt-snow", + 39: "night-thunderstorm", + 40: "snow-wind", + 41: "tornado", + 42: "tornado", + 43: "windy", + 44: "smoke", + 45: "sandstorm", + 46: "thunderstorm", + 47: "thunderstorm", + 48: "tornado" + }; + + return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null; + } +}); diff --git a/MagicMirror/modules/default/weather/providers/openmeteo.js b/MagicMirror/modules/default/weather/providers/openmeteo.js new file mode 100644 index 0000000000000000000000000000000000000000..9d95aea1f7857cf57bebb79b5e78f1e337481803 --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/openmeteo.js @@ -0,0 +1,537 @@ +/* global WeatherProvider, WeatherObject */ + +/* MagicMirror² + * Module: Weather + * Provider: Open-Meteo + * + * By Andrés Vanegas + * MIT Licensed + * + * This class is a provider for Open-Meteo, based on Andrew Pometti's class + * for Weatherbit. + */ +// https://www.bigdatacloud.com/docs/api/free-reverse-geocode-to-city-api +const GEOCODE_BASE = "https://api.bigdatacloud.net/data/reverse-geocode-client"; +const OPEN_METEO_BASE = "https://api.open-meteo.com/v1"; + +WeatherProvider.register("openmeteo", { + // Set the name of the provider. + // Not strictly required, but helps for debugging. + providerName: "Open-Meteo", + + // Set the default config properties that is specific to this provider + defaults: { + apiBase: OPEN_METEO_BASE, + lat: 0, + lon: 0, + pastDays: 0, + type: "current" + }, + + // https://open-meteo.com/en/docs + hourlyParams: [ + // Air temperature at 2 meters above ground + "temperature_2m", + // Relative humidity at 2 meters above ground + "relativehumidity_2m", + // Dew point temperature at 2 meters above ground + "dewpoint_2m", + // Apparent temperature is the perceived feels-like temperature combining wind chill factor, relative humidity and solar radiation + "apparent_temperature", + // Atmospheric air pressure reduced to mean sea level (msl) or pressure at surface. Typically pressure on mean sea level is used in meteorology. Surface pressure gets lower with increasing elevation. + "pressure_msl", + "surface_pressure", + // Total cloud cover as an area fraction + "cloudcover", + // Low level clouds and fog up to 3 km altitude + "cloudcover_low", + // Mid level clouds from 3 to 8 km altitude + "cloudcover_mid", + // High level clouds from 8 km altitude + "cloudcover_high", + // Wind speed at 10, 80, 120 or 180 meters above ground. Wind speed on 10 meters is the standard level. + "windspeed_10m", + "windspeed_80m", + "windspeed_120m", + "windspeed_180m", + // Wind direction at 10, 80, 120 or 180 meters above ground + "winddirection_10m", + "winddirection_80m", + "winddirection_120m", + "winddirection_180m", + // Gusts at 10 meters above ground as a maximum of the preceding hour + "windgusts_10m", + // Shortwave solar radiation as average of the preceding hour. This is equal to the total global horizontal irradiation + "shortwave_radiation", + // Direct solar radiation as average of the preceding hour on the horizontal plane and the normal plane (perpendicular to the sun) + "direct_radiation", + "direct_normal_irradiance", + // Diffuse solar radiation as average of the preceding hour + "diffuse_radiation", + // Vapor Pressure Deificit (VPD) in kilopascal (kPa). For high VPD (>1.6), water transpiration of plants increases. For low VPD (<0.4), transpiration decreases + "vapor_pressure_deficit", + // Evapotranspration from land surface and plants that weather models assumes for this location. Available soil water is considered. 1 mm evapotranspiration per hour equals 1 liter of water per spare meter. + "evapotranspiration", + // ETâ‚€ Reference Evapotranspiration of a well watered grass field. Based on FAO-56 Penman-Monteith equations ETâ‚€ is calculated from temperature, wind speed, humidity and solar radiation. Unlimited soil water is assumed. ETâ‚€ is commonly used to estimate the required irrigation for plants. + "et0_fao_evapotranspiration", + // Total precipitation (rain, showers, snow) sum of the preceding hour + "precipitation", + // Snowfall amount of the preceding hour in centimeters. For the water equivalent in millimeter, divide by 7. E.g. 7 cm snow = 10 mm precipitation water equivalent + "snowfall", + // Rain from large scale weather systems of the preceding hour in millimeter + "rain", + // Showers from convective precipitation in millimeters from the preceding hour + "showers", + // Weather condition as a numeric code. Follow WMO weather interpretation codes. + "weathercode", + // Snow depth on the ground + "snow_depth", + // Altitude above sea level of the 0°C level + "freezinglevel_height", + // Temperature in the soil at 0, 6, 18 and 54 cm depths. 0 cm is the surface temperature on land or water surface temperature on water. + "soil_temperature_0cm", + "soil_temperature_6cm", + "soil_temperature_18cm", + "soil_temperature_54cm", + // Average soil water content as volumetric mixing ratio at 0-1, 1-3, 3-9, 9-27 and 27-81 cm depths. + "soil_moisture_0_1cm", + "soil_moisture_1_3cm", + "soil_moisture_3_9cm", + "soil_moisture_9_27cm", + "soil_moisture_27_81cm" + ], + + dailyParams: [ + // Maximum and minimum daily air temperature at 2 meters above ground + "temperature_2m_max", + "temperature_2m_min", + // Maximum and minimum daily apparent temperature + "apparent_temperature_min", + "apparent_temperature_max", + // Sum of daily precipitation (including rain, showers and snowfall) + "precipitation_sum", + // Sum of daily rain + "rain_sum", + // Sum of daily showers + "showers_sum", + // Sum of daily snowfall + "snowfall_sum", + // The number of hours with rain + "precipitation_hours", + // The most severe weather condition on a given day + "weathercode", + // Sun rise and set times + "sunrise", + "sunset", + // Maximum wind speed and gusts on a day + "windspeed_10m_max", + "windgusts_10m_max", + // Dominant wind direction + "winddirection_10m_dominant", + // The sum of solar radiation on a given day in Megajoules + "shortwave_radiation_sum", + // Daily sum of ETâ‚€ Reference Evapotranspiration of a well watered grass field + "et0_fao_evapotranspiration" + ], + + fetchedLocation: function () { + return this.fetchedLocationName || ""; + }, + + fetchCurrentWeather() { + this.fetchData(this.getUrl()) + .then((data) => this.parseWeatherApiResponse(data)) + .then((parsedData) => { + if (!parsedData) { + // No usable data? + return; + } + + const currentWeather = this.generateWeatherDayFromCurrentWeather(parsedData); + this.setCurrentWeather(currentWeather); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + fetchWeatherForecast() { + this.fetchData(this.getUrl()) + .then((data) => this.parseWeatherApiResponse(data)) + .then((parsedData) => { + if (!parsedData) { + // No usable data? + return; + } + + const dailyForecast = this.generateWeatherObjectsFromForecast(parsedData); + this.setWeatherForecast(dailyForecast); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + fetchWeatherHourly() { + this.fetchData(this.getUrl()) + .then((data) => this.parseWeatherApiResponse(data)) + .then((parsedData) => { + if (!parsedData) { + // No usable data? + return; + } + + const hourlyForecast = this.generateWeatherObjectsFromHourly(parsedData); + this.setWeatherHourly(hourlyForecast); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + /** + * Overrides method for setting config to check if endpoint is correct for hourly + * + * @param {object} config The configuration object + */ + setConfig(config) { + this.config = { + lang: config.lang ?? "en", + ...this.defaults, + ...config + }; + + // Set properly maxNumberOfDays and max Entries properties according to config and value ranges allowed in the documentation + const maxEntriesLimit = ["daily", "forecast"].includes(this.config.type) ? 7 : this.config.type === "hourly" ? 48 : 0; + if (this.config.hasOwnProperty("maxNumberOfDays") && !isNaN(parseFloat(this.config.maxNumberOfDays))) { + const daysFactor = ["daily", "forecast"].includes(this.config.type) ? 1 : this.config.type === "hourly" ? 24 : 0; + this.config.maxEntries = Math.max(1, Math.min(Math.round(parseFloat(this.config.maxNumberOfDays)) * daysFactor, maxEntriesLimit)); + this.config.maxNumberOfDays = Math.ceil(this.config.maxEntries / Math.max(1, daysFactor)); + } + this.config.maxEntries = Math.max(1, Math.min(this.config.maxEntries, maxEntriesLimit)); + + if (!this.config.type) { + Log.error("type not configured and could not resolve it"); + } + + this.fetchLocation(); + }, + + // Generate valid query params to perform the request + getQueryParameters() { + let params = { + latitude: this.config.lat, + longitude: this.config.lon, + timeformat: "unixtime", + timezone: "auto", + past_days: this.config.pastDays ?? 0, + daily: this.dailyParams, + hourly: this.hourlyParams, + // Fixed units as metric + temperature_unit: "celsius", + windspeed_unit: "ms", + precipitation_unit: "mm" + }; + + const startDate = moment().startOf("day"); + const endDate = moment(startDate) + .add(Math.max(0, Math.min(7, this.config.maxNumberOfDays)), "days") + .endOf("day"); + + params["start_date"] = startDate.format("YYYY-MM-DD"); + + switch (this.config.type) { + case "hourly": + case "daily": + case "forecast": + params["end_date"] = endDate.format("YYYY-MM-DD"); + break; + case "current": + params["current_weather"] = true; + params["end_date"] = params["start_date"]; + break; + default: + // Failsafe + return ""; + } + + return Object.keys(params) + .filter((key) => (params[key] ? true : false)) + .map((key) => { + switch (key) { + case "hourly": + case "daily": + return `${encodeURIComponent(key)}=${params[key].join(",")}`; + default: + return `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`; + } + }) + .join("&"); + }, + + // Create a URL from the config and base URL. + getUrl() { + return `${this.config.apiBase}/forecast?${this.getQueryParameters()}`; + }, + + // Transpose hourly and daily data matrices + transposeDataMatrix(data) { + return data.time.map((_, index) => + Object.keys(data).reduce((row, key) => { + return { + ...row, + // Parse time values as momentjs instances + [key]: ["time", "sunrise", "sunset"].includes(key) ? moment.unix(data[key][index]) : data[key][index] + }; + }, {}) + ); + }, + + // Sanitize and validate API response + parseWeatherApiResponse(data) { + const validByType = { + current: data.current_weather && data.current_weather.time, + hourly: data.hourly && data.hourly.time && Array.isArray(data.hourly.time) && data.hourly.time.length > 0, + daily: data.daily && data.daily.time && Array.isArray(data.daily.time) && data.daily.time.length > 0 + }; + // backwards compatibility + const type = ["daily", "forecast"].includes(this.config.type) ? "daily" : this.config.type; + + if (!validByType[type]) return; + + switch (type) { + case "current": + if (!validByType.daily && !validByType.hourly) { + return; + } + break; + case "hourly": + case "daily": + break; + default: + return; + } + + for (const key of ["hourly", "daily"]) { + if (typeof data[key] === "object") { + data[key] = this.transposeDataMatrix(data[key]); + } + } + + if (data.current_weather) { + data.current_weather.time = moment.unix(data.current_weather.time); + } + + return data; + }, + + // Reverse geocoding from latitude and longitude provided + fetchLocation() { + this.fetchData(`${GEOCODE_BASE}?latitude=${this.config.lat}&longitude=${this.config.lon}&localityLanguage=${this.config.lang}`) + .then((data) => { + if (!data || !data.city) { + // No usable data? + return; + } + this.fetchedLocationName = `${data.city}, ${data.principalSubdivisionCode}`; + }) + .catch((request) => { + Log.error("Could not load data ... ", request); + }); + }, + + // Implement WeatherDay generator. + generateWeatherDayFromCurrentWeather(weather) { + /** + * Since some units comes from API response "splitted" into daily, hourly and current_weather + * every time you request it, you have to ensure to get the data from the right place every time. + * For the current weather case, the response have the following structure (after transposing): + * ``` + * { + * current_weather: { ...<some current weather here> }, + * hourly: [ + * 0: {...<data for hour zero here> }, + * 1: {...<data for hour one here> }, + * ... + * ], + * daily: [ + * {...<summary data for current day here> }, + * ] + * } + * ``` + * Some data should be returned from `hourly` array data when the index matches the current hour, + * some data from the first and only one object received in `daily` array and some from the + * `current_weather` object. + */ + const h = moment().hour(); + const currentWeather = new WeatherObject(); + + currentWeather.date = weather.current_weather.time; + currentWeather.windSpeed = weather.current_weather.windspeed; + currentWeather.windFromDirection = weather.current_weather.winddirection; + currentWeather.sunrise = weather.daily[0].sunrise; + currentWeather.sunset = weather.daily[0].sunset; + currentWeather.temperature = parseFloat(weather.current_weather.temperature); + currentWeather.minTemperature = parseFloat(weather.daily[0].temperature_2m_min); + currentWeather.maxTemperature = parseFloat(weather.daily[0].temperature_2m_max); + currentWeather.weatherType = this.convertWeatherType(weather.current_weather.weathercode, currentWeather.isDayTime()); + currentWeather.humidity = parseFloat(weather.hourly[h].relativehumidity_2m); + currentWeather.rain = parseFloat(weather.hourly[h].rain); + currentWeather.snow = parseFloat(weather.hourly[h].snowfall * 10); + currentWeather.precipitationAmount = parseFloat(weather.hourly[h].precipitation); + + return currentWeather; + }, + + // Implement WeatherForecast generator. + generateWeatherObjectsFromForecast(weathers) { + const days = []; + + weathers.daily.forEach((weather, i) => { + const currentWeather = new WeatherObject(); + + currentWeather.date = weather.time; + currentWeather.windSpeed = weather.windspeed_10m_max; + currentWeather.windFromDirection = weather.winddirection_10m_dominant; + currentWeather.sunrise = weather.sunrise; + currentWeather.sunset = weather.sunset; + currentWeather.temperature = parseFloat((weather.apparent_temperature_max + weather.apparent_temperature_min) / 2); + currentWeather.minTemperature = parseFloat(weather.apparent_temperature_min); + currentWeather.maxTemperature = parseFloat(weather.apparent_temperature_max); + currentWeather.weatherType = this.convertWeatherType(weather.weathercode, currentWeather.isDayTime()); + currentWeather.rain = parseFloat(weather.rain_sum); + currentWeather.snow = parseFloat(weather.snowfall_sum * 10); + currentWeather.precipitationAmount = parseFloat(weather.precipitation_sum); + + days.push(currentWeather); + }); + + return days; + }, + + // Implement WeatherHourly generator. + generateWeatherObjectsFromHourly(weathers) { + const hours = []; + const now = moment(); + + weathers.hourly.forEach((weather, i) => { + if ((hours.length === 0 && weather.time.hour() <= now.hour()) || hours.length >= this.config.maxEntries) { + return; + } + + const currentWeather = new WeatherObject(); + const h = Math.ceil((i + 1) / 24) - 1; + + currentWeather.date = weather.time; + currentWeather.windSpeed = weather.windspeed_10m; + currentWeather.windFromDirection = weather.winddirection_10m; + currentWeather.sunrise = weathers.daily[h].sunrise; + currentWeather.sunset = weathers.daily[h].sunset; + currentWeather.temperature = parseFloat(weather.apparent_temperature); + currentWeather.minTemperature = parseFloat(weathers.daily[h].apparent_temperature_min); + currentWeather.maxTemperature = parseFloat(weathers.daily[h].apparent_temperature_max); + currentWeather.weatherType = this.convertWeatherType(weather.weathercode, currentWeather.isDayTime()); + currentWeather.humidity = parseFloat(weather.relativehumidity_2m); + currentWeather.rain = parseFloat(weather.rain); + currentWeather.snow = parseFloat(weather.snowfall * 10); + currentWeather.precipitationAmount = parseFloat(weather.precipitation); + + hours.push(currentWeather); + }); + + return hours; + }, + + // Map icons from Dark Sky to our icons. + convertWeatherType(weathercode, isDayTime) { + const weatherConditions = { + 0: "clear", + 1: "mainly-clear", + 2: "partly-cloudy", + 3: "overcast", + 45: "fog", + 48: "depositing-rime-fog", + 51: "drizzle-light-intensity", + 53: "drizzle-moderate-intensity", + 55: "drizzle-dense-intensity", + 56: "freezing-drizzle-light-intensity", + 57: "freezing-drizzle-dense-intensity", + 61: "rain-slight-intensity", + 63: "rain-moderate-intensity", + 65: "rain-heavy-intensity", + 66: "freezing-rain-light-heavy-intensity", + 67: "freezing-rain-heavy-intensity", + 71: "snow-fall-slight-intensity", + 73: "snow-fall-moderate-intensity", + 75: "snow-fall-heavy-intensity", + 77: "snow-grains", + 80: "rain-showers-slight", + 81: "rain-showers-moderate", + 82: "rain-showers-violent", + 85: "snow-showers-slight", + 86: "snow-showers-heavy", + 95: "thunderstorm", + 96: "thunderstorm-slight-hail", + 99: "thunderstorm-heavy-hail" + }; + + if (!Object.keys(weatherConditions).includes(`${weathercode}`)) return null; + + switch (weatherConditions[`${weathercode}`]) { + case "clear": + return isDayTime ? "day-sunny" : "night-clear"; + case "mainly-clear": + case "partly-cloudy": + return isDayTime ? "day-cloudy" : "night-alt-cloudy"; + case "overcast": + return isDayTime ? "day-sunny-overcast" : "night-alt-partly-cloudy"; + case "fog": + case "depositing-rime-fog": + return isDayTime ? "day-fog" : "night-fog"; + case "drizzle-light-intensity": + case "rain-slight-intensity": + case "rain-showers-slight": + return isDayTime ? "day-sprinkle" : "night-sprinkle"; + case "drizzle-moderate-intensity": + case "rain-moderate-intensity": + case "rain-showers-moderate": + return isDayTime ? "day-showers" : "night-showers"; + case "drizzle-dense-intensity": + case "rain-heavy-intensity": + case "rain-showers-violent": + return isDayTime ? "day-thunderstorm" : "night-thunderstorm"; + case "freezing-rain-light-intensity": + return isDayTime ? "day-rain-mix" : "night-rain-mix"; + case "freezing-drizzle-light-intensity": + case "freezing-drizzle-dense-intensity": + return "snowflake-cold"; + case "snow-grains": + return isDayTime ? "day-sleet" : "night-sleet"; + case "snow-fall-slight-intensity": + case "snow-fall-moderate-intensity": + return isDayTime ? "day-snow-wind" : "night-snow-wind"; + case "snow-fall-heavy-intensity": + case "freezing-rain-heavy-intensity": + return isDayTime ? "day-snow-thunderstorm" : "night-snow-thunderstorm"; + case "snow-showers-slight": + case "snow-showers-heavy": + return isDayTime ? "day-rain-mix" : "night-rain-mix"; + case "thunderstorm": + return isDayTime ? "day-thunderstorm" : "night-thunderstorm"; + case "thunderstorm-slight-hail": + return isDayTime ? "day-sleet" : "night-sleet"; + case "thunderstorm-heavy-hail": + return isDayTime ? "day-sleet-storm" : "night-sleet-storm"; + default: + return "na"; + } + }, + + // Define required scripts. + getScripts: function () { + return ["moment.js"]; + } +}); diff --git a/MagicMirror/modules/default/weather/providers/openweathermap.js b/MagicMirror/modules/default/weather/providers/openweathermap.js new file mode 100644 index 0000000000000000000000000000000000000000..d1bd378c552cf6a9f5e9994f92e0ee70b59b677c --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/openweathermap.js @@ -0,0 +1,448 @@ +/* global WeatherProvider, WeatherObject */ + +/* MagicMirror² + * Module: Weather + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + * + * This class is the blueprint for a weather provider. + */ +WeatherProvider.register("openweathermap", { + // Set the name of the provider. + // This isn't strictly necessary, since it will fallback to the provider identifier + // 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: "", // can be "onecall", "forecast" or "weather" (for current) + locationID: false, + location: false, + lat: 0, // the onecall endpoint needs lat / lon values, it doesn't support the locationId + lon: 0, + apiKey: "" + }, + + // Overwrite the fetchCurrentWeather method. + fetchCurrentWeather() { + this.fetchData(this.getUrl()) + .then((data) => { + let currentWeather; + if (this.config.weatherEndpoint === "/onecall") { + currentWeather = this.generateWeatherObjectsFromOnecall(data).current; + this.setFetchedLocation(`${data.timezone}`); + } else { + currentWeather = this.generateWeatherObjectFromCurrentWeather(data); + } + this.setCurrentWeather(currentWeather); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + // Overwrite the fetchWeatherForecast method. + fetchWeatherForecast() { + this.fetchData(this.getUrl()) + .then((data) => { + let forecast; + let location; + if (this.config.weatherEndpoint === "/onecall") { + forecast = this.generateWeatherObjectsFromOnecall(data).days; + location = `${data.timezone}`; + } else { + forecast = this.generateWeatherObjectsFromForecast(data.list); + location = `${data.city.name}, ${data.city.country}`; + } + this.setWeatherForecast(forecast); + this.setFetchedLocation(location); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + // Overwrite the fetchWeatherHourly method. + fetchWeatherHourly() { + this.fetchData(this.getUrl()) + .then((data) => { + if (!data) { + // Did not receive usable new data. + // Maybe this needs a better check? + return; + } + + this.setFetchedLocation(`(${data.lat},${data.lon})`); + + const weatherData = this.generateWeatherObjectsFromOnecall(data); + this.setWeatherHourly(weatherData.hours); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + /** + * Overrides method for setting config to check if endpoint is correct for hourly + * + * @param {object} config The configuration object + */ + 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 + */ + getUrl() { + return this.config.apiBase + this.config.apiVersion + this.config.weatherEndpoint + this.getParams(); + }, + + /* + * Generate a WeatherObject based on currentWeatherInformation + */ + generateWeatherObjectFromCurrentWeather(currentWeatherData) { + const currentWeather = new WeatherObject(); + + currentWeather.date = moment.unix(currentWeatherData.dt); + currentWeather.humidity = currentWeatherData.main.humidity; + currentWeather.temperature = currentWeatherData.main.temp; + currentWeather.feelsLikeTemp = currentWeatherData.main.feels_like; + currentWeather.windSpeed = currentWeatherData.wind.speed; + currentWeather.windFromDirection = currentWeatherData.wind.deg; + currentWeather.weatherType = this.convertWeatherType(currentWeatherData.weather[0].icon); + currentWeather.sunrise = moment.unix(currentWeatherData.sys.sunrise); + currentWeather.sunset = moment.unix(currentWeatherData.sys.sunset); + + return currentWeather; + }, + + /* + * Generate WeatherObjects based on forecast information + */ + generateWeatherObjectsFromForecast(forecasts) { + if (this.config.weatherEndpoint === "/forecast") { + return this.generateForecastHourly(forecasts); + } else if (this.config.weatherEndpoint === "/forecast/daily") { + return this.generateForecastDaily(forecasts); + } + // if weatherEndpoint does not match forecast or forecast/daily, what should be returned? + return [new WeatherObject()]; + }, + + /* + * Generate WeatherObjects based on One Call forecast information + */ + generateWeatherObjectsFromOnecall(data) { + if (this.config.weatherEndpoint === "/onecall") { + return this.fetchOnecall(data); + } + // if weatherEndpoint does not match onecall, what should be returned? + return { current: new WeatherObject(), hours: [], days: [] }; + }, + + /* + * Generate forecast information for 3-hourly forecast (available for free + * subscription). + */ + generateForecastHourly(forecasts) { + // initial variable declaration + const days = []; + // variables for temperature range and rain + let minTemp = []; + let maxTemp = []; + let rain = 0; + let snow = 0; + // variable for date + let date = ""; + let weather = new WeatherObject(); + + for (const forecast of forecasts) { + if (date !== moment.unix(forecast.dt).format("YYYY-MM-DD")) { + // calculate minimum/maximum temperature, specify rain amount + weather.minTemperature = Math.min.apply(null, minTemp); + weather.maxTemperature = Math.max.apply(null, maxTemp); + weather.rain = rain; + weather.snow = snow; + weather.precipitationAmount = (weather.rain ?? 0) + (weather.snow ?? 0); + // push weather information to days array + days.push(weather); + // create new weather-object + weather = new WeatherObject(); + + minTemp = []; + maxTemp = []; + rain = 0; + snow = 0; + + // set new date + date = moment.unix(forecast.dt).format("YYYY-MM-DD"); + + // specify date + weather.date = moment.unix(forecast.dt); + + // If the first value of today is later than 17:00, we have an icon at least! + weather.weatherType = this.convertWeatherType(forecast.weather[0].icon); + } + + if (moment.unix(forecast.dt).format("H") >= 8 && moment.unix(forecast.dt).format("H") <= 17) { + weather.weatherType = this.convertWeatherType(forecast.weather[0].icon); + } + + // the same day as before + // add values from forecast to corresponding variables + minTemp.push(forecast.main.temp_min); + maxTemp.push(forecast.main.temp_max); + + if (forecast.hasOwnProperty("rain") && !isNaN(forecast.rain["3h"])) { + rain += forecast.rain["3h"]; + } + + if (forecast.hasOwnProperty("snow") && !isNaN(forecast.snow["3h"])) { + snow += forecast.snow["3h"]; + } + } + + // last day + // calculate minimum/maximum temperature, specify rain amount + weather.minTemperature = Math.min.apply(null, minTemp); + weather.maxTemperature = Math.max.apply(null, maxTemp); + weather.rain = rain; + weather.snow = snow; + weather.precipitationAmount = (weather.rain ?? 0) + (weather.snow ?? 0); + // push weather information to days array + days.push(weather); + return days.slice(1); + }, + + /* + * Generate forecast information for daily forecast (available for paid + * subscription or old apiKey). + */ + generateForecastDaily(forecasts) { + // initial variable declaration + const days = []; + + for (const forecast of forecasts) { + const weather = new WeatherObject(); + + weather.date = moment.unix(forecast.dt); + weather.minTemperature = forecast.temp.min; + weather.maxTemperature = forecast.temp.max; + weather.weatherType = this.convertWeatherType(forecast.weather[0].icon); + weather.rain = 0; + weather.snow = 0; + + // forecast.rain not available if amount is zero + // The API always returns in millimeters + if (forecast.hasOwnProperty("rain") && !isNaN(forecast.rain)) { + weather.rain = forecast.rain; + } + + // forecast.snow not available if amount is zero + // The API always returns in millimeters + if (forecast.hasOwnProperty("snow") && !isNaN(forecast.snow)) { + weather.snow = forecast.snow; + } + + weather.precipitationAmount = weather.rain + weather.snow; + weather.precipitationProbability = forecast.pop ? forecast.pop * 100 : undefined; + + days.push(weather); + } + + return days; + }, + + /* + * Fetch One Call forecast information (available for free subscription). + * Factors in timezone offsets. + * Minutely forecasts are excluded for the moment, see getParams(). + */ + fetchOnecall(data) { + let precip = false; + + // get current weather, if requested + const current = new WeatherObject(); + if (data.hasOwnProperty("current")) { + current.date = moment.unix(data.current.dt).utcOffset(data.timezone_offset / 60); + current.windSpeed = data.current.wind_speed; + current.windFromDirection = data.current.wind_deg; + current.sunrise = moment.unix(data.current.sunrise).utcOffset(data.timezone_offset / 60); + current.sunset = moment.unix(data.current.sunset).utcOffset(data.timezone_offset / 60); + current.temperature = data.current.temp; + current.weatherType = this.convertWeatherType(data.current.weather[0].icon); + current.humidity = data.current.humidity; + if (data.current.hasOwnProperty("rain") && !isNaN(data.current["rain"]["1h"])) { + current.rain = data.current["rain"]["1h"]; + precip = true; + } + if (data.current.hasOwnProperty("snow") && !isNaN(data.current["snow"]["1h"])) { + current.snow = data.current["snow"]["1h"]; + precip = true; + } + if (precip) { + current.precipitationAmount = (current.rain ?? 0) + (current.snow ?? 0); + } + current.feelsLikeTemp = data.current.feels_like; + } + + let weather = new WeatherObject(); + + // get hourly weather, if requested + const hours = []; + if (data.hasOwnProperty("hourly")) { + for (const hour of data.hourly) { + weather.date = moment.unix(hour.dt).utcOffset(data.timezone_offset / 60); + weather.temperature = hour.temp; + weather.feelsLikeTemp = hour.feels_like; + weather.humidity = hour.humidity; + weather.windSpeed = hour.wind_speed; + weather.windFromDirection = hour.wind_deg; + weather.weatherType = this.convertWeatherType(hour.weather[0].icon); + weather.precipitationProbability = hour.pop ? hour.pop * 100 : undefined; + precip = false; + if (hour.hasOwnProperty("rain") && !isNaN(hour.rain["1h"])) { + weather.rain = hour.rain["1h"]; + precip = true; + } + if (hour.hasOwnProperty("snow") && !isNaN(hour.snow["1h"])) { + weather.snow = hour.snow["1h"]; + precip = true; + } + if (precip) { + weather.precipitationAmount = (weather.rain ?? 0) + (weather.snow ?? 0); + } + + hours.push(weather); + weather = new WeatherObject(); + } + } + + // get daily weather, if requested + const days = []; + if (data.hasOwnProperty("daily")) { + for (const day of data.daily) { + weather.date = moment.unix(day.dt).utcOffset(data.timezone_offset / 60); + weather.sunrise = moment.unix(day.sunrise).utcOffset(data.timezone_offset / 60); + weather.sunset = moment.unix(day.sunset).utcOffset(data.timezone_offset / 60); + weather.minTemperature = day.temp.min; + weather.maxTemperature = day.temp.max; + weather.humidity = day.humidity; + weather.windSpeed = day.wind_speed; + weather.windFromDirection = day.wind_deg; + weather.weatherType = this.convertWeatherType(day.weather[0].icon); + weather.precipitationProbability = day.pop ? day.pop * 100 : undefined; + precip = false; + if (!isNaN(day.rain)) { + weather.rain = day.rain; + precip = true; + } + if (!isNaN(day.snow)) { + weather.snow = day.snow; + precip = true; + } + if (precip) { + weather.precipitationAmount = (weather.rain ?? 0) + (weather.snow ?? 0); + } + + days.push(weather); + weather = new WeatherObject(); + } + } + + return { current: current, hours: hours, days: days }; + }, + + /* + * Convert the OpenWeatherMap icons to a more usable name. + */ + convertWeatherType(weatherType) { + const weatherTypes = { + "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" + }; + + return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null; + }, + + /* getParams(compliments) + * Generates an url with api parameters based on the config. + * + * return String - URL params. + */ + getParams() { + let params = "?"; + if (this.config.weatherEndpoint === "/onecall") { + params += `lat=${this.config.lat}`; + params += `&lon=${this.config.lon}`; + if (this.config.type === "current") { + params += "&exclude=minutely,hourly,daily"; + } else if (this.config.type === "hourly") { + params += "&exclude=current,minutely,daily"; + } else if (this.config.type === "daily" || this.config.type === "forecast") { + params += "&exclude=current,minutely,hourly"; + } 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) { + params += `q=${this.config.location}`; + } else if (this.firstEvent && this.firstEvent.geo) { + params += `lat=${this.firstEvent.geo.lat}&lon=${this.firstEvent.geo.lon}`; + } else if (this.firstEvent && this.firstEvent.location) { + params += `q=${this.firstEvent.location}`; + } else { + this.hide(this.config.animationSpeed, { lockString: this.identifier }); + return; + } + + params += "&units=metric"; // WeatherProviders should use metric internally and use the units only for when displaying data + params += `&lang=${this.config.lang}`; + params += `&APPID=${this.config.apiKey}`; + + return params; + } +}); diff --git a/MagicMirror/modules/default/weather/providers/pirateweather.js b/MagicMirror/modules/default/weather/providers/pirateweather.js new file mode 100644 index 0000000000000000000000000000000000000000..1bb956112af70321f1a1830e545bbce43ab7c01c --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/pirateweather.js @@ -0,0 +1,133 @@ +/* global WeatherProvider, WeatherObject */ + +/* MagicMirror² + * Module: Weather + * Provider: Pirate Weather + * + * Written by Nicholas Hubbard https://github.com/nhubbard for formerly Dark Sky Provider + * Modified by Karsten Hassel for Pirate Weather + * MIT Licensed + * + * This class is a provider for Pirate Weather, it is a replacement for Dark Sky (same api). + */ +WeatherProvider.register("pirateweather", { + // Set the name of the provider. + // Not strictly required, but helps for debugging. + providerName: "pirateweather", + + // Set the default config properties that is specific to this provider + defaults: { + useCorsProxy: true, + apiBase: "https://api.pirateweather.net", + weatherEndpoint: "/forecast", + apiKey: "", + lat: 0, + lon: 0 + }, + + fetchCurrentWeather() { + this.fetchData(this.getUrl()) + .then((data) => { + if (!data || !data.currently || typeof data.currently.temperature === "undefined") { + // No usable data? + return; + } + + const currentWeather = this.generateWeatherDayFromCurrentWeather(data); + this.setCurrentWeather(currentWeather); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + fetchWeatherForecast() { + this.fetchData(this.getUrl()) + .then((data) => { + if (!data || !data.daily || !data.daily.data.length) { + // No usable data? + return; + } + + const forecast = this.generateWeatherObjectsFromForecast(data.daily.data); + this.setWeatherForecast(forecast); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + // Create a URL from the config and base URL. + getUrl() { + return `${this.config.apiBase}${this.config.weatherEndpoint}/${this.config.apiKey}/${this.config.lat},${this.config.lon}?units=si&lang=${this.config.lang}`; + }, + + // Implement WeatherDay generator. + generateWeatherDayFromCurrentWeather(currentWeatherData) { + const currentWeather = new WeatherObject(); + + currentWeather.date = moment(); + currentWeather.humidity = parseFloat(currentWeatherData.currently.humidity); + currentWeather.temperature = parseFloat(currentWeatherData.currently.temperature); + currentWeather.windSpeed = parseFloat(currentWeatherData.currently.windSpeed); + currentWeather.windFromDirection = currentWeatherData.currently.windBearing; + currentWeather.weatherType = this.convertWeatherType(currentWeatherData.currently.icon); + currentWeather.sunrise = moment.unix(currentWeatherData.daily.data[0].sunriseTime); + currentWeather.sunset = moment.unix(currentWeatherData.daily.data[0].sunsetTime); + + return currentWeather; + }, + + generateWeatherObjectsFromForecast(forecasts) { + const days = []; + + for (const forecast of forecasts) { + const weather = new WeatherObject(); + + weather.date = moment.unix(forecast.time); + weather.minTemperature = forecast.temperatureMin; + weather.maxTemperature = forecast.temperatureMax; + weather.weatherType = this.convertWeatherType(forecast.icon); + weather.snow = 0; + weather.rain = 0; + + let precip = 0; + if (forecast.hasOwnProperty("precipAccumulation")) { + precip = forecast.precipAccumulation * 10; + } + + weather.precipitationAmount = precip; + if (forecast.hasOwnProperty("precipType")) { + if (forecast.precipType === "snow") { + weather.snow = precip; + } else { + weather.rain = precip; + } + } + + days.push(weather); + } + + return days; + }, + + // Map icons from Pirate Weather to our icons. + convertWeatherType(weatherType) { + const weatherTypes = { + "clear-day": "day-sunny", + "clear-night": "night-clear", + rain: "rain", + snow: "snow", + sleet: "snow", + wind: "wind", + fog: "fog", + cloudy: "cloudy", + "partly-cloudy-day": "day-cloudy", + "partly-cloudy-night": "night-cloudy" + }; + + return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null; + } +}); diff --git a/MagicMirror/modules/default/weather/providers/smhi.js b/MagicMirror/modules/default/weather/providers/smhi.js new file mode 100644 index 0000000000000000000000000000000000000000..0115bcf5bba5a1c468609122cffb0b282e504524 --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/smhi.js @@ -0,0 +1,344 @@ +/* global WeatherProvider, WeatherObject */ + +/* MagicMirror² + * Module: Weather + * Provider: SMHI + * + * By BuXXi https://github.com/buxxi + * MIT Licensed + * + * This class is a provider for SMHI (Sweden only). Metric system is the only + * supported unit. + */ +WeatherProvider.register("smhi", { + providerName: "SMHI", + + // Set the default config properties that is specific to this provider + defaults: { + lat: 0, // Cant have more than 6 digits + lon: 0, // Cant have more than 6 digits + precipitationValue: "pmedian", + location: false + }, + + /** + * Implements method in interface for fetching current weather. + */ + fetchCurrentWeather() { + this.fetchData(this.getURL()) + .then((data) => { + const closest = this.getClosestToCurrentTime(data.timeSeries); + const coordinates = this.resolveCoordinates(data); + const weatherObject = this.convertWeatherDataToObject(closest, coordinates); + this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`); + this.setCurrentWeather(weatherObject); + }) + .catch((error) => Log.error(`Could not load data: ${error.message}`)) + .finally(() => this.updateAvailable()); + }, + + /** + * Implements method in interface for fetching a multi-day forecast. + */ + fetchWeatherForecast() { + this.fetchData(this.getURL()) + .then((data) => { + const coordinates = this.resolveCoordinates(data); + const weatherObjects = this.convertWeatherDataGroupedBy(data.timeSeries, coordinates); + this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`); + this.setWeatherForecast(weatherObjects); + }) + .catch((error) => Log.error(`Could not load data: ${error.message}`)) + .finally(() => this.updateAvailable()); + }, + + /** + * Implements method in interface for fetching hourly forecasts. + */ + fetchWeatherHourly() { + this.fetchData(this.getURL()) + .then((data) => { + const coordinates = this.resolveCoordinates(data); + const weatherObjects = this.convertWeatherDataGroupedBy(data.timeSeries, coordinates, "hour"); + this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`); + this.setWeatherHourly(weatherObjects); + }) + .catch((error) => Log.error(`Could not load data: ${error.message}`)) + .finally(() => this.updateAvailable()); + }, + + /** + * Overrides method for setting config with checks for the precipitationValue being unset or invalid + * + * @param {object} config The configuration object + */ + setConfig(config) { + this.config = config; + if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) === -1) { + Log.log(`invalid or not set: ${config.precipitationValue}`); + config.precipitationValue = this.defaults.precipitationValue; + } + }, + + /** + * Of all the times returned find out which one is closest to the current time, should be the first if the data isn't old. + * + * @param {object[]} times Array of time objects + * @returns {object} The weatherdata closest to the current time + */ + getClosestToCurrentTime(times) { + let now = moment(); + let minDiff = undefined; + for (const time of times) { + let diff = Math.abs(moment(time.validTime).diff(now)); + if (!minDiff || diff < Math.abs(moment(minDiff.validTime).diff(now))) { + minDiff = time; + } + } + return minDiff; + }, + + /** + * Get the forecast url for the configured coordinates + * + * @returns {string} the url for the specified coordinates + */ + getURL() { + const formatter = new Intl.NumberFormat("en-US", { + minimumFractionDigits: 6, + maximumFractionDigits: 6 + }); + const lon = formatter.format(this.config.lon); + const lat = formatter.format(this.config.lat); + return `https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/${lon}/lat/${lat}/data.json`; + }, + + /** + * Calculates the apparent temperature based on known atmospheric data. + * + * @param {object} weatherData Weatherdata to use for the calculation + * @returns {number} The apparent temperature + */ + calculateApparentTemperature(weatherData) { + const Ta = this.paramValue(weatherData, "t"); + const rh = this.paramValue(weatherData, "r"); + const ws = this.paramValue(weatherData, "ws"); + const p = (rh / 100) * 6.105 * Math.E * ((17.27 * Ta) / (237.7 + Ta)); + + return Ta + 0.33 * p - 0.7 * ws - 4; + }, + + /** + * Converts the returned data into a WeatherObject with required properties set for both current weather and forecast. + * The returned units is always in metric system. + * Requires coordinates to determine if its daytime or nighttime to know which icon to use and also to set sunrise and sunset. + * + * @param {object} weatherData Weatherdata to convert + * @param {object} coordinates Coordinates of the locations of the weather + * @returns {WeatherObject} The converted weatherdata at the specified location + */ + convertWeatherDataToObject(weatherData, coordinates) { + let currentWeather = new WeatherObject(); + + currentWeather.date = moment(weatherData.validTime); + currentWeather.updateSunTime(coordinates.lat, coordinates.lon); + currentWeather.humidity = this.paramValue(weatherData, "r"); + currentWeather.temperature = this.paramValue(weatherData, "t"); + currentWeather.windSpeed = this.paramValue(weatherData, "ws"); + currentWeather.windFromDirection = this.paramValue(weatherData, "wd"); + currentWeather.weatherType = this.convertWeatherType(this.paramValue(weatherData, "Wsymb2"), currentWeather.isDayTime()); + currentWeather.feelsLikeTemp = this.calculateApparentTemperature(weatherData); + + // Determine the precipitation amount and category and update the + // weatherObject with it, the valuetype to use can be configured or uses + // median as default. + let precipitationValue = this.paramValue(weatherData, this.config.precipitationValue); + switch (this.paramValue(weatherData, "pcat")) { + // 0 = No precipitation + case 1: // Snow + currentWeather.snow += precipitationValue; + currentWeather.precipitationAmount += precipitationValue; + break; + case 2: // Snow and rain, treat it as 50/50 snow and rain + currentWeather.snow += precipitationValue / 2; + currentWeather.rain += precipitationValue / 2; + currentWeather.precipitationAmount += precipitationValue; + break; + case 3: // Rain + case 4: // Drizzle + case 5: // Freezing rain + case 6: // Freezing drizzle + currentWeather.rain += precipitationValue; + currentWeather.precipitationAmount += precipitationValue; + break; + } + + return currentWeather; + }, + + /** + * Takes all the data points and converts it to one WeatherObject per day. + * + * @param {object[]} allWeatherData Array of weatherdata + * @param {object} coordinates Coordinates of the locations of the weather + * @param {string} groupBy The interval to use for grouping the data (day, hour) + * @returns {WeatherObject[]} Array of weatherobjects + */ + convertWeatherDataGroupedBy(allWeatherData, coordinates, groupBy = "day") { + let currentWeather; + let result = []; + + let allWeatherObjects = this.fillInGaps(allWeatherData).map((weatherData) => this.convertWeatherDataToObject(weatherData, coordinates)); + let dayWeatherTypes = []; + + for (const weatherObject of allWeatherObjects) { + //If its the first object or if a day/hour change we need to reset the summary object + if (!currentWeather || !currentWeather.date.isSame(weatherObject.date, groupBy)) { + currentWeather = new WeatherObject(); + dayWeatherTypes = []; + currentWeather.temperature = weatherObject.temperature; + currentWeather.date = weatherObject.date; + currentWeather.minTemperature = Infinity; + currentWeather.maxTemperature = -Infinity; + currentWeather.snow = 0; + currentWeather.rain = 0; + currentWeather.precipitationAmount = 0; + result.push(currentWeather); + } + + //Keep track of what icons have been used for each hour of daytime and use the middle one for the forecast + if (weatherObject.isDayTime()) { + dayWeatherTypes.push(weatherObject.weatherType); + } + if (dayWeatherTypes.length > 0) { + currentWeather.weatherType = dayWeatherTypes[Math.floor(dayWeatherTypes.length / 2)]; + } else { + currentWeather.weatherType = weatherObject.weatherType; + } + + //All other properties is either a sum, min or max of each hour + currentWeather.minTemperature = Math.min(currentWeather.minTemperature, weatherObject.temperature); + currentWeather.maxTemperature = Math.max(currentWeather.maxTemperature, weatherObject.temperature); + currentWeather.snow += weatherObject.snow; + currentWeather.rain += weatherObject.rain; + currentWeather.precipitationAmount += weatherObject.precipitationAmount; + } + + return result; + }, + + /** + * Resolve coordinates from the response data (probably preferably to use + * this if it's not matching the config values exactly) + * + * @param {object} data Response data from the weather service + * @returns {{lon, lat}} the lat/long coordinates of the data + */ + resolveCoordinates(data) { + return { lat: data.geometry.coordinates[0][1], lon: data.geometry.coordinates[0][0] }; + }, + + /** + * The distance between the data points is increasing in the data the more distant the prediction is. + * Find these gaps and fill them with the previous hours data to make the data returned a complete set. + * + * @param {object[]} data Response data from the weather service + * @returns {object[]} Given data with filled gaps + */ + fillInGaps(data) { + let result = []; + for (let i = 1; i < data.length; i++) { + let to = moment(data[i].validTime); + let from = moment(data[i - 1].validTime); + let hours = moment.duration(to.diff(from)).asHours(); + // For each hour add a datapoint but change the validTime + for (let j = 0; j < hours; j++) { + let current = Object.assign({}, data[i]); + current.validTime = from.clone().add(j, "hours").toISOString(); + result.push(current); + } + } + return result; + }, + + /** + * Helper method to get a property from the returned data set. + * + * @param {object} currentWeatherData Weatherdata to get from + * @param {string} name The name of the property + * @returns {*} The value of the property in the weatherdata + */ + paramValue(currentWeatherData, name) { + return currentWeatherData.parameters.filter((p) => p.name === name).flatMap((p) => p.values)[0]; + }, + + /** + * Map the icon value from SMHI to an icon that MagicMirror² understands. + * Uses different icons depending on if its daytime or nighttime. + * SMHI's description of what the numeric value means is the comment after the case. + * + * @param {number} input The SMHI icon value + * @param {boolean} isDayTime True if the icon should be for daytime, false for nighttime + * @returns {string} The icon name for the MagicMirror + */ + convertWeatherType(input, isDayTime) { + switch (input) { + case 1: + return isDayTime ? "day-sunny" : "night-clear"; // Clear sky + case 2: + return isDayTime ? "day-sunny-overcast" : "night-partly-cloudy"; // Nearly clear sky + case 3: + return isDayTime ? "day-cloudy" : "night-cloudy"; // Variable cloudiness + case 4: + return isDayTime ? "day-cloudy" : "night-cloudy"; // Halfclear sky + case 5: + return "cloudy"; // Cloudy sky + case 6: + return "cloudy"; // Overcast + case 7: + return "fog"; // Fog + case 8: + return "showers"; // Light rain showers + case 9: + return "showers"; // Moderate rain showers + case 10: + return "showers"; // Heavy rain showers + case 11: + return "thunderstorm"; // Thunderstorm + case 12: + return "sleet"; // Light sleet showers + case 13: + return "sleet"; // Moderate sleet showers + case 14: + return "sleet"; // Heavy sleet showers + case 15: + return "snow"; // Light snow showers + case 16: + return "snow"; // Moderate snow showers + case 17: + return "snow"; // Heavy snow showers + case 18: + return "rain"; // Light rain + case 19: + return "rain"; // Moderate rain + case 20: + return "rain"; // Heavy rain + case 21: + return "thunderstorm"; // Thunder + case 22: + return "sleet"; // Light sleet + case 23: + return "sleet"; // Moderate sleet + case 24: + return "sleet"; // Heavy sleet + case 25: + return "snow"; // Light snowfall + case 26: + return "snow"; // Moderate snowfall + case 27: + return "snow"; // Heavy snowfall + default: + return ""; + } + } +}); diff --git a/MagicMirror/modules/default/weather/providers/ukmetoffice.js b/MagicMirror/modules/default/weather/providers/ukmetoffice.js new file mode 100644 index 0000000000000000000000000000000000000000..8f03cbe6af4ded1f07c58f66afb903a51de60501 --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/ukmetoffice.js @@ -0,0 +1,202 @@ +/* global WeatherProvider, WeatherObject, WeatherUtils */ + +/* MagicMirror² + * Module: Weather + * + * By Malcolm Oakes https://github.com/maloakes + * MIT Licensed. + * + * This class is a provider for UK Met Office Datapoint. + */ +WeatherProvider.register("ukmetoffice", { + // Set the name of the provider. + // This isn't strictly necessary, since it will fallback to the provider identifier + // 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: "" + }, + + // Overwrite the fetchCurrentWeather method. + fetchCurrentWeather() { + this.fetchData(this.getUrl("3hourly")) + .then((data) => { + if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) { + // Did not receive usable new data. + // Maybe this needs a better check? + return; + } + + this.setFetchedLocation(`${data.SiteRep.DV.Location.name}, ${data.SiteRep.DV.Location.country}`); + + const currentWeather = this.generateWeatherObjectFromCurrentWeather(data); + this.setCurrentWeather(currentWeather); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + // Overwrite the fetchCurrentWeather method. + fetchWeatherForecast() { + this.fetchData(this.getUrl("daily")) + .then((data) => { + if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) { + // Did not receive usable new data. + // Maybe this needs a better check? + return; + } + + this.setFetchedLocation(`${data.SiteRep.DV.Location.name}, ${data.SiteRep.DV.Location.country}`); + + const forecast = this.generateWeatherObjectsFromForecast(data); + this.setWeatherForecast(forecast); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + /** UK Met Office Specific Methods - These are not part of the default provider methods */ + /* + * Gets the complete url for the request + */ + getUrl(forecastType) { + return this.config.apiBase + this.config.locationID + this.getParams(forecastType); + }, + + /* + * Generate a WeatherObject based on currentWeatherInformation + */ + generateWeatherObjectFromCurrentWeather(currentWeatherData) { + const currentWeather = new WeatherObject(); + const location = currentWeatherData.SiteRep.DV.Location; + + // data times are always UTC + let nowUtc = moment.utc(); + let midnightUtc = nowUtc.clone().startOf("day"); + let timeInMins = nowUtc.diff(midnightUtc, "minutes"); + + // loop round each of the (5) periods, look for today (the first period may be yesterday) + for (const period of location.Period) { + const periodDate = moment.utc(period.value.substr(0, 10), "YYYY-MM-DD"); + + // ignore if period is before today + if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) { + // check this is the period we want, after today the diff will be -ve + if (moment().diff(periodDate, "minutes") > 0) { + // loop round the reports looking for the one we are in + // $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260 + for (const rep of period.Rep) { + const p = rep.$; + if (timeInMins >= p && timeInMins - 180 < p) { + // finally got the one we want, so populate weather object + currentWeather.humidity = rep.H; + currentWeather.temperature = rep.T; + currentWeather.feelsLikeTemp = rep.F; + currentWeather.precipitationProbability = parseInt(rep.Pp); + currentWeather.windSpeed = WeatherUtils.convertWindToMetric(rep.S); + currentWeather.windFromDirection = WeatherUtils.convertWindDirection(rep.D); + currentWeather.weatherType = this.convertWeatherType(rep.W); + } + } + } + } + } + + // determine the sunrise/sunset times - not supplied in UK Met Office data + currentWeather.updateSunTime(location.lat, location.lon); + + return currentWeather; + }, + + /* + * Generate WeatherObjects based on forecast information + */ + generateWeatherObjectsFromForecast(forecasts) { + const days = []; + + // loop round the (5) periods getting the data + // for each period array, Day is [0], Night is [1] + for (const period of forecasts.SiteRep.DV.Location.Period) { + const weather = new WeatherObject(); + + // data times are always UTC + const dateStr = period.value; + let periodDate = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD"); + + // ignore if period is before today + if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) { + // populate the weather object + weather.date = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD"); + weather.minTemperature = period.Rep[1].Nm; + weather.maxTemperature = period.Rep[0].Dm; + weather.weatherType = this.convertWeatherType(period.Rep[0].W); + weather.precipitationProbability = parseInt(period.Rep[0].PPd); + + days.push(weather); + } + } + + return days; + }, + + /* + * Convert the Met Office icons to a more usable name. + */ + convertWeatherType(weatherType) { + const weatherTypes = { + 0: "night-clear", + 1: "day-sunny", + 2: "night-alt-cloudy", + 3: "day-cloudy", + 5: "fog", + 6: "fog", + 7: "cloudy", + 8: "cloud", + 9: "night-sprinkle", + 10: "day-sprinkle", + 11: "raindrops", + 12: "sprinkle", + 13: "night-alt-showers", + 14: "day-showers", + 15: "rain", + 16: "night-alt-sleet", + 17: "day-sleet", + 18: "sleet", + 19: "night-alt-hail", + 20: "day-hail", + 21: "hail", + 22: "night-alt-snow", + 23: "day-snow", + 24: "snow", + 25: "night-alt-snow", + 26: "day-snow", + 27: "snow", + 28: "night-alt-thunderstorm", + 29: "day-thunderstorm", + 30: "thunderstorm" + }; + + return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null; + }, + + /** + * Generates an url with api parameters based on the config. + * + * @param {string} forecastType daily or 3hourly forecast + * @returns {string} url + */ + getParams(forecastType) { + let params = "?"; + params += `res=${forecastType}`; + params += `&key=${this.config.apiKey}`; + return params; + } +}); diff --git a/MagicMirror/modules/default/weather/providers/ukmetofficedatahub.js b/MagicMirror/modules/default/weather/providers/ukmetofficedatahub.js new file mode 100644 index 0000000000000000000000000000000000000000..a4d41267aa385278d5ec054a24fbcc8c1fdbaf1f --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/ukmetofficedatahub.js @@ -0,0 +1,271 @@ +/* global WeatherProvider, WeatherObject */ + +/* MagicMirror² + * Module: Weather + * + * By Malcolm Oakes https://github.com/maloakes + * Existing Met Office provider edited for new MetOffice Data Hub by CreepinJesus http://github.com/XBCreepinJesus + * MIT Licensed. + * + * This class is a provider for UK Met Office Data Hub (the replacement for their Data Point services). + * For more information on Data Hub, see https://www.metoffice.gov.uk/services/data/datapoint/notifications/weather-datahub + * Data available: + * Hourly data for next 2 days ("hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-hourly.pdf + * 3-hourly data for the next 7 days ("3hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-3-hourly.pdf + * Daily data for the next 7 days ("daily") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-daily.pdf + * + * NOTES + * This provider requires longitude/latitude coordinates, rather than a location ID (as with the previous Met Office provider) + * Provide the following in your config.js file: + * weatherProvider: "ukmetofficedatahub", + * apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/", + * apiKey: "[YOUR API KEY]", + * apiSecret: "[YOUR API SECRET]", + * lat: [LATITUDE (DECIMAL)], + * lon: [LONGITUDE (DECIMAL)] + * + * At time of writing, free accounts are limited to 360 requests a day per service (hourly, 3hourly, daily); take this in mind when + * setting your update intervals. For reference, 360 requests per day is once every 4 minutes. + * + * Pay attention to the units of the supplied data from the Met Office - it is given in SI/metric units where applicable: + * - Temperatures are in degrees Celsius (°C) + * - Wind speeds are in metres per second (m/s) + * - Wind direction given in degrees (°) + * - Pressures are in Pascals (Pa) + * - Distances are in metres (m) + * - Probabilities and humidity are given as percentages (%) + * - Precipitation is measured in millimetres (mm) with rates per hour (mm/h) + * + * See the PDFs linked above for more information on the data their corresponding units. + */ + +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 + }, + + // Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api) + getUrl(forecastType) { + let queryStrings = "?"; + queryStrings += `latitude=${this.config.lat}`; + queryStrings += `&longitude=${this.config.lon}`; + queryStrings += `&includeLocationName=${true}`; + + // Return URL, making sure there is a trailing "/" in the base URL. + return this.config.apiBase + (this.config.apiBase.endsWith("/") ? "" : "/") + forecastType + queryStrings; + }, + + // Build the list of headers for the request + // For DataHub requests, the API key/secret are sent in the headers rather than as query strings. + // Headers defined according to Data Hub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api) + getHeaders() { + return { + accept: "application/json", + "x-ibm-client-id": this.config.apiKey, + "x-ibm-client-secret": this.config.apiSecret + }; + }, + + // Fetch data using supplied URL and request headers + async fetchWeather(url, headers) { + const response = await fetch(url, { headers: headers }); + + // Return JSON data + return response.json(); + }, + + // Fetch hourly forecast data (to use for current weather) + fetchCurrentWeather() { + this.fetchWeather(this.getUrl("hourly"), this.getHeaders()) + .then((data) => { + // Check data is usable + if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length === 0) { + // Did not receive usable new data. + // Maybe this needs a better check? + Log.error("Possibly bad current/hourly data?"); + Log.error(data); + return; + } + + // Set location name + this.setFetchedLocation(`${data.features[0].properties.location.name}`); + + // Generate current weather data + const currentWeather = this.generateWeatherObjectFromCurrentWeather(data); + this.setCurrentWeather(currentWeather); + }) + + // Catch any error(s) + .catch((error) => Log.error(`Could not load data: ${error.message}`)) + + // Let the module know there is data available + .finally(() => this.updateAvailable()); + }, + + // Create a WeatherObject using current weather data (data for the current hour) + generateWeatherObjectFromCurrentWeather(currentWeatherData) { + const currentWeather = new WeatherObject(); + + // Extract the actual forecasts + let forecastDataHours = currentWeatherData.features[0].properties.timeSeries; + + // Define now + let nowUtc = moment.utc(); + + // Find hour that contains the current time + for (let hour in forecastDataHours) { + let forecastTime = moment.utc(forecastDataHours[hour].time); + if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) { + currentWeather.date = forecastTime; + currentWeather.windSpeed = forecastDataHours[hour].windSpeed10m; + currentWeather.windFromDirection = forecastDataHours[hour].windDirectionFrom10m; + currentWeather.temperature = forecastDataHours[hour].screenTemperature; + currentWeather.minTemperature = forecastDataHours[hour].minScreenAirTemp; + currentWeather.maxTemperature = forecastDataHours[hour].maxScreenAirTemp; + currentWeather.weatherType = this.convertWeatherType(forecastDataHours[hour].significantWeatherCode); + currentWeather.humidity = forecastDataHours[hour].screenRelativeHumidity; + currentWeather.rain = forecastDataHours[hour].totalPrecipAmount; + currentWeather.snow = forecastDataHours[hour].totalSnowAmount; + currentWeather.precipitationProbability = forecastDataHours[hour].probOfPrecipitation; + currentWeather.feelsLikeTemp = forecastDataHours[hour].feelsLikeTemperature; + + // Pass on full details, so they can be used in custom templates + // Note the units of the supplied data when using this (see top of file) + currentWeather.rawData = forecastDataHours[hour]; + } + } + + // Determine the sunrise/sunset times - (still) not supplied in UK Met Office data + // Passes {longitude, latitude} to SunCalc, could pass height to, but + // SunCalc.getTimes doesn't take that into account + currentWeather.updateSunTime(this.config.lat, this.config.lon); + + return currentWeather; + }, + + // Fetch daily forecast data + fetchWeatherForecast() { + this.fetchWeather(this.getUrl("daily"), this.getHeaders()) + .then((data) => { + // Check data is usable + if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length === 0) { + // Did not receive usable new data. + // Maybe this needs a better check? + Log.error("Possibly bad forecast data?"); + Log.error(data); + return; + } + + // Set location name + this.setFetchedLocation(`${data.features[0].properties.location.name}`); + + // Generate the forecast data + const forecast = this.generateWeatherObjectsFromForecast(data); + this.setWeatherForecast(forecast); + }) + + // Catch any error(s) + .catch((error) => Log.error(`Could not load data: ${error.message}`)) + + // Let the module know there is new data available + .finally(() => this.updateAvailable()); + }, + + // Create a WeatherObject for each day using daily forecast data + generateWeatherObjectsFromForecast(forecasts) { + const dailyForecasts = []; + + // Extract the actual forecasts + let forecastDataDays = forecasts.features[0].properties.timeSeries; + + // Define today + let today = moment.utc().startOf("date"); + + // Go through each day in the forecasts + for (let day in forecastDataDays) { + const forecastWeather = new WeatherObject(); + + // Get date of forecast + let forecastDate = moment.utc(forecastDataDays[day].time); + + // Check if forecast is for today or in the future (i.e., ignore yesterday's forecast) + if (forecastDate.isSameOrAfter(today)) { + forecastWeather.date = forecastDate; + forecastWeather.minTemperature = forecastDataDays[day].nightMinScreenTemperature; + forecastWeather.maxTemperature = forecastDataDays[day].dayMaxScreenTemperature; + + // Using daytime forecast values + forecastWeather.windSpeed = forecastDataDays[day].midday10MWindSpeed; + forecastWeather.windFromDirection = forecastDataDays[day].midday10MWindDirection; + forecastWeather.weatherType = this.convertWeatherType(forecastDataDays[day].daySignificantWeatherCode); + forecastWeather.precipitationProbability = forecastDataDays[day].dayProbabilityOfPrecipitation; + forecastWeather.temperature = forecastDataDays[day].dayMaxScreenTemperature; + forecastWeather.humidity = forecastDataDays[day].middayRelativeHumidity; + forecastWeather.rain = forecastDataDays[day].dayProbabilityOfRain; + forecastWeather.snow = forecastDataDays[day].dayProbabilityOfSnow; + forecastWeather.feelsLikeTemp = forecastDataDays[day].dayMaxFeelsLikeTemp; + + // Pass on full details, so they can be used in custom templates + // Note the units of the supplied data when using this (see top of file) + forecastWeather.rawData = forecastDataDays[day]; + + dailyForecasts.push(forecastWeather); + } + } + + return dailyForecasts; + }, + + // Set the fetched location name. + setFetchedLocation: function (name) { + this.fetchedLocationName = name; + }, + + // Match the Met Office "significant weather code" to a weathericons.css icon + // Use: https://metoffice.apiconnect.ibmcloud.com/metoffice/production/node/264 + // and: https://erikflowers.github.io/weather-icons/ + convertWeatherType(weatherType) { + const weatherTypes = { + 0: "night-clear", + 1: "day-sunny", + 2: "night-alt-cloudy", + 3: "day-cloudy", + 5: "fog", + 6: "fog", + 7: "cloudy", + 8: "cloud", + 9: "night-sprinkle", + 10: "day-sprinkle", + 11: "raindrops", + 12: "sprinkle", + 13: "night-alt-showers", + 14: "day-showers", + 15: "rain", + 16: "night-alt-sleet", + 17: "day-sleet", + 18: "sleet", + 19: "night-alt-hail", + 20: "day-hail", + 21: "hail", + 22: "night-alt-snow", + 23: "day-snow", + 24: "snow", + 25: "night-alt-snow", + 26: "day-snow", + 27: "snow", + 28: "night-alt-thunderstorm", + 29: "day-thunderstorm", + 30: "thunderstorm" + }; + + return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null; + } +}); diff --git a/MagicMirror/modules/default/weather/providers/weatherbit.js b/MagicMirror/modules/default/weather/providers/weatherbit.js new file mode 100644 index 0000000000000000000000000000000000000000..7d0468bccb8b6d79a9dc71a2f60df9ffadac97f5 --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/weatherbit.js @@ -0,0 +1,209 @@ +/* global WeatherProvider, WeatherObject */ + +/* MagicMirror² + * Module: Weather + * Provider: Weatherbit + * + * By Andrew Pometti + * MIT Licensed + * + * This class is a provider for Weatherbit, based on Nicholas Hubbard's class + * for Dark Sky & Vince Peri's class for Weather.gov. + */ +WeatherProvider.register("weatherbit", { + // Set the name of the provider. + // 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", + apiKey: "", + lat: 0, + lon: 0 + }, + + fetchedLocation: function () { + return this.fetchedLocationName || ""; + }, + + fetchCurrentWeather() { + this.fetchData(this.getUrl()) + .then((data) => { + if (!data || !data.data[0] || typeof data.data[0].temp === "undefined") { + // No usable data? + return; + } + + const currentWeather = this.generateWeatherDayFromCurrentWeather(data); + this.setCurrentWeather(currentWeather); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + fetchWeatherForecast() { + this.fetchData(this.getUrl()) + .then((data) => { + if (!data || !data.data) { + // No usable data? + return; + } + + const forecast = this.generateWeatherObjectsFromForecast(data.data); + this.setWeatherForecast(forecast); + + this.fetchedLocationName = `${data.city_name}, ${data.state_code}`; + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + /** + * Overrides method for setting config to check if endpoint is correct for hourly + * + * @param {object} config The configuration object + */ + setConfig(config) { + this.config = config; + if (!this.config.weatherEndpoint) { + switch (this.config.type) { + case "hourly": + this.config.weatherEndpoint = "/forecast/hourly"; + break; + case "daily": + case "forecast": + this.config.weatherEndpoint = "/forecast/daily"; + break; + case "current": + this.config.weatherEndpoint = "/current"; + break; + default: + Log.error("weatherEndpoint not configured and could not resolve it based on type"); + } + } + }, + + // Create a URL from the config and base URL. + getUrl() { + return `${this.config.apiBase}${this.config.weatherEndpoint}?lat=${this.config.lat}&lon=${this.config.lon}&units=M&key=${this.config.apiKey}`; + }, + + // Implement WeatherDay generator. + generateWeatherDayFromCurrentWeather(currentWeatherData) { + //Calculate TZ Offset and invert to convert Sunrise/Sunset times to Local + const d = new Date(); + let tzOffset = d.getTimezoneOffset(); + tzOffset = tzOffset * -1; + + const currentWeather = new WeatherObject(); + + currentWeather.date = moment.unix(currentWeatherData.data[0].ts); + currentWeather.humidity = parseFloat(currentWeatherData.data[0].rh); + currentWeather.temperature = parseFloat(currentWeatherData.data[0].temp); + currentWeather.windSpeed = parseFloat(currentWeatherData.data[0].wind_spd); + currentWeather.windFromDirection = currentWeatherData.data[0].wind_dir; + currentWeather.weatherType = this.convertWeatherType(currentWeatherData.data[0].weather.icon); + currentWeather.sunrise = moment(currentWeatherData.data[0].sunrise, "HH:mm").add(tzOffset, "m"); + currentWeather.sunset = moment(currentWeatherData.data[0].sunset, "HH:mm").add(tzOffset, "m"); + + this.fetchedLocationName = `${currentWeatherData.data[0].city_name}, ${currentWeatherData.data[0].state_code}`; + + return currentWeather; + }, + + generateWeatherObjectsFromForecast(forecasts) { + const days = []; + + for (const forecast of forecasts) { + const weather = new WeatherObject(); + + weather.date = moment(forecast.datetime, "YYYY-MM-DD"); + weather.minTemperature = forecast.min_temp; + weather.maxTemperature = forecast.max_temp; + weather.precipitationAmount = forecast.precip; + weather.precipitationProbability = forecast.pop; + weather.weatherType = this.convertWeatherType(forecast.weather.icon); + + days.push(weather); + } + + return days; + }, + + // Map icons from Dark Sky to our icons. + convertWeatherType(weatherType) { + const weatherTypes = { + t01d: "day-thunderstorm", + t01n: "night-alt-thunderstorm", + t02d: "day-thunderstorm", + t02n: "night-alt-thunderstorm", + t03d: "thunderstorm", + t03n: "thunderstorm", + t04d: "day-thunderstorm", + t04n: "night-alt-thunderstorm", + t05d: "day-sleet-storm", + t05n: "night-alt-sleet-storm", + d01d: "day-sprinkle", + d01n: "night-alt-sprinkle", + d02d: "day-sprinkle", + d02n: "night-alt-sprinkle", + d03d: "day-shower", + d03n: "night-alt-shower", + r01d: "day-shower", + r01n: "night-alt-shower", + r02d: "day-rain", + r02n: "night-alt-rain", + r03d: "day-rain", + r03n: "night-alt-rain", + r04d: "day-sprinkle", + r04n: "night-alt-sprinkle", + r05d: "day-shower", + r05n: "night-alt-shower", + r06d: "day-shower", + r06n: "night-alt-shower", + f01d: "day-sleet", + f01n: "night-alt-sleet", + s01d: "day-snow", + s01n: "night-alt-snow", + s02d: "day-snow-wind", + s02n: "night-alt-snow-wind", + s03d: "snowflake-cold", + s03n: "snowflake-cold", + s04d: "day-rain-mix", + s04n: "night-alt-rain-mix", + s05d: "day-sleet", + s05n: "night-alt-sleet", + s06d: "day-snow", + s06n: "night-alt-snow", + a01d: "day-haze", + a01n: "dust", + a02d: "smoke", + a02n: "smoke", + a03d: "day-haze", + a03n: "dust", + a04d: "dust", + a04n: "dust", + a05d: "day-fog", + a05n: "night-fog", + a06d: "fog", + a06n: "fog", + c01d: "day-sunny", + c01n: "night-clear", + c02d: "day-sunny-overcast", + c02n: "night-alt-partly-cloudy", + c03d: "day-cloudy", + c03n: "night-alt-cloudy", + c04d: "cloudy", + c04n: "cloudy", + u00d: "rain-mix", + u00n: "rain-mix" + }; + + return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null; + } +}); diff --git a/MagicMirror/modules/default/weather/providers/weatherflow.js b/MagicMirror/modules/default/weather/providers/weatherflow.js new file mode 100644 index 0000000000000000000000000000000000000000..aecfb63e41bf109c2ac8ee50f25838711db2a2c6 --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/weatherflow.js @@ -0,0 +1,77 @@ +/* global WeatherProvider, WeatherObject, WeatherUtils */ + +/* MagicMirror² + * Module: Weather + * Provider: Weatherflow + * + * By Tobias Dreyem https://github.com/10bias + * MIT Licensed + * + * This class is a provider for Weatherflow. + * Note that the Weatherflow API does not provide snowfall. + */ + +WeatherProvider.register("weatherflow", { + // Set the name of the provider. + // Not strictly required, but helps for debugging + providerName: "WeatherFlow", + + // Set the default config properties that is specific to this provider + defaults: { + apiBase: "https://swd.weatherflow.com/swd/rest/", + token: "", + stationid: "" + }, + + fetchCurrentWeather() { + this.fetchData(this.getUrl()) + .then((data) => { + const currentWeather = new WeatherObject(); + currentWeather.date = moment(); + + currentWeather.humidity = data.current_conditions.relative_humidity; + currentWeather.temperature = data.current_conditions.air_temperature; + currentWeather.windSpeed = WeatherUtils.convertWindToMs(data.current_conditions.wind_avg); + currentWeather.windFromDirection = data.current_conditions.wind_direction; + currentWeather.weatherType = data.forecast.daily[0].icon; + currentWeather.sunrise = moment.unix(data.forecast.daily[0].sunrise); + currentWeather.sunset = moment.unix(data.forecast.daily[0].sunset); + this.setCurrentWeather(currentWeather); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + fetchWeatherForecast() { + this.fetchData(this.getUrl()) + .then((data) => { + const days = []; + + for (const forecast of data.forecast.daily) { + const weather = new WeatherObject(); + + weather.date = moment.unix(forecast.day_start_local); + weather.minTemperature = forecast.air_temp_low; + weather.maxTemperature = forecast.air_temp_high; + weather.precipitationProbability = forecast.precip_probability; + weather.weatherType = forecast.icon; + weather.snow = 0; + + days.push(weather); + } + + this.setWeatherForecast(days); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + // Create a URL from the config and base URL. + getUrl() { + return `${this.config.apiBase}better_forecast?station_id=${this.config.stationid}&units_temp=c&units_wind=kph&units_pressure=mb&units_precip=mm&units_distance=km&token=${this.config.token}`; + } +}); diff --git a/MagicMirror/modules/default/weather/providers/weathergov.js b/MagicMirror/modules/default/weather/providers/weathergov.js new file mode 100644 index 0000000000000000000000000000000000000000..b1c69ee75344d8eb876c964a4c3d0d2501a0499f --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/weathergov.js @@ -0,0 +1,367 @@ +/* global WeatherProvider, WeatherObject, WeatherUtils */ + +/* MagicMirror² + * Module: Weather + * Provider: weather.gov + * https://weather-gov.github.io/api/general-faqs + * + * Original by Vince Peri + * MIT Licensed. + * + * This class is a provider for weather.gov. + * Note that this is only for US locations (lat and lon) and does not require an API key + * Since it is free, there are some items missing - like sunrise, sunset + */ + +WeatherProvider.register("weathergov", { + // Set the name of the provider. + // This isn't strictly necessary, since it will fallback to the provider identifier + // 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.weather.gov/points/", + lat: 0, + lon: 0 + }, + + // Flag all needed URLs availability + configURLs: false, + + //This API has multiple urls involved + forecastURL: "tbd", + forecastHourlyURL: "tbd", + forecastGridDataURL: "tbd", + observationStationsURL: "tbd", + stationObsURL: "tbd", + + // Called to set the config, this config is the same as the weather module's config. + setConfig: function (config) { + this.config = config; + this.config.apiBase = "https://api.weather.gov"; + this.fetchWxGovURLs(this.config); + }, + + // Called when the weather provider is about to start. + start: function () { + Log.info(`Weather provider: ${this.providerName} started.`); + }, + + // This returns the name of the fetched location or an empty string. + fetchedLocation: function () { + return this.fetchedLocationName || ""; + }, + + // Overwrite the fetchCurrentWeather method. + fetchCurrentWeather() { + if (!this.configURLs) { + Log.info("fetchCurrentWeather: fetch wx waiting on config URLs"); + return; + } + this.fetchData(this.stationObsURL) + .then((data) => { + if (!data || !data.properties) { + // Did not receive usable new data. + return; + } + const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties); + this.setCurrentWeather(currentWeather); + }) + .catch(function (request) { + Log.error("Could not load station obs data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + // Overwrite the fetchWeatherForecast method. + fetchWeatherForecast() { + if (!this.configURLs) { + Log.info("fetchWeatherForecast: fetch wx waiting on config URLs"); + return; + } + this.fetchData(this.forecastURL) + .then((data) => { + if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) { + // Did not receive usable new data. + return; + } + const forecast = this.generateWeatherObjectsFromForecast(data.properties.periods); + this.setWeatherForecast(forecast); + }) + .catch(function (request) { + Log.error("Could not load forecast hourly data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + // Overwrite the fetchWeatherHourly method. + fetchWeatherHourly() { + if (!this.configURLs) { + Log.info("fetchWeatherHourly: fetch wx waiting on config URLs"); + return; + } + this.fetchData(this.forecastHourlyURL) + .then((data) => { + if (!data) { + // Did not receive usable new data. + // Maybe this needs a better check? + return; + } + const hourly = this.generateWeatherObjectsFromHourly(data.properties.periods); + this.setWeatherHourly(hourly); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + + /** Weather.gov Specific Methods - These are not part of the default provider methods */ + + /* + * Get specific URLs + */ + fetchWxGovURLs(config) { + this.fetchData(`${config.apiBase}/points/${config.lat},${config.lon}`) + .then((data) => { + if (!data || !data.properties) { + // points URL did not respond with usable data. + return; + } + this.fetchedLocationName = `${data.properties.relativeLocation.properties.city}, ${data.properties.relativeLocation.properties.state}`; + Log.log(`Forecast location is ${this.fetchedLocationName}`); + this.forecastURL = `${data.properties.forecast}?units=si`; + this.forecastHourlyURL = `${data.properties.forecastHourly}?units=si`; + this.forecastGridDataURL = data.properties.forecastGridData; + this.observationStationsURL = data.properties.observationStations; + // with this URL, we chain another promise for the station obs URL + return this.fetchData(data.properties.observationStations); + }) + .then((obsData) => { + if (!obsData || !obsData.features) { + // obs station URL did not respond with usable data. + return; + } + this.stationObsURL = `${obsData.features[0].id}/observations/latest`; + }) + .catch((err) => { + Log.error(err); + }) + .finally(() => { + // excellent, let's fetch some actual wx data + this.configURLs = true; + + // handle 'forecast' config, fall back to 'current' + if (config.type === "forecast") { + this.fetchWeatherForecast(); + } else if (config.type === "hourly") { + this.fetchWeatherHourly(); + } else { + this.fetchCurrentWeather(); + } + }); + }, + /* + * Generate a WeatherObject based on hourlyWeatherInformation + * Weather.gov API uses specific units; API does not include choice of units + * ... object needs data in units based on config! + */ + generateWeatherObjectsFromHourly(forecasts) { + const days = []; + + // variable for date + let weather = new WeatherObject(); + for (const forecast of forecasts) { + weather.date = moment(forecast.startTime.slice(0, 19)); + if (forecast.windSpeed.search(" ") < 0) { + weather.windSpeed = forecast.windSpeed; + } else { + weather.windSpeed = forecast.windSpeed.slice(0, forecast.windSpeed.search(" ")); + } + weather.windSpeed = WeatherUtils.convertWindToMs(weather.windSpeed); + weather.windFromDirection = forecast.windDirection; + weather.temperature = forecast.temperature; + // use the forecast isDayTime attribute to help build the weatherType label + weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime); + + days.push(weather); + + weather = new WeatherObject(); + } + + // push weather information to days array + days.push(weather); + return days; + }, + + /* + * Generate a WeatherObject based on currentWeatherInformation + * Weather.gov API uses specific units; API does not include choice of units + * ... object needs data in units based on config! + */ + generateWeatherObjectFromCurrentWeather(currentWeatherData) { + const currentWeather = new WeatherObject(); + + currentWeather.date = moment(currentWeatherData.timestamp); + currentWeather.temperature = currentWeatherData.temperature.value; + currentWeather.windSpeed = WeatherUtils.convertWindToMs(currentWeatherData.windSpeed.value); + currentWeather.windFromDirection = currentWeatherData.windDirection.value; + currentWeather.minTemperature = currentWeatherData.minTemperatureLast24Hours.value; + currentWeather.maxTemperature = currentWeatherData.maxTemperatureLast24Hours.value; + currentWeather.humidity = Math.round(currentWeatherData.relativeHumidity.value); + currentWeather.precipitationAmount = currentWeatherData.precipitationLastHour.value; + if (currentWeatherData.heatIndex.value !== null) { + currentWeather.feelsLikeTemp = currentWeatherData.heatIndex.value; + } else if (currentWeatherData.windChill.value !== null) { + currentWeather.feelsLikeTemp = currentWeatherData.windChill.value; + } else { + currentWeather.feelsLikeTemp = currentWeatherData.temperature.value; + } + // determine the sunrise/sunset times - not supplied in weather.gov data + currentWeather.updateSunTime(this.config.lat, this.config.lon); + + // update weatherType + currentWeather.weatherType = this.convertWeatherType(currentWeatherData.textDescription, currentWeather.isDayTime()); + + return currentWeather; + }, + + /* + * Generate WeatherObjects based on forecast information + */ + generateWeatherObjectsFromForecast(forecasts) { + return this.fetchForecastDaily(forecasts); + }, + + /* + * fetch forecast information for daily forecast. + */ + fetchForecastDaily(forecasts) { + const precipitationProbabilityRegEx = "Chance of precipitation is ([0-9]+?)%"; + + // initial variable declaration + const days = []; + // variables for temperature range and rain + let minTemp = []; + let maxTemp = []; + // variable for date + let date = ""; + let weather = new WeatherObject(); + + for (const forecast of forecasts) { + if (date !== moment(forecast.startTime).format("YYYY-MM-DD")) { + // calculate minimum/maximum temperature, specify rain amount + weather.minTemperature = Math.min.apply(null, minTemp); + weather.maxTemperature = Math.max.apply(null, maxTemp); + + // push weather information to days array + days.push(weather); + // create new weather-object + weather = new WeatherObject(); + + minTemp = []; + maxTemp = []; + const precipitation = new RegExp(precipitationProbabilityRegEx, "g").exec(forecast.detailedForecast); + if (precipitation) weather.precipitationProbability = precipitation[1]; + + // set new date + date = moment(forecast.startTime).format("YYYY-MM-DD"); + + // specify date + weather.date = moment(forecast.startTime); + + // use the forecast isDayTime attribute to help build the weatherType label + weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime); + } + + if (moment(forecast.startTime).format("H") >= 8 && moment(forecast.startTime).format("H") <= 17) { + weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime); + } + + // the same day as before + // add values from forecast to corresponding variables + minTemp.push(forecast.temperature); + maxTemp.push(forecast.temperature); + } + + // last day + // calculate minimum/maximum temperature + weather.minTemperature = Math.min.apply(null, minTemp); + weather.maxTemperature = Math.max.apply(null, maxTemp); + + // push weather information to days array + days.push(weather); + return days.slice(1); + }, + + /* + * Convert the icons to a more usable name. + */ + convertWeatherType(weatherType, isDaytime) { + //https://w1.weather.gov/xml/current_obs/weather.php + // There are way too many types to create, so lets just look for certain strings + + if (weatherType.includes("Cloudy") || weatherType.includes("Partly")) { + if (isDaytime) { + return "day-cloudy"; + } + + return "night-cloudy"; + } else if (weatherType.includes("Overcast")) { + if (isDaytime) { + return "cloudy"; + } + + return "night-cloudy"; + } else if (weatherType.includes("Freezing") || weatherType.includes("Ice")) { + return "rain-mix"; + } else if (weatherType.includes("Snow")) { + if (isDaytime) { + return "snow"; + } + + return "night-snow"; + } else if (weatherType.includes("Thunderstorm")) { + if (isDaytime) { + return "thunderstorm"; + } + + return "night-thunderstorm"; + } else if (weatherType.includes("Showers")) { + if (isDaytime) { + return "showers"; + } + + return "night-showers"; + } else if (weatherType.includes("Rain") || weatherType.includes("Drizzle")) { + if (isDaytime) { + return "rain"; + } + + return "night-rain"; + } else if (weatherType.includes("Breezy") || weatherType.includes("Windy")) { + if (isDaytime) { + return "cloudy-windy"; + } + + return "night-alt-cloudy-windy"; + } else if (weatherType.includes("Fair") || weatherType.includes("Clear") || weatherType.includes("Few") || weatherType.includes("Sunny")) { + if (isDaytime) { + return "day-sunny"; + } + + return "night-clear"; + } else if (weatherType.includes("Dust") || weatherType.includes("Sand")) { + return "dust"; + } else if (weatherType.includes("Fog")) { + return "fog"; + } else if (weatherType.includes("Smoke")) { + return "smoke"; + } else if (weatherType.includes("Haze")) { + return "day-haze"; + } + + return null; + } +}); diff --git a/MagicMirror/modules/default/weather/providers/yr.js b/MagicMirror/modules/default/weather/providers/yr.js new file mode 100644 index 0000000000000000000000000000000000000000..09e2643df1a4ce3c4d338cd7aa52ca1cc1cb15eb --- /dev/null +++ b/MagicMirror/modules/default/weather/providers/yr.js @@ -0,0 +1,630 @@ +/* global WeatherProvider, WeatherObject */ + +/* MagicMirror² + * Module: Weather + * Provider: Yr.no + * + * By Magnus Marthinsen + * MIT Licensed + * + * This class is a provider for Yr.no, a norwegian weather service. + * + * Terms of service: https://developer.yr.no/doc/TermsOfService/ + */ +WeatherProvider.register("yr", { + providerName: "Yr", + + // Set the default config properties that is specific to this provider + defaults: { + useCorsProxy: true, + apiBase: "https://api.met.no/weatherapi", + altitude: 0, + currentForecastHours: 1 //1, 6 or 12 + }, + + start() { + if (typeof Storage === "undefined") { + //local storage unavailable + Log.error("The Yr weather provider requires local storage."); + throw new Error("Local storage not available"); + } + Log.info(`Weather provider: ${this.providerName} started.`); + }, + + fetchCurrentWeather() { + this.getCurrentWeather() + .then((currentWeather) => { + this.setCurrentWeather(currentWeather); + this.updateAvailable(); + }) + .catch((error) => { + Log.error(error); + throw new Error(error); + }); + }, + + async getCurrentWeather() { + const getRequests = [this.getWeatherData(), this.getStellarData()]; + const [weatherData, stellarData] = await Promise.all(getRequests); + if (!stellarData) { + Log.warn("No stellar data available."); + } + if (!weatherData.properties.timeseries || !weatherData.properties.timeseries[0]) { + Log.error("No weather data available."); + return; + } + const currentTime = moment(); + let forecast = weatherData.properties.timeseries[0]; + let closestTimeInPast = currentTime.diff(moment(forecast.time)); + for (const forecastTime of weatherData.properties.timeseries) { + const comparison = currentTime.diff(moment(forecastTime.time)); + if (0 < comparison && comparison < closestTimeInPast) { + closestTimeInPast = comparison; + forecast = forecastTime; + } + } + const forecastXHours = this.getForecastForXHoursFrom(forecast.data); + forecast.weatherType = this.convertWeatherType(forecastXHours.summary.symbol_code, forecast.time); + forecast.precipitationAmount = forecastXHours.details?.precipitation_amount; + forecast.precipitationProbability = forecastXHours.details?.probability_of_precipitation; + forecast.minTemperature = forecastXHours.details?.air_temperature_min; + forecast.maxTemperature = forecastXHours.details?.air_temperature_max; + return this.getWeatherDataFrom(forecast, stellarData, weatherData.properties.meta.units); + }, + + getWeatherData() { + return new Promise((resolve, reject) => { + // If a user has several Yr-modules, for instance one current and one forecast, the API calls must be synchronized across classes. + // This is to avoid multiple similar calls to the API. + let shouldWait = localStorage.getItem("yrIsFetchingWeatherData"); + if (shouldWait) { + const checkForGo = setInterval(function () { + shouldWait = localStorage.getItem("yrIsFetchingWeatherData"); + }, 100); + setTimeout(function () { + clearInterval(checkForGo); + shouldWait = false; + }, 5000); //Assume other fetch finished but failed to remove lock + const attemptFetchWeather = setInterval(() => { + if (!shouldWait) { + clearInterval(checkForGo); + clearInterval(attemptFetchWeather); + this.getWeatherDataFromYrOrCache(resolve, reject); + } + }, 100); + } else { + this.getWeatherDataFromYrOrCache(resolve, reject); + } + }); + }, + + getWeatherDataFromYrOrCache(resolve, reject) { + localStorage.setItem("yrIsFetchingWeatherData", "true"); + + let weatherData = this.getWeatherDataFromCache(); + if (this.weatherDataIsValid(weatherData)) { + localStorage.removeItem("yrIsFetchingWeatherData"); + Log.debug("Weather data found in cache."); + resolve(weatherData); + } else { + this.getWeatherDataFromYr(weatherData?.downloadedAt) + .then((weatherData) => { + Log.debug("Got weather data from yr."); + if (weatherData) { + this.cacheWeatherData(weatherData); + } else { + //Undefined if unchanged + weatherData = this.getWeatherDataFromCache(); + } + resolve(weatherData); + }) + .catch((err) => { + Log.error(err); + reject("Unable to get weather data from Yr."); + }) + .finally(() => { + localStorage.removeItem("yrIsFetchingWeatherData"); + }); + } + }, + + weatherDataIsValid(weatherData) { + return ( + weatherData && + weatherData.timeout && + 0 < moment(weatherData.timeout).diff(moment()) && + (!weatherData.geometry || !weatherData.geometry.coordinates || !weatherData.geometry.coordinates.length < 2 || (weatherData.geometry.coordinates[0] === this.config.lat && weatherData.geometry.coordinates[1] === this.config.lon)) + ); + }, + + getWeatherDataFromCache() { + const weatherData = localStorage.getItem("weatherData"); + if (weatherData) { + return JSON.parse(weatherData); + } else { + return undefined; + } + }, + + getWeatherDataFromYr(currentDataFetchedAt) { + const requestHeaders = [{ name: "Accept", value: "application/json" }]; + if (currentDataFetchedAt) { + requestHeaders.push({ name: "If-Modified-Since", value: currentDataFetchedAt }); + } + + const expectedResponseHeaders = ["expires", "date"]; + + return this.fetchData(this.getForecastUrl(), "json", requestHeaders, expectedResponseHeaders) + .then((data) => { + if (!data || !data.headers) return data; + data.timeout = data.headers.find((header) => header.name === "expires").value; + data.downloadedAt = data.headers.find((header) => header.name === "date").value; + data.headers = undefined; + return data; + }) + .catch((err) => { + Log.error("Could not load weather data.", err); + throw new Error(err); + }); + }, + + getForecastUrl() { + if (!this.config.lat) { + Log.error("Latitude not provided."); + throw new Error("Latitude not provided."); + } + if (!this.config.lon) { + Log.error("Longitude not provided."); + throw new Error("Longitude not provided."); + } + + let lat = this.config.lat.toString(); + let lon = this.config.lon.toString(); + const altitude = this.config.altitude ?? 0; + + if (lat.includes(".") && lat.split(".")[1].length > 4) { + Log.warn("Latitude is too specific for weather data. Do not use more than four decimals. Trimming to maximum length."); + const latParts = lat.split("."); + lat = `${latParts[0]}.${latParts[1].substring(0, 4)}`; + } + if (lon.includes(".") && lon.split(".")[1].length > 4) { + Log.warn("Longitude is too specific for weather data. Do not use more than four decimals. Trimming to maximum length."); + const lonParts = lon.split("."); + lon = `${lonParts[0]}.${lonParts[1].substring(0, 4)}`; + } + + return `${this.config.apiBase}/locationforecast/2.0/complete?&altitude=${altitude}&lat=${lat}&lon=${lon}`; + }, + + cacheWeatherData(weatherData) { + localStorage.setItem("weatherData", JSON.stringify(weatherData)); + }, + + getAuthenticationString() { + if (!this.config.authenticationEmail) throw new Error("Authentication email not provided."); + return `${this.config.applicaitionName} ${this.config.authenticationEmail}`; + }, + + getStellarData() { + // If a user has several Yr-modules, for instance one current and one forecast, the API calls must be synchronized across classes. + // This is to avoid multiple similar calls to the API. + return new Promise((resolve, reject) => { + let shouldWait = localStorage.getItem("yrIsFetchingStellarData"); + if (shouldWait) { + const checkForGo = setInterval(function () { + shouldWait = localStorage.getItem("yrIsFetchingStellarData"); + }, 100); + setTimeout(function () { + clearInterval(checkForGo); + shouldWait = false; + }, 5000); //Assume other fetch finished but failed to remove lock + const attemptFetchWeather = setInterval(() => { + if (!shouldWait) { + clearInterval(checkForGo); + clearInterval(attemptFetchWeather); + this.getStellarDataFromYrOrCache(resolve, reject); + } + }, 100); + } else { + this.getStellarDataFromYrOrCache(resolve, reject); + } + }); + }, + + getStellarDataFromYrOrCache(resolve, reject) { + localStorage.setItem("yrIsFetchingStellarData", "true"); + + let stellarData = this.getStellarDataFromCache(); + const today = moment().format("YYYY-MM-DD"); + const tomorrow = moment().add(1, "days").format("YYYY-MM-DD"); + if (stellarData && stellarData.today && stellarData.today.date === today && stellarData.tomorrow && stellarData.tomorrow.date === tomorrow) { + Log.debug("Stellar data found in cache."); + localStorage.removeItem("yrIsFetchingStellarData"); + resolve(stellarData); + } else if (stellarData && stellarData.tomorrow && stellarData.tomorrow.date === today) { + Log.debug("stellar data for today found in cache, but not for tomorrow."); + stellarData.today = stellarData.tomorrow; + this.getStellarDataFromYr(tomorrow) + .then((data) => { + if (data) { + data.date = tomorrow; + stellarData.tomorrow = data; + this.cacheStellarData(stellarData); + resolve(stellarData); + } else { + reject(`No stellar data returned from Yr for ${tomorrow}`); + } + }) + .catch((err) => { + Log.error(err); + reject(`Unable to get stellar data from Yr for ${tomorrow}`); + }) + .finally(() => { + localStorage.removeItem("yrIsFetchingStellarData"); + }); + } else { + this.getStellarDataFromYr(today, 2) + .then((stellarData) => { + if (stellarData) { + stellarData = { + today: stellarData + }; + stellarData.tomorrow = Object.assign({}, stellarData.today); + stellarData.today.date = today; + stellarData.tomorrow.date = tomorrow; + this.cacheStellarData(stellarData); + resolve(stellarData); + } else { + Log.error(`Something went wrong when fetching stellar data. Responses: ${stellarData}`); + reject(stellarData); + } + }) + .catch((err) => { + Log.error(err); + reject("Unable to get stellar data from Yr."); + }) + .finally(() => { + localStorage.removeItem("yrIsFetchingStellarData"); + }); + } + }, + + getStellarDataFromCache() { + const stellarData = localStorage.getItem("stellarData"); + if (stellarData) { + return JSON.parse(stellarData); + } else { + return undefined; + } + }, + + getStellarDataFromYr(date, days = 1) { + const requestHeaders = [{ name: "Accept", value: "application/json" }]; + return this.fetchData(this.getStellarDatatUrl(date, days), "json", requestHeaders) + .then((data) => { + Log.debug("Got stellar data from yr."); + return data; + }) + .catch((err) => { + Log.error("Could not load weather data.", err); + throw new Error(err); + }); + }, + + getStellarDatatUrl(date, days) { + if (!this.config.lat) { + Log.error("Latitude not provided."); + throw new Error("Latitude not provided."); + } + if (!this.config.lon) { + Log.error("Longitude not provided."); + throw new Error("Longitude not provided."); + } + + let lat = this.config.lat.toString(); + let lon = this.config.lon.toString(); + const altitude = this.config.altitude ?? 0; + + if (lat.includes(".") && lat.split(".")[1].length > 4) { + Log.warn("Latitude is too specific for stellar data. Do not use more than four decimals. Trimming to maximum length."); + const latParts = lat.split("."); + lat = `${latParts[0]}.${latParts[1].substring(0, 4)}`; + } + if (lon.includes(".") && lon.split(".")[1].length > 4) { + Log.warn("Longitude is too specific for stellar data. Do not use more than four decimals. Trimming to maximum length."); + const lonParts = lon.split("."); + lon = `${lonParts[0]}.${lonParts[1].substring(0, 4)}`; + } + + let utcOffset = moment().utcOffset() / 60; + let utcOffsetPrefix = "%2B"; + if (utcOffset < 0) { + utcOffsetPrefix = "-"; + } + utcOffset = Math.abs(utcOffset); + let minutes = "00"; + if (utcOffset % 1 !== 0) { + minutes = "30"; + } + let hours = Math.floor(utcOffset).toString(); + if (hours.length < 2) { + hours = `0${hours}`; + } + + return `${this.config.apiBase}/sunrise/2.0/.json?date=${date}&days=${days}&height=${altitude}&lat=${lat}&lon=${lon}&offset=${utcOffsetPrefix}${hours}%3A${minutes}`; + }, + + cacheStellarData(data) { + localStorage.setItem("stellarData", JSON.stringify(data)); + }, + + getWeatherDataFrom(forecast, stellarData, units) { + const weather = new WeatherObject(); + const stellarTimesToday = stellarData?.today ? this.getStellarTimesFrom(stellarData.today, moment().format("YYYY-MM-DD")) : undefined; + const stellarTimesTomorrow = stellarData?.tomorrow ? this.getStellarTimesFrom(stellarData.tomorrow, moment().add(1, "days").format("YYYY-MM-DD")) : undefined; + + weather.date = moment(forecast.time); + weather.windSpeed = forecast.data.instant.details.wind_speed; + weather.windFromDirection = forecast.data.instant.details.wind_from_direction; + weather.temperature = forecast.data.instant.details.air_temperature; + weather.minTemperature = forecast.minTemperature; + weather.maxTemperature = forecast.maxTemperature; + weather.weatherType = forecast.weatherType; + weather.humidity = forecast.data.instant.details.relative_humidity; + weather.precipitationAmount = forecast.precipitationAmount; + weather.precipitationProbability = forecast.precipitationProbability; + weather.precipitationUnits = units.precipitation_amount; + + if (stellarTimesToday) { + weather.sunset = moment(stellarTimesToday.sunset.time); + weather.sunrise = weather.sunset < moment() && stellarTimesTomorrow ? moment(stellarTimesTomorrow.sunrise.time) : moment(stellarTimesToday.sunrise.time); + } + + return weather; + }, + + convertWeatherType(weatherType, weatherTime) { + const weatherHour = moment(weatherTime).format("HH"); + + const weatherTypes = { + clearsky_day: "day-sunny", + clearsky_night: "night-clear", + clearsky_polartwilight: weatherHour < 14 ? "sunrise" : "sunset", + cloudy: "cloudy", + fair_day: "day-sunny-overcast", + fair_night: "night-alt-partly-cloudy", + fair_polartwilight: "day-sunny-overcast", + fog: "fog", + heavyrain: "rain", // Possibly raindrops or raindrop + heavyrainandthunder: "thunderstorm", + heavyrainshowers_day: "day-rain", + heavyrainshowers_night: "night-alt-rain", + heavyrainshowers_polartwilight: "day-rain", + heavyrainshowersandthunder_day: "day-thunderstorm", + heavyrainshowersandthunder_night: "night-alt-thunderstorm", + heavyrainshowersandthunder_polartwilight: "day-thunderstorm", + heavysleet: "sleet", + heavysleetandthunder: "day-sleet-storm", + heavysleetshowers_day: "day-sleet", + heavysleetshowers_night: "night-alt-sleet", + heavysleetshowers_polartwilight: "day-sleet", + heavysleetshowersandthunder_day: "day-sleet-storm", + heavysleetshowersandthunder_night: "night-alt-sleet-storm", + heavysleetshowersandthunder_polartwilight: "day-sleet-storm", + heavysnow: "snow-wind", + heavysnowandthunder: "day-snow-thunderstorm", + heavysnowshowers_day: "day-snow-wind", + heavysnowshowers_night: "night-alt-snow-wind", + heavysnowshowers_polartwilight: "day-snow-wind", + heavysnowshowersandthunder_day: "day-snow-thunderstorm", + heavysnowshowersandthunder_night: "night-alt-snow-thunderstorm", + heavysnowshowersandthunder_polartwilight: "day-snow-thunderstorm", + lightrain: "rain-mix", + lightrainandthunder: "thunderstorm", + lightrainshowers_day: "day-rain-mix", + lightrainshowers_night: "night-alt-rain-mix", + lightrainshowers_polartwilight: "day-rain-mix", + lightrainshowersandthunder_day: "thunderstorm", + lightrainshowersandthunder_night: "thunderstorm", + lightrainshowersandthunder_polartwilight: "thunderstorm", + lightsleet: "day-sleet", + lightsleetandthunder: "day-sleet-storm", + lightsleetshowers_day: "day-sleet", + lightsleetshowers_night: "night-alt-sleet", + lightsleetshowers_polartwilight: "day-sleet", + lightsnow: "snowflake-cold", + lightsnowandthunder: "day-snow-thunderstorm", + lightsnowshowers_day: "day-snow-wind", + lightsnowshowers_night: "night-alt-snow-wind", + lightsnowshowers_polartwilight: "day-snow-wind", + lightssleetshowersandthunder_day: "day-sleet-storm", + lightssleetshowersandthunder_night: "night-alt-sleet-storm", + lightssleetshowersandthunder_polartwilight: "day-sleet-storm", + lightssnowshowersandthunder_day: "day-snow-thunderstorm", + lightssnowshowersandthunder_night: "night-alt-snow-thunderstorm", + lightssnowshowersandthunder_polartwilight: "day-snow-thunderstorm", + partlycloudy_day: "day-cloudy", + partlycloudy_night: "night-alt-cloudy", + partlycloudy_polartwilight: "day-cloudy", + rain: "rain", + rainandthunder: "thunderstorm", + rainshowers_day: "day-rain", + rainshowers_night: "night-alt-rain", + rainshowers_polartwilight: "day-rain", + rainshowersandthunder_day: "thunderstorm", + rainshowersandthunder_night: "lightning", + rainshowersandthunder_polartwilight: "thunderstorm", + sleet: "sleet", + sleetandthunder: "day-sleet-storm", + sleetshowers_day: "day-sleet", + sleetshowers_night: "night-alt-sleet", + sleetshowers_polartwilight: "day-sleet", + sleetshowersandthunder_day: "day-sleet-storm", + sleetshowersandthunder_night: "night-alt-sleet-storm", + sleetshowersandthunder_polartwilight: "day-sleet-storm", + snow: "snowflake-cold", + snowandthunder: "lightning", + snowshowers_day: "day-snow-wind", + snowshowers_night: "night-alt-snow-wind", + snowshowers_polartwilight: "day-snow-wind", + snowshowersandthunder_day: "day-snow-thunderstorm", + snowshowersandthunder_night: "night-alt-snow-thunderstorm", + snowshowersandthunder_polartwilight: "day-snow-thunderstorm" + }; + + return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null; + }, + + getStellarTimesFrom(stellarData, date) { + for (const time of stellarData.location.time) { + if (time.date === date) { + return time; + } + } + return undefined; + }, + + getForecastForXHoursFrom(weather) { + if (this.config.currentForecastHours === 1) { + if (weather.next_1_hours) { + return weather.next_1_hours; + } else if (weather.next_6_hours) { + return weather.next_6_hours; + } else { + return weather.next_12_hours; + } + } else if (this.config.currentForecastHours === 6) { + if (weather.next_6_hours) { + return weather.next_6_hours; + } else if (weather.next_12_hours) { + return weather.next_12_hours; + } else { + return weather.next_1_hours; + } + } else { + if (weather.next_12_hours) { + return weather.next_12_hours; + } else if (weather.next_6_hours) { + return weather.next_6_hours; + } else { + return weather.next_1_hours; + } + } + }, + + fetchWeatherHourly() { + this.getWeatherForecast("hourly") + .then((forecast) => { + this.setWeatherHourly(forecast); + this.updateAvailable(); + }) + .catch((error) => { + Log.error(error); + throw new Error(error); + }); + }, + + async getWeatherForecast(type) { + const getRequests = [this.getWeatherData(), this.getStellarData()]; + const [weatherData, stellarData] = await Promise.all(getRequests); + if (!weatherData.properties.timeseries || !weatherData.properties.timeseries[0]) { + Log.error("No weather data available."); + return; + } + if (!stellarData) { + Log.warn("No stellar data available."); + } + let forecasts; + switch (type) { + case "hourly": + forecasts = this.getHourlyForecastFrom(weatherData); + break; + case "daily": + default: + forecasts = this.getDailyForecastFrom(weatherData); + break; + } + const series = []; + for (const forecast of forecasts) { + series.push(this.getWeatherDataFrom(forecast, stellarData, weatherData.properties.meta.units)); + } + return series; + }, + + getHourlyForecastFrom(weatherData) { + const series = []; + + for (const forecast of weatherData.properties.timeseries) { + forecast.symbol = forecast.data.next_1_hours?.summary?.symbol_code; + forecast.precipitationAmount = forecast.data.next_1_hours?.details?.precipitation_amount; + forecast.precipitationProbability = forecast.data.next_1_hours?.details?.probability_of_precipitation; + forecast.minTemperature = forecast.data.next_1_hours?.details?.air_temperature_min; + forecast.maxTemperature = forecast.data.next_1_hours?.details?.air_temperature_max; + forecast.weatherType = this.convertWeatherType(forecast.symbol, forecast.time); + series.push(forecast); + } + return series; + }, + + getDailyForecastFrom(weatherData) { + const series = []; + + const days = weatherData.properties.timeseries.reduce(function (days, forecast) { + const date = moment(forecast.time).format("YYYY-MM-DD"); + days[date] = days[date] || []; + days[date].push(forecast); + return days; + }, Object.create(null)); + + Object.keys(days).forEach(function (time, index) { + let minTemperature = undefined; + let maxTemperature = undefined; + + //Default to first entry + let forecast = days[time][0]; + forecast.symbol = forecast.data.next_12_hours?.summary?.symbol_code; + forecast.precipitation = forecast.data.next_12_hours?.details?.precipitation_amount; + + //Coming days + let forecastDiffToEight = undefined; + for (const timeseries of days[time]) { + if (!timeseries.data.next_6_hours) continue; //next_6_hours has the most data + + if (!minTemperature || timeseries.data.next_6_hours.details.air_temperature_min < minTemperature) minTemperature = timeseries.data.next_6_hours.details.air_temperature_min; + if (!maxTemperature || maxTemperature < timeseries.data.next_6_hours.details.air_temperature_max) maxTemperature = timeseries.data.next_6_hours.details.air_temperature_max; + + let closestTime = Math.abs(moment(timeseries.time).local().set({ hour: 8, minute: 0, second: 0, millisecond: 0 }).diff(moment(timeseries.time).local())); + if ((forecastDiffToEight === undefined || closestTime < forecastDiffToEight) && timeseries.data.next_12_hours) { + forecastDiffToEight = closestTime; + forecast = timeseries; + } + } + const forecastXHours = forecast.data.next_12_hours ?? forecast.data.next_6_hours ?? forecast.data.next_1_hours; + if (forecastXHours) { + forecast.symbol = forecastXHours.summary?.symbol_code; + forecast.precipitationAmount = forecastXHours.details?.precipitation_amount ?? forecast.data.next_6_hours?.details?.precipitation_amount; // 6 hours is likely to have precipitation amount even if 12 hours does not + forecast.precipitationProbability = forecastXHours.details?.probability_of_precipitation; + forecast.minTemperature = minTemperature; + forecast.maxTemperature = maxTemperature; + + series.push(forecast); + } + }); + for (const forecast of series) { + forecast.weatherType = this.convertWeatherType(forecast.symbol, forecast.time); + } + return series; + }, + + fetchWeatherForecast() { + this.getWeatherForecast("daily") + .then((forecast) => { + this.setWeatherForecast(forecast); + this.updateAvailable(); + }) + .catch((error) => { + Log.error(error); + throw new Error(error); + }); + } +}); diff --git a/MagicMirror/modules/default/weather/weather.css b/MagicMirror/modules/default/weather/weather.css new file mode 100644 index 0000000000000000000000000000000000000000..c2b7fe5ec1c083f4c598be79483569b91cf613cd --- /dev/null +++ b/MagicMirror/modules/default/weather/weather.css @@ -0,0 +1,48 @@ +.weather .weathericon, +.weather .fa-home { + font-size: 75%; + line-height: 65px; + display: inline-block; + transform: translate(0, -3px); +} + +.weather .humidity-icon { + padding-right: 4px; +} + +.weather .humidity-padding { + padding-bottom: 6px; +} + +.weather .day { + padding-left: 0; + padding-right: 25px; +} + +.weather .weather-icon { + padding-right: 30px; + text-align: center; +} + +.weather .min-temp { + padding-left: 20px; + padding-right: 0; +} + +.weather .precipitation-amount, +.weather .precipitation-prob { + padding-left: 20px; + padding-right: 0; +} + +.weather tr .weathericon { + line-height: 25px; +} + +.weather tr.colored .min-temp { + color: #bcddff; +} + +.weather tr.colored .max-temp { + color: #ff8e99; +} diff --git a/MagicMirror/modules/default/weather/weather.js b/MagicMirror/modules/default/weather/weather.js new file mode 100644 index 0000000000000000000000000000000000000000..949c11cd24366dc48207061264a1a8f8897aff12 --- /dev/null +++ b/MagicMirror/modules/default/weather/weather.js @@ -0,0 +1,309 @@ +/* global WeatherProvider, WeatherUtils */ + +/* MagicMirror² + * Module: Weather + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +Module.register("weather", { + // Default module config. + defaults: { + weatherProvider: "openweathermap", + roundTemp: false, + type: "current", // current, forecast, daily (equivalent to forecast), hourly (only with OpenWeatherMap /onecall endpoint) + lang: config.language, + units: config.units, + tempUnits: config.units, + windUnits: config.units, + timeFormat: config.timeFormat, + updateInterval: 10 * 60 * 1000, // every 10 minutes + animationSpeed: 1000, + showFeelsLike: true, + showHumidity: false, + showIndoorHumidity: false, + showIndoorTemperature: false, + showPeriod: true, + showPeriodUpper: false, + showPrecipitationAmount: false, + showPrecipitationProbability: false, + showSun: true, + showWindDirection: true, + showWindDirectionAsArrow: false, + degreeLabel: false, + decimalSymbol: ".", + maxNumberOfDays: 5, + maxEntries: 5, + ignoreToday: false, + fade: true, + fadePoint: 0.25, // Start on 1/4th of the list. + initialLoadDelay: 0, // 0 seconds delay + appendLocationNameToHeader: true, + calendarClass: "calendar", + tableClass: "small", + onlyTemp: false, + colored: false, + absoluteDates: false, + hourlyForecastIncrements: 1 + }, + + // Module properties. + weatherProvider: null, + + // Can be used by the provider to display location of event if nothing else is specified + firstEvent: null, + + // Define required scripts. + getStyles: function () { + return ["font-awesome.css", "weather-icons.css", "weather.css"]; + }, + + // Return the scripts that are necessary for the weather module. + getScripts: function () { + return ["moment.js", this.file("../utils.js"), "weatherutils.js", "weatherprovider.js", "weatherobject.js", "suncalc.js", this.file(`providers/${this.config.weatherProvider.toLowerCase()}.js`)]; + }, + + // Override getHeader method. + getHeader: function () { + if (this.config.appendLocationNameToHeader && this.weatherProvider) { + if (this.data.header) return `${this.data.header} ${this.weatherProvider.fetchedLocation()}`; + else return this.weatherProvider.fetchedLocation(); + } + + return this.data.header ? this.data.header : ""; + }, + + // Start the weather module. + start: function () { + moment.locale(this.config.lang); + + if (this.config.useKmh) { + Log.warn("Your are using the deprecated config values 'useKmh'. Please switch to windUnits!"); + this.windUnits = "kmh"; + } else if (this.config.useBeaufort) { + Log.warn("Your are using the deprecated config values 'useBeaufort'. Please switch to windUnits!"); + this.windUnits = "beaufort"; + } + + // Initialize the weather provider. + this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this); + + // Let the weather provider know we are starting. + this.weatherProvider.start(); + + // Add custom filters + this.addFilters(); + + // Schedule the first update. + this.scheduleUpdate(this.config.initialLoadDelay); + }, + + // Override notification handler. + notificationReceived: function (notification, payload, sender) { + if (notification === "CALENDAR_EVENTS") { + const senderClasses = sender.data.classes.toLowerCase().split(" "); + if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) { + this.firstEvent = null; + for (let event of payload) { + if (event.location || event.geo) { + this.firstEvent = event; + Log.debug("First upcoming event with location: ", event); + break; + } + } + } + } else if (notification === "INDOOR_TEMPERATURE") { + this.indoorTemperature = this.roundValue(payload); + this.updateDom(300); + } else if (notification === "INDOOR_HUMIDITY") { + this.indoorHumidity = this.roundValue(payload); + this.updateDom(300); + } + }, + + // Select the template depending on the display type. + getTemplate: function () { + switch (this.config.type.toLowerCase()) { + case "current": + return "current.njk"; + case "hourly": + return "hourly.njk"; + case "daily": + case "forecast": + return "forecast.njk"; + //Make the invalid values use the "Loading..." from forecast + default: + return "forecast.njk"; + } + }, + + // Add all the data to the template. + getTemplateData: function () { + const currentData = this.weatherProvider.currentWeather(); + const forecastData = this.weatherProvider.weatherForecast(); + + // Skip some hourly forecast entries if configured + const hourlyData = this.weatherProvider.weatherHourly()?.filter((e, i) => (i + 1) % this.config.hourlyForecastIncrements === this.config.hourlyForecastIncrements - 1); + + return { + config: this.config, + current: currentData, + forecast: forecastData, + hourly: hourlyData, + indoor: { + humidity: this.indoorHumidity, + temperature: this.indoorTemperature + } + }; + }, + + // What to do when the weather provider has new information available? + updateAvailable: function () { + 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("-", "_") }); + } + + const notificationPayload = { + currentWeather: this.weatherProvider?.currentWeatherObject?.simpleClone() ?? null, + forecastArray: this.weatherProvider?.weatherForecastArray?.map((ar) => ar.simpleClone()) ?? [], + hourlyArray: this.weatherProvider?.weatherHourlyArray?.map((ar) => ar.simpleClone()) ?? [], + locationName: this.weatherProvider?.fetchedLocationName, + providerName: this.weatherProvider.providerName + }; + this.sendNotification("WEATHER_UPDATED", notificationPayload); + }, + + scheduleUpdate: function (delay = null) { + let nextLoad = this.config.updateInterval; + if (delay !== null && delay >= 0) { + nextLoad = delay; + } + + setTimeout(() => { + 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) { + const decimals = this.config.roundTemp ? 0 : 1; + const roundValue = parseFloat(temperature).toFixed(decimals); + return roundValue === "-0" ? 0 : roundValue; + }, + + addFilters() { + this.nunjucksEnvironment().addFilter( + "formatTime", + function (date) { + date = moment(date); + + if (this.config.timeFormat !== 24) { + if (this.config.showPeriod) { + if (this.config.showPeriodUpper) { + return date.format("h:mm A"); + } else { + return date.format("h:mm a"); + } + } else { + return date.format("h:mm"); + } + } + + return date.format("HH:mm"); + }.bind(this) + ); + + this.nunjucksEnvironment().addFilter( + "unit", + function (value, type, valueUnit) { + if (type === "temperature") { + value = `${this.roundValue(WeatherUtils.convertTemp(value, this.config.tempUnits))}°`; + if (this.config.degreeLabel) { + if (this.config.tempUnits === "metric") { + value += "C"; + } else if (this.config.tempUnits === "imperial") { + value += "F"; + } else { + value += "K"; + } + } + } else if (type === "precip") { + if (value === null || isNaN(value) || value === 0 || value.toFixed(2) === "0.00") { + value = ""; + } else { + value = WeatherUtils.convertPrecipitationUnit(value, valueUnit, this.config.units); + } + } else if (type === "humidity") { + value += "%"; + } else if (type === "wind") { + value = WeatherUtils.convertWind(value, this.config.windUnits); + } + return value; + }.bind(this) + ); + + this.nunjucksEnvironment().addFilter( + "roundValue", + function (value) { + return this.roundValue(value); + }.bind(this) + ); + + this.nunjucksEnvironment().addFilter( + "decimalSymbol", + function (value) { + return value.toString().replace(/\./g, this.config.decimalSymbol); + }.bind(this) + ); + + this.nunjucksEnvironment().addFilter( + "calcNumSteps", + function (forecast) { + return Math.min(forecast.length, this.config.maxNumberOfDays); + }.bind(this) + ); + + this.nunjucksEnvironment().addFilter( + "calcNumEntries", + function (dataArray) { + return Math.min(dataArray.length, this.config.maxEntries); + }.bind(this) + ); + + this.nunjucksEnvironment().addFilter( + "opacity", + function (currentStep, numSteps) { + if (this.config.fade && this.config.fadePoint < 1) { + if (this.config.fadePoint < 0) { + this.config.fadePoint = 0; + } + const startingPoint = numSteps * this.config.fadePoint; + const numFadesteps = numSteps - startingPoint; + if (currentStep >= startingPoint) { + return 1 - (currentStep - startingPoint) / numFadesteps; + } else { + return 1; + } + } else { + return 1; + } + }.bind(this) + ); + } +}); diff --git a/MagicMirror/modules/default/weather/weatherobject.js b/MagicMirror/modules/default/weather/weatherobject.js new file mode 100644 index 0000000000000000000000000000000000000000..565061da083b98f57c68311349316870f57e3821 --- /dev/null +++ b/MagicMirror/modules/default/weather/weatherobject.js @@ -0,0 +1,140 @@ +/* global SunCalc, WeatherUtils */ + +/* MagicMirror² + * Module: Weather + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + * + * This class is the blueprint for a day which includes weather information. + * + * Currently this is focused on the information which is necessary for the current weather. + * As soon as we start implementing the forecast, mode properties will be added. + */ + +/** + * @external Moment + */ +class WeatherObject { + /** + * Constructor for a WeatherObject + */ + constructor() { + this.date = null; + this.windSpeed = null; + this.windFromDirection = null; + this.sunrise = null; + this.sunset = null; + this.temperature = null; + this.minTemperature = null; + this.maxTemperature = null; + this.weatherType = null; + this.humidity = null; + this.precipitationAmount = null; + this.precipitationUnits = null; + this.precipitationProbability = null; + this.feelsLikeTemp = null; + } + + cardinalWindDirection() { + if (this.windFromDirection > 11.25 && this.windFromDirection <= 33.75) { + return "NNE"; + } else if (this.windFromDirection > 33.75 && this.windFromDirection <= 56.25) { + return "NE"; + } else if (this.windFromDirection > 56.25 && this.windFromDirection <= 78.75) { + return "ENE"; + } else if (this.windFromDirection > 78.75 && this.windFromDirection <= 101.25) { + return "E"; + } else if (this.windFromDirection > 101.25 && this.windFromDirection <= 123.75) { + return "ESE"; + } else if (this.windFromDirection > 123.75 && this.windFromDirection <= 146.25) { + return "SE"; + } else if (this.windFromDirection > 146.25 && this.windFromDirection <= 168.75) { + return "SSE"; + } else if (this.windFromDirection > 168.75 && this.windFromDirection <= 191.25) { + return "S"; + } else if (this.windFromDirection > 191.25 && this.windFromDirection <= 213.75) { + return "SSW"; + } else if (this.windFromDirection > 213.75 && this.windFromDirection <= 236.25) { + return "SW"; + } else if (this.windFromDirection > 236.25 && this.windFromDirection <= 258.75) { + return "WSW"; + } else if (this.windFromDirection > 258.75 && this.windFromDirection <= 281.25) { + return "W"; + } else if (this.windFromDirection > 281.25 && this.windFromDirection <= 303.75) { + return "WNW"; + } else if (this.windFromDirection > 303.75 && this.windFromDirection <= 326.25) { + return "NW"; + } else if (this.windFromDirection > 326.25 && this.windFromDirection <= 348.75) { + return "NNW"; + } else { + return "N"; + } + } + + /** + * Determines if the sun sets or rises next. Uses the current time and not + * the date from the weather-forecast. + * + * @param {Moment} date an optional date where you want to get the next + * action for. Useful only in tests, defaults to the current time. + * @returns {string} "sunset" or "sunrise" + */ + nextSunAction(date = moment()) { + return date.isBetween(this.sunrise, this.sunset) ? "sunset" : "sunrise"; + } + + feelsLike() { + if (this.feelsLikeTemp) { + return this.feelsLikeTemp; + } + return WeatherUtils.calculateFeelsLike(this.temperature, this.windSpeed, this.humidity); + } + + /** + * Checks if the weatherObject is at dayTime. + * + * @returns {boolean} true if it is at dayTime + */ + isDayTime() { + const now = !this.date ? moment() : this.date; + return now.isBetween(this.sunrise, this.sunset, undefined, "[]"); + } + + /** + * Update the sunrise / sunset time depending on the location. This can be + * used if your provider doesn't provide that data by itself. Then SunCalc + * is used here to calculate them according to the location. + * + * @param {number} lat latitude + * @param {number} lon longitude + */ + updateSunTime(lat, lon) { + const now = !this.date ? new Date() : this.date.toDate(); + const times = SunCalc.getTimes(now, lat, lon); + this.sunrise = moment(times.sunrise); + this.sunset = moment(times.sunset); + } + + /** + * Clone to simple object to prevent mutating and deprecation of legacy library. + * + * Before being handed to other modules, mutable values must be cloned safely. + * Especially 'moment' object is not immutable, so original 'date', 'sunrise', 'sunset' could be corrupted or changed by other modules. + * + * @returns {object} plained object clone of original weatherObject + */ + simpleClone() { + const toFlat = ["date", "sunrise", "sunset"]; + let clone = { ...this }; + for (const prop of toFlat) { + clone[prop] = clone?.[prop]?.valueOf() ?? clone?.[prop]; + } + return clone; + } +} + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = WeatherObject; +} diff --git a/MagicMirror/modules/default/weather/weatherprovider.js b/MagicMirror/modules/default/weather/weatherprovider.js new file mode 100644 index 0000000000000000000000000000000000000000..e7bfe5b7d7f118c1966aaedd7b7ff5f22db10eb5 --- /dev/null +++ b/MagicMirror/modules/default/weather/weatherprovider.js @@ -0,0 +1,171 @@ +/* global Class, performWebRequest */ + +/* MagicMirror² + * Module: Weather + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + * + * This class is the blueprint for a weather provider. + */ +const 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, + weatherHourlyArray: null, + fetchedLocationName: null, + + // The following properties will be set automatically. + // You do not need to overwrite these properties. + config: null, + delegate: null, + providerIdentifier: null, + + // Weather Provider Methods + // All the following methods can be overwritten, although most are good as they are. + + // Called when a weather provider is initialized. + init: function (config) { + this.config = config; + Log.info(`Weather provider: ${this.providerName} initialized.`); + }, + + // Called to set the config, this config is the same as the weather module's config. + setConfig: function (config) { + this.config = config; + Log.info(`Weather provider: ${this.providerName} config set.`, this.config); + }, + + // Called when the weather provider is about to start. + start: function () { + Log.info(`Weather provider: ${this.providerName} started.`); + }, + + // This method should start the API request to fetch the current weather. + // This method should definitely be overwritten in the provider. + fetchCurrentWeather: function () { + Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchCurrentWeather method.`); + }, + + // This method should start the API request to fetch the weather forecast. + // This method should definitely be overwritten in the provider. + fetchWeatherForecast: function () { + Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`); + }, + + // This method should start the API request to fetch the weather hourly. + // This method should definitely be overwritten in the provider. + fetchWeatherHourly: function () { + Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherHourly method.`); + }, + + // This returns a WeatherDay object for the current weather. + currentWeather: function () { + return this.currentWeatherObject; + }, + + // This returns an array of WeatherDay objects for the weather forecast. + weatherForecast: function () { + return this.weatherForecastArray; + }, + + // This returns an object containing WeatherDay object(s) depending on the type of call. + weatherHourly: function () { + return this.weatherHourlyArray; + }, + + // This returns the name of the fetched location or an empty string. + fetchedLocation: function () { + return this.fetchedLocationName || ""; + }, + + // Set the currentWeather and notify the delegate that new information is available. + setCurrentWeather: function (currentWeatherObject) { + // We should check here if we are passing a WeatherDay + this.currentWeatherObject = currentWeatherObject; + }, + + // Set the weatherForecastArray and notify the delegate that new information is available. + setWeatherForecast: function (weatherForecastArray) { + // We should check here if we are passing a WeatherDay + this.weatherForecastArray = weatherForecastArray; + }, + + // Set the weatherHourlyArray and notify the delegate that new information is available. + setWeatherHourly: function (weatherHourlyArray) { + this.weatherHourlyArray = weatherHourlyArray; + }, + + // Set the fetched location name. + setFetchedLocation: function (name) { + this.fetchedLocationName = name; + }, + + // Notify the delegate that new weather is available. + updateAvailable: function () { + this.delegate.updateAvailable(this); + }, + + /** + * A convenience function to make requests. + * + * @param {string} url the url to fetch from + * @param {string} type what contenttype to expect in the response, can be "json" or "xml" + * @param {Array.<{name: string, value:string}>} requestHeaders the HTTP headers to send + * @param {Array.<string>} expectedResponseHeaders the expected HTTP headers to recieve + * @returns {Promise} resolved when the fetch is done + */ + fetchData: async function (url, type = "json", requestHeaders = undefined, expectedResponseHeaders = undefined) { + const mockData = this.config.mockData; + if (mockData) { + const data = mockData.substring(1, mockData.length - 1); + return JSON.parse(data); + } + const useCorsProxy = typeof this.config.useCorsProxy !== "undefined" && this.config.useCorsProxy; + return performWebRequest(url, type, useCorsProxy, requestHeaders, expectedResponseHeaders); + } +}); + +/** + * Collection of registered weather providers. + */ +WeatherProvider.providers = []; + +/** + * Static method to register a new weather provider. + * + * @param {string} providerIdentifier The name of the weather provider + * @param {object} providerDetails The details of the weather provider + */ +WeatherProvider.register = function (providerIdentifier, providerDetails) { + WeatherProvider.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails); +}; + +/** + * Static method to initialize a new weather provider. + * + * @param {string} providerIdentifier The name of the weather provider + * @param {object} delegate The weather module + * @returns {object} The new weather provider + */ +WeatherProvider.initialize = function (providerIdentifier, delegate) { + providerIdentifier = providerIdentifier.toLowerCase(); + + const provider = new WeatherProvider.providers[providerIdentifier](); + const config = Object.assign({}, provider.defaults, delegate.config); + + provider.delegate = delegate; + provider.setConfig(config); + + provider.providerIdentifier = providerIdentifier; + if (!provider.providerName) { + provider.providerName = providerIdentifier; + } + + return provider; +}; diff --git a/MagicMirror/modules/default/weather/weatherutils.js b/MagicMirror/modules/default/weather/weatherutils.js new file mode 100644 index 0000000000000000000000000000000000000000..42b5da1ec9ba4939db2d290b01e6cb6e98c63131 --- /dev/null +++ b/MagicMirror/modules/default/weather/weatherutils.js @@ -0,0 +1,144 @@ +/* MagicMirror² + * Weather Util Methods + * + * By Rejas + * MIT Licensed. + */ +const WeatherUtils = { + /** + * Convert wind (from m/s) to beaufort scale + * + * @param {number} speedInMS the windspeed you want to convert + * @returns {number} the speed in beaufort + */ + beaufortWindSpeed(speedInMS) { + const windInKmh = this.convertWind(speedInMS, "kmh"); + const speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000]; + for (const [index, speed] of speeds.entries()) { + if (speed > windInKmh) { + return index; + } + } + return 12; + }, + + /** + * Convert a value in a given unit to a string with a converted + * value and a postfix matching the output unit system. + * + * @param {number} value - The value to convert. + * @param {string} valueUnit - The unit the values has. Default is mm. + * @param {string} outputUnit - The unit system (imperial/metric) the return value should have. + * @returns {string} - A string with tha value and a unit postfix. + */ + convertPrecipitationUnit(value, valueUnit, outputUnit) { + if (outputUnit === "imperial") { + if (valueUnit && valueUnit.toLowerCase() === "cm") value = value * 0.3937007874; + else value = value * 0.03937007874; + valueUnit = "in"; + } else { + valueUnit = valueUnit ? valueUnit : "mm"; + } + + if (valueUnit === "%") return `${value.toFixed(0)} ${valueUnit}`; + + return `${value.toFixed(2)} ${valueUnit}`; + }, + + /** + * Convert temp (from degrees C) into imperial or metric unit depending on + * your config + * + * @param {number} tempInC the temperature in celsius you want to convert + * @param {string} unit can be 'imperial' or 'metric' + * @returns {number} the converted temperature + */ + convertTemp(tempInC, unit) { + return unit === "imperial" ? tempInC * 1.8 + 32 : tempInC; + }, + + /** + * Convert wind speed into another unit. + * + * @param {number} windInMS the windspeed in meter/sec you want to convert + * @param {string} unit can be 'beaufort', 'kmh', 'knots, 'imperial' (mph) + * or 'metric' (mps) + * @returns {number} the converted windspeed + */ + convertWind(windInMS, unit) { + switch (unit) { + case "beaufort": + return this.beaufortWindSpeed(windInMS); + case "kmh": + return (windInMS * 3600) / 1000; + case "knots": + return windInMS * 1.943844; + case "imperial": + return windInMS * 2.2369362920544; + case "metric": + default: + return windInMS; + } + }, + + /* + * Convert the wind direction cardinal to value + */ + convertWindDirection(windDirection) { + const windCardinals = { + N: 0, + NNE: 22, + NE: 45, + ENE: 67, + E: 90, + ESE: 112, + SE: 135, + SSE: 157, + S: 180, + SSW: 202, + SW: 225, + WSW: 247, + W: 270, + WNW: 292, + NW: 315, + NNW: 337 + }; + + return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null; + }, + + convertWindToMetric(mph) { + return mph / 2.2369362920544; + }, + + convertWindToMs(kmh) { + return kmh * 0.27777777777778; + }, + + calculateFeelsLike(temperature, windSpeed, humidity) { + const windInMph = this.convertWind(windSpeed, "imperial"); + const tempInF = this.convertTemp(temperature, "imperial"); + let feelsLike = tempInF; + + if (windInMph > 3 && tempInF < 50) { + feelsLike = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16)); + } else if (tempInF > 80 && humidity > 40) { + feelsLike = + -42.379 + + 2.04901523 * tempInF + + 10.14333127 * humidity - + 0.22475541 * tempInF * humidity - + 6.83783 * Math.pow(10, -3) * tempInF * tempInF - + 5.481717 * Math.pow(10, -2) * humidity * humidity + + 1.22874 * Math.pow(10, -3) * tempInF * tempInF * humidity + + 8.5282 * Math.pow(10, -4) * tempInF * humidity * humidity - + 1.99 * Math.pow(10, -6) * tempInF * tempInF * humidity * humidity; + } + + return ((feelsLike - 32) * 5) / 9; + } +}; + +if (typeof module !== "undefined") { + module.exports = WeatherUtils; +} diff --git a/MagicMirror/package-lock.json b/MagicMirror/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..2a6e8a4987e59ef6d6742bfd4b2764f3987ed4c7 --- /dev/null +++ b/MagicMirror/package-lock.json @@ -0,0 +1,15967 @@ +{ + "name": "magicmirror", + "version": "2.23.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "magicmirror", + "version": "2.23.0", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "colors": "^1.4.0", + "console-stamp": "^3.1.1", + "digest-fetch": "^2.0.1", + "envsub": "^4.1.0", + "eslint": "^8.36.0", + "express": "^4.18.2", + "express-ipfilter": "^1.3.1", + "feedme": "^2.0.2", + "helmet": "^6.0.1", + "iconv-lite": "^0.6.3", + "luxon": "^1.28.1", + "module-alias": "^2.2.2", + "moment": "^2.29.4", + "node-fetch": "^2.6.9", + "node-ical": "^0.16.0", + "socket.io": "^4.6.1" + }, + "devDependencies": { + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^27.2.1", + "eslint-plugin-jsdoc": "^40.1.0", + "eslint-plugin-prettier": "^4.2.1", + "express-basic-auth": "^1.2.1", + "husky": "^8.0.3", + "jest": "^29.5.0", + "jsdom": "^21.1.1", + "lodash": "^4.17.21", + "playwright": "^1.32.1", + "prettier": "^2.8.7", + "pretty-quick": "^3.1.3", + "sinon": "^15.0.2", + "stylelint": "^15.3.0", + "stylelint-config-standard": "^31.0.0", + "stylelint-prettier": "^3.0.0", + "suncalc": "^1.9.0" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "electron": "^22.3.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", + "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", + "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.3", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.3", + "@babel/types": "^7.21.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", + "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", + "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", + "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.3", + "@babel/types": "^7.21.3", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", + "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.0.1.tgz", + "integrity": "sha512-B9/8PmOtU6nBiibJg0glnNktQDZ3rZnGn/7UmDfrm2vMtrdlXO3p7ErE95N0up80IRk9YEtB5jyj/TmQ1WH3dw==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^2.0.0" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.0.tgz", + "integrity": "sha512-dtqFyoJBHUxGi9zPZdpCKP1xk8tq6KPHJ/NY4qWXiYo6IcSGwzk3L8x2XzZbbyOyBs9xQARoGveU2AsgLj6D2A==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.1.tgz", + "integrity": "sha512-X2/OuzEbjaxhzm97UJ+95GrMeT29d1Ib+Pu+paGLuRWZnWRK9sI9r3ikmKXPWGA1C4y4JEdBEFpp9jEqCvLeRA==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^2.0.0", + "@csstools/css-tokenizer": "^2.0.0" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz", + "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4", + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@electron/get": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz", + "integrity": "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.37.0.tgz", + "integrity": "sha512-hjK0wnsPCYLlF+HHB4R/RbUjOWeLW2SlarB67+Do5WsKILOkmIZvvPJFbtWSmbypxcjpoECLAMzoao0D4Bg5ZQ==", + "dev": true, + "dependencies": { + "comment-parser": "1.3.1", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": "^14 || ^16 || ^17 || ^18 || ^19" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz", + "integrity": "sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", + "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", + "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", + "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", + "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "optional": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "optional": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "optional": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.18.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.18.tgz", + "integrity": "sha512-fwGw1uvQAzabxL1pyoknPlJIF2t7+K90uTqynleKRx24n3lYcxWa3+KByLhgkF8GEAK2c7hC8Ki0RkNM5H15jQ==" + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.22", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz", + "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", + "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", + "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", + "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", + "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/typescript-estree": "5.56.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", + "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.56.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "optional": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "optional": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001468", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001468.tgz", + "integrity": "sha512-zgAo8D5kbOyUcRAgSmgyuvBkjrGk5CGYG5TYgFdpQv+ywcyEpo1LOWoG8YmoflGnh+V+UsNuKYedsoYs0hzV5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "engines": { + "node": "*" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/clarinet": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/clarinet/-/clarinet-0.12.5.tgz", + "integrity": "sha512-4833ySquSUW91fnPaYI94LX3OdnyfwD8/NrMi6a4Kt6EmOsphLWmEzx9bZPqO9+DtQzSv2s3WSYNLsrXt59FKg==", + "engines": { + "chrome": ">=16.0.912", + "firefox": ">=0.8.0", + "node": ">=0.3.6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "optional": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", + "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-stamp": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/console-stamp/-/console-stamp-3.1.1.tgz", + "integrity": "sha512-NTbqQ9X57xffQKTJbAm4h/ro7JNR2uD3369NeTjmRdfPJ2QmkmCUnMvC1QSxZPQOof3WPrkuA6DQV5+n35ZiIA==", + "dependencies": { + "chalk": "^4.1.2", + "dateformat": "^4.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", + "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "dev": true, + "dependencies": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "engines": { + "node": "*" + } + }, + "node_modules/css-functions-list": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", + "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", + "dev": true, + "engines": { + "node": ">=12.22" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "dev": true, + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/data-urls": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "optional": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/digest-fetch": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-2.0.1.tgz", + "integrity": "sha512-OgzIWveqj8BlQ8hfJv97a9iOzWOgvI5Z3rGAnjkeNpHepHZpD/DHBDJ9mtfDclH5vkbUSGRqNEosYCH1FSO6Pg==", + "dependencies": { + "base-64": "^0.1.0", + "js-sha256": "^0.9.0", + "js-sha512": "^0.8.0", + "md5": "^2.3.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron": { + "version": "22.3.4", + "resolved": "https://registry.npmjs.org/electron/-/electron-22.3.4.tgz", + "integrity": "sha512-EY/ieC3gnKYUNOQPJSCIbiMBwEnGs/j0yIAUf0pXPK4BRh2nvXTD5d9OdouAIN7bRNLLPgqoTm0uXgZPAWTVkg==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^16.11.26", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.333", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.333.tgz", + "integrity": "sha512-YyE8+GKyGtPEP1/kpvqsdhD6rA/TP1DUFDN4uiU/YI52NzDxmwHkEb3qjId8hLBa5siJvG0sfC3O66501jMruQ==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "devOptional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/envsub": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/envsub/-/envsub-4.1.0.tgz", + "integrity": "sha512-B44hta3xNFu6+zDhOha1TIrZkQHGDO3G5K8D2sJIkm/s3XyQjxWBGp1B+b/Y74Go1PqMP+cp8moPR4JullnD9Q==", + "dependencies": { + "bluebird": "^3.7.2", + "chalk": "^3.0.0", + "commander": "^4.0.1", + "diff": "^4.0.1", + "handlebars": "^4.5.3", + "lodash": "^4.17.15", + "replace-last": "^1.2.6", + "string.prototype.matchall": "^4.0.8" + }, + "bin": { + "envsub": "bin/envsub.js", + "envsubh": "bin/envsubh.js" + } + }, + "node_modules/envsub/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "optional": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", + "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.1", + "@eslint/js": "8.36.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.5.0", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "27.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz", + "integrity": "sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "40.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.1.0.tgz", + "integrity": "sha512-ANvrhiu62VlSorARM0hup60VQsS3hNyp0Ca7cnJDj8tpJzM7tNhBVqMVYXSuLzEmqrpwx6aAh+NAN2DdAGG5fQ==", + "dev": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.37.0", + "comment-parser": "1.3.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.5.0", + "semver": "^7.3.8", + "spdx-expression-parse": "^3.0.1" + }, + "engines": { + "node": "^14 || ^16 || ^17 || ^18 || ^19" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", + "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-basic-auth": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz", + "integrity": "sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1" + } + }, + "node_modules/express-ipfilter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/express-ipfilter/-/express-ipfilter-1.3.1.tgz", + "integrity": "sha512-9WZC8wGkI6I6ygZNzuZ2MbFJiGoDXs1dM+E8LKtSP13pdgqrnkonWlgvvbxG3YZpa7Haz7Ndum9/J6qkj52OqA==", + "dependencies": { + "ip": "^1.1.8", + "lodash": "^4.17.11", + "proxy-addr": "^2.0.7", + "range_check": "^2.0.4" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "optional": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/feedme": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/feedme/-/feedme-2.0.2.tgz", + "integrity": "sha512-0RNn0uLaSey8EThxgABR0T1Q47kSRatYnAXy1cfUc8/eypqXiAu38XGthuwwzb7mESCD9465k6Nym8D9AtB8HA==", + "dependencies": { + "clarinet": "^0.12.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "optional": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "devOptional": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "optional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/global-agent/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "optional": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/helmet": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-6.0.1.tgz", + "integrity": "sha512-8wo+VdQhTMVBMCITYZaGTbE4lvlthelPYSvoyNvk4RECTmrVjMerp9RfUOQXZWLvCcAn1pKj7ZRxK4lI9Alrcw==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/html-tags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "optional": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, + "node_modules/ip6": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/ip6/-/ip6-0.2.10.tgz", + "integrity": "sha512-1LdpyKjhvepd6EbAU6rW4g14vuYtx5TnJX9TfZZBhsM6DsyPQLNzW12rtbUqXBMwqFrLVV/Gcxv0GNFvJp2cYA==", + "bin": { + "ip6": "ip6-cli.js" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "node_modules/js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdom": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.1.tgz", + "integrity": "sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.2", + "acorn-globals": "^7.0.0", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", + "decimal.js": "^10.4.3", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "optional": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optional": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "optional": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz", + "integrity": "sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==", + "dev": true + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/luxon": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", + "engines": { + "node": "*" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.41", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.41.tgz", + "integrity": "sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multimatch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", + "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/nise": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", + "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-ical": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/node-ical/-/node-ical-0.16.0.tgz", + "integrity": "sha512-LgLN6gm2D1AIaBQnbAw8nz+lH2ZT08lOSGhpzi3z+f2JDt3rSkKrWCx96URO8RmGxgx2Cojw15OBWZSpL18kag==", + "dependencies": { + "axios": "1.3.4", + "moment-timezone": "^0.5.31", + "rrule": "2.6.4", + "uuid": "^9.0.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-package-data/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "optional": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/playwright": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.32.1.tgz", + "integrity": "sha512-GnEizysWMvoqHC3I9l8+4/ZxeLwLNdJJG76xdKGxzOcIZDcw5RSk/FKrFb5CuA+zcLpjIM2p9eR9Z4CuUDkWXg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "playwright-core": "1.32.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/playwright-core": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.1.tgz", + "integrity": "sha512-KZYUQC10mXD2Am1rGlidaalNGYk3LU1vZqqNk0gT4XPty1jOqgup8KDP8l2CUlqoNKhXM5IfGjWgW37xvGllBA==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", + "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-quick": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-3.1.3.tgz", + "integrity": "sha512-kOCi2FJabvuh1as9enxYmrnBC6tVMoVOenMaBqRfsvBHB0cbpYHjdQEpSglpASDFEXVwplpcGR4CLEaisYAFcA==", + "dev": true, + "dependencies": { + "chalk": "^3.0.0", + "execa": "^4.0.0", + "find-up": "^4.1.0", + "ignore": "^5.1.4", + "mri": "^1.1.5", + "multimatch": "^4.0.0" + }, + "bin": { + "pretty-quick": "bin/pretty-quick.js" + }, + "engines": { + "node": ">=10.13" + }, + "peerDependencies": { + "prettier": ">=2.0.0" + } + }, + "node_modules/pretty-quick/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-quick/node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/pretty-quick/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-quick/node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/pretty-quick/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-quick/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-quick/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "devOptional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", + "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range_check": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/range_check/-/range_check-2.0.4.tgz", + "integrity": "sha512-aed0ocXXj+SIiNNN9b+mZWA3Ow2GXHtftOGk2xQwshK5GbEZAvUcPWNQBLTx/lPcdFRIUFlFCRtHTQNIFMqynQ==", + "dependencies": { + "ip6": "^0.2.0", + "ipaddr.js": "^1.9.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/replace-last": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/replace-last/-/replace-last-1.2.6.tgz", + "integrity": "sha512-Cj+MK38VtNu1S5J73mEZY3ciQb9dJajNq1Q8inP4dn/MhJMjHwoAF3Z3FjspwAEV9pfABl565MQucmrjOkty4g==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "optional": true + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.1.tgz", + "integrity": "sha512-OEJWVeimw8mgQuj3HfkNl4KqRevH7lzeQNaWRPfx0PPse7Jk6ozcsG4FKVgtzDsC1KUF+YlTHh17NcgHOPykLw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "optional": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rrule": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.4.tgz", + "integrity": "sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA==", + "dependencies": { + "tslib": "^1.10.0" + }, + "optionalDependencies": { + "luxon": "^1.21.3" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "devOptional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "optional": true + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sinon": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.2.tgz", + "integrity": "sha512-PCVP63XZkg0/LOqQH5rEU4LILuvTFMb5tNxTHfs6VUMNnZz2XrnGSTZbAGITjzwQWbl/Bl/8hi4G3zZWjyBwHg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/samsam": "^7.0.1", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/socket.io": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "optional": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true + }, + "node_modules/stylelint": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.3.0.tgz", + "integrity": "sha512-9UYBYk7K9rtlKcTUDZrtntE840sZM00qyYBQHHe7tjwMNUsPsGvR6Fd43IxHEAhRrDLzpy3TVaHb6CReBB3eFg==", + "dev": true, + "dependencies": { + "@csstools/css-parser-algorithms": "^2.0.1", + "@csstools/css-tokenizer": "^2.1.0", + "@csstools/media-query-list-parser": "^2.0.1", + "@csstools/selector-specificity": "^2.1.1", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^8.1.0", + "css-functions-list": "^3.1.0", + "css-tree": "^2.3.1", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^6.0.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.2.0", + "ignore": "^5.2.4", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.27.0", + "mathml-tag-names": "^2.1.3", + "meow": "^9.0.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.21", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "style-search": "^0.1.0", + "supports-hyperlinks": "^3.0.0", + "svg-tags": "^1.0.0", + "table": "^6.8.1", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^5.0.0" + }, + "bin": { + "stylelint": "bin/stylelint.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-11.0.0.tgz", + "integrity": "sha512-SoGIHNI748OCZn6BxFYT83ytWoYETCINVHV3LKScVAWQQauWdvmdDqJC5YXWjpBbxg2E761Tg5aUGKLFOVhEkA==", + "dev": true, + "peerDependencies": { + "stylelint": "^15.3.0" + } + }, + "node_modules/stylelint-config-standard": { + "version": "31.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-31.0.0.tgz", + "integrity": "sha512-CUGAmtROCvX0YgMY2+6P9tqSkHj5z/75XxrQ8bGxvkCa1xYdGDx4poM0pa7cXc3s74/PZLJH/okxZZouRfOSGw==", + "dev": true, + "dependencies": { + "stylelint-config-recommended": "^11.0.0" + }, + "peerDependencies": { + "stylelint": "^15.3.0" + } + }, + "node_modules/stylelint-prettier": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-3.0.0.tgz", + "integrity": "sha512-kIks1xw6np0zElokMT2kP6ar3S4MBoj6vUtPJuND1pFELMpZxVS/0uHPR4HDAVn0WAD3I5oF0IA3qBFxBpMkLg==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "prettier": ">=2.0.0", + "stylelint": ">=14.0.0" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/write-file-atomic": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.0.tgz", + "integrity": "sha512-R7NYMnHSlV42K54lwY9lvW6MnSm1HSJqZL3xiSgi9E7//FYaI74r2G0rd+/X6VAMkHEdzxQaU5HUOXWUz5kA/w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "optional": true, + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/suncalc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", + "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==", + "dev": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dev": true, + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "optional": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "dev": true, + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "optional": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", + "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "dev": true + }, + "@babel/core": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", + "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.3", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.3", + "@babel/types": "^7.21.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "dependencies": { + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", + "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "dev": true, + "requires": { + "@babel/types": "^7.21.3", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "requires": { + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true + }, + "@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", + "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", + "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", + "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.3", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.3", + "@babel/types": "^7.21.3", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", + "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@csstools/css-parser-algorithms": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.0.1.tgz", + "integrity": "sha512-B9/8PmOtU6nBiibJg0glnNktQDZ3rZnGn/7UmDfrm2vMtrdlXO3p7ErE95N0up80IRk9YEtB5jyj/TmQ1WH3dw==", + "dev": true, + "requires": {} + }, + "@csstools/css-tokenizer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.0.tgz", + "integrity": "sha512-dtqFyoJBHUxGi9zPZdpCKP1xk8tq6KPHJ/NY4qWXiYo6IcSGwzk3L8x2XzZbbyOyBs9xQARoGveU2AsgLj6D2A==", + "dev": true + }, + "@csstools/media-query-list-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.1.tgz", + "integrity": "sha512-X2/OuzEbjaxhzm97UJ+95GrMeT29d1Ib+Pu+paGLuRWZnWRK9sI9r3ikmKXPWGA1C4y4JEdBEFpp9jEqCvLeRA==", + "dev": true, + "requires": {} + }, + "@csstools/selector-specificity": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz", + "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==", + "dev": true, + "requires": {} + }, + "@electron/get": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz", + "integrity": "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==", + "optional": true, + "requires": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "global-agent": "^3.0.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + } + }, + "@es-joy/jsdoccomment": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.37.0.tgz", + "integrity": "sha512-hjK0wnsPCYLlF+HHB4R/RbUjOWeLW2SlarB67+Do5WsKILOkmIZvvPJFbtWSmbypxcjpoECLAMzoao0D4Bg5ZQ==", + "dev": true, + "requires": { + "comment-parser": "1.3.1", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "~4.0.0" + } + }, + "@eslint-community/eslint-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz", + "integrity": "sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==", + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", + "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==" + }, + "@eslint/eslintrc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", + "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@eslint/js": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", + "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==" + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + } + }, + "@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "requires": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + } + }, + "@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3" + } + }, + "@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + } + }, + "@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + } + }, + "@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "requires": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "optional": true + }, + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0" + } + }, + "@sinonjs/samsam": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", + "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "optional": true, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "optional": true, + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "requires": { + "@types/node": "*" + } + }, + "@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "optional": true + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "@types/node": { + "version": "16.18.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.18.tgz", + "integrity": "sha512-fwGw1uvQAzabxL1pyoknPlJIF2t7+K90uTqynleKRx24n3lYcxWa3+KByLhgkF8GEAK2c7hC8Ki0RkNM5H15jQ==" + }, + "@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.22", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz", + "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", + "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0" + } + }, + "@typescript-eslint/types": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", + "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", + "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/visitor-keys": "5.56.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/utils": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", + "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.56.0", + "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/typescript-estree": "5.56.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "dependencies": { + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", + "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.56.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" + }, + "acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "requires": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "requires": {} + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, + "array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, + "axios": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "requires": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "optional": true + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "optional": true + }, + "cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "optional": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "dependencies": { + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + } + } + }, + "caniuse-lite": { + "version": "1.0.30001468", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001468.tgz", + "integrity": "sha512-zgAo8D5kbOyUcRAgSmgyuvBkjrGk5CGYG5TYgFdpQv+ywcyEpo1LOWoG8YmoflGnh+V+UsNuKYedsoYs0hzV5A==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==" + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "clarinet": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/clarinet/-/clarinet-0.12.5.tgz", + "integrity": "sha512-4833ySquSUW91fnPaYI94LX3OdnyfwD8/NrMi6a4Kt6EmOsphLWmEzx9bZPqO9+DtQzSv2s3WSYNLsrXt59FKg==" + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "optional": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" + }, + "comment-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", + "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "console-stamp": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/console-stamp/-/console-stamp-3.1.1.tgz", + "integrity": "sha512-NTbqQ9X57xffQKTJbAm4h/ro7JNR2uD3369NeTjmRdfPJ2QmkmCUnMvC1QSxZPQOof3WPrkuA6DQV5+n35ZiIA==", + "requires": { + "chalk": "^4.1.2", + "dateformat": "^4.6.3" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", + "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "dev": true, + "requires": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==" + }, + "css-functions-list": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", + "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", + "dev": true + }, + "css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "requires": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cssstyle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "dev": true, + "requires": { + "rrweb-cssom": "^0.6.0" + } + }, + "data-urls": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.0" + } + }, + "dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true + } + } + }, + "decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "optional": true, + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "optional": true + } + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "optional": true + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "optional": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + }, + "diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true + }, + "digest-fetch": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-2.0.1.tgz", + "integrity": "sha512-OgzIWveqj8BlQ8hfJv97a9iOzWOgvI5Z3rGAnjkeNpHepHZpD/DHBDJ9mtfDclH5vkbUSGRqNEosYCH1FSO6Pg==", + "requires": { + "base-64": "^0.1.0", + "js-sha256": "^0.9.0", + "js-sha512": "^0.8.0", + "md5": "^2.3.0" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "electron": { + "version": "22.3.4", + "resolved": "https://registry.npmjs.org/electron/-/electron-22.3.4.tgz", + "integrity": "sha512-EY/ieC3gnKYUNOQPJSCIbiMBwEnGs/j0yIAUf0pXPK4BRh2nvXTD5d9OdouAIN7bRNLLPgqoTm0uXgZPAWTVkg==", + "optional": true, + "requires": { + "@electron/get": "^2.0.0", + "@types/node": "^16.11.26", + "extract-zip": "^2.0.1" + } + }, + "electron-to-chromium": { + "version": "1.4.333", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.333.tgz", + "integrity": "sha512-YyE8+GKyGtPEP1/kpvqsdhD6rA/TP1DUFDN4uiU/YI52NzDxmwHkEb3qjId8hLBa5siJvG0sfC3O66501jMruQ==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "devOptional": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + }, + "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "requires": {} + } + } + }, + "engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==" + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true + }, + "envsub": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/envsub/-/envsub-4.1.0.tgz", + "integrity": "sha512-B44hta3xNFu6+zDhOha1TIrZkQHGDO3G5K8D2sJIkm/s3XyQjxWBGp1B+b/Y74Go1PqMP+cp8moPR4JullnD9Q==", + "requires": { + "bluebird": "^3.7.2", + "chalk": "^3.0.0", + "commander": "^4.0.1", + "diff": "^4.0.1", + "handlebars": "^4.5.3", + "lodash": "^4.17.15", + "replace-last": "^1.2.6", + "string.prototype.matchall": "^4.0.8" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "optional": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", + "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.1", + "@eslint/js": "8.36.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.5.0", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + } + }, + "eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + } + } + }, + "eslint-plugin-jest": { + "version": "27.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz", + "integrity": "sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "^5.10.0" + } + }, + "eslint-plugin-jsdoc": { + "version": "40.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.1.0.tgz", + "integrity": "sha512-ANvrhiu62VlSorARM0hup60VQsS3hNyp0Ca7cnJDj8tpJzM7tNhBVqMVYXSuLzEmqrpwx6aAh+NAN2DdAGG5fQ==", + "dev": true, + "requires": { + "@es-joy/jsdoccomment": "~0.37.0", + "comment-parser": "1.3.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.5.0", + "semver": "^7.3.8", + "spdx-expression-parse": "^3.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" + }, + "espree": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", + "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + } + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "express-basic-auth": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz", + "integrity": "sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==", + "dev": true, + "requires": { + "basic-auth": "^2.0.1" + } + }, + "express-ipfilter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/express-ipfilter/-/express-ipfilter-1.3.1.tgz", + "integrity": "sha512-9WZC8wGkI6I6ygZNzuZ2MbFJiGoDXs1dM+E8LKtSP13pdgqrnkonWlgvvbxG3YZpa7Haz7Ndum9/J6qkj52OqA==", + "requires": { + "ip": "^1.1.8", + "lodash": "^4.17.11", + "proxy-addr": "^2.0.7", + "range_check": "^2.0.4" + } + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "optional": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "optional": true, + "requires": { + "pend": "~1.2.0" + } + }, + "feedme": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/feedme/-/feedme-2.0.2.tgz", + "integrity": "sha512-0RNn0uLaSey8EThxgABR0T1Q47kSRatYnAXy1cfUc8/eypqXiAu38XGthuwwzb7mESCD9465k6Nym8D9AtB8HA==", + "requires": { + "clarinet": "^0.12.4", + "sax": "^1.2.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "optional": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "devOptional": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "requires": { + "is-glob": "^4.0.3" + } + }, + "global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "optional": true, + "requires": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "optional": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "requires": { + "global-prefix": "^3.0.0" + } + }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "requires": { + "type-fest": "^0.20.2" + } + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "requires": { + "define-properties": "^1.1.3" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "optional": true, + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "helmet": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-6.0.1.tgz", + "integrity": "sha512-8wo+VdQhTMVBMCITYZaGTbE4lvlthelPYSvoyNvk4RECTmrVjMerp9RfUOQXZWLvCcAn1pKj7ZRxK4lI9Alrcw==" + }, + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "html-tags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "optional": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "optional": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, + "ip6": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/ip6/-/ip6-0.2.10.tgz", + "integrity": "sha512-1LdpyKjhvepd6EbAU6rW4g14vuYtx5TnJX9TfZZBhsM6DsyPQLNzW12rtbUqXBMwqFrLVV/Gcxv0GNFvJp2cYA==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + } + }, + "jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + } + }, + "jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true + }, + "jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true + }, + "jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "requires": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + } + }, + "jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + } + }, + "jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + } + }, + "jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "requires": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==" + }, + "js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true + }, + "jsdom": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.1.tgz", + "integrity": "sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "acorn": "^8.8.2", + "acorn-globals": "^7.0.0", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", + "decimal.js": "^10.4.3", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", + "xml-name-validator": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "optional": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "optional": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "optional": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "known-css-properties": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz", + "integrity": "sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "optional": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "luxon": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==" + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true + }, + "matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "optional": true, + "requires": { + "escape-string-regexp": "^4.0.0" + } + }, + "mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true + }, + "md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "dependencies": { + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + } + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "optional": true + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true + } + } + }, + "module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" + }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, + "moment-timezone": { + "version": "0.5.41", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.41.tgz", + "integrity": "sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg==", + "requires": { + "moment": "^2.29.4" + } + }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multimatch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", + "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "dev": true, + "requires": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + } + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "nise": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", + "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, + "node-ical": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/node-ical/-/node-ical-0.16.0.tgz", + "integrity": "sha512-LgLN6gm2D1AIaBQnbAw8nz+lH2ZT08lOSGhpzi3z+f2JDt3rSkKrWCx96URO8RmGxgx2Cojw15OBWZSpL18kag==", + "requires": { + "axios": "1.3.4", + "moment-timezone": "^0.5.31", + "rrule": "2.6.4", + "uuid": "^9.0.0" + } + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "optional": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "optional": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "optional": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "playwright": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.32.1.tgz", + "integrity": "sha512-GnEizysWMvoqHC3I9l8+4/ZxeLwLNdJJG76xdKGxzOcIZDcw5RSk/FKrFb5CuA+zcLpjIM2p9eR9Z4CuUDkWXg==", + "dev": true, + "requires": { + "playwright-core": "1.32.1" + } + }, + "playwright-core": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.1.tgz", + "integrity": "sha512-KZYUQC10mXD2Am1rGlidaalNGYk3LU1vZqqNk0gT4XPty1jOqgup8KDP8l2CUlqoNKhXM5IfGjWgW37xvGllBA==", + "dev": true + }, + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, + "postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "requires": {} + }, + "postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, + "prettier": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", + "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "pretty-quick": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-3.1.3.tgz", + "integrity": "sha512-kOCi2FJabvuh1as9enxYmrnBC6tVMoVOenMaBqRfsvBHB0cbpYHjdQEpSglpASDFEXVwplpcGR4CLEaisYAFcA==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "execa": "^4.0.0", + "find-up": "^4.1.0", + "ignore": "^5.1.4", + "mri": "^1.1.5", + "multimatch": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "optional": true + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "devOptional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" + }, + "pure-rand": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", + "integrity": "sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "optional": true + }, + "range_check": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/range_check/-/range_check-2.0.4.tgz", + "integrity": "sha512-aed0ocXXj+SIiNNN9b+mZWA3Ow2GXHtftOGk2xQwshK5GbEZAvUcPWNQBLTx/lPcdFRIUFlFCRtHTQNIFMqynQ==", + "requires": { + "ip6": "^0.2.0", + "ipaddr.js": "^1.9.1" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "replace-last": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/replace-last/-/replace-last-1.2.6.tgz", + "integrity": "sha512-Cj+MK38VtNu1S5J73mEZY3ciQb9dJajNq1Q8inP4dn/MhJMjHwoAF3Z3FjspwAEV9pfABl565MQucmrjOkty4g==" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "optional": true + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "resolve.exports": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.1.tgz", + "integrity": "sha512-OEJWVeimw8mgQuj3HfkNl4KqRevH7lzeQNaWRPfx0PPse7Jk6ozcsG4FKVgtzDsC1KUF+YlTHh17NcgHOPykLw==", + "dev": true + }, + "responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "optional": true, + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "optional": true, + "requires": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + } + }, + "rrule": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.4.tgz", + "integrity": "sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA==", + "requires": { + "luxon": "^1.21.3", + "tslib": "^1.10.0" + } + }, + "rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "devOptional": true + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "optional": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "optional": true, + "requires": { + "type-fest": "^0.13.1" + }, + "dependencies": { + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "optional": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sinon": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.2.tgz", + "integrity": "sha512-PCVP63XZkg0/LOqQH5rEU4LILuvTFMb5tNxTHfs6VUMNnZz2XrnGSTZbAGITjzwQWbl/Bl/8hi4G3zZWjyBwHg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/samsam": "^7.0.1", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + } + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "socket.io": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" + } + }, + "socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "requires": { + "ws": "~8.11.0" + }, + "dependencies": { + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "requires": {} + } + } + }, + "socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "optional": true + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string.prototype.matchall": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4" + } + }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true + }, + "stylelint": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.3.0.tgz", + "integrity": "sha512-9UYBYk7K9rtlKcTUDZrtntE840sZM00qyYBQHHe7tjwMNUsPsGvR6Fd43IxHEAhRrDLzpy3TVaHb6CReBB3eFg==", + "dev": true, + "requires": { + "@csstools/css-parser-algorithms": "^2.0.1", + "@csstools/css-tokenizer": "^2.1.0", + "@csstools/media-query-list-parser": "^2.0.1", + "@csstools/selector-specificity": "^2.1.1", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^8.1.0", + "css-functions-list": "^3.1.0", + "css-tree": "^2.3.1", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^6.0.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.2.0", + "ignore": "^5.2.4", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.27.0", + "mathml-tag-names": "^2.1.3", + "meow": "^9.0.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.21", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "style-search": "^0.1.0", + "supports-hyperlinks": "^3.0.0", + "svg-tags": "^1.0.0", + "table": "^6.8.1", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^5.0.0" + }, + "dependencies": { + "balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "write-file-atomic": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.0.tgz", + "integrity": "sha512-R7NYMnHSlV42K54lwY9lvW6MnSm1HSJqZL3xiSgi9E7//FYaI74r2G0rd+/X6VAMkHEdzxQaU5HUOXWUz5kA/w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + } + } + }, + "stylelint-config-recommended": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-11.0.0.tgz", + "integrity": "sha512-SoGIHNI748OCZn6BxFYT83ytWoYETCINVHV3LKScVAWQQauWdvmdDqJC5YXWjpBbxg2E761Tg5aUGKLFOVhEkA==", + "dev": true, + "requires": {} + }, + "stylelint-config-standard": { + "version": "31.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-31.0.0.tgz", + "integrity": "sha512-CUGAmtROCvX0YgMY2+6P9tqSkHj5z/75XxrQ8bGxvkCa1xYdGDx4poM0pa7cXc3s74/PZLJH/okxZZouRfOSGw==", + "dev": true, + "requires": { + "stylelint-config-recommended": "^11.0.0" + } + }, + "stylelint-prettier": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-3.0.0.tgz", + "integrity": "sha512-kIks1xw6np0zElokMT2kP6ar3S4MBoj6vUtPJuND1pFELMpZxVS/0uHPR4HDAVn0WAD3I5oF0IA3qBFxBpMkLg==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "optional": true, + "requires": { + "debug": "^4.1.0" + } + }, + "suncalc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", + "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + } + } + }, + "tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dev": true, + "requires": { + "punycode": "^2.3.0" + } + }, + "trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true + }, + "tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, + "typescript": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "dev": true, + "peer": true + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "optional": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "dependencies": { + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + } + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, + "whatwg-url": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "dev": true, + "requires": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "requires": {} + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "optional": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + } + } +} diff --git a/MagicMirror/package.json b/MagicMirror/package.json new file mode 100644 index 0000000000000000000000000000000000000000..2f51a2da7c0be38b12f3461b585dbdb3aa8a736a --- /dev/null +++ b/MagicMirror/package.json @@ -0,0 +1,100 @@ +{ + "name": "magicmirror", + "version": "2.23.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-mm": "npm install --no-audit --no-fund --no-update-notifier --only=prod --omit=dev", + "install-mm:dev": "npm install --no-audit --no-fund --no-update-notifier", + "install-vendor": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error --no-audit --no-fund --no-update-notifier", + "install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error --no-audit --no-fund --no-update-notifier", + "postinstall": "npm run install-vendor && npm run install-fonts && echo \"MagicMirror² installation finished successfully! \n\"", + "test": "NODE_ENV=test jest -i --forceExit", + "test:coverage": "NODE_ENV=test jest --coverage -i --verbose false --forceExit", + "test:electron": "NODE_ENV=test jest --selectProjects electron -i --forceExit", + "test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit", + "test:unit": "NODE_ENV=test jest --selectProjects unit", + "test:prettier": "prettier . --check", + "test:js": "eslint 'js/**/*.js' 'modules/default/**/*.js' 'clientonly/*.js' 'serveronly/*.js' 'translations/*.js' 'vendor/*.js' 'tests/**/*.js' 'config/*' --config .eslintrc.json", + "test:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json", + "test:calendar": "node ./modules/default/calendar/debug.js", + "config:check": "node js/check_config.js", + "lint:prettier": "prettier . --write", + "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' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json --fix", + "lint:staged": "pretty-quick --staged", + "prepare": "[ -f node_modules/.bin/husky ] && husky install || echo no husky installed." + }, + "repository": { + "type": "git", + "url": "git+https://github.com/MichMich/MagicMirror.git" + }, + "keywords": [ + "magic mirror", + "magicmirror", + "smart mirror", + "mirror UI", + "modular" + ], + "author": "Michael Teeuw", + "contributors": [ + "https://github.com/MichMich/MagicMirror/graphs/contributors" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/MichMich/MagicMirror/issues" + }, + "homepage": "https://magicmirror.builders", + "devDependencies": { + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^27.2.1", + "eslint-plugin-jsdoc": "^40.1.0", + "eslint-plugin-prettier": "^4.2.1", + "express-basic-auth": "^1.2.1", + "husky": "^8.0.3", + "jest": "^29.5.0", + "jsdom": "^21.1.1", + "lodash": "^4.17.21", + "playwright": "^1.32.1", + "prettier": "^2.8.7", + "pretty-quick": "^3.1.3", + "sinon": "^15.0.2", + "stylelint": "^15.3.0", + "stylelint-config-standard": "^31.0.0", + "stylelint-prettier": "^3.0.0", + "suncalc": "^1.9.0" + }, + "optionalDependencies": { + "electron": "^22.3.4" + }, + "dependencies": { + "colors": "^1.4.0", + "console-stamp": "^3.1.1", + "digest-fetch": "^2.0.1", + "envsub": "^4.1.0", + "eslint": "^8.36.0", + "express": "^4.18.2", + "express-ipfilter": "^1.3.1", + "feedme": "^2.0.2", + "helmet": "^6.0.1", + "iconv-lite": "^0.6.3", + "luxon": "^1.28.1", + "module-alias": "^2.2.2", + "moment": "^2.29.4", + "node-fetch": "^2.6.9", + "node-ical": "^0.16.0", + "socket.io": "^4.6.1" + }, + "_moduleAliases": { + "node_helper": "js/node_helper.js", + "logger": "js/logger.js", + "fetch": "js/fetch.js" + }, + "engines": { + "node": ">=14" + } +} diff --git a/MagicMirror/serveronly/index.js b/MagicMirror/serveronly/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3cfa7969d274463edd46ce26d5fa9512db93de6e --- /dev/null +++ b/MagicMirror/serveronly/index.js @@ -0,0 +1,8 @@ +const app = require("../js/app"); +const Log = require("../js/logger"); + +app.start().then((config) => { + const bindAddress = config.address ? config.address : "localhost"; + const httpType = config.useHttps ? "https" : "http"; + Log.log(`\nReady to go! Please point your browser to: ${httpType}://${bindAddress}:${config.port}`); +}); diff --git a/MagicMirror/splashscreen/MagicMirror.plymouth b/MagicMirror/splashscreen/MagicMirror.plymouth new file mode 100644 index 0000000000000000000000000000000000000000..b6887bf56bba210354befcc5038c6f04a9f08e84 --- /dev/null +++ b/MagicMirror/splashscreen/MagicMirror.plymouth @@ -0,0 +1,8 @@ +[Plymouth Theme] +Name=MagicMirror +Description=Mirror Splash +ModuleName=script + +[script] +ImageDir=/usr/share/plymouth/themes/MagicMirror +ScriptFile=/usr/share/plymouth/themes/MagicMirror/MagicMirror.script diff --git a/MagicMirror/splashscreen/MagicMirror.script b/MagicMirror/splashscreen/MagicMirror.script new file mode 100644 index 0000000000000000000000000000000000000000..6e2f5643a9bc30576819e6a69be454ce213636eb --- /dev/null +++ b/MagicMirror/splashscreen/MagicMirror.script @@ -0,0 +1,53 @@ +screen_width = Window.GetWidth(); +screen_height = Window.GetHeight(); + +if (Plymouth.GetMode() != "shutdown") +{ + theme_image = Image("splash.png"); +} +else +{ + theme_image = Image("splash_halt.png"); +} + +image_width = theme_image.GetWidth(); +image_height = theme_image.GetHeight(); + +scale_x = image_width / screen_width; +scale_y = image_height / screen_height; + +if (scale_x > 1 || scale_y > 1) +{ + if (scale_x > scale_y) + { + resized_image = theme_image.Scale (screen_width, image_height / scale_x); + image_x = 0; + image_y = (screen_height - ((image_height * screen_width) / image_width)) / 2; + } + else + { + resized_image = theme_image.Scale (image_width / scale_y, screen_height); + image_x = (screen_width - ((image_width * screen_height) / image_height)) / 2; + image_y = 0; + } +} +else +{ + resized_image = theme_image.Scale (image_width, image_height); + image_x = (screen_width - image_width) / 2; + image_y = (screen_height - image_height) / 2; +} + +sprite = Sprite (resized_image); +sprite.SetPosition (image_x, image_y, -100); + +message_sprite = Sprite(); +message_sprite.SetPosition(screen_width * 0.1, screen_height * 0.9, 10000); + +fun message_callback (text) { + my_image = Image.Text(text, 1, 1, 1); + message_sprite.SetImage(my_image); + sprite.SetImage (resized_image); +} + +Plymouth.SetUpdateStatusFunction(message_callback); diff --git a/MagicMirror/splashscreen/splash.png b/MagicMirror/splashscreen/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..b2acc49539505c9bcc991e155f5b4decc9534069 Binary files /dev/null and b/MagicMirror/splashscreen/splash.png differ diff --git a/MagicMirror/splashscreen/splash_halt.png b/MagicMirror/splashscreen/splash_halt.png new file mode 100644 index 0000000000000000000000000000000000000000..dcf9d8be0de9cc11079c61ef6e57c5d52f9cab15 Binary files /dev/null and b/MagicMirror/splashscreen/splash_halt.png differ diff --git a/MagicMirror/tests/configs/default.js b/MagicMirror/tests/configs/default.js new file mode 100644 index 0000000000000000000000000000000000000000..509dfd9860c5e272827aaaf833eba4280afb8c86 --- /dev/null +++ b/MagicMirror/tests/configs/default.js @@ -0,0 +1,21 @@ +/* MagicMirror² Test default config for modules + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +exports.configFactory = (options) => { + return Object.assign( + { + electronOptions: { + webPreferences: { + nodeIntegration: true, + enableRemoteModule: true, + contextIsolation: false + } + }, + + modules: [] + }, + options + ); +}; diff --git a/MagicMirror/tests/configs/empty_ipWhiteList.js b/MagicMirror/tests/configs/empty_ipWhiteList.js new file mode 100644 index 0000000000000000000000000000000000000000..984d1738569124455b6c78b4b710e97c7bc886f7 --- /dev/null +++ b/MagicMirror/tests/configs/empty_ipWhiteList.js @@ -0,0 +1,13 @@ +/* MagicMirror² Test config sample ipWhitelist + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({ + ipWhitelist: [] +}); + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/alert/default.js b/MagicMirror/tests/configs/modules/alert/default.js new file mode 100644 index 0000000000000000000000000000000000000000..45ffefe5b2560450b4932ed576a0d684e5318b6f --- /dev/null +++ b/MagicMirror/tests/configs/modules/alert/default.js @@ -0,0 +1,21 @@ +/* MagicMirror² Test config sample module alert + * + * By rejas + * MIT Licensed. + */ +let config = { + modules: [ + { + module: "alert", + config: { + display_time: 1000000, + welcome_message: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/calendar/auth-default.js b/MagicMirror/tests/configs/modules/calendar/auth-default.js new file mode 100644 index 0000000000000000000000000000000000000000..0b2491612fdb54f246922bca96e88ed696d3fc23 --- /dev/null +++ b/MagicMirror/tests/configs/modules/calendar/auth-default.js @@ -0,0 +1,32 @@ +/* MagicMirror² Test config default calendar with auth by default + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "calendar", + position: "bottom_bar", + config: { + calendars: [ + { + maximumNumberOfDays: 10000, + url: "http://localhost:8080/tests/mocks/calendar_test.ics", + auth: { + user: "MagicMirror", + pass: "CallMeADog" + } + } + ] + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/calendar/basic-auth.js b/MagicMirror/tests/configs/modules/calendar/basic-auth.js new file mode 100644 index 0000000000000000000000000000000000000000..5cbebceb2f5a0ed810ca3141c58336eac9010a0c --- /dev/null +++ b/MagicMirror/tests/configs/modules/calendar/basic-auth.js @@ -0,0 +1,33 @@ +/* MagicMirror² Test config default calendar + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "calendar", + position: "bottom_bar", + config: { + calendars: [ + { + maximumNumberOfDays: 10000, + url: "http://localhost:8080/tests/mocks/calendar_test.ics", + auth: { + user: "MagicMirror", + pass: "CallMeADog", + method: "basic" + } + } + ] + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/calendar/changed-port.js b/MagicMirror/tests/configs/modules/calendar/changed-port.js new file mode 100644 index 0000000000000000000000000000000000000000..5027726115c8406de29f455463b4001fc10e3376 --- /dev/null +++ b/MagicMirror/tests/configs/modules/calendar/changed-port.js @@ -0,0 +1,32 @@ +/* MagicMirror² Test config default calendar with auth by default + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "calendar", + position: "bottom_bar", + config: { + calendars: [ + { + maximumNumberOfDays: 10000, + url: "http://localhost:8010/tests/mocks/calendar_test.ics", + auth: { + user: "MagicMirror", + pass: "CallMeADog" + } + } + ] + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/calendar/custom.js b/MagicMirror/tests/configs/modules/calendar/custom.js new file mode 100644 index 0000000000000000000000000000000000000000..993ac483e67897ec9ecfadcb83102f194d7d4320 --- /dev/null +++ b/MagicMirror/tests/configs/modules/calendar/custom.js @@ -0,0 +1,33 @@ +/* MagicMirror² Test config custom calendar + * + * By Rejas + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "calendar", + position: "bottom_bar", + config: { + customEvents: [{ keyword: "CustomEvent", symbol: "dice" }], + calendars: [ + { + maximumEntries: 5, + maximumNumberOfDays: 10000, + symbol: "birthday-cake", + fullDaySymbol: "calendar-day", + recurringSymbol: "undo", + url: "http://localhost:8080/tests/mocks/calendar_test_icons.ics" + } + ] + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/calendar/default.js b/MagicMirror/tests/configs/modules/calendar/default.js new file mode 100644 index 0000000000000000000000000000000000000000..2dee121203898d5ffa8d726b959e6fd6c2ea83e1 --- /dev/null +++ b/MagicMirror/tests/configs/modules/calendar/default.js @@ -0,0 +1,28 @@ +/* MagicMirror² Test config default calendar + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "calendar", + position: "bottom_bar", + config: { + calendars: [ + { + maximumNumberOfDays: 10000, + url: "http://localhost:8080/tests/mocks/calendar_test.ics" + } + ] + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/calendar/fail-basic-auth.js b/MagicMirror/tests/configs/modules/calendar/fail-basic-auth.js new file mode 100644 index 0000000000000000000000000000000000000000..e0b3ff15aec7f857be665e733ce1bff5ae600463 --- /dev/null +++ b/MagicMirror/tests/configs/modules/calendar/fail-basic-auth.js @@ -0,0 +1,35 @@ +/* MagicMirror² Test calendar calendar + * + * This configuration is a wrong authentication + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "calendar", + position: "bottom_bar", + config: { + calendars: [ + { + maximumNumberOfDays: 10000, + url: "http://localhost:8020/tests/mocks/calendar_test.ics", + auth: { + user: "MagicMirror", + pass: "StairwayToHeaven", + method: "basic" + } + } + ] + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/calendar/old-basic-auth.js b/MagicMirror/tests/configs/modules/calendar/old-basic-auth.js new file mode 100644 index 0000000000000000000000000000000000000000..01805e5c8cd109980f02d38f0faebb578a32d9d4 --- /dev/null +++ b/MagicMirror/tests/configs/modules/calendar/old-basic-auth.js @@ -0,0 +1,30 @@ +/* MagicMirror² Test config default calendar + * with authentication old config + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "calendar", + position: "bottom_bar", + config: { + calendars: [ + { + maximumNumberOfDays: 10000, + url: "http://localhost:8080/tests/mocks/calendar_test.ics", + user: "MagicMirror", + pass: "CallMeADog" + } + ] + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/calendar/recurring.js b/MagicMirror/tests/configs/modules/calendar/recurring.js new file mode 100644 index 0000000000000000000000000000000000000000..d05cce6c9b77ccbd577f32ba5bef58ee0eaa091f --- /dev/null +++ b/MagicMirror/tests/configs/modules/calendar/recurring.js @@ -0,0 +1,29 @@ +/* MagicMirror² Test config custom calendar + * + * By Rejas + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "calendar", + position: "bottom_bar", + config: { + calendars: [ + { + maximumEntries: 6, + maximumNumberOfDays: 3650, + url: "http://localhost:8080/tests/mocks/calendar_test_recurring.ics" + } + ] + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/clock_12hr.js b/MagicMirror/tests/configs/modules/clock/clock_12hr.js new file mode 100644 index 0000000000000000000000000000000000000000..18170e7c9c5bb999466fec3bed727899697a7986 --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/clock_12hr.js @@ -0,0 +1,20 @@ +/* MagicMirror² Test config for default clock module + * + * By Sergey Morozov + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "clock", + position: "middle_center" + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/clock_24hr.js b/MagicMirror/tests/configs/modules/clock/clock_24hr.js new file mode 100644 index 0000000000000000000000000000000000000000..f9bd4514369e1de9d4c5585be589aa3236b169b6 --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/clock_24hr.js @@ -0,0 +1,18 @@ +/* MagicMirror² Test config for default clock module + * + * By Sergey Morozov + * MIT Licensed. + */ +let config = { + modules: [ + { + module: "clock", + position: "middle_center" + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/clock_analog.js b/MagicMirror/tests/configs/modules/clock/clock_analog.js new file mode 100644 index 0000000000000000000000000000000000000000..fdca561562738dc457c064fc2d2fb3e57cbbd7dd --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/clock_analog.js @@ -0,0 +1,21 @@ +/* MagicMirror² Test config for analog clock face + * + * MIT Licensed. + */ +let config = { + modules: [ + { + module: "clock", + position: "middle_center", + config: { + displayType: "analog", + analogFace: "face-006" + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/clock_displaySeconds_false.js b/MagicMirror/tests/configs/modules/clock/clock_displaySeconds_false.js new file mode 100644 index 0000000000000000000000000000000000000000..cf33332090e59d7ee407e46d2adbb0f6eeb63b03 --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/clock_displaySeconds_false.js @@ -0,0 +1,23 @@ +/* MagicMirror² Test config for default clock module + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "clock", + position: "middle_center", + config: { + displaySeconds: false + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/clock_showPeriodUpper.js b/MagicMirror/tests/configs/modules/clock/clock_showPeriodUpper.js new file mode 100644 index 0000000000000000000000000000000000000000..3dd8b5c52fbacf86817ece8408b79ae8a54a5261 --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/clock_showPeriodUpper.js @@ -0,0 +1,23 @@ +/* MagicMirror² Test config for default clock module + * + * By Sergey Morozov + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "clock", + position: "middle_center", + config: { + showPeriodUpper: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/clock_showTime.js b/MagicMirror/tests/configs/modules/clock/clock_showTime.js new file mode 100644 index 0000000000000000000000000000000000000000..17ea4590d165eecb1a50f671fb5617c45eb3c6ca --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/clock_showTime.js @@ -0,0 +1,23 @@ +/* MagicMirror² Test config for default clock module + * + * By Johan Hammar + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "clock", + position: "middle_center", + config: { + showTime: false + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/clock_showWeek.js b/MagicMirror/tests/configs/modules/clock/clock_showWeek.js new file mode 100644 index 0000000000000000000000000000000000000000..71377e0bc5c1643948f8decc684bcd37790c7e7a --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/clock_showWeek.js @@ -0,0 +1,23 @@ +/* MagicMirror² Test config for default clock module + * + * By Johan Hammar + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "clock", + position: "middle_center", + config: { + showWeek: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/es/clock_12hr.js b/MagicMirror/tests/configs/modules/clock/es/clock_12hr.js new file mode 100644 index 0000000000000000000000000000000000000000..44444c06e487fecf4f4149d71129989954271bd1 --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/es/clock_12hr.js @@ -0,0 +1,21 @@ +/* MagicMirror² Test config for default clock module + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + language: "es", + timeFormat: 12, + + modules: [ + { + module: "clock", + position: "middle_center" + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/es/clock_24hr.js b/MagicMirror/tests/configs/modules/clock/es/clock_24hr.js new file mode 100644 index 0000000000000000000000000000000000000000..66b5304d179b0299ef39aab510b57155c964e972 --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/es/clock_24hr.js @@ -0,0 +1,20 @@ +/* MagicMirror² Test config for default clock module + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + language: "es", + + modules: [ + { + module: "clock", + position: "middle_center" + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/es/clock_showPeriodUpper.js b/MagicMirror/tests/configs/modules/clock/es/clock_showPeriodUpper.js new file mode 100644 index 0000000000000000000000000000000000000000..5e1fbb4be2af7469de8ec74a4623472403d4118c --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/es/clock_showPeriodUpper.js @@ -0,0 +1,24 @@ +/* MagicMirror² Test config for default clock module + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + language: "es", + timeFormat: 12, + + modules: [ + { + module: "clock", + position: "middle_center", + config: { + showPeriodUpper: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/clock/es/clock_showWeek.js b/MagicMirror/tests/configs/modules/clock/es/clock_showWeek.js new file mode 100644 index 0000000000000000000000000000000000000000..73635c0ec0b67bb29e266b28aebce6531f14543e --- /dev/null +++ b/MagicMirror/tests/configs/modules/clock/es/clock_showWeek.js @@ -0,0 +1,25 @@ +/* MagicMirror² Test config for default clock module + * Language es for showWeek feature + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + language: "es", + timeFormat: 12, + + modules: [ + { + module: "clock", + position: "middle_center", + config: { + showWeek: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/compliments/compliments_anytime.js b/MagicMirror/tests/configs/modules/compliments/compliments_anytime.js new file mode 100644 index 0000000000000000000000000000000000000000..89253d9140ed71ae398ace099fc01d45e1524e36 --- /dev/null +++ b/MagicMirror/tests/configs/modules/compliments/compliments_anytime.js @@ -0,0 +1,28 @@ +/* MagicMirror² Test config compliments with anytime type + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "compliments", + position: "middle_center", + config: { + compliments: { + morning: [], + afternoon: [], + evening: [], + anytime: ["Anytime here"] + } + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/compliments/compliments_date.js b/MagicMirror/tests/configs/modules/compliments/compliments_date.js new file mode 100644 index 0000000000000000000000000000000000000000..2d9018b1d620c52d729b83f9e769a3e0f0f10d2f --- /dev/null +++ b/MagicMirror/tests/configs/modules/compliments/compliments_date.js @@ -0,0 +1,28 @@ +/* MagicMirror² Test config compliments with date type + * + * By Rejas + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "compliments", + position: "middle_center", + config: { + compliments: { + morning: [], + afternoon: [], + evening: [], + "....-01-01": ["Happy new year!"] + } + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/compliments/compliments_only_anytime.js b/MagicMirror/tests/configs/modules/compliments/compliments_only_anytime.js new file mode 100644 index 0000000000000000000000000000000000000000..f1da683a944556f3ac16c8d8af7f128df30991eb --- /dev/null +++ b/MagicMirror/tests/configs/modules/compliments/compliments_only_anytime.js @@ -0,0 +1,25 @@ +/* MagicMirror² Test config compliments with anytime type + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "compliments", + position: "middle_center", + config: { + compliments: { + anytime: ["Anytime here"] + } + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/compliments/compliments_parts_day.js b/MagicMirror/tests/configs/modules/compliments/compliments_parts_day.js new file mode 100644 index 0000000000000000000000000000000000000000..ff34803a5bf35d6115d949bd48bbb01b4c155b7e --- /dev/null +++ b/MagicMirror/tests/configs/modules/compliments/compliments_parts_day.js @@ -0,0 +1,27 @@ +/* MagicMirror² Test config for default compliments + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "compliments", + position: "middle_center", + config: { + compliments: { + morning: ["Hi", "Good Morning", "Morning test"], + afternoon: ["Hello", "Good Afternoon", "Afternoon test"], + evening: ["Hello There", "Good Evening", "Evening test"] + } + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/compliments/compliments_remote.js b/MagicMirror/tests/configs/modules/compliments/compliments_remote.js new file mode 100644 index 0000000000000000000000000000000000000000..465892968243280ec775c61fff2a41430efdf050 --- /dev/null +++ b/MagicMirror/tests/configs/modules/compliments/compliments_remote.js @@ -0,0 +1,21 @@ +/* MagicMirror² Test config compliments with remote file + * + * By Rejas + * MIT Licensed. + */ +let config = { + modules: [ + { + module: "compliments", + position: "middle_center", + config: { + remoteFile: "http://localhost:8080/tests/mocks/compliments_test.json" + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/display.js b/MagicMirror/tests/configs/modules/display.js new file mode 100644 index 0000000000000000000000000000000000000000..a416607743ef21e09be48417ba7f3196ffb2ffae --- /dev/null +++ b/MagicMirror/tests/configs/modules/display.js @@ -0,0 +1,29 @@ +/* MagicMirror² Test config for display setters module using the helloworld module + * + * By Rejas + * MIT Licensed. + */ +let config = { + modules: [ + { + module: "helloworld", + position: "top_bar", + header: "test_header", + config: { + text: "Test Display Header" + } + }, + { + module: "helloworld", + position: "bottom_bar", + config: { + text: "Test Hide Header" + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/helloworld/helloworld.js b/MagicMirror/tests/configs/modules/helloworld/helloworld.js new file mode 100644 index 0000000000000000000000000000000000000000..7225662127bbe35e6a05656d83fce931e027be76 --- /dev/null +++ b/MagicMirror/tests/configs/modules/helloworld/helloworld.js @@ -0,0 +1,21 @@ +/* MagicMirror² Test config sample module hello world + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + modules: [ + { + module: "helloworld", + position: "bottom_bar", + config: { + text: "Test HelloWorld Module" + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/helloworld/helloworld_default.js b/MagicMirror/tests/configs/modules/helloworld/helloworld_default.js new file mode 100644 index 0000000000000000000000000000000000000000..06e6fe8dda06dfb8923256f5dc46eebaffc2aa4a --- /dev/null +++ b/MagicMirror/tests/configs/modules/helloworld/helloworld_default.js @@ -0,0 +1,18 @@ +/* MagicMirror² Test config sample module hello world default config + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + modules: [ + { + module: "helloworld", + position: "bottom_bar" + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/newsfeed/default.js b/MagicMirror/tests/configs/modules/newsfeed/default.js new file mode 100644 index 0000000000000000000000000000000000000000..34a7587fb022719ad0e628771f21ae6556c5774b --- /dev/null +++ b/MagicMirror/tests/configs/modules/newsfeed/default.js @@ -0,0 +1,28 @@ +/* MagicMirror² Test config newsfeed module + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "newsfeed", + position: "bottom_bar", + config: { + feeds: [ + { + title: "Rodrigo Ramirez Blog", + url: "http://localhost:8080/tests/mocks/newsfeed_test.xml" + } + ] + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/newsfeed/ignore_items.js b/MagicMirror/tests/configs/modules/newsfeed/ignore_items.js new file mode 100644 index 0000000000000000000000000000000000000000..fd104ba41457b5abe8f093ca7528b2c3b2133ef7 --- /dev/null +++ b/MagicMirror/tests/configs/modules/newsfeed/ignore_items.js @@ -0,0 +1,28 @@ +/* MagicMirror² Test config newsfeed module + * + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "newsfeed", + position: "bottom_bar", + config: { + feeds: [ + { + title: "Rodrigo Ramirez Blog", + url: "http://localhost:8080/tests/mocks/newsfeed_test.xml" + } + ], + ignoreOldItems: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/newsfeed/incorrect_url.js b/MagicMirror/tests/configs/modules/newsfeed/incorrect_url.js new file mode 100644 index 0000000000000000000000000000000000000000..f9d75b09a39cc68dbabfaa51f572ea6b741511d7 --- /dev/null +++ b/MagicMirror/tests/configs/modules/newsfeed/incorrect_url.js @@ -0,0 +1,27 @@ +/* MagicMirror² Test config newsfeed module + * + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + 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; +} diff --git a/MagicMirror/tests/configs/modules/newsfeed/prohibited_words.js b/MagicMirror/tests/configs/modules/newsfeed/prohibited_words.js new file mode 100644 index 0000000000000000000000000000000000000000..4ff6aec0fa911f5c47bfe378b41ee2839dcaf8ba --- /dev/null +++ b/MagicMirror/tests/configs/modules/newsfeed/prohibited_words.js @@ -0,0 +1,29 @@ +/* MagicMirror² Test config newsfeed module + * + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "newsfeed", + position: "bottom_bar", + config: { + feeds: [ + { + title: "Rodrigo Ramirez Blog", + url: "http://localhost:8080/tests/mocks/newsfeed_test.xml" + } + ], + prohibitedWords: ["QPanel"], + showDescription: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/positions.js b/MagicMirror/tests/configs/modules/positions.js new file mode 100644 index 0000000000000000000000000000000000000000..a40bfab959b2332ff8d9faaab4a74804745368bf --- /dev/null +++ b/MagicMirror/tests/configs/modules/positions.js @@ -0,0 +1,28 @@ +/* MagicMirror² Test config for position setters module using the helloworld module + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + modules: + // Using exotic content. This is why don't accept go to JSON configuration file + (() => { + let positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"]; + let modules = Array(); + for (let idx in positions) { + modules.push({ + module: "helloworld", + position: positions[idx], + config: { + text: `Text in ${positions[idx]}` + } + }); + } + return modules; + })() +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/currentweather_compliments.js b/MagicMirror/tests/configs/modules/weather/currentweather_compliments.js new file mode 100644 index 0000000000000000000000000000000000000000..4af544663f1249ce90048d22d33194551db87247 --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/currentweather_compliments.js @@ -0,0 +1,32 @@ +/* MagicMirror² Test config current weather compliments + * + * By rejas https://github.com/rejas + * MIT Licensed. + */ +let config = { + modules: [ + { + module: "compliments", + position: "top_bar", + config: { + compliments: { + snow: ["snow"] + }, + updateInterval: 3000 + } + }, + { + module: "weather", + position: "bottom_bar", + config: { + location: "Munich", + mockData: '"#####WEATHERDATA#####"' + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/currentweather_default.js b/MagicMirror/tests/configs/modules/weather/currentweather_default.js new file mode 100644 index 0000000000000000000000000000000000000000..e2ef16b7647cc71dc835d8066d755d1431ed3ae8 --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/currentweather_default.js @@ -0,0 +1,24 @@ +/* MagicMirror² Test config default weather + * + * By fewieden https://github.com/fewieden + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "weather", + position: "bottom_bar", + config: { + location: "Munich", + mockData: '"#####WEATHERDATA#####"' + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/currentweather_options.js b/MagicMirror/tests/configs/modules/weather/currentweather_options.js new file mode 100644 index 0000000000000000000000000000000000000000..2dd9216327cef6859cac0234e6a5fcb6e09d696c --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/currentweather_options.js @@ -0,0 +1,28 @@ +/* MagicMirror² Test config default weather + * + * By fewieden https://github.com/fewieden + * MIT Licensed. + */ +let config = { + modules: [ + { + module: "weather", + position: "bottom_bar", + config: { + location: "Munich", + mockData: '"#####WEATHERDATA#####"', + windUnits: "beaufort", + showWindDirectionAsArrow: true, + showSun: false, + showHumidity: true, + roundTemp: true, + degreeLabel: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/currentweather_units.js b/MagicMirror/tests/configs/modules/weather/currentweather_units.js new file mode 100644 index 0000000000000000000000000000000000000000..ac007314a5715501969367d79351803c5e58af13 --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/currentweather_units.js @@ -0,0 +1,26 @@ +/* MagicMirror² Test config default weather + * + * By fewieden https://github.com/fewieden + * MIT Licensed. + */ +let config = { + units: "imperial", + + modules: [ + { + module: "weather", + position: "bottom_bar", + config: { + location: "Munich", + mockData: '"#####WEATHERDATA#####"', + decimalSymbol: ",", + showHumidity: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/forecastweather_absolute.js b/MagicMirror/tests/configs/modules/weather/forecastweather_absolute.js new file mode 100644 index 0000000000000000000000000000000000000000..9972383b4e76bd4da297b66c8d442cc4cd2befca --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/forecastweather_absolute.js @@ -0,0 +1,27 @@ +/* MagicMirror² Test config default weather + * + * By fewieden https://github.com/fewieden + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "weather", + position: "bottom_bar", + config: { + type: "forecast", + location: "Munich", + mockData: '"#####WEATHERDATA#####"', + weatherEndpoint: "/forecast/daily", + absoluteDates: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/forecastweather_default.js b/MagicMirror/tests/configs/modules/weather/forecastweather_default.js new file mode 100644 index 0000000000000000000000000000000000000000..6f535b39c384779d9b8438cbde6839444c0b8b2d --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/forecastweather_default.js @@ -0,0 +1,26 @@ +/* MagicMirror² Test config default weather + * + * By fewieden https://github.com/fewieden + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "weather", + position: "bottom_bar", + config: { + type: "forecast", + location: "Munich", + mockData: '"#####WEATHERDATA#####"', + weatherEndpoint: "/forecast/daily" + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/forecastweather_options.js b/MagicMirror/tests/configs/modules/weather/forecastweather_options.js new file mode 100644 index 0000000000000000000000000000000000000000..23cda84f670cc8426e55d7c62c05068efd8899d8 --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/forecastweather_options.js @@ -0,0 +1,29 @@ +/* MagicMirror² Test config default weather + * + * By fewieden https://github.com/fewieden + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "weather", + position: "bottom_bar", + config: { + type: "forecast", + location: "Munich", + mockData: '"#####WEATHERDATA#####"', + weatherEndpoint: "/forecast/daily", + showPrecipitationAmount: true, + colored: true, + tableClass: "myTableClass" + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/forecastweather_units.js b/MagicMirror/tests/configs/modules/weather/forecastweather_units.js new file mode 100644 index 0000000000000000000000000000000000000000..22a9820a1e564e459d72fabb20651f29fbe5106a --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/forecastweather_units.js @@ -0,0 +1,28 @@ +/* MagicMirror² Test config default weather + * + * By rejas + * MIT Licensed. + */ +let config = { + units: "imperial", + + modules: [ + { + module: "weather", + position: "bottom_bar", + config: { + type: "forecast", + location: "Munich", + mockData: '"#####WEATHERDATA#####"', + weatherEndpoint: "/forecast/daily", + decimalSymbol: "_", + showPrecipitationAmount: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/hourlyweather_default.js b/MagicMirror/tests/configs/modules/weather/hourlyweather_default.js new file mode 100644 index 0000000000000000000000000000000000000000..aca5ced8f86704d2a62f184602fe3fba1b6d6ce4 --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/hourlyweather_default.js @@ -0,0 +1,25 @@ +/* MagicMirror² Test config hourly weather + * + * By rejas https://github.com/rejas + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "weather", + position: "bottom_bar", + config: { + type: "hourly", + location: "Berlin", + mockData: '"#####WEATHERDATA#####"' + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/hourlyweather_options.js b/MagicMirror/tests/configs/modules/weather/hourlyweather_options.js new file mode 100644 index 0000000000000000000000000000000000000000..0d73311768d4deb127ff5c86af8ef0e67adc7492 --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/hourlyweather_options.js @@ -0,0 +1,26 @@ +/* MagicMirror² Test config hourly weather options + * + * By rejas https://github.com/rejas + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "weather", + position: "bottom_bar", + config: { + type: "hourly", + location: "Berlin", + mockData: '"#####WEATHERDATA#####"', + hourlyForecastIncrements: 2 + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/modules/weather/hourlyweather_showPrecipitation.js b/MagicMirror/tests/configs/modules/weather/hourlyweather_showPrecipitation.js new file mode 100644 index 0000000000000000000000000000000000000000..4ba579e1d316255f4544b4e3ed2b3864764f9425 --- /dev/null +++ b/MagicMirror/tests/configs/modules/weather/hourlyweather_showPrecipitation.js @@ -0,0 +1,27 @@ +/* MagicMirror² Test config hourly weather + * + * By rejas https://github.com/rejas + * MIT Licensed. + */ +let config = { + timeFormat: 12, + + modules: [ + { + module: "weather", + position: "bottom_bar", + config: { + type: "hourly", + location: "Berlin", + mockData: '"#####WEATHERDATA#####"', + showPrecipitationAmount: true, + showPrecipitationProbability: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/noIpWhiteList.js b/MagicMirror/tests/configs/noIpWhiteList.js new file mode 100644 index 0000000000000000000000000000000000000000..4e26b1a06c2ffd51c8c72bd0851ca9fa805282dd --- /dev/null +++ b/MagicMirror/tests/configs/noIpWhiteList.js @@ -0,0 +1,13 @@ +/* MagicMirror² Test config sample ipWhitelist + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({ + ipWhitelist: ["x.x.x.x"] +}); + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/port_8090.js b/MagicMirror/tests/configs/port_8090.js new file mode 100644 index 0000000000000000000000000000000000000000..25d4bce2248a1cc6b96b1d446b5b55f25f8df282 --- /dev/null +++ b/MagicMirror/tests/configs/port_8090.js @@ -0,0 +1,13 @@ +/* MagicMirror² Test config sample environment set port 8090 + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({ + port: 8090 +}); + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/port_variable.env b/MagicMirror/tests/configs/port_variable.env new file mode 100644 index 0000000000000000000000000000000000000000..2b19af01131dc0510b7e6531a6d4ed50798012bd --- /dev/null +++ b/MagicMirror/tests/configs/port_variable.env @@ -0,0 +1 @@ +MM_PORT=8090 diff --git a/MagicMirror/tests/configs/port_variable.js.template b/MagicMirror/tests/configs/port_variable.js.template new file mode 100644 index 0000000000000000000000000000000000000000..3d8d9791ada1843df933f5cda3c0b1955b1d290d --- /dev/null +++ b/MagicMirror/tests/configs/port_variable.js.template @@ -0,0 +1,13 @@ +/* MagicMirror² Test config sample environment set port 8090 + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = require(process.cwd() + "/tests/configs/default.js").configFactory({ + port: ${MM_PORT} +}); + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/configs/without_modules.js b/MagicMirror/tests/configs/without_modules.js new file mode 100644 index 0000000000000000000000000000000000000000..51ce02f6e0d602ea00a81e819454e5d418ce02a6 --- /dev/null +++ b/MagicMirror/tests/configs/without_modules.js @@ -0,0 +1,13 @@ +/* MagicMirror² Test default config for modules + * + * By Rodrigo RamÃrez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ +let config = { + ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.10.1"] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/MagicMirror/tests/e2e/env_spec.js b/MagicMirror/tests/e2e/env_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..a62ab54448a03b5a165a85cd787d796eb59891de --- /dev/null +++ b/MagicMirror/tests/e2e/env_spec.js @@ -0,0 +1,27 @@ +const helpers = require("./helpers/global-setup"); + +describe("App environment", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/default.js"); + await helpers.getDocument(); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + it("get request from http://localhost:8080 should return 200", async () => { + const res = await helpers.fetch("http://localhost:8080"); + expect(res.status).toBe(200); + }); + + it("get request from http://localhost:8080/nothing should return 404", async () => { + const res = await helpers.fetch("http://localhost:8080/nothing"); + expect(res.status).toBe(404); + }); + + it("should show the title MagicMirror²", async () => { + const elem = await helpers.waitForElement("title"); + expect(elem).not.toBe(null); + expect(elem.textContent).toBe("MagicMirror²"); + }); +}); diff --git a/MagicMirror/tests/e2e/fonts_spec.js b/MagicMirror/tests/e2e/fonts_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..160359ecae80011983447c8723dcde0b764d7a02 --- /dev/null +++ b/MagicMirror/tests/e2e/fonts_spec.js @@ -0,0 +1,28 @@ +const helpers = require("./helpers/global-setup"); + +describe("All font files from roboto.css should be downloadable", () => { + const fontFiles = []; + // Statements below filters out all 'url' lines in the CSS file + const fileContent = require("fs").readFileSync(`${__dirname}/../../fonts/roboto.css`, "utf8"); + const regex = /\burl\(['"]([^'"]+)['"]\)/g; + let match = regex.exec(fileContent); + while (match !== null) { + // Push 1st match group onto fontFiles stack + fontFiles.push(match[1]); + // Find the next one + match = regex.exec(fileContent); + } + + beforeAll(async () => { + await helpers.startApplication("tests/configs/without_modules.js"); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + test.each(fontFiles)("should return 200 HTTP code for file '%s'", async (fontFile) => { + const fontUrl = `http://localhost:8080/fonts/${fontFile}`; + const res = await helpers.fetch(fontUrl); + expect(res.status).toBe(200); + }); +}); diff --git a/MagicMirror/tests/e2e/helpers/basic-auth.js b/MagicMirror/tests/e2e/helpers/basic-auth.js new file mode 100644 index 0000000000000000000000000000000000000000..8307464e23a292f548be26c5946c7aaff5f406e3 --- /dev/null +++ b/MagicMirror/tests/e2e/helpers/basic-auth.js @@ -0,0 +1,29 @@ +const path = require("path"); +const auth = require("express-basic-auth"); +const express = require("express"); +const app = express(); + +const basicAuth = auth({ + realm: "MagicMirror² Area restricted.", + users: { MagicMirror: "CallMeADog" } +}); + +app.use(basicAuth); + +// Set available directories +const directories = ["/tests/configs", "/tests/mocks"]; +const rootPath = path.resolve(`${__dirname}/../../../`); + +for (let directory of directories) { + app.use(directory, express.static(path.resolve(rootPath + directory))); +} + +let server; + +exports.listen = (...args) => { + server = app.listen.apply(app, args); +}; + +exports.close = async () => { + await server.close(); +}; diff --git a/MagicMirror/tests/e2e/helpers/global-setup.js b/MagicMirror/tests/e2e/helpers/global-setup.js new file mode 100644 index 0000000000000000000000000000000000000000..b79c1f1f13189cac742c4f7234968a7303b83c37 --- /dev/null +++ b/MagicMirror/tests/e2e/helpers/global-setup.js @@ -0,0 +1,95 @@ +const jsdom = require("jsdom"); +const corefetch = require("fetch"); + +exports.startApplication = async (configFilename, exec) => { + jest.resetModules(); + if (global.app) { + await this.stopApplication(); + } + // Set config sample for use in test + if (configFilename === "") { + process.env.MM_CONFIG_FILE = "config/config.js"; + } else { + process.env.MM_CONFIG_FILE = configFilename; + } + if (exec) exec; + global.app = require("../../../js/app"); + + return global.app.start(); +}; + +exports.stopApplication = async () => { + if (!global.app) { + return Promise.resolve(); + } + await global.app.stop(); + delete global.app; +}; + +exports.getDocument = () => { + return new Promise((resolve) => { + const url = `http://${config.address || "localhost"}:${config.port || "8080"}`; + jsdom.JSDOM.fromURL(url, { resources: "usable", runScripts: "dangerously" }).then((dom) => { + dom.window.name = "jsdom"; + dom.window.fetch = corefetch; + dom.window.onload = () => { + global.document = dom.window.document; + resolve(); + }; + }); + }); +}; + +exports.waitForElement = (selector, ignoreValue = "") => { + return new Promise((resolve) => { + let oldVal = "dummy12345"; + const interval = setInterval(() => { + const element = document.querySelector(selector); + if (element) { + let newVal = element.textContent; + if (newVal === oldVal) { + clearInterval(interval); + resolve(element); + } else { + if (ignoreValue === "") { + oldVal = newVal; + } else { + if (!newVal.includes(ignoreValue)) oldVal = newVal; + } + } + } + }, 100); + }); +}; + +exports.waitForAllElements = (selector) => { + return new Promise((resolve) => { + let oldVal = 999999; + const interval = setInterval(() => { + const element = document.querySelectorAll(selector); + if (element) { + let newVal = element.length; + if (newVal === oldVal) { + clearInterval(interval); + resolve(element); + } else { + if (newVal !== 0) oldVal = newVal; + } + } + }, 100); + }); +}; + +exports.fetch = (url) => { + return new Promise((resolve) => { + corefetch(url).then((res) => { + resolve(res); + }); + }); +}; + +exports.testMatch = async (element, regex) => { + const elem = await this.waitForElement(element); + expect(elem).not.toBe(null); + expect(elem.textContent).toMatch(regex); +}; diff --git a/MagicMirror/tests/e2e/helpers/mock-console.js b/MagicMirror/tests/e2e/helpers/mock-console.js new file mode 100644 index 0000000000000000000000000000000000000000..3f9909f11af83866c7caff7091a938cd05ef9ccc --- /dev/null +++ b/MagicMirror/tests/e2e/helpers/mock-console.js @@ -0,0 +1,21 @@ +/** + * Suppresses errors concerning web server already shut down. + * + * @param {string} err The error message. + */ +const mockError = (err) => { + if (err.includes("ECONNREFUSED") || err.includes("ECONNRESET") || err.includes("socket hang up") || err.includes("exports is not defined") || err.includes("write EPIPE")) { + jest.fn(); + } else { + console.dir(err); + } +}; + +global.console = { + log: jest.fn(), + dir: console.dir, + error: mockError, + warn: console.warn, + info: jest.fn(), + debug: console.debug +}; diff --git a/MagicMirror/tests/e2e/helpers/weather-functions.js b/MagicMirror/tests/e2e/helpers/weather-functions.js new file mode 100644 index 0000000000000000000000000000000000000000..e28eb9de0247c2660e3e538417696b052a811c1b --- /dev/null +++ b/MagicMirror/tests/e2e/helpers/weather-functions.js @@ -0,0 +1,19 @@ +const { injectMockData } = require("../../utils/weather_mocker"); +const helpers = require("./global-setup"); + +exports.getText = async (element, result) => { + const elem = await helpers.waitForElement(element); + expect(elem).not.toBe(null); + expect( + elem.textContent + .trim() + .replace(/(\r\n|\n|\r)/gm, "") + .replace(/[ ]+/g, " ") + ).toBe(result); +}; + +exports.startApp = async (configFileName, additionalMockData) => { + injectMockData(configFileName, additionalMockData); + await helpers.startApplication(""); + await helpers.getDocument(); +}; diff --git a/MagicMirror/tests/e2e/ipWhitelist_spec.js b/MagicMirror/tests/e2e/ipWhitelist_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4b3d3c0ff2da4bef05227f6382884ac3a4139ee5 --- /dev/null +++ b/MagicMirror/tests/e2e/ipWhitelist_spec.js @@ -0,0 +1,31 @@ +const helpers = require("./helpers/global-setup"); + +describe("ipWhitelist directive configuration", () => { + describe("Set ipWhitelist without access", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/noIpWhiteList.js"); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + it("should return 403", async () => { + const res = await helpers.fetch("http://localhost:8080"); + expect(res.status).toBe(403); + }); + }); + + describe("Set ipWhitelist []", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/empty_ipWhiteList.js"); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + it("should return 200", async () => { + const res = await helpers.fetch("http://localhost:8080"); + expect(res.status).toBe(200); + }); + }); +}); diff --git a/MagicMirror/tests/e2e/modules/alert_spec.js b/MagicMirror/tests/e2e/modules/alert_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..c118e17ef3af55019a75cf2743ec708db5754f36 --- /dev/null +++ b/MagicMirror/tests/e2e/modules/alert_spec.js @@ -0,0 +1,17 @@ +const helpers = require("../helpers/global-setup"); + +describe("Alert module", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/alert/default.js"); + await helpers.getDocument(); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + it("should show the welcome message", async () => { + const elem = await helpers.waitForElement(".ns-box .ns-box-inner .light.bright.small"); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain("Welcome, start was successful!"); + }); +}); diff --git a/MagicMirror/tests/e2e/modules/calendar_spec.js b/MagicMirror/tests/e2e/modules/calendar_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6932f124faabc13ee8ad40256cb804508b59bc18 --- /dev/null +++ b/MagicMirror/tests/e2e/modules/calendar_spec.js @@ -0,0 +1,164 @@ +const helpers = require("../helpers/global-setup"); +const serverBasicAuth = require("../helpers/basic-auth"); + +describe("Calendar module", () => { + /** + * @param {string} element css selector + * @param {string} result expected number + * @param {string} not reverse result + */ + const testElementLength = async (element, result, not) => { + const elem = await helpers.waitForAllElements(element); + expect(elem).not.toBe(null); + if (not === "not") { + expect(elem.length).not.toBe(result); + } else { + expect(elem.length).toBe(result); + } + }; + + const testTextContain = async (element, text) => { + const elem = await helpers.waitForElement(element, "undefinedLoading"); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain(text); + }; + + afterAll(async () => { + await helpers.stopApplication(); + }); + + describe("Default configuration", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/calendar/default.js"); + await helpers.getDocument(); + }); + + it("should show the default maximumEntries of 10", async () => { + await testElementLength(".calendar .event", 10); + }); + + it("should show the default calendar symbol in each event", async () => { + await testElementLength(".calendar .event .fa-calendar-alt", 0, "not"); + }); + }); + + describe("Custom configuration", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/calendar/custom.js"); + await helpers.getDocument(); + }); + + it("should show the custom maximumEntries of 5", async () => { + await testElementLength(".calendar .event", 5); + }); + + it("should show the custom calendar symbol in four events", async () => { + await testElementLength(".calendar .event .fa-birthday-cake", 4); + }); + + it("should show a customEvent calendar symbol in one event", async () => { + await testElementLength(".calendar .event .fa-dice", 1); + }); + + it("should show two custom icons for repeating events", async () => { + await testElementLength(".calendar .event .fa-undo", 2); + }); + + it("should show two custom icons for day events", async () => { + await testElementLength(".calendar .event .fa-calendar-day", 2); + }); + }); + + describe("Recurring event", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/calendar/recurring.js"); + await helpers.getDocument(); + }); + + it("should show the recurring birthday event 6 times", async () => { + await testElementLength(".calendar .event", 6); + }); + }); + + process.setMaxListeners(0); + for (let i = -12; i < 12; i++) { + describe("Recurring event per timezone", () => { + beforeAll(async () => { + Date.prototype.getTimezoneOffset = () => { + return i * 60; + }; + await helpers.startApplication("tests/configs/modules/calendar/recurring.js"); + await helpers.getDocument(); + }); + + it(`should contain text "Mar 25th" in timezone UTC ${-i}`, async () => { + await testTextContain(".calendar", "Mar 25th"); + }); + }); + } + + describe("Changed port", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/calendar/changed-port.js"); + serverBasicAuth.listen(8010); + await helpers.getDocument(); + }); + + afterAll(async () => { + await serverBasicAuth.close(); + }); + + it("should return TestEvents", async () => { + await testElementLength(".calendar .event", 0, "not"); + }); + }); + + describe("Basic auth", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/calendar/basic-auth.js"); + await helpers.getDocument(); + }); + + it("should return TestEvents", async () => { + await testElementLength(".calendar .event", 0, "not"); + }); + }); + + describe("Basic auth by default", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/calendar/auth-default.js"); + await helpers.getDocument(); + }); + + it("should return TestEvents", async () => { + await testElementLength(".calendar .event", 0, "not"); + }); + }); + + describe("Basic auth backward compatibility configuration: DEPRECATED", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/calendar/old-basic-auth.js"); + await helpers.getDocument(); + }); + + it("should return TestEvents", async () => { + await testElementLength(".calendar .event", 0, "not"); + }); + }); + + describe("Fail Basic auth", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/calendar/fail-basic-auth.js"); + serverBasicAuth.listen(8020); + await helpers.getDocument(); + }); + + afterAll(async () => { + await serverBasicAuth.close(); + }); + + it("should show Unauthorized error", async () => { + await testTextContain(".calendar", "Error in the calendar module. Authorization failed"); + }); + }); +}); diff --git a/MagicMirror/tests/e2e/modules/clock_es_spec.js b/MagicMirror/tests/e2e/modules/clock_es_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4bdc4bccf748cb97b6a70926720367c838966b3e --- /dev/null +++ b/MagicMirror/tests/e2e/modules/clock_es_spec.js @@ -0,0 +1,65 @@ +const helpers = require("../helpers/global-setup"); + +describe("Clock set to spanish language module", () => { + afterAll(async () => { + await helpers.stopApplication(); + }); + + describe("with default 24hr clock config", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/es/clock_24hr.js"); + await helpers.getDocument(); + }); + + it("shows date with correct format", async () => { + const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/; + await helpers.testMatch(".clock .date", dateRegex); + }); + + it("shows time in 24hr format", async () => { + const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/; + await helpers.testMatch(".clock .time", timeRegex); + }); + }); + + describe("with default 12hr clock config", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/es/clock_12hr.js"); + await helpers.getDocument(); + }); + + it("shows date with correct format", async () => { + const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/; + await helpers.testMatch(".clock .date", dateRegex); + }); + + it("shows time in 12hr format", async () => { + const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/; + await helpers.testMatch(".clock .time", timeRegex); + }); + }); + + describe("with showPeriodUpper config enabled", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/es/clock_showPeriodUpper.js"); + await helpers.getDocument(); + }); + + it("shows 12hr time with upper case AM/PM", async () => { + const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/; + await helpers.testMatch(".clock .time", timeRegex); + }); + }); + + describe("with showWeek config enabled", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/es/clock_showWeek.js"); + await helpers.getDocument(); + }); + + it("shows week with correct format", async () => { + const weekRegex = /^Semana [0-9]{1,2}$/; + await helpers.testMatch(".clock .week", weekRegex); + }); + }); +}); diff --git a/MagicMirror/tests/e2e/modules/clock_spec.js b/MagicMirror/tests/e2e/modules/clock_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..1ffa74a1bf9225a2a2bb7cf28ded2d30339c37cd --- /dev/null +++ b/MagicMirror/tests/e2e/modules/clock_spec.js @@ -0,0 +1,110 @@ +const moment = require("moment"); +const helpers = require("../helpers/global-setup"); + +describe("Clock module", () => { + afterAll(async () => { + await helpers.stopApplication(); + }); + + describe("with default 24hr clock config", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/clock_24hr.js"); + await helpers.getDocument(); + }); + + it("should show the date in the correct format", async () => { + const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/; + await helpers.testMatch(".clock .date", dateRegex); + }); + + it("should show the time in 24hr format", async () => { + const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/; + await helpers.testMatch(".clock .time", timeRegex); + }); + }); + + describe("with default 12hr clock config", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/clock_12hr.js"); + await helpers.getDocument(); + }); + + it("should show the date in the correct format", async () => { + const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/; + await helpers.testMatch(".clock .date", dateRegex); + }); + + it("should show the time in 12hr format", async () => { + const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/; + await helpers.testMatch(".clock .time", timeRegex); + }); + }); + + describe("with showPeriodUpper config enabled", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/clock_showPeriodUpper.js"); + await helpers.getDocument(); + }); + + it("should show 12hr time with upper case AM/PM", async () => { + const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/; + await helpers.testMatch(".clock .time", timeRegex); + }); + }); + + describe("with displaySeconds config disabled", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/clock_displaySeconds_false.js"); + await helpers.getDocument(); + }); + + it("should show 12hr time without seconds am/pm", async () => { + const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/; + await helpers.testMatch(".clock .time", timeRegex); + }); + }); + + describe("with showTime config disabled", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/clock_showTime.js"); + await helpers.getDocument(); + }); + + it("should not show the time when digital clock is shown", async () => { + const elem = await document.querySelector(".clock .digital .time"); + expect(elem).toBe(null); + }); + }); + + describe("with showWeek config enabled", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/clock_showWeek.js"); + await helpers.getDocument(); + }); + + it("should show the week in the correct format", async () => { + const weekRegex = /^Week [0-9]{1,2}$/; + await helpers.testMatch(".clock .week", weekRegex); + }); + + it("should show the week with the correct number of week of year", async () => { + const currentWeekNumber = moment().week(); + const weekToShow = `Week ${currentWeekNumber}`; + const elem = await helpers.waitForElement(".clock .week"); + expect(elem).not.toBe(null); + expect(elem.textContent).toBe(weekToShow); + }); + }); + + describe("with analog clock face enabled", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/clock/clock_analog.js"); + await helpers.getDocument(); + }); + + it("should show the analog clock face", async () => { + const elem = helpers.waitForElement(".clock-circle"); + expect(elem).not.toBe(null); + }); + }); +}); diff --git a/MagicMirror/tests/e2e/modules/compliments_spec.js b/MagicMirror/tests/e2e/modules/compliments_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..52d232e5dc822c54fcfce8e6a0426927e6c04b6c --- /dev/null +++ b/MagicMirror/tests/e2e/modules/compliments_spec.js @@ -0,0 +1,55 @@ +const helpers = require("../helpers/global-setup"); + +describe("Compliments module", () => { + /** + * move similar tests in function doTest + * + * @param {Array} complimentsArray The array of compliments. + */ + const doTest = async (complimentsArray) => { + let elem = await helpers.waitForElement(".compliments"); + expect(elem).not.toBe(null); + elem = await helpers.waitForElement(".module-content"); + expect(elem).not.toBe(null); + expect(complimentsArray).toContain(elem.textContent); + }; + + afterAll(async () => { + await helpers.stopApplication(); + }); + + describe("Feature anytime in compliments module", () => { + describe("Set anytime and empty compliments for morning, evening and afternoon ", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/compliments/compliments_anytime.js"); + await helpers.getDocument(); + }); + + it("Show anytime because if configure empty parts of day compliments and set anytime compliments", async () => { + await doTest(["Anytime here"]); + }); + }); + + describe("Only anytime present in configuration compliments", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/compliments/compliments_only_anytime.js"); + await helpers.getDocument(); + }); + + it("Show anytime compliments", async () => { + await doTest(["Anytime here"]); + }); + }); + }); + + describe("remoteFile option", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/compliments/compliments_remote.js"); + await helpers.getDocument(); + }); + + it("should show compliments from a remote file", async () => { + await doTest(["Remote compliment file works!"]); + }); + }); +}); diff --git a/MagicMirror/tests/e2e/modules/helloworld_spec.js b/MagicMirror/tests/e2e/modules/helloworld_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ecdcdf20767bea4f531628e6111e3b2b5d8cda6f --- /dev/null +++ b/MagicMirror/tests/e2e/modules/helloworld_spec.js @@ -0,0 +1,33 @@ +const helpers = require("../helpers/global-setup"); + +describe("Test helloworld module", () => { + afterAll(async () => { + await helpers.stopApplication(); + }); + + describe("helloworld set config text", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/helloworld/helloworld.js"); + await helpers.getDocument(); + }); + + it("Test message helloworld module", async () => { + const elem = await helpers.waitForElement(".helloworld"); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain("Test HelloWorld Module"); + }); + }); + + describe("helloworld default config text", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/helloworld/helloworld_default.js"); + await helpers.getDocument(); + }); + + it("Test message helloworld module", async () => { + const elem = await helpers.waitForElement(".helloworld"); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain("Hello World!"); + }); + }); +}); diff --git a/MagicMirror/tests/e2e/modules/newsfeed_spec.js b/MagicMirror/tests/e2e/modules/newsfeed_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..bf3066e753ac5372805c79b7222298c09ed9a8ef --- /dev/null +++ b/MagicMirror/tests/e2e/modules/newsfeed_spec.js @@ -0,0 +1,77 @@ +const helpers = require("../helpers/global-setup"); + +describe("Newsfeed module", () => { + afterAll(async () => { + await helpers.stopApplication(); + }); + + describe("Default configuration", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/newsfeed/default.js"); + await helpers.getDocument(); + }); + + it("should show the newsfeed title", async () => { + const elem = await helpers.waitForElement(".newsfeed .newsfeed-source"); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain("Rodrigo Ramirez Blog"); + }); + + it("should show the newsfeed article", async () => { + const elem = await helpers.waitForElement(".newsfeed .newsfeed-title"); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain("QPanel"); + }); + + it("should NOT show the newsfeed description", async () => { + await helpers.waitForElement(".newsfeed"); + const element = document.querySelector(".newsfeed .newsfeed-desc"); + expect(element).toBe(null); + }); + }); + + describe("Custom configuration", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/newsfeed/prohibited_words.js"); + await helpers.getDocument(); + }); + + it("should not show articles with prohibited words", async () => { + const elem = await helpers.waitForElement(".newsfeed .newsfeed-title"); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain("Problema VirtualBox"); + }); + + it("should show the newsfeed description", async () => { + const elem = await helpers.waitForElement(".newsfeed .newsfeed-desc"); + expect(elem).not.toBe(null); + expect(elem.textContent.length).not.toBe(0); + }); + }); + + describe("Invalid configuration", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/newsfeed/incorrect_url.js"); + await helpers.getDocument(); + }); + + it("should show malformed url warning", async () => { + const elem = await helpers.waitForElement(".newsfeed .small", "No news at the moment."); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain("Error in the Newsfeed module. Malformed url."); + }); + }); + + describe("Ignore items", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/newsfeed/ignore_items.js"); + await helpers.getDocument(); + }); + + it("should show empty items info message", async () => { + const elem = await helpers.waitForElement(".newsfeed .small"); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain("No news at the moment."); + }); + }); +}); diff --git a/MagicMirror/tests/e2e/modules/weather_current_spec.js b/MagicMirror/tests/e2e/modules/weather_current_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6cea8e152f6f4b6c8db265d3bb53b26880b21838 --- /dev/null +++ b/MagicMirror/tests/e2e/modules/weather_current_spec.js @@ -0,0 +1,84 @@ +const helpers = require("../helpers/global-setup"); +const weatherFunc = require("../helpers/weather-functions"); + +describe("Weather module", () => { + afterAll(async () => { + await helpers.stopApplication(); + }); + + describe("Current weather", () => { + describe("Default configuration", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/currentweather_default.js", {}); + }); + + it("should render wind speed and wind direction", async () => { + await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "12 WSW"); + }); + + it("should render temperature with icon", async () => { + await weatherFunc.getText(".weather .large.light span.bright", "1.5°"); + }); + + it("should render feels like temperature", async () => { + await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -5.6°"); + }); + }); + }); + + describe("Compliments Integration", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/currentweather_compliments.js", {}); + }); + + it("should render a compliment based on the current weather", async () => { + await weatherFunc.getText(".compliments .module-content span", "snow"); + }); + }); + + describe("Configuration Options", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/currentweather_options.js", {}); + }); + + it("should render windUnits in beaufort", async () => { + await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "6"); + }); + + it("should render windDirection with an arrow", async () => { + const elem = await helpers.waitForElement(".weather .normal.medium sup i.fa-long-arrow-alt-down"); + expect(elem).not.toBe(null); + expect(elem.outerHTML).toContain("transform:rotate(250deg);"); + }); + + it("should render humidity", async () => { + await weatherFunc.getText(".weather .normal.medium span:nth-child(3)", "93.7"); + }); + + it("should render degreeLabel for temp", async () => { + await weatherFunc.getText(".weather .large.light span.bright", "1°C"); + }); + + it("should render degreeLabel for feels like", async () => { + await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -6°C"); + }); + }); + + describe("Current weather with imperial units", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/currentweather_units.js", {}); + }); + + it("should render wind in imperial units", async () => { + await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "26 WSW"); + }); + + it("should render temperatures in fahrenheit", async () => { + await weatherFunc.getText(".weather .large.light span.bright", "34,7°"); + }); + + it("should render 'feels like' in fahrenheit", async () => { + await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like 21,9°"); + }); + }); +}); diff --git a/MagicMirror/tests/e2e/modules/weather_forecast_spec.js b/MagicMirror/tests/e2e/modules/weather_forecast_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..2f10692276a5f4c6c897e0c866cacae4e842ab72 --- /dev/null +++ b/MagicMirror/tests/e2e/modules/weather_forecast_spec.js @@ -0,0 +1,118 @@ +const helpers = require("../helpers/global-setup"); +const weatherFunc = require("../helpers/weather-functions"); + +describe("Weather module: Weather Forecast", () => { + afterAll(async () => { + await helpers.stopApplication(); + }); + + describe("Default configuration", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/forecastweather_default.js", {}); + }); + + const days = ["Today", "Tomorrow", "Sun", "Mon", "Tue"]; + for (const [index, day] of days.entries()) { + it(`should render day ${day}`, async () => { + await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`, day); + }); + } + + const icons = ["day-cloudy", "rain", "day-sunny", "day-sunny", "day-sunny"]; + for (const [index, icon] of icons.entries()) { + it(`should render icon ${icon}`, async () => { + const elem = await helpers.waitForElement(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(2) span.wi-${icon}`); + expect(elem).not.toBe(null); + }); + } + + const maxTemps = ["24.4°", "21.0°", "22.9°", "23.4°", "20.6°"]; + for (const [index, temp] of maxTemps.entries()) { + it(`should render max temperature ${temp}`, async () => { + await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp); + }); + } + + const minTemps = ["15.3°", "13.6°", "13.8°", "13.9°", "10.9°"]; + for (const [index, temp] of minTemps.entries()) { + it(`should render min temperature ${temp}`, async () => { + await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(4)`, temp); + }); + } + + const opacities = [1, 1, 0.8, 0.5333333333333333, 0.2666666666666667]; + for (const [index, opacity] of opacities.entries()) { + it(`should render fading of rows with opacity=${opacity}`, async () => { + const elem = await helpers.waitForElement(`.weather table.small tr:nth-child(${index + 1})`); + expect(elem).not.toBe(null); + expect(elem.outerHTML).toContain(`<tr style="opacity: ${opacity};">`); + }); + } + }); + + describe("Absolute configuration", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/forecastweather_absolute.js", {}); + }); + + const days = ["Fri", "Sat", "Sun", "Mon", "Tue"]; + for (const [index, day] of days.entries()) { + it(`should render day ${day}`, async () => { + await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`, day); + }); + } + }); + + describe("Configuration Options", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/forecastweather_options.js", {}); + }); + + it("should render custom table class", async () => { + const elem = await helpers.waitForElement(".weather table.myTableClass"); + expect(elem).not.toBe(null); + }); + + it("should render colored rows", async () => { + const table = await helpers.waitForElement(".weather table.myTableClass"); + expect(table).not.toBe(null); + expect(table.rows).not.toBe(null); + expect(table.rows.length).toBe(5); + }); + + const precipitations = [undefined, "2.51 mm"]; + for (const [index, precipitation] of precipitations.entries()) { + if (precipitation) { + it(`should render precipitation amount ${precipitation}`, async () => { + await weatherFunc.getText(`.weather table tr:nth-child(${index + 1}) td.precipitation-amount`, precipitation); + }); + } + } + }); + + describe("Forecast weather with imperial units", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/forecastweather_units.js", {}); + }); + + describe("Temperature units", () => { + const temperatures = ["75_9°", "69_8°", "73_2°", "74_1°", "69_1°"]; + for (const [index, temp] of temperatures.entries()) { + it(`should render custom decimalSymbol = '_' for temp ${temp}`, async () => { + await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp); + }); + } + }); + + describe("Precipitation units", () => { + const precipitations = [undefined, "0.10 in"]; + for (const [index, precipitation] of precipitations.entries()) { + if (precipitation) { + it(`should render precipitation amount ${precipitation}`, async () => { + await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td.precipitation-amount`, precipitation); + }); + } + } + }); + }); +}); diff --git a/MagicMirror/tests/e2e/modules/weather_hourly_spec.js b/MagicMirror/tests/e2e/modules/weather_hourly_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..3a5f03f138c951968ec8c0d457352370644b0bbc --- /dev/null +++ b/MagicMirror/tests/e2e/modules/weather_hourly_spec.js @@ -0,0 +1,64 @@ +const helpers = require("../helpers/global-setup"); +const weatherFunc = require("../helpers/weather-functions"); + +describe("Weather module: Weather Hourly Forecast", () => { + afterAll(async () => { + await helpers.stopApplication(); + }); + + describe("Default configuration", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/hourlyweather_default.js", {}); + }); + + const minTemps = ["7:00 pm", "8:00 pm", "9:00 pm", "10:00 pm", "11:00 pm"]; + for (const [index, hour] of minTemps.entries()) { + it(`should render forecast for hour ${hour}`, async () => { + await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td.day`, hour); + }); + } + }); + + describe("Hourly weather options", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/hourlyweather_options.js", {}); + }); + + describe("Hourly increments of 2", () => { + const minTemps = ["7:00 pm", "9:00 pm", "11:00 pm", "1:00 am", "3:00 am"]; + for (const [index, hour] of minTemps.entries()) { + it(`should render forecast for hour ${hour}`, async () => { + await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td.day`, hour); + }); + } + }); + }); + + describe("Show precipitations", () => { + beforeAll(async () => { + await weatherFunc.startApp("tests/configs/modules/weather/hourlyweather_showPrecipitation.js", {}); + }); + + describe("Shows precipitation amount", () => { + const amounts = [undefined, undefined, undefined, "0.13 mm", "0.13 mm"]; + for (const [index, amount] of amounts.entries()) { + if (amount) { + it(`should render precipitation amount ${amount}`, async () => { + await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td.precipitation-amount`, amount); + }); + } + } + }); + + describe("Shows precipitation probability", () => { + const propabilities = [undefined, undefined, "12 %", "36 %", "44 %"]; + for (const [index, pop] of propabilities.entries()) { + if (pop) { + it(`should render probability ${pop}`, async () => { + await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td.precipitation-prob`, pop); + }); + } + } + }); + }); +}); diff --git a/MagicMirror/tests/e2e/modules_display_spec.js b/MagicMirror/tests/e2e/modules_display_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..15520d05c97625ba282959d64c48036edbfb1ea2 --- /dev/null +++ b/MagicMirror/tests/e2e/modules_display_spec.js @@ -0,0 +1,24 @@ +const helpers = require("./helpers/global-setup"); + +describe("Display of modules", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/display.js"); + await helpers.getDocument(); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + it("should show the test header", async () => { + const elem = await helpers.waitForElement("#module_0_helloworld .module-header"); + expect(elem).not.toBe(null); + // textContent gibt hier lowercase zurück, das uppercase wird durch css realisiert, was daher nicht in textContent landet + expect(elem.textContent).toBe("test_header"); + }); + + it("should show no header if no header text is specified", async () => { + const elem = await helpers.waitForElement("#module_1_helloworld .module-header"); + expect(elem).not.toBe(null); + expect(elem.textContent).toBe("undefined"); + }); +}); diff --git a/MagicMirror/tests/e2e/modules_empty_spec.js b/MagicMirror/tests/e2e/modules_empty_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ddd08e821ecfb50b77a29148fd8819a03c7efd19 --- /dev/null +++ b/MagicMirror/tests/e2e/modules_empty_spec.js @@ -0,0 +1,23 @@ +const helpers = require("./helpers/global-setup"); + +describe("Check configuration without modules", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/without_modules.js"); + await helpers.getDocument(); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + it("Show the message MagicMirror² title", async () => { + const elem = await helpers.waitForElement("#module_1_helloworld .module-content"); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain("MagicMirror²"); + }); + + it("Show the url of michael's website", async () => { + const elem = await helpers.waitForElement("#module_5_helloworld .module-content"); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain("www.michaelteeuw.nl"); + }); +}); diff --git a/MagicMirror/tests/e2e/modules_position_spec.js b/MagicMirror/tests/e2e/modules_position_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..b23ebeb24e4ed6a5a11230350fa38db6a13636eb --- /dev/null +++ b/MagicMirror/tests/e2e/modules_position_spec.js @@ -0,0 +1,22 @@ +const helpers = require("./helpers/global-setup"); + +describe("Position of modules", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/modules/positions.js"); + await helpers.getDocument(); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + const positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"]; + + for (const position of positions) { + const className = position.replace("_", "."); + it(`should show text in ${position}`, async () => { + const elem = await helpers.waitForElement(`.${className}`); + expect(elem).not.toBe(null); + expect(elem.textContent).toContain(`Text in ${position}`); + }); + } +}); diff --git a/MagicMirror/tests/e2e/port_spec.js b/MagicMirror/tests/e2e/port_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..104b9373dc4211c4ab39a1ba7202463684941ac7 --- /dev/null +++ b/MagicMirror/tests/e2e/port_spec.js @@ -0,0 +1,31 @@ +const helpers = require("./helpers/global-setup"); + +describe("port directive configuration", () => { + describe("Set port 8090", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/port_8090.js"); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + it("should return 200", async () => { + const res = await helpers.fetch("http://localhost:8090"); + expect(res.status).toBe(200); + }); + }); + + describe("Set port 8100 on environment variable MM_PORT", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/port_8090.js", (process.env.MM_PORT = 8100)); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + it("should return 200", async () => { + const res = await helpers.fetch("http://localhost:8100"); + expect(res.status).toBe(200); + }); + }); +}); diff --git a/MagicMirror/tests/e2e/template_spec.js b/MagicMirror/tests/e2e/template_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..0c706c1cc54e0e517c219b274ab511d1fbdce6b2 --- /dev/null +++ b/MagicMirror/tests/e2e/template_spec.js @@ -0,0 +1,15 @@ +const helpers = require("./helpers/global-setup"); + +describe("templated config with port variable", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/port_variable.js"); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + it("should return 200", async () => { + const res = await helpers.fetch("http://localhost:8090"); + expect(res.status).toBe(200); + }); +}); diff --git a/MagicMirror/tests/e2e/translations_spec.js b/MagicMirror/tests/e2e/translations_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ec1387572f9397c5475070954edec6c295fb551f --- /dev/null +++ b/MagicMirror/tests/e2e/translations_spec.js @@ -0,0 +1,218 @@ +const fs = require("fs"); +const path = require("path"); +const helmet = require("helmet"); +const { JSDOM } = require("jsdom"); +const express = require("express"); +const sinon = require("sinon"); +const translations = require("../../translations/translations"); + +describe("Translations", () => { + let server; + + beforeAll(() => { + const app = express(); + app.use(helmet()); + app.use((req, res, next) => { + res.header("Access-Control-Allow-Origin", "*"); + next(); + }); + app.use("/translations", express.static(path.join(__dirname, "..", "..", "translations"))); + + server = app.listen(3000); + }); + + afterAll(async () => { + await server.close(); + }); + + it("should have a translation file in the specified path", () => { + for (let language in translations) { + const file = fs.statSync(translations[language]); + expect(file.isFile()).toBe(true); + } + }); + + describe("loadTranslations", () => { + let dom; + + beforeEach(() => { + dom = new JSDOM( + `<script>var Translator = {}; var Log = {log: () => {}}; var config = {language: 'de'};</script>\ + <script src="file://${path.join(__dirname, "..", "..", "js", "class.js")}"></script>\ + <script src="file://${path.join(__dirname, "..", "..", "js", "module.js")}"></script>`, + { runScripts: "dangerously", resources: "usable" } + ); + }); + + it("should load translation file", (done) => { + dom.window.onload = async () => { + const { Translator, Module, config } = dom.window; + config.language = "en"; + Translator.load = sinon.stub().callsFake((_m, _f, _fb) => null); + + Module.register("name", { getTranslations: () => translations }); + const MMM = Module.create("name"); + + await MMM.loadTranslations(); + + expect(Translator.load.args.length).toBe(1); + expect(Translator.load.calledWith(MMM, "translations/en.json", false)).toBe(true); + + done(); + }; + }); + + it("should load translation + fallback file", (done) => { + dom.window.onload = async () => { + const { Translator, Module } = dom.window; + Translator.load = sinon.stub().callsFake((_m, _f, _fb) => null); + + Module.register("name", { getTranslations: () => translations }); + const MMM = Module.create("name"); + + await MMM.loadTranslations(); + + expect(Translator.load.args.length).toBe(2); + expect(Translator.load.calledWith(MMM, "translations/de.json", false)).toBe(true); + expect(Translator.load.calledWith(MMM, "translations/en.json", true)).toBe(true); + + done(); + }; + }); + + it("should load translation fallback file", (done) => { + dom.window.onload = async () => { + const { Translator, Module, config } = dom.window; + config.language = "--"; + Translator.load = sinon.stub().callsFake((_m, _f, _fb) => null); + + Module.register("name", { getTranslations: () => translations }); + const MMM = Module.create("name"); + + await MMM.loadTranslations(); + + expect(Translator.load.args.length).toBe(1); + expect(Translator.load.calledWith(MMM, "translations/en.json", true)).toBe(true); + + done(); + }; + }); + + it("should load no file", (done) => { + dom.window.onload = async () => { + const { Translator, Module } = dom.window; + Translator.load = sinon.stub(); + + Module.register("name", {}); + const MMM = Module.create("name"); + + await MMM.loadTranslations(); + + expect(Translator.load.callCount).toBe(0); + + done(); + }; + }); + }); + + const mmm = { + name: "TranslationTest", + file(file) { + return `http://localhost:3000/${file}`; + } + }; + + describe("Parsing language files through the Translator class", () => { + for (let language in translations) { + it(`should parse ${language}`, (done) => { + const dom = new JSDOM( + `<script>var translations = ${JSON.stringify(translations)}; var Log = {log: () => {}};</script>\ + <script src="file://${path.join(__dirname, "..", "..", "js", "translator.js")}">`, + { runScripts: "dangerously", resources: "usable" } + ); + dom.window.onload = async () => { + const { Translator } = dom.window; + + await Translator.load(mmm, translations[language], false); + expect(typeof Translator.translations[mmm.name]).toBe("object"); + expect(Object.keys(Translator.translations[mmm.name]).length).toBeGreaterThanOrEqual(1); + done(); + }; + }); + } + }); + + describe("Same keys", () => { + let base; + let missing = []; + + beforeAll((done) => { + const dom = new JSDOM( + `<script>var translations = ${JSON.stringify(translations)}; var Log = {log: () => {}};</script>\ + <script src="file://${path.join(__dirname, "..", "..", "js", "translator.js")}">`, + { runScripts: "dangerously", resources: "usable" } + ); + dom.window.onload = async () => { + const { Translator } = dom.window; + + await Translator.load(mmm, translations.de, false); + base = Object.keys(Translator.translations[mmm.name]).sort(); + done(); + }; + }); + + afterAll(() => { + console.log(missing); + }); + + // Using German as the base rather than English, since + // at least one translated word doesn't exist in English. + for (let language in translations) { + if (language === "de") { + continue; + } + + describe(`Translation keys of ${language}`, () => { + let keys; + + beforeAll((done) => { + const dom = new JSDOM( + `<script>var translations = ${JSON.stringify(translations)}; var Log = {log: () => {}};</script>\ + <script src="file://${path.join(__dirname, "..", "..", "js", "translator.js")}">`, + { runScripts: "dangerously", resources: "usable" } + ); + dom.window.onload = async () => { + const { Translator } = dom.window; + + await Translator.load(mmm, translations[language], false); + keys = Object.keys(Translator.translations[mmm.name]).sort(); + done(); + }; + }); + + it(`${language} keys should be in base`, () => { + keys.forEach((key) => { + expect(base.indexOf(key)).toBeGreaterThanOrEqual(0); + }); + }); + + it(`${language} should contain all base keys`, () => { + // TODO: when all translations are fixed, use + // expect(keys).toEqual(base); + // instead of the try-catch-block + + try { + expect(keys).toEqual(base); + } catch (e) { + if (e.message.match(/expect.*toEqual/)) { + const diff = base.filter((key) => !keys.includes(key)); + missing.push(`Missing Translations for language ${language}: ${diff}`); + } else { + throw e; + } + } + }); + }); + } + }); +}); diff --git a/MagicMirror/tests/e2e/vendor_spec.js b/MagicMirror/tests/e2e/vendor_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..dff9585810e7bebd5162e8c441a32864467ad6b8 --- /dev/null +++ b/MagicMirror/tests/e2e/vendor_spec.js @@ -0,0 +1,30 @@ +const helpers = require("./helpers/global-setup"); + +describe("Vendors", () => { + beforeAll(async () => { + await helpers.startApplication("tests/configs/default.js"); + }); + afterAll(async () => { + await helpers.stopApplication(); + }); + + describe("Get list vendors", () => { + const vendors = require(`${__dirname}/../../vendor/vendor.js`); + + Object.keys(vendors).forEach((vendor) => { + it(`should return 200 HTTP code for vendor "${vendor}"`, async () => { + const urlVendor = `http://localhost:8080/vendor/${vendors[vendor]}`; + const res = await helpers.fetch(urlVendor); + expect(res.status).toBe(200); + }); + }); + + Object.keys(vendors).forEach((vendor) => { + it(`should return 404 HTTP code for vendor https://localhost/"${vendor}"`, async () => { + const urlVendor = `http://localhost:8080/${vendors[vendor]}`; + const res = await helpers.fetch(urlVendor); + expect(res.status).toBe(404); + }); + }); + }); +}); diff --git a/MagicMirror/tests/electron/env_spec.js b/MagicMirror/tests/electron/env_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..32b9f6fc3d185dc4f254f7b06c88d56588c06979 --- /dev/null +++ b/MagicMirror/tests/electron/env_spec.js @@ -0,0 +1,37 @@ +const events = require("events"); +const helpers = require("./helpers/global-setup"); + +describe("Electron app environment", () => { + beforeEach(async () => { + await helpers.startApplication("tests/configs/modules/display.js"); + }); + + afterEach(async () => { + await helpers.stopApplication(); + }); + + it("should open browserwindow", async () => { + const module = await helpers.getElement("#module_0_helloworld"); + expect(await module.textContent()).toContain("Test Display Header"); + expect(global.electronApp.windows().length).toBe(1); + }); +}); + +describe("Development console tests", () => { + beforeEach(async () => { + await helpers.startApplication("tests/configs/modules/display.js", null, ["js/electron.js", "dev"]); + }); + + afterEach(async () => { + await helpers.stopApplication(); + }); + + it("should open browserwindow and dev console", async () => { + while (global.electronApp.windows().length < 2) await events.once(global.electronApp, "window"); + const pageArray = await global.electronApp.windows(); + expect(pageArray.length).toBe(2); + for (const page of pageArray) { + expect(["MagicMirror²", "DevTools"]).toContain(await page.title()); + } + }); +}); diff --git a/MagicMirror/tests/electron/helpers/global-setup.js b/MagicMirror/tests/electron/helpers/global-setup.js new file mode 100644 index 0000000000000000000000000000000000000000..f6e071be7256b4523d647024182c8a1e882f92dc --- /dev/null +++ b/MagicMirror/tests/electron/helpers/global-setup.js @@ -0,0 +1,45 @@ +// see https://playwright.dev/docs/api/class-electronapplication +// https://github.com/microsoft/playwright/issues/6347#issuecomment-1085850728 +// https://www.anycodings.com/1questions/958135/can-i-set-the-date-for-playwright-browser +const { _electron: electron } = require("playwright"); + +exports.startApplication = async (configFilename, systemDate = null, electronParams = ["js/electron.js"]) => { + global.electronApp = null; + global.page = null; + process.env.MM_CONFIG_FILE = configFilename; + process.env.TZ = "GMT"; + global.electronApp = await electron.launch({ args: electronParams }); + + await global.electronApp.firstWindow(); + + for (const win of global.electronApp.windows()) { + const title = await win.title(); + expect(["MagicMirror²", "DevTools"]).toContain(title); + if (title === "MagicMirror²") { + global.page = win; + if (systemDate) { + await global.page.evaluate((systemDate) => { + Date.now = () => { + return new Date(systemDate); + }; + }, systemDate); + } + } + } +}; + +exports.stopApplication = async () => { + if (global.electronApp) { + await global.electronApp.close(); + } + global.electronApp = null; + global.page = null; +}; + +exports.getElement = async (selector) => { + expect(global.page); + let elem = global.page.locator(selector); + await elem.waitFor(); + expect(elem).not.toBe(null); + return elem; +}; diff --git a/MagicMirror/tests/electron/helpers/weather-setup.js b/MagicMirror/tests/electron/helpers/weather-setup.js new file mode 100644 index 0000000000000000000000000000000000000000..4dd3cdb2a6e415a1dde18ec6069cefa122f6d424 --- /dev/null +++ b/MagicMirror/tests/electron/helpers/weather-setup.js @@ -0,0 +1,19 @@ +const { injectMockData } = require("../../utils/weather_mocker"); +const helpers = require("./global-setup"); + +exports.getText = async (element, result) => { + const elem = await helpers.getElement(element); + await expect(elem).not.toBe(null); + const text = await elem.textContent(); + await expect( + text + .trim() + .replace(/(\r\n|\n|\r)/gm, "") + .replace(/[ ]+/g, " ") + ).toBe(result); +}; + +exports.startApp = async (configFileNameName, systemDate) => { + injectMockData(configFileNameName); + await helpers.startApplication("", systemDate); +}; diff --git a/MagicMirror/tests/electron/modules/calendar_spec.js b/MagicMirror/tests/electron/modules/calendar_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..cacc550da50257e1aaa3690bd11303728c567a3c --- /dev/null +++ b/MagicMirror/tests/electron/modules/calendar_spec.js @@ -0,0 +1,44 @@ +const helpers = require("../helpers/global-setup"); + +describe("Calendar module", () => { + /** + * move similar tests in function doTest + * + * @param {string} cssClass css selector + */ + const doTest = async (cssClass) => { + let elem = await helpers.getElement(`.calendar .module-content .event${cssClass}`); + expect(await elem.isVisible()).toBe(true); + }; + + afterEach(async () => { + await helpers.stopApplication(); + }); + + describe("Test css classes", () => { + it("has css class dayBeforeYesterday", async () => { + await helpers.startApplication("tests/configs/modules/calendar/custom.js", "03 Jan 2030 12:30:00 GMT"); + await doTest(".dayBeforeYesterday"); + }); + + it("has css class yesterday", async () => { + await helpers.startApplication("tests/configs/modules/calendar/custom.js", "02 Jan 2030 12:30:00 GMT"); + await doTest(".yesterday"); + }); + + it("has css class today", async () => { + await helpers.startApplication("tests/configs/modules/calendar/custom.js", "01 Jan 2030 12:30:00 GMT"); + await doTest(".today"); + }); + + it("has css class tomorrow", async () => { + await helpers.startApplication("tests/configs/modules/calendar/custom.js", "31 Dec 2029 12:30:00 GMT"); + await doTest(".tomorrow"); + }); + + it("has css class dayAfterTomorrow", async () => { + await helpers.startApplication("tests/configs/modules/calendar/custom.js", "30 Dec 2029 12:30:00 GMT"); + await doTest(".dayAfterTomorrow"); + }); + }); +}); diff --git a/MagicMirror/tests/electron/modules/compliments_spec.js b/MagicMirror/tests/electron/modules/compliments_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..3afa83de83b21f5af88ae8d97e40aba68ba8ef99 --- /dev/null +++ b/MagicMirror/tests/electron/modules/compliments_spec.js @@ -0,0 +1,45 @@ +const helpers = require("../helpers/global-setup"); + +describe("Compliments module", () => { + /** + * move similar tests in function doTest + * + * @param {Array} complimentsArray The array of compliments. + */ + const doTest = async (complimentsArray) => { + await helpers.getElement(".compliments"); + const elem = await helpers.getElement(".module-content"); + expect(elem).not.toBe(null); + expect(complimentsArray).toContain(await elem.textContent()); + }; + + afterEach(async () => { + await helpers.stopApplication(); + }); + + describe("parts of days", () => { + it("Morning compliments for that part of day", async () => { + await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js", "01 Oct 2022 10:00:00 GMT"); + await doTest(["Hi", "Good Morning", "Morning test"]); + }); + + it("Afternoon show Compliments for that part of day", async () => { + await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js", "01 Oct 2022 15:00:00 GMT"); + await doTest(["Hello", "Good Afternoon", "Afternoon test"]); + }); + + it("Evening show Compliments for that part of day", async () => { + await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js", "01 Oct 2022 20:00:00 GMT"); + await doTest(["Hello There", "Good Evening", "Evening test"]); + }); + }); + + describe("Feature date in compliments module", () => { + describe("Set date and empty compliments for anytime, morning, evening and afternoon", () => { + it("Show happy new year compliment on new years day", async () => { + await helpers.startApplication("tests/configs/modules/compliments/compliments_date.js", "01 Jan 2022 10:00:00 GMT"); + await doTest(["Happy new year!"]); + }); + }); + }); +}); diff --git a/MagicMirror/tests/electron/modules/weather_spec.js b/MagicMirror/tests/electron/modules/weather_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..fe7774311075abf1751dcbb07efde546eb0ac5fc --- /dev/null +++ b/MagicMirror/tests/electron/modules/weather_spec.js @@ -0,0 +1,28 @@ +const helpers = require("../helpers/global-setup"); +const weatherHelper = require("../helpers/weather-setup"); + +describe("Weather module", () => { + afterEach(async () => { + await helpers.stopApplication(); + }); + + describe("Current weather with sunrise", () => { + beforeAll(async () => { + await weatherHelper.startApp("tests/configs/modules/weather/currentweather_default.js", "13 Jan 2019 00:30:00 GMT"); + }); + + it("should render sunrise", async () => { + await weatherHelper.getText(".weather .normal.medium span:nth-child(4)", "7:00 am"); + }); + }); + + describe("Current weather with sunset", () => { + beforeAll(async () => { + await weatherHelper.startApp("tests/configs/modules/weather/currentweather_default.js", "13 Jan 2019 12:30:00 GMT"); + }); + + it("should render sunset", async () => { + await weatherHelper.getText(".weather .normal.medium span:nth-child(4)", "3:45 pm"); + }); + }); +}); diff --git a/MagicMirror/tests/mocks/calendar_test.ics b/MagicMirror/tests/mocks/calendar_test.ics new file mode 100644 index 0000000000000000000000000000000000000000..63e001ceb39bacc13acec614e1cec37482b83940 --- /dev/null +++ b/MagicMirror/tests/mocks/calendar_test.ics @@ -0,0 +1,190 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:MagicMirrorTest +X-WR-TIMEZONE:America/Santiago +X-WR-CALDESC:Testing propose MagicMirror +BEGIN:VTIMEZONE +TZID:America/Santiago +X-LIC-LOCATION:America/Santiago +BEGIN:STANDARD +TZOFFSETFROM:-0300 +TZOFFSETTO:-0400 +TZNAME:-04 +DTSTART:19700510T000000 +RDATE:19700510T030000 +RDATE:19710509T030000 +RDATE:19720514T030000 +RDATE:19730513T030000 +RDATE:19740512T030000 +RDATE:19750511T030000 +RDATE:19760509T030000 +RDATE:19770515T030000 +RDATE:19780514T030000 +RDATE:19790513T030000 +RDATE:19800511T030000 +RDATE:19810510T030000 +RDATE:19820509T030000 +RDATE:19830515T030000 +RDATE:19840513T030000 +RDATE:19850512T030000 +RDATE:19860511T030000 +RDATE:19870510T030000 +RDATE:19880515T030000 +RDATE:19890514T030000 +RDATE:19900513T030000 +RDATE:19910512T030000 +RDATE:19920510T030000 +RDATE:19930509T030000 +RDATE:19940515T030000 +RDATE:19950514T030000 +RDATE:19960512T030000 +RDATE:19970511T030000 +RDATE:19980510T030000 +RDATE:19990509T030000 +RDATE:20000514T030000 +RDATE:20010513T030000 +RDATE:20020512T030000 +RDATE:20030511T030000 +RDATE:20040509T030000 +RDATE:20050515T030000 +RDATE:20060514T030000 +RDATE:20070513T030000 +RDATE:20080511T030000 +RDATE:20090510T030000 +RDATE:20100509T030000 +RDATE:20110515T030000 +RDATE:20120513T030000 +RDATE:20130512T030000 +RDATE:20140511T030000 +RDATE:20150510T030000 +RDATE:20160515T030000 +RDATE:20170514T030000 +RDATE:20180513T030000 +RDATE:20190512T030000 +RDATE:20200510T030000 +RDATE:20210509T030000 +RDATE:20220515T030000 +RDATE:20230514T030000 +RDATE:20240512T030000 +RDATE:20250511T030000 +RDATE:20260510T030000 +RDATE:20270509T030000 +RDATE:20280514T030000 +RDATE:20290513T030000 +RDATE:20300512T030000 +RDATE:20310511T030000 +RDATE:20320509T030000 +RDATE:20330515T030000 +RDATE:20340514T030000 +RDATE:20350513T030000 +RDATE:20360511T030000 +RDATE:20370510T030000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETFROM:-0300 +TZOFFSETTO:-0400 +TZNAME:-04 +DTSTART:20380509T000000 +RRULE:FREQ=YEARLY;BYMONTH=5;BYDAY=2SU +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:-0400 +TZOFFSETTO:-0300 +TZNAME:-03 +DTSTART:19700809T000000 +RDATE:19700809T040000 +RDATE:19710815T040000 +RDATE:19720813T040000 +RDATE:19730812T040000 +RDATE:19740811T040000 +RDATE:19750810T040000 +RDATE:19760815T040000 +RDATE:19770814T040000 +RDATE:19780813T040000 +RDATE:19790812T040000 +RDATE:19800810T040000 +RDATE:19810809T040000 +RDATE:19820815T040000 +RDATE:19830814T040000 +RDATE:19840812T040000 +RDATE:19850811T040000 +RDATE:19860810T040000 +RDATE:19870809T040000 +RDATE:19880814T040000 +RDATE:19890813T040000 +RDATE:19900812T040000 +RDATE:19910811T040000 +RDATE:19920809T040000 +RDATE:19930815T040000 +RDATE:19940814T040000 +RDATE:19950813T040000 +RDATE:19960811T040000 +RDATE:19970810T040000 +RDATE:19980809T040000 +RDATE:19990815T040000 +RDATE:20000813T040000 +RDATE:20010812T040000 +RDATE:20020811T040000 +RDATE:20030810T040000 +RDATE:20040815T040000 +RDATE:20050814T040000 +RDATE:20060813T040000 +RDATE:20070812T040000 +RDATE:20080810T040000 +RDATE:20090809T040000 +RDATE:20100815T040000 +RDATE:20110814T040000 +RDATE:20120812T040000 +RDATE:20130811T040000 +RDATE:20140810T040000 +RDATE:20150809T040000 +RDATE:20160814T040000 +RDATE:20170813T040000 +RDATE:20180812T040000 +RDATE:20190811T040000 +RDATE:20200809T040000 +RDATE:20210815T040000 +RDATE:20220814T040000 +RDATE:20230813T040000 +RDATE:20240811T040000 +RDATE:20250810T040000 +RDATE:20260809T040000 +RDATE:20270815T040000 +RDATE:20280813T040000 +RDATE:20290812T040000 +RDATE:20300811T040000 +RDATE:20310810T040000 +RDATE:20320815T040000 +RDATE:20330814T040000 +RDATE:20340813T040000 +RDATE:20350812T040000 +RDATE:20360810T040000 +RDATE:20370809T040000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZOFFSETFROM:-0400 +TZOFFSETTO:-0300 +TZNAME:-03 +DTSTART:20380815T000000 +RRULE:FREQ=YEARLY;BYMONTH=8;BYDAY=2SU +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Santiago:20170309T100000 +DTEND;TZID=America/Santiago:20170309T110000 +RRULE:FREQ=MONTHLY;INTERVAL=30;BYMONTHDAY=9 +DTSTAMP:20170310T172720Z +UID:80rl9kuu5bq49gme99eklov27k@google.com +CREATED:20170310T172400Z +DESCRIPTION: +LAST-MODIFIED:20170310T172400Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:TestEvent +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/MagicMirror/tests/mocks/calendar_test_icons.ics b/MagicMirror/tests/mocks/calendar_test_icons.ics new file mode 100644 index 0000000000000000000000000000000000000000..41e64dea27cbdd4323208f53d481044cda84ab06 --- /dev/null +++ b/MagicMirror/tests/mocks/calendar_test_icons.ics @@ -0,0 +1,63 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//ical.marudot.com//iCal Event Maker +X-WR-CALNAME:TestEvents +NAME:TestEvents +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Europe/Berlin +TZURL:http://tzurl.org/zoneinfo-outlook/Europe/Berlin +X-LIC-LOCATION:Europe/Berlin +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20200719T094531Z +UID:20200719T094531Z-1871115387@marudot.com +DTSTART;TZID=Europe/Berlin:20300101T120000 +DTEND;TZID=Europe/Berlin:20300101T130000 +SUMMARY:TestEvent +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20200719T094531Z +UID:20200719T094531Z-1929725136@marudot.com +DTSTART;TZID=Europe/Berlin:20300701T120000 +RRULE:FREQ=YEARLY;BYMONTH=7;BYMONTHDAY=1 +DTEND;TZID=Europe/Berlin:20300701T130000 +SUMMARY:TestEventRepeat +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20200719T094531Z +UID:20200719T094531Z-371801474@marudot.com +DTSTART;VALUE=DATE:20300401 +DTEND;VALUE=DATE:20300402 +SUMMARY:TestEventDay +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20200719T094531Z +UID:20200719T094531Z-133401084@marudot.com +DTSTART;VALUE=DATE:20301001 +RRULE:FREQ=YEARLY;BYMONTH=10;BYMONTHDAY=1 +DTEND;VALUE=DATE:20301002 +SUMMARY:TestEventRepeatDay +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20200721T094531Z +UID:20200719T094531Z-167389794@marudot.com +DTSTART;TZID=Europe/Berlin:20301112T120000 +DTEND;TZID=Europe/Berlin:20301112T130000 +SUMMARY:TestEventCustomEventIcon +END:VEVENT +END:VCALENDAR diff --git a/MagicMirror/tests/mocks/calendar_test_recurring.ics b/MagicMirror/tests/mocks/calendar_test_recurring.ics new file mode 100644 index 0000000000000000000000000000000000000000..d749a737f16cca74f3b5a400c10d708d63c1ae98 --- /dev/null +++ b/MagicMirror/tests/mocks/calendar_test_recurring.ics @@ -0,0 +1,37 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:xxx@gmail.com +X-WR-TIMEZONE:Europe/Zurich +BEGIN:VTIMEZONE +TZID:Etc/UTC +X-LIC-LOCATION:Etc/UTC +BEGIN:STANDARD +TZOFFSETFROM:+0000 +TZOFFSETTO:+0000 +TZNAME:GMT +DTSTART:19700101T000000 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;VALUE=DATE:20210325 +DTEND;VALUE=DATE:20210326 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1 +DTSTAMP:20210421T154106Z +UID:zzz@google.com +REATED:20200831T200244Z +DESCRIPTION: +LAST-MODIFIED:20200831T200244Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Birthday +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +TRIGGER:-P0DT7H0M0S +END:VALARM +END:VEVENT diff --git a/MagicMirror/tests/mocks/compliments_test.json b/MagicMirror/tests/mocks/compliments_test.json new file mode 100644 index 0000000000000000000000000000000000000000..fa1d0b9c6c0221618f2f245d15387a4cb98db939 --- /dev/null +++ b/MagicMirror/tests/mocks/compliments_test.json @@ -0,0 +1,3 @@ +{ + "anytime": ["Remote compliment file works!"] +} diff --git a/MagicMirror/tests/mocks/newsfeed_test.xml b/MagicMirror/tests/mocks/newsfeed_test.xml new file mode 100644 index 0000000000000000000000000000000000000000..b781a8a8246a7614886d83f15ddc37915252195c --- /dev/null +++ b/MagicMirror/tests/mocks/newsfeed_test.xml @@ -0,0 +1,410 @@ +<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0" + xmlns:content="http://purl.org/rss/1.0/modules/content/" + xmlns:wfw="http://wellformedweb.org/CommentAPI/" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:atom="http://www.w3.org/2005/Atom" + xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" + xmlns:slash="http://purl.org/rss/1.0/modules/slash/" +> + <channel> + <title>Rodrigo RamÃrez Norambuena</title> + <atom:link href="https://rodrigoramirez.com/feed/" rel="self" type="application/rss+xml"/> + <link>https://rodrigoramirez.com</link> + <description>Temas sobre Linux, VoIP, Open Source, tecnologÃa y lo relacionado.</description> + <lastBuildDate>Fri, 21 Oct 2016 21:30:22 +0000</lastBuildDate> + <language>es-ES</language> + <sy:updatePeriod>hourly</sy:updatePeriod> + <sy:updateFrequency>1</sy:updateFrequency> + <generator>https://wordpress.org/?v=4.7.3</generator> + <item> + <title>QPanel 0.13.0</title> + <link>https://rodrigoramirez.com/qpanel-0-13-0/</link> + <comments>https://rodrigoramirez.com/qpanel-0-13-0/#comments</comments> + <pubDate>Tue, 20 Sep 2016 11:16:08 +0000</pubDate> + <dc:creator><![CDATA[decipher]]></dc:creator> + <category><![CDATA[Software]]></category> + <category><![CDATA[app_queue]]></category> + <category><![CDATA[asterisk]]></category> + <category><![CDATA[FreeSWITCH]]></category> + <category><![CDATA[qpanel]]></category> + <category><![CDATA[queue]]></category> + <category><![CDATA[spy]]></category> + <category><![CDATA[supervision]]></category> + <category><![CDATA[templates]]></category> + <category><![CDATA[whisper]]></category> + + <guid isPermaLink="false">https://rodrigoramirez.com/?p=1299</guid> + <description><![CDATA[<p>Ya está disponible la versión 0.13.0 de QPanel Para instalar esta nueva versión, la debes descargar de https://github.com/roramirez/qpanel/tree/0.13.0 En al README.md puedes encontrar las instrucciones para hacer que funcione en tu sistema. En esta nueva versión cuenta con los siguientes cambios: Se establece un limite para el reciclado del tiempo de conexión a la base […]</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-13-0/">QPanel 0.13.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></description> + <content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible la versión 0.13.0 de QPanel</p> +<p>Para instalar esta nueva versión, la debes descargar de</p> +<ul> +<li><a href="https://github.com/roramirez/qpanel/tree/0.13.0">https://github.com/roramirez/qpanel/tree/0.13.0</a></li> +</ul> +<p>En al README.md puedes encontrar las instrucciones para hacer que funcione en tu sistema.</p> +<p>En esta nueva versión cuenta con los siguientes cambios:</p> +<ul> +<li>Se establece un limite para el reciclado del tiempo de conexión a la base de datos que contenga QueueLog. Esto evita problemas en bases de datos como MySQL que finaliza o da timeout a las conexiones.</li> +<li>Ahora la py-asterisk va dentro del archivo requirements.txt y no como submodulo del proyecto.</li> +<li>Se remueven la mayorÃa de las libs externas para Javascript y CSS para manejarlos desde ahora con <a href="https://bower.io/">Bower</a>.</li> +<li>Se incluye un script para WSGI que permite su utilización con Apache.</li> +<li>Actualización para los idiomas Ruso y Portugues.</li> +</ul> +<p>Si deseas colaborar con el proyecto puedes agregar nuevas sugerencias mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a> ó colaborar mediante <a href="https://github.com/roramirez/qpanel/blob/dd42cf0f534408505f57b0d387dffee2f3688711/README.md#how-to-contribute">mediante un Pull Request.</a></p> +<p>Ahora si necesitas <a href="https://boxtub.com/qpanel/">soporte comercial para instalaciones, personalizaciones o nuevas caracterÃsticas lo puedes solicitar en https://boxtub.com/qpanel/</a></p> +<p> </p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-13-0/">QPanel 0.13.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></content:encoded> + <wfw:commentRss>https://rodrigoramirez.com/qpanel-0-13-0/feed/</wfw:commentRss> + <slash:comments>3</slash:comments> + </item> + <item> + <title>Problema VirtualBox “starting virtual machine” …</title> + <link>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/</link> + <comments>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/#respond</comments> + <pubDate>Sat, 10 Sep 2016 22:50:13 +0000</pubDate> + <dc:creator><![CDATA[decipher]]></dc:creator> + <category><![CDATA[Linux]]></category> + <category><![CDATA[no arranca]]></category> + <category><![CDATA[Problema]]></category> + <category><![CDATA[VirtualBox]]></category> + + <guid isPermaLink="false">https://rodrigoramirez.com/?p=1284</guid> + <description><![CDATA[<p>Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox. La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual querÃa arrancar, se quedaba en un largo limbo con el mensaje “starting virtual machine”, como el de la imagen de a continuación. […]</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/">Problema VirtualBox “starting virtual machine” …</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></description> + <content:encoded><![CDATA[<p>Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox. La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual querÃa arrancar, se quedaba en un largo limbo con el mensaje “starting virtual machine”, como el de la imagen de a continuación.</p> +<p><a href="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09.png"><img class="aligncenter wp-image-1290 size-full" src="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09.png" alt="Starting virtual machine ... VirtualBox" width="648" height="554" srcset="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09.png 648w, https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09-300x256.png 300w" sizes="(max-width: 648px) 100vw, 648px" /></a></p> +<p>Ninguna, pero ninguna maquina arrancó, se quedaban en ese mensaje. Fue de esos instantes en que sudas helado … <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p> +<p>Con un poco de investigación fue a parar al archivo<em> ~/.VirtualBox/VBoxSVC.log </em>que indicaba</p> +<pre>$ tail -f ~/.VirtualBox/VBoxSVC.log + 00:08:32.932717 nspr-7 Failed to open "/dev/vboxdrvu", errno=13, rc=VERR_VM_DRIVER_NOT_ACCESSIBLE + 00:08:33.555836 nspr-6 Failed to open "/dev/vboxdrvu", errno=13, rc=VERR_VM_DRIVER_NOT_ACCESSIBLE</pre> +<p> </p> +<p>Fui… algo de donde agarrarse. Mirando un poco mas se trataba de problemas con los permisos al vboxdrvu, mirando indicaba que tenÃa 0600.</p> +<p> </p> +<pre>$ ls -lh /dev/vboxdrvu + crw------- 1 root root 10, 56 Sep 10 12:47 /dev/vboxdrvu</pre> +<p> </p> +<p>El tema es que deben estar en 0666, le cambias los permisos y eso soluciona el problema <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p> +<pre> +$ sudo chmod 0666 /dev/vboxdrvu +$ ls -lh /dev/vboxdrvu + crw-rw-rw- 1 root root 10, 56 Sep 10 12:47 /dev/vboxdrvu</pre> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/">Problema VirtualBox “starting virtual machine” …</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></content:encoded> + <wfw:commentRss>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/feed/</wfw:commentRss> + <slash:comments>0</slash:comments> + </item> + <item> + <title>Mejorando la consola interactiva de Python</title> + <link>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/</link> + <comments>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/#comments</comments> + <pubDate>Tue, 06 Sep 2016 04:24:43 +0000</pubDate> + <dc:creator><![CDATA[decipher]]></dc:creator> + <category><![CDATA[desarrollo]]></category> + <category><![CDATA[Desarrollo]]></category> + <category><![CDATA[Python]]></category> + + <guid isPermaLink="false">https://rodrigoramirez.com/?p=1247</guid> + <description><![CDATA[<p>Cuando estás desarrollando en Python es muy cool estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente. La consola de Python funciona y cumple su cometido. Solo al tipear python te permite entrar en modo interactivo e ir probando cosas. El punto es que a veces […]</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/">Mejorando la consola interactiva de Python</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></description> + <content:encoded><![CDATA[<p>Cuando estás desarrollando en Python es muy <em>cool</em> estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente.</p> +<p>La consola de Python funciona y cumple su cometido. Solo al tipear <em>python </em>te permite entrar en modo interactivo e ir probando cosas.</p> +<p>El punto es que a veces uno necesita ir un poco más allá. Como autocomentado de código o resaltado de sintaxis, para eso tengo dos truco que utilizo generalmente.</p> +<h2>Truco a)</h2> +<p>Este permite añadirle algunos esteriodes a la consolta, en realidad uno, el autocompletado. Esto es de gran ayuda para ir conociendo los metodo que puede tener un objecto, funciones u operaciones.</p> +<p>Para esto se ocupo <em>rlcompleter</em> y <em>readline. </em></p> +<p> </p> +<p>Lo que hace que hacer luego de tipear python es agregar lo siguiente dentro de la consola interativa</p> +<p><em>import rlcompleter, readline</em><br /> +<em>readline.parse_and_bind(‘tab:complete’)</em></p> +<p>Ya con esto te permite autocomentar código <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p> +<p><a href="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-03-01-14-32.png"><img class="aligncenter wp-image-1279 size-full" src="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-03-01-14-32.png" width="689" height="421" srcset="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-03-01-14-32.png 689w, https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-03-01-14-32-300x183.png 300w" sizes="(max-width: 689px) 100vw, 689px" /></a></p> +<p> </p> +<h2>Truco b)</h2> +<p>Esto es mejorar un poco más. Es utilizar embed de <a href="https://ipython.org/">IPython,</a> ya en la consola digita (copias o pegas) lo siguiente</p> +<p><em>from IPython import embed</em><br /> +<em>embed()</em></p> +<p>Y el resultado será lo que se ve a continuación… bueno, no?</p> +<p> </p> +<p><a href="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-07-25-15-48-39.png"><img class="aligncenter wp-image-1262 size-full" src="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-07-25-15-48-39.png" width="743" height="293" srcset="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-07-25-15-48-39.png 743w, https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-07-25-15-48-39-300x118.png 300w" sizes="(max-width: 743px) 100vw, 743px" /></a></p> +<p> </p> +<p>Si no quieres estar escribiendo cada vez que entras, agregas estas instrucciones en tu archivo <em>~/.pythonrc.py </em> y lo hará cada vez que entras en el modo interactivo de la consola de Python. Lo que si, tu archivo pythonrc.py debe estar seteado en variable de entorno PYTHONSTARTUP</p> +<p>ejemplo</p> +<p><em>export PYTHONSTARTUP=~/.pythonrc.py</em></p> +<p>O lo agregas a un bashrc, zshrc o la shell que ocupes.</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/">Mejorando la consola interactiva de Python</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></content:encoded> + <wfw:commentRss>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/feed/</wfw:commentRss> + <slash:comments>4</slash:comments> + </item> + <item> + <title>QPanel 0.12.0 con estadÃsticas</title> + <link>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/</link> + <comments>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/#respond</comments> + <pubDate>Mon, 22 Aug 2016 04:19:03 +0000</pubDate> + <dc:creator><![CDATA[decipher]]></dc:creator> + <category><![CDATA[Software]]></category> + <category><![CDATA[app_queue]]></category> + <category><![CDATA[asterisk]]></category> + <category><![CDATA[FreeSWITCH]]></category> + <category><![CDATA[qpanel]]></category> + <category><![CDATA[queue]]></category> + <category><![CDATA[spy]]></category> + <category><![CDATA[supervision]]></category> + <category><![CDATA[templates]]></category> + <category><![CDATA[whisper]]></category> + + <guid isPermaLink="false">https://rodrigoramirez.com/?p=1268</guid> + <description><![CDATA[<p>Ya está disponible una nueva versión de QPanel, esta es la 0.12.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.12.0 En esta nueva versión las funcionalidades agregadas son: Permite remover los agentes de las cola Posibilidad de cancelar llamadas que están en espera de atención EstadÃsticas por rango de fecha obtenidas desde […]</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/">QPanel 0.12.0 con estadÃsticas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></description> + <content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible una nueva versión de QPanel, esta es la 0.12.0</p> +<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p> +<ul> +<li><a href="https://github.com/roramirez/qpanel/tree/0.12.0">https://github.com/roramirez/qpanel/tree/0.12.0</a></li> +</ul> +<p>En esta nueva versión las funcionalidades agregadas son:</p> +<ul> +<li>Permite remover los agentes de las cola</li> +<li>Posibilidad de cancelar llamadas que están en espera de atención</li> +<li>EstadÃsticas por rango de fecha obtenidas desde el queue_log de Asterisk</li> +<li>Se actualiza a Flask 0.11</li> +</ul> +<p>Si deseas colaborar con el proyecto puedes agregar nuevas sugerencias mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a> ó colaborar mediante <a href="https://github.com/roramirez/qpanel/blob/dd42cf0f534408505f57b0d387dffee2f3688711/README.md#how-to-contribute">mediante un Pull Request</a></p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/">QPanel 0.12.0 con estadÃsticas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></content:encoded> + <wfw:commentRss>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/feed/</wfw:commentRss> + <slash:comments>0</slash:comments> + </item> + <item> + <title>QPanel 0.11.0 con Spy, Whisper y mas</title> + <link>https://rodrigoramirez.com/qpanel-spy-supervisor/</link> + <comments>https://rodrigoramirez.com/qpanel-spy-supervisor/#comments</comments> + <pubDate>Thu, 21 Jul 2016 01:53:21 +0000</pubDate> + <dc:creator><![CDATA[decipher]]></dc:creator> + <category><![CDATA[Software]]></category> + <category><![CDATA[app_queue]]></category> + <category><![CDATA[asterisk]]></category> + <category><![CDATA[FreeSWITCH]]></category> + <category><![CDATA[qpanel]]></category> + <category><![CDATA[queue]]></category> + <category><![CDATA[spy]]></category> + <category><![CDATA[supervision]]></category> + <category><![CDATA[templates]]></category> + <category><![CDATA[whisper]]></category> + + <guid isPermaLink="false">https://rodrigoramirez.com/?p=1245</guid> + <description><![CDATA[<p>Ya está disponible una nueva versión de QPanel, esta es la 0.11.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.11.0 Esta versión hemos agregado algunas funcionalidades que los usuarios han ido solicitando. Para esta versión es posible realizar Spy, Whisper o Barge a un canal para la supervisión de los miembros que […]</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-spy-supervisor/">QPanel 0.11.0 con Spy, Whisper y mas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></description> + <content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible una nueva versión de QPanel, esta es la 0.11.0</p> +<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p> +<ul> +<li><a href="https://github.com/roramirez/qpanel/tree/0.11.0">https://github.com/roramirez/qpanel/tree/0.11.0</a></li> +</ul> +<p>Esta versión hemos agregado algunas funcionalidades que los usuarios han ido solicitando.</p> +<p>Para esta versión es posible realizar Spy, Whisper o Barge a un canal para la supervisión de los miembros que están en una cola.</p> +<p>También el sistema de plantillas se hecho una refactorización para eliminar exceso de codigo HTML usando uno de base.</p> +<p>Se han agregado una suite de tests unitarios que al contar del avance del proyecto deberÃan ir incrementando.</p> +<p>Se ha solucionado un bug con la actualización del color del estado del agente cuando es uno nuevo agregado a la cola.</p> +<p> </p> +<p>El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a>.</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-spy-supervisor/">QPanel 0.11.0 con Spy, Whisper y mas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></content:encoded> + <wfw:commentRss>https://rodrigoramirez.com/qpanel-spy-supervisor/feed/</wfw:commentRss> + <slash:comments>4</slash:comments> + </item> + <item> + <title>Añadir Swap a un sistema</title> + <link>https://rodrigoramirez.com/crear-swap/</link> + <comments>https://rodrigoramirez.com/crear-swap/#respond</comments> + <pubDate>Fri, 15 Jul 2016 05:07:43 +0000</pubDate> + <dc:creator><![CDATA[decipher]]></dc:creator> + <category><![CDATA[Linux]]></category> + + <guid isPermaLink="false">https://rodrigoramirez.com/?p=1234</guid> + <description><![CDATA[<p>Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap. La memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM. El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto […]</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/crear-swap/">Añadir Swap a un sistema</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></description> + <content:encoded><![CDATA[<p>Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap.</p> +<p>La memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM.</p> +<p>El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto un espacio para la Swap, lo que te lleva a que el sistema pueda tener crash durante la ejecución.</p> +<p>Para comprobar la asignación de memoria, al ejecutar el comando <em>free</em> nos deberÃa mostrar como algo similar a lo siguiente</p> +<p> </p> +<pre>$ free -m +            total      used      free    shared   buffers    cached +Mem:          494       488         6         1        54        75 +-/+ buffers/cache:       357       136 +Swap:           0         0         0</pre> +<p>En la zona de swap indica que no asignada, valor 0.</p> +<p>Para asignar swap al sistema se debe un archivo en disco para que sea utilizado como espacio de intercambio, en este caso lo vamos crear uno de 3GB en la raÃz del sistema</p> +<pre class="code-pre "><code>fallocate -l 3G /swapfile</code></pre> +<p>Comprobamos que ha sido creado</p> +<pre>$ ls -lh /swapfile +-rw-r--r-- 1 root root 3.0G Jul 11 13:10 /swapfile +</pre> +<h3>Habilitación del archivo Swap</h3> +<p>Ahora nos toca habilitar el archivo creado. Para eso le asignaremos los permisos</p> +<pre>chmod 600 /swapfile</pre> +<p>Lo siguiente es para convertir el archivo para swap</p> +<pre>mkswap /swapfile</pre> +<p>Para habilitar y asignarla eso como memoria swap al sistema usamos</p> +<pre>swapon /swapfile</pre> +<p>Ya con esto podrémos ver en nuestro sistema la memoria asignada para swap</p> +<pre>$ free -m +            total      used      free    shared   buffers    cached +Mem:          494       486         7         1        51        77 +-/+ buffers/cache:       358       136 +Swap:        3071         0      3071</pre> +<p> </p> +<p>Para que al reiniciar el sistema esto se mantenga, debemos agregar la siguiente lÃnea al archivo /etc/fstab</p> +<pre><span class="pl-s">/swapfile none swap sw 0 0</span></pre> +<p> </p> +<p>Podemos editar /etc/fstab con algún editor como vim, nano o podemos agregar la linea directamente en la desde la cli de la siguiente manera</p> +<pre><span class="pl-c1">echo</span> <span class="pl-s"><span class="pl-pds">"</span>/swapfile none swap sw 0 0<span class="pl-pds">"</span></span> <span class="pl-k">>></span> /etc/fstab</pre> +<p> </p> +<p> </p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/crear-swap/">Añadir Swap a un sistema</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></content:encoded> + <wfw:commentRss>https://rodrigoramirez.com/crear-swap/feed/</wfw:commentRss> + <slash:comments>0</slash:comments> + </item> + <item> + <title>QPanel 0.10.0 con vista consolidada</title> + <link>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/</link> + <comments>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/#respond</comments> + <pubDate>Mon, 20 Jun 2016 19:32:55 +0000</pubDate> + <dc:creator><![CDATA[decipher]]></dc:creator> + <category><![CDATA[Linux]]></category> + <category><![CDATA[app_queue]]></category> + <category><![CDATA[asterisk]]></category> + <category><![CDATA[FreeSWITCH]]></category> + <category><![CDATA[qpanel]]></category> + <category><![CDATA[queue]]></category> + + <guid isPermaLink="false">https://rodrigoramirez.com/?p=1227</guid> + <description><![CDATA[<p>Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible. Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.10.0 Esta versión versión nos preocupamos de realizar mejoras, refactorizaciones y agregamos una nueva funcionalidad. La nueva funcionalidad incluida es que ahora es posible contar con una vista consolidada para […]</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/">QPanel 0.10.0 con vista consolidada</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></description> + <content:encoded><![CDATA[<p><img class="alignleft" src="https://raw.githubusercontent.com/roramirez/qpanel/0.10.0/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="403" height="227" />Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible.</p> +<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p> +<ul> +<li><a href="https://github.com/roramirez/qpanel/tree/0.10.0">https://github.com/roramirez/qpanel/tree/0.10.0</a></li> +</ul> +<p>Esta versión versión nos preocupamos de realizar mejoras, refactorizaciones y agregamos una nueva funcionalidad.</p> +<p>La nueva funcionalidad incluida es que ahora es posible contar con una vista consolidada para la información de todas las colas. Que hace tener un mejor control y visualización de lo que está pasando en las colas.</p> +<p>El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a>.</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/">QPanel 0.10.0 con vista consolidada</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></content:encoded> + <wfw:commentRss>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/feed/</wfw:commentRss> + <slash:comments>0</slash:comments> + </item> + <item> + <title>Nerdearla 2016, WebRTC Glue</title> + <link>https://rodrigoramirez.com/nerdearla-2016/</link> + <comments>https://rodrigoramirez.com/nerdearla-2016/#respond</comments> + <pubDate>Wed, 15 Jun 2016 17:55:41 +0000</pubDate> + <dc:creator><![CDATA[decipher]]></dc:creator> + <category><![CDATA[Linux]]></category> + <category><![CDATA[baires]]></category> + <category><![CDATA[charla]]></category> + <category><![CDATA[Computación]]></category> + <category><![CDATA[informatica]]></category> + <category><![CDATA[tech]]></category> + <category><![CDATA[ti]]></category> + <category><![CDATA[webrtc]]></category> + + <guid isPermaLink="false">https://rodrigoramirez.com/?p=1218</guid> + <description><![CDATA[<p>DÃas atrás estuve participando en el evento llamado Nerdearla en Buenos Aires. El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes. HabÃan muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te […]</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/nerdearla-2016/">Nerdearla 2016, WebRTC Glue</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></description> + <content:encoded><![CDATA[<p>DÃas atrás estuve participando en el evento llamado <a href="https://nerdear.la/">Nerdearla</a> en Buenos Aires. El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes.</p> +<p>HabÃan muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te lo perdiste te recomiendo que estés pendiente para el proximo año.</p> +<p> </p> +<p>Te podias encontrar con una nuestra como esta<a href="https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS.jpg"><img class="aligncenter size-medium wp-image-1221" src="https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-300x169.jpg" alt="Kaypro II" width="300" height="169" srcset="https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-300x169.jpg 300w, https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-768x432.jpg 768w, https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-1024x576.jpg 1024w, https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS.jpg 1200w" sizes="(max-width: 300px) 100vw, 300px" /></a></p> +<p>Puedes dar un vistaso a lo registrado por algunos <a href="https://twitter.com/hashtag/nerdearla?f=tweets&vertical=default&src=hash">usuarios en Twitter</a></p> +<p>El primer dÃa hice un workshop denominado WebRTC Glue, donde muestra como hacer como unificar la experiencia de atención del centro de contacto directamente en la web. Es una presentación práctica donde puedes ver los ejemplos y usarlos como gustes. Están en <a href="https://gitlab.com/roramirez/webrtc-glue">el repositorio en Gitlab</a>. La presentación <a href="/charlas/webrtc-glue/index.html">la puedes ver aquÃ</a></p> +<p> </p> +<p><a href="https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW.jpg"><img class="aligncenter size-medium wp-image-1222" src="https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW-300x169.jpg" alt="WebRTC Glue" width="300" height="169" srcset="https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW-300x169.jpg 300w, https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW-768x432.jpg 768w, https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW-1024x576.jpg 1024w, https://rodrigoramirez.com/wp-content/uploads/CkhxulgWYAAp7fW.jpg 1200w" sizes="(max-width: 300px) 100vw, 300px" /></a></p> +<p>Haber si nos vemos el próximo año.</p> +<p> </p> +<p><strong>Update</strong>: Puedes ver una parte sin la demostración del workshop</p> +<p><iframe src="https://www.youtube.com/embed/qDreeYk-UK4?list=PLTTdzfRyGY1vQukK8L4QeAfYdbURSzFq5" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"></iframe><br /> + </p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/nerdearla-2016/">Nerdearla 2016, WebRTC Glue</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></content:encoded> + <wfw:commentRss>https://rodrigoramirez.com/nerdearla-2016/feed/</wfw:commentRss> + <slash:comments>0</slash:comments> + </item> + <item> + <title>QPanel 0.9.0</title> + <link>https://rodrigoramirez.com/qpanel-0-9-0/</link> + <comments>https://rodrigoramirez.com/qpanel-0-9-0/#respond</comments> + <pubDate>Mon, 09 May 2016 18:40:23 +0000</pubDate> + <dc:creator><![CDATA[decipher]]></dc:creator> + <category><![CDATA[Software]]></category> + <category><![CDATA[asterisk]]></category> + <category><![CDATA[callcenter]]></category> + <category><![CDATA[colas]]></category> + <category><![CDATA[monitor]]></category> + <category><![CDATA[monitoreo]]></category> + <category><![CDATA[panel]]></category> + <category><![CDATA[qpanel]]></category> + <category><![CDATA[queues]]></category> + + <guid isPermaLink="false">https://rodrigoramirez.com/?p=1206</guid> + <description><![CDATA[<p>El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.9.0 Esta versión versión nos preocupamos de realizar mejoras y refactorizaciones en el codigo para dar un mejor rendimiento, como también de la compatibilidad con la versión 11 de […]</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-9-0/">QPanel 0.9.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></description> + <content:encoded><![CDATA[<p><img class="alignleft" src="https://raw.githubusercontent.com/roramirez/qpanel/0.9.0/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="403" height="227" />El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0</p> +<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p> +<ul> +<li><a href="https://github.com/roramirez/qpanel/tree/0.9.0">https://github.com/roramirez/qpanel/tree/0.9.0</a></li> +</ul> +<p>Esta versión versión nos preocupamos de realizar mejoras y refactorizaciones en el codigo para dar un mejor rendimiento, como también de la compatibilidad con la versión 11 de Asterisk.</p> +<p>Dentro de las cosas que podamos mencionar:</p> +<ul> +<li> Actualización del repositorio y versión de py-asterisk, biblioteca para trabajar con Asterisk. Acá la ocupamos principalmente para uso del Manager.</li> +<li>Portación de parche de funcionalidades como pausa, tiempo, razón de una pausa para Asterisk 11.</li> +<li>Cambio del comportamiento en el conteo cuando el participante en una cola está ocupado (busy)</li> +</ul> +<p>El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a>.</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-9-0/">QPanel 0.9.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></content:encoded> + <wfw:commentRss>https://rodrigoramirez.com/qpanel-0-9-0/feed/</wfw:commentRss> + <slash:comments>0</slash:comments> + </item> + <item> + <title>Mandar un email desde la shell</title> + <link>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/</link> + <comments>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/#comments</comments> + <pubDate>Wed, 13 Apr 2016 13:05:13 +0000</pubDate> + <dc:creator><![CDATA[decipher]]></dc:creator> + <category><![CDATA[Linux]]></category> + <category><![CDATA[mini-tips]]></category> + <category><![CDATA[bash]]></category> + <category><![CDATA[cli]]></category> + <category><![CDATA[Email]]></category> + <category><![CDATA[mail]]></category> + <category><![CDATA[sh]]></category> + <category><![CDATA[shell]]></category> + + <guid isPermaLink="false">https://rodrigoramirez.com/?p=1172</guid> + <description><![CDATA[<p>Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando mail en un servidor con Linux. Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un […]</p> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mandar-un-email-desde-la-shell/">Mandar un email desde la shell</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></description> + <content:encoded><![CDATA[<p>Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando <em>mail</em> en un servidor con Linux.</p> +<p>Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un <em>echo</em> le pasas por pipe a <em>mail</em></p> +<pre>echo "Cuerpo del mensaje" | mail -s Asunto a@rodrigoramirez.com</pre> +<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mandar-un-email-desde-la-shell/">Mandar un email desde la shell</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo RamÃrez Norambuena</a>.</p> +]]></content:encoded> + <wfw:commentRss>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/feed/</wfw:commentRss> + <slash:comments>4</slash:comments> + </item> + </channel> +</rss> diff --git a/MagicMirror/tests/mocks/translation_test.json b/MagicMirror/tests/mocks/translation_test.json new file mode 100644 index 0000000000000000000000000000000000000000..dd7b02fbb43ea313737da0edcba75564c9a981b5 --- /dev/null +++ b/MagicMirror/tests/mocks/translation_test.json @@ -0,0 +1,33 @@ +{ + "LOADING": "Loading …", + + "TODAY": "Today", + "TOMORROW": "Tomorrow", + "DAYAFTERTOMORROW": "In 2 days", + "RUNNING": "Ends in", + "EMPTY": "No upcoming events.", + + "WEEK": "Week {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "UPDATE_NOTIFICATION": "MagicMirror² update available.", + "UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.", + "UPDATE_INFO_SINGLE": "The current installation is COMMIT_COUNT commit behind on the BRANCH_NAME branch.", + "UPDATE_INFO_MULTIPLE": "The current installation is COMMIT_COUNT commits behind on the BRANCH_NAME branch." +} diff --git a/MagicMirror/tests/mocks/weather_current.json b/MagicMirror/tests/mocks/weather_current.json new file mode 100644 index 0000000000000000000000000000000000000000..8d46c971ab81ef2f2370c15114b032b74dc7ff67 --- /dev/null +++ b/MagicMirror/tests/mocks/weather_current.json @@ -0,0 +1,48 @@ +{ + "coord": { + "lon": 11.58, + "lat": 48.14 + }, + "weather": [ + { + "id": 615, + "main": "Snow", + "description": "light rain and snow", + "icon": "13d" + }, + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "base": "stations", + "main": { + "temp": 1.49, + "pressure": 1005, + "humidity": 93.7, + "temp_min": 1, + "temp_max": 2 + }, + "visibility": 7000, + "wind": { + "speed": 11.8, + "deg": 250 + }, + "clouds": { + "all": 75 + }, + "dt": 1547387400, + "sys": { + "type": 1, + "id": 1267, + "message": 0.0031, + "country": "DE", + "sunrise": 1547362817, + "sunset": 1547394301 + }, + "id": 2867714, + "name": "Munich", + "cod": 200 +} diff --git a/MagicMirror/tests/mocks/weather_forecast.json b/MagicMirror/tests/mocks/weather_forecast.json new file mode 100644 index 0000000000000000000000000000000000000000..ed062c3f6a15786cf8672f75374577af36ac61c8 --- /dev/null +++ b/MagicMirror/tests/mocks/weather_forecast.json @@ -0,0 +1,202 @@ +{ + "city": { + "id": 2867714, + "name": "Munich", + "coord": { + "lon": 11.5754, + "lat": 48.1371 + }, + "country": "DE", + "population": 1260391, + "timezone": 7200 + }, + "cod": "200", + "message": 0.9653487, + "cnt": 7, + "list": [ + { + "dt": 1568372400, + "sunrise": 1568350044, + "sunset": 1568395948, + "temp": { + "day": 24.44, + "min": 15.35, + "max": 24.44, + "night": 15.35, + "eve": 18, + "morn": 23.03 + }, + "pressure": 1031.65, + "humidity": 70, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "few clouds", + "icon": "02d" + } + ], + "speed": 3.35, + "deg": 314, + "clouds": 21 + }, + { + "dt": 1568458800, + "sunrise": 1568436525, + "sunset": 1568482223, + "temp": { + "day": 20.81, + "min": 13.56, + "max": 21.02, + "night": 13.56, + "eve": 16.6, + "morn": 15.88 + }, + "pressure": 1028.81, + "humidity": 72, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "speed": 2.21, + "deg": 81, + "clouds": 100, + "pop": 0.7, + "rain": 2.51 + }, + { + "dt": 1568545200, + "sunrise": 1568523007, + "sunset": 1568568497, + "temp": { + "day": 22.65, + "min": 13.76, + "max": 22.88, + "night": 15.27, + "eve": 17.45, + "morn": 13.76 + }, + "pressure": 1023.75, + "humidity": 64, + "weather": [ + { + "id": 800, + "main": "Clear", + "description": "sky is clear", + "icon": "01d" + } + ], + "speed": 1.15, + "deg": 7, + "clouds": 0 + }, + { + "dt": 1568631600, + "sunrise": 1568609489, + "sunset": 1568654771, + "temp": { + "day": 23.45, + "min": 13.95, + "max": 23.45, + "night": 13.95, + "eve": 17.75, + "morn": 15.21 + }, + "pressure": 1020.41, + "humidity": 64, + "weather": [ + { + "id": 800, + "main": "Clear", + "description": "sky is clear", + "icon": "01d" + } + ], + "speed": 3.07, + "deg": 298, + "clouds": 7 + }, + { + "dt": 1568718000, + "sunrise": 1568695970, + "sunset": 1568741045, + "temp": { + "day": 20.55, + "min": 10.95, + "max": 20.55, + "night": 10.95, + "eve": 14.82, + "morn": 13.24 + }, + "pressure": 1019.4, + "humidity": 66, + "weather": [ + { + "id": 800, + "main": "Clear", + "description": "sky is clear", + "icon": "01d" + } + ], + "speed": 2.8, + "deg": 333, + "clouds": 2 + }, + { + "dt": 1568804400, + "sunrise": 1568782452, + "sunset": 1568827319, + "temp": { + "day": 18.15, + "min": 7.75, + "max": 18.15, + "night": 7.75, + "eve": 12.45, + "morn": 9.41 + }, + "pressure": 1017.56, + "humidity": 52, + "weather": [ + { + "id": 800, + "main": "Clear", + "description": "sky is clear", + "icon": "01d" + } + ], + "speed": 2.92, + "deg": 34, + "clouds": 0 + }, + { + "dt": 1568890800, + "sunrise": 1568868934, + "sunset": 1568913593, + "temp": { + "day": 14.85, + "min": 5.56, + "max": 15.05, + "night": 5.56, + "eve": 9.56, + "morn": 6.25 + }, + "pressure": 1022.7, + "humidity": 59, + "weather": [ + { + "id": 800, + "main": "Clear", + "description": "sky is clear", + "icon": "01d" + } + ], + "speed": 2.89, + "deg": 51, + "clouds": 1 + } + ] +} diff --git a/MagicMirror/tests/mocks/weather_hourly.json b/MagicMirror/tests/mocks/weather_hourly.json new file mode 100644 index 0000000000000000000000000000000000000000..b0b2e662452a5b87c0507c787e09d15392b0d8a8 --- /dev/null +++ b/MagicMirror/tests/mocks/weather_hourly.json @@ -0,0 +1,1114 @@ +{ + "hourly": [ + { + "dt": 1673204400, + "temp": 27.31, + "feels_like": 29.59, + "pressure": 1013, + "humidity": 72, + "dew_point": 21.82, + "uvi": 0, + "clouds": 31, + "visibility": 10000, + "wind_speed": 2.05, + "wind_deg": 200, + "wind_gust": 1.91, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673208000, + "temp": 27.31, + "feels_like": 29.69, + "pressure": 1013, + "humidity": 73, + "dew_point": 22.04, + "uvi": 0, + "clouds": 30, + "visibility": 10000, + "wind_speed": 2.14, + "wind_deg": 186, + "wind_gust": 1.9, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673211600, + "temp": 27.29, + "feels_like": 29.65, + "pressure": 1013, + "humidity": 73, + "dew_point": 22.03, + "uvi": 0, + "clouds": 31, + "visibility": 10000, + "wind_speed": 2.16, + "wind_deg": 193, + "wind_gust": 1.91, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0.12 + }, + { + "dt": 1673215200, + "temp": 27.21, + "feels_like": 29.6, + "pressure": 1013, + "humidity": 74, + "dew_point": 22.17, + "uvi": 0, + "clouds": 32, + "visibility": 10000, + "wind_speed": 2.13, + "wind_deg": 206, + "wind_gust": 1.91, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "Leichter Regen", + "icon": "10n" + } + ], + "pop": 0.36, + "rain": { + "1h": 0.13 + } + }, + { + "dt": 1673218800, + "temp": 27.1, + "feels_like": 29.39, + "pressure": 1014, + "humidity": 74, + "dew_point": 22.07, + "uvi": 0, + "clouds": 38, + "visibility": 10000, + "wind_speed": 1.41, + "wind_deg": 227, + "wind_gust": 1.3, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "Leichter Regen", + "icon": "10n" + } + ], + "pop": 0.44, + "rain": { + "1h": 0.13 + } + }, + { + "dt": 1673222400, + "temp": 26.95, + "feels_like": 29.19, + "pressure": 1013, + "humidity": 75, + "dew_point": 22.14, + "uvi": 0, + "clouds": 41, + "visibility": 10000, + "wind_speed": 1.65, + "wind_deg": 227, + "wind_gust": 1.5, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0.52 + }, + { + "dt": 1673226000, + "temp": 26.72, + "feels_like": 28.83, + "pressure": 1012, + "humidity": 76, + "dew_point": 22.15, + "uvi": 0, + "clouds": 22, + "visibility": 10000, + "wind_speed": 1.88, + "wind_deg": 218, + "wind_gust": 1.71, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "Ein paar Wolken", + "icon": "02n" + } + ], + "pop": 0.08 + }, + { + "dt": 1673229600, + "temp": 26.57, + "feels_like": 26.57, + "pressure": 1012, + "humidity": 76, + "dew_point": 22.05, + "uvi": 0, + "clouds": 20, + "visibility": 10000, + "wind_speed": 1.51, + "wind_deg": 221, + "wind_gust": 1.3, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "Ein paar Wolken", + "icon": "02n" + } + ], + "pop": 0.08 + }, + { + "dt": 1673233200, + "temp": 26.46, + "feels_like": 26.46, + "pressure": 1011, + "humidity": 77, + "dew_point": 22.12, + "uvi": 0, + "clouds": 32, + "visibility": 10000, + "wind_speed": 1.71, + "wind_deg": 210, + "wind_gust": 1.52, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0.04 + }, + { + "dt": 1673236800, + "temp": 26.38, + "feels_like": 26.38, + "pressure": 1011, + "humidity": 78, + "dew_point": 22.22, + "uvi": 0, + "clouds": 49, + "visibility": 10000, + "wind_speed": 1.84, + "wind_deg": 213, + "wind_gust": 1.61, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673240400, + "temp": 26.32, + "feels_like": 26.32, + "pressure": 1012, + "humidity": 78, + "dew_point": 22.12, + "uvi": 0, + "clouds": 48, + "visibility": 10000, + "wind_speed": 1.83, + "wind_deg": 216, + "wind_gust": 1.6, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673244000, + "temp": 26.32, + "feels_like": 26.32, + "pressure": 1012, + "humidity": 78, + "dew_point": 22.26, + "uvi": 0, + "clouds": 43, + "visibility": 10000, + "wind_speed": 2.11, + "wind_deg": 205, + "wind_gust": 1.72, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673247600, + "temp": 26.44, + "feels_like": 26.44, + "pressure": 1013, + "humidity": 79, + "dew_point": 22.44, + "uvi": 0.53, + "clouds": 90, + "visibility": 10000, + "wind_speed": 2.78, + "wind_deg": 207, + "wind_gust": 2.51, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "Bedeckt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673251200, + "temp": 26.45, + "feels_like": 26.45, + "pressure": 1013, + "humidity": 78, + "dew_point": 22.22, + "uvi": 2.13, + "clouds": 93, + "visibility": 10000, + "wind_speed": 2.43, + "wind_deg": 190, + "wind_gust": 2.21, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "Bedeckt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673254800, + "temp": 26.54, + "feels_like": 26.54, + "pressure": 1014, + "humidity": 78, + "dew_point": 22.32, + "uvi": 4.92, + "clouds": 68, + "visibility": 10000, + "wind_speed": 3.04, + "wind_deg": 188, + "wind_gust": 2.91, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673258400, + "temp": 26.61, + "feels_like": 26.61, + "pressure": 1013, + "humidity": 77, + "dew_point": 22.28, + "uvi": 8.04, + "clouds": 56, + "visibility": 10000, + "wind_speed": 3.37, + "wind_deg": 183, + "wind_gust": 3.22, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673262000, + "temp": 26.76, + "feels_like": 28.9, + "pressure": 1013, + "humidity": 76, + "dew_point": 22.24, + "uvi": 10.6, + "clouds": 62, + "visibility": 10000, + "wind_speed": 3.51, + "wind_deg": 175, + "wind_gust": 3.4, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673265600, + "temp": 26.91, + "feels_like": 29.11, + "pressure": 1012, + "humidity": 75, + "dew_point": 22.24, + "uvi": 11.58, + "clouds": 54, + "visibility": 10000, + "wind_speed": 3.82, + "wind_deg": 174, + "wind_gust": 3.8, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673269200, + "temp": 27.04, + "feels_like": 29.27, + "pressure": 1011, + "humidity": 74, + "dew_point": 22.02, + "uvi": 10.65, + "clouds": 84, + "visibility": 10000, + "wind_speed": 4.06, + "wind_deg": 177, + "wind_gust": 4.02, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673272800, + "temp": 27.12, + "feels_like": 29.33, + "pressure": 1011, + "humidity": 73, + "dew_point": 21.94, + "uvi": 8.07, + "clouds": 81, + "visibility": 10000, + "wind_speed": 3.75, + "wind_deg": 187, + "wind_gust": 3.6, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673276400, + "temp": 27.17, + "feels_like": 29.33, + "pressure": 1010, + "humidity": 72, + "dew_point": 21.8, + "uvi": 4.84, + "clouds": 87, + "visibility": 10000, + "wind_speed": 3.35, + "wind_deg": 177, + "wind_gust": 3.2, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "Bedeckt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673280000, + "temp": 27.28, + "feels_like": 29.43, + "pressure": 1011, + "humidity": 71, + "dew_point": 21.56, + "uvi": 2.16, + "clouds": 90, + "visibility": 10000, + "wind_speed": 2.35, + "wind_deg": 177, + "wind_gust": 2.21, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "Bedeckt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673283600, + "temp": 27.28, + "feels_like": 29.43, + "pressure": 1011, + "humidity": 71, + "dew_point": 21.52, + "uvi": 0.54, + "clouds": 88, + "visibility": 10000, + "wind_speed": 2.36, + "wind_deg": 173, + "wind_gust": 2.22, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "Bedeckt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673287200, + "temp": 27.34, + "feels_like": 29.54, + "pressure": 1012, + "humidity": 71, + "dew_point": 21.62, + "uvi": 0, + "clouds": 77, + "visibility": 10000, + "wind_speed": 2.14, + "wind_deg": 172, + "wind_gust": 2.01, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673290800, + "temp": 27.25, + "feels_like": 29.38, + "pressure": 1013, + "humidity": 71, + "dew_point": 21.55, + "uvi": 0, + "clouds": 47, + "visibility": 10000, + "wind_speed": 1.62, + "wind_deg": 158, + "wind_gust": 1.51, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673294400, + "temp": 27.25, + "feels_like": 29.38, + "pressure": 1014, + "humidity": 71, + "dew_point": 21.52, + "uvi": 0, + "clouds": 29, + "visibility": 10000, + "wind_speed": 1.53, + "wind_deg": 126, + "wind_gust": 1.41, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673298000, + "temp": 27.17, + "feels_like": 29.24, + "pressure": 1015, + "humidity": 71, + "dew_point": 21.55, + "uvi": 0, + "clouds": 24, + "visibility": 10000, + "wind_speed": 1.16, + "wind_deg": 115, + "wind_gust": 1, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "Ein paar Wolken", + "icon": "02n" + } + ], + "pop": 0 + }, + { + "dt": 1673301600, + "temp": 27.07, + "feels_like": 29.06, + "pressure": 1015, + "humidity": 71, + "dew_point": 21.45, + "uvi": 0, + "clouds": 21, + "visibility": 10000, + "wind_speed": 1.13, + "wind_deg": 164, + "wind_gust": 1, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "Ein paar Wolken", + "icon": "02n" + } + ], + "pop": 0 + }, + { + "dt": 1673305200, + "temp": 26.99, + "feels_like": 29.09, + "pressure": 1014, + "humidity": 73, + "dew_point": 21.77, + "uvi": 0, + "clouds": 19, + "visibility": 10000, + "wind_speed": 1.85, + "wind_deg": 173, + "wind_gust": 1.72, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "Ein paar Wolken", + "icon": "02n" + } + ], + "pop": 0 + }, + { + "dt": 1673308800, + "temp": 26.83, + "feels_like": 28.8, + "pressure": 1014, + "humidity": 73, + "dew_point": 21.66, + "uvi": 0, + "clouds": 26, + "visibility": 10000, + "wind_speed": 1.83, + "wind_deg": 170, + "wind_gust": 1.71, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673312400, + "temp": 26.68, + "feels_like": 28.54, + "pressure": 1013, + "humidity": 73, + "dew_point": 21.52, + "uvi": 0, + "clouds": 80, + "visibility": 10000, + "wind_speed": 0.93, + "wind_deg": 164, + "wind_gust": 0.9, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1673316000, + "temp": 26.54, + "feels_like": 26.54, + "pressure": 1013, + "humidity": 74, + "dew_point": 21.46, + "uvi": 0, + "clouds": 70, + "visibility": 10000, + "wind_speed": 0.98, + "wind_deg": 156, + "wind_gust": 0.91, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1673319600, + "temp": 26.54, + "feels_like": 26.54, + "pressure": 1012, + "humidity": 75, + "dew_point": 21.8, + "uvi": 0, + "clouds": 52, + "visibility": 10000, + "wind_speed": 2.26, + "wind_deg": 173, + "wind_gust": 2.2, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1673323200, + "temp": 26.43, + "feels_like": 26.43, + "pressure": 1012, + "humidity": 75, + "dew_point": 21.75, + "uvi": 0, + "clouds": 43, + "visibility": 10000, + "wind_speed": 2.12, + "wind_deg": 173, + "wind_gust": 2, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673326800, + "temp": 26.38, + "feels_like": 26.38, + "pressure": 1013, + "humidity": 76, + "dew_point": 21.91, + "uvi": 0, + "clouds": 42, + "visibility": 10000, + "wind_speed": 2.57, + "wind_deg": 165, + "wind_gust": 2.5, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673330400, + "temp": 26.36, + "feels_like": 26.36, + "pressure": 1013, + "humidity": 77, + "dew_point": 21.97, + "uvi": 0, + "clouds": 42, + "visibility": 10000, + "wind_speed": 2.92, + "wind_deg": 167, + "wind_gust": 2.91, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "Mäßig bewölkt", + "icon": "03n" + } + ], + "pop": 0 + }, + { + "dt": 1673334000, + "temp": 26.45, + "feels_like": 26.45, + "pressure": 1014, + "humidity": 77, + "dew_point": 22.06, + "uvi": 0.52, + "clouds": 96, + "visibility": 10000, + "wind_speed": 3.09, + "wind_deg": 185, + "wind_gust": 3.1, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "Bedeckt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673337600, + "temp": 26.54, + "feels_like": 26.54, + "pressure": 1014, + "humidity": 77, + "dew_point": 22.14, + "uvi": 2.1, + "clouds": 87, + "visibility": 10000, + "wind_speed": 3.38, + "wind_deg": 176, + "wind_gust": 3.4, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "Bedeckt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673341200, + "temp": 26.63, + "feels_like": 26.63, + "pressure": 1014, + "humidity": 77, + "dew_point": 22.24, + "uvi": 4.86, + "clouds": 83, + "visibility": 10000, + "wind_speed": 3.4, + "wind_deg": 179, + "wind_gust": 3.4, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673344800, + "temp": 26.62, + "feels_like": 26.62, + "pressure": 1014, + "humidity": 77, + "dew_point": 22.23, + "uvi": 8.38, + "clouds": 72, + "visibility": 10000, + "wind_speed": 3.47, + "wind_deg": 178, + "wind_gust": 3.5, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673348400, + "temp": 26.71, + "feels_like": 28.81, + "pressure": 1014, + "humidity": 76, + "dew_point": 22.32, + "uvi": 11.06, + "clouds": 62, + "visibility": 10000, + "wind_speed": 3.82, + "wind_deg": 178, + "wind_gust": 3.81, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673352000, + "temp": 26.81, + "feels_like": 29, + "pressure": 1013, + "humidity": 76, + "dew_point": 22.32, + "uvi": 12.08, + "clouds": 57, + "visibility": 10000, + "wind_speed": 4.38, + "wind_deg": 181, + "wind_gust": 4.42, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "Überwiegend bewölkt", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1673355600, + "temp": 26.91, + "feels_like": 29.19, + "pressure": 1012, + "humidity": 76, + "dew_point": 22.32, + "uvi": 11.21, + "clouds": 14, + "visibility": 10000, + "wind_speed": 4.96, + "wind_deg": 183, + "wind_gust": 5.01, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "Ein paar Wolken", + "icon": "02d" + } + ], + "pop": 0 + }, + { + "dt": 1673359200, + "temp": 27.02, + "feels_like": 29.32, + "pressure": 1012, + "humidity": 75, + "dew_point": 22.23, + "uvi": 8.49, + "clouds": 13, + "visibility": 10000, + "wind_speed": 4.72, + "wind_deg": 179, + "wind_gust": 4.82, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "Ein paar Wolken", + "icon": "02d" + } + ], + "pop": 0 + }, + { + "dt": 1673362800, + "temp": 27.03, + "feels_like": 29.25, + "pressure": 1011, + "humidity": 74, + "dew_point": 22.14, + "uvi": 5.1, + "clouds": 14, + "visibility": 10000, + "wind_speed": 4.15, + "wind_deg": 180, + "wind_gust": 4.22, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "Ein paar Wolken", + "icon": "02d" + } + ], + "pop": 0 + }, + { + "dt": 1673366400, + "temp": 27.12, + "feels_like": 29.42, + "pressure": 1011, + "humidity": 74, + "dew_point": 22.03, + "uvi": 2.21, + "clouds": 13, + "visibility": 10000, + "wind_speed": 3.61, + "wind_deg": 174, + "wind_gust": 3.71, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "Ein paar Wolken", + "icon": "02d" + } + ], + "pop": 0 + }, + { + "dt": 1673370000, + "temp": 27.1, + "feels_like": 29.29, + "pressure": 1012, + "humidity": 73, + "dew_point": 21.92, + "uvi": 0.55, + "clouds": 11, + "visibility": 10000, + "wind_speed": 3.48, + "wind_deg": 171, + "wind_gust": 3.5, + "weather": [ + { + "id": 801, + "main": "Clouds", + "description": "Ein paar Wolken", + "icon": "02d" + } + ], + "pop": 0 + }, + { + "dt": 1673373600, + "temp": 27.18, + "feels_like": 29.54, + "pressure": 1012, + "humidity": 74, + "dew_point": 22.05, + "uvi": 0, + "clouds": 9, + "visibility": 10000, + "wind_speed": 3.39, + "wind_deg": 170, + "wind_gust": 3.51, + "weather": [ + { + "id": 800, + "main": "Clear", + "description": "Klarer Himmel", + "icon": "01d" + } + ], + "pop": 0 + } + ] +} diff --git a/MagicMirror/tests/unit/classes/class_spec.js b/MagicMirror/tests/unit/classes/class_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..5122840f3fb8d79325efe6c291235edc666bfdd9 --- /dev/null +++ b/MagicMirror/tests/unit/classes/class_spec.js @@ -0,0 +1,108 @@ +const path = require("path"); +const { JSDOM } = require("jsdom"); + +describe("File js/class", () => { + describe("Test function cloneObject", () => { + let clone; + let dom; + + beforeAll((done) => { + dom = new JSDOM( + `<script>var Log = {log: () => {}};</script>\ + <script src="file://${path.join(__dirname, "..", "..", "..", "js", "class.js")}">`, + { runScripts: "dangerously", resources: "usable" } + ); + dom.window.onload = () => { + const { cloneObject } = dom.window; + clone = cloneObject; + done(); + }; + }); + + it("should clone object", () => { + const expected = { name: "Rodrigo", web: "https://rodrigoramirez.com", project: "MagicMirror" }; + const obj = clone(expected); + expect(obj).toEqual(expected); + expect(expected === obj).toBe(false); + }); + + it("should clone array", () => { + const expected = [1, null, undefined, "TEST"]; + const obj = clone(expected); + expect(obj).toEqual(expected); + expect(expected === obj).toBe(false); + }); + + it("should clone number", () => { + let expected = 1; + let obj = clone(expected); + expect(obj).toBe(expected); + + expected = 1.23; + obj = clone(expected); + expect(obj).toBe(expected); + }); + + it("should clone string", () => { + const expected = "Perfect stranger"; + const obj = clone(expected); + expect(obj).toBe(expected); + }); + + it("should clone undefined", () => { + const expected = undefined; + const obj = clone(expected); + expect(obj).toBe(expected); + }); + + it("should clone null", () => { + const expected = null; + const obj = clone(expected); + expect(obj).toBe(expected); + }); + + it("should clone nested object", () => { + const expected = { + name: "fewieden", + link: "https://github.com/fewieden", + versions: ["2.0", "2.1", "2.2"], + answerForAllQuestions: 42, + properties: { + items: [{ foo: "bar" }, { lorem: "ipsum" }], + invalid: undefined, + nothing: null + } + }; + const obj = clone(expected); + expect(obj).toEqual(expected); + expect(expected === obj).toBe(false); + expect(expected.versions === obj.versions).toBe(false); + expect(expected.properties === obj.properties).toBe(false); + expect(expected.properties.items === obj.properties.items).toBe(false); + expect(expected.properties.items[0] === obj.properties.items[0]).toBe(false); + expect(expected.properties.items[1] === obj.properties.items[1]).toBe(false); + }); + + describe("Test lockstring code", () => { + let log; + + beforeAll(() => { + log = dom.window.Log.log; + dom.window.Log.log = (str) => { + expect(str).toBe("lockStrings"); + }; + }); + + afterAll(() => { + dom.window.Log.log = log; + }); + + it("should clone object and log lockStrings", () => { + const expected = { name: "Module", lockStrings: "stringLock" }; + const obj = clone(expected); + expect(obj).toEqual(expected); + expect(expected === obj).toBe(false); + }); + }); + }); +}); diff --git a/MagicMirror/tests/unit/classes/deprecated_spec.js b/MagicMirror/tests/unit/classes/deprecated_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ef78db911d1cce3d2d0c8ca083060afa7d952b68 --- /dev/null +++ b/MagicMirror/tests/unit/classes/deprecated_spec.js @@ -0,0 +1,15 @@ +const deprecated = require("../../../js/deprecated"); + +describe("Deprecated", () => { + it("should be an object", () => { + expect(typeof deprecated).toBe("object"); + }); + + it("should contain configs array with deprecated options as strings", () => { + expect(Array.isArray(["deprecated.configs"])).toBe(true); + for (let option of deprecated.configs) { + expect(typeof option).toBe("string"); + } + expect(deprecated.configs).toEqual(expect.arrayContaining(["kioskmode"])); + }); +}); diff --git a/MagicMirror/tests/unit/classes/translator_spec.js b/MagicMirror/tests/unit/classes/translator_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..410234acf97dd6b797e4be119e32aa2454a32f88 --- /dev/null +++ b/MagicMirror/tests/unit/classes/translator_spec.js @@ -0,0 +1,285 @@ +const path = require("path"); +const helmet = require("helmet"); +const { JSDOM } = require("jsdom"); +const express = require("express"); + +const sockets = new Set(); + +describe("Translator", () => { + let server; + + beforeAll(() => { + const app = express(); + app.use(helmet()); + app.use((req, res, next) => { + res.header("Access-Control-Allow-Origin", "*"); + next(); + }); + app.use("/translations", express.static(path.join(__dirname, "..", "..", "..", "tests", "mocks"))); + + server = app.listen(3000); + + server.on("connection", (socket) => { + sockets.add(socket); + }); + }); + + afterAll(async () => { + for (const socket of sockets) { + socket.destroy(); + sockets.delete(socket); + } + + await server.close(); + }); + + describe("translate", () => { + const translations = { + "MMM-Module": { + Hello: "Hallo", + "Hello {username}": "Hallo {username}" + } + }; + + const coreTranslations = { + Hello: "XXX", + "Hello {username}": "XXX", + FOO: "Foo", + "BAR {something}": "Bar {something}" + }; + + const translationsFallback = { + "MMM-Module": { + Hello: "XXX", + "Hello {username}": "XXX", + FOO: "XXX", + "BAR {something}": "XXX", + "A key": "A translation" + } + }; + + const coreTranslationsFallback = { + FOO: "XXX", + "BAR {something}": "XXX", + Hello: "XXX", + "Hello {username}": "XXX", + "A key": "XXX", + Fallback: "core fallback" + }; + + /** + * @param {object} Translator the global Translator object + */ + const setTranslations = (Translator) => { + Translator.translations = translations; + Translator.coreTranslations = coreTranslations; + Translator.translationsFallback = translationsFallback; + Translator.coreTranslationsFallback = coreTranslationsFallback; + }; + + it("should return custom module translation", (done) => { + const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" }); + dom.window.onload = () => { + const { Translator } = dom.window; + setTranslations(Translator); + let translation = Translator.translate({ name: "MMM-Module" }, "Hello"); + expect(translation).toBe("Hallo"); + translation = Translator.translate({ name: "MMM-Module" }, "Hello {username}", { username: "fewieden" }); + expect(translation).toBe("Hallo fewieden"); + done(); + }; + }); + + it("should return core translation", (done) => { + const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" }); + dom.window.onload = () => { + const { Translator } = dom.window; + setTranslations(Translator); + let translation = Translator.translate({ name: "MMM-Module" }, "FOO"); + expect(translation).toBe("Foo"); + translation = Translator.translate({ name: "MMM-Module" }, "BAR {something}", { something: "Lorem Ipsum" }); + expect(translation).toBe("Bar Lorem Ipsum"); + done(); + }; + }); + + it("should return custom module translation fallback", (done) => { + const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" }); + dom.window.onload = () => { + const { Translator } = dom.window; + setTranslations(Translator); + const translation = Translator.translate({ name: "MMM-Module" }, "A key"); + expect(translation).toBe("A translation"); + done(); + }; + }); + + it("should return core translation fallback", (done) => { + const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" }); + dom.window.onload = () => { + const { Translator } = dom.window; + setTranslations(Translator); + const translation = Translator.translate({ name: "MMM-Module" }, "Fallback"); + expect(translation).toBe("core fallback"); + done(); + }; + }); + + it("should return translation with placeholder for missing variables", (done) => { + const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" }); + dom.window.onload = () => { + const { Translator } = dom.window; + setTranslations(Translator); + const translation = Translator.translate({ name: "MMM-Module" }, "Hello {username}"); + expect(translation).toBe("Hallo {username}"); + done(); + }; + }); + + it("should return key if no translation was found", (done) => { + const dom = new JSDOM(`<script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" }); + dom.window.onload = () => { + const { Translator } = dom.window; + setTranslations(Translator); + const translation = Translator.translate({ name: "MMM-Module" }, "MISSING"); + expect(translation).toBe("MISSING"); + done(); + }; + }); + }); + + describe("load", () => { + const mmm = { + name: "TranslationTest", + file(file) { + return `http://localhost:3000/translations/${file}`; + } + }; + + it("should load translations", (done) => { + const dom = new JSDOM(`<script>var Log = {log: () => {}};</script><script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" }); + dom.window.onload = async () => { + const { Translator } = dom.window; + const file = "translation_test.json"; + + await Translator.load(mmm, file, false); + const json = require(path.join(__dirname, "..", "..", "..", "tests", "mocks", file)); + expect(Translator.translations[mmm.name]).toEqual(json); + done(); + }; + }); + + it("should load translation fallbacks", (done) => { + const dom = new JSDOM(`<script>var Log = {log: () => {}};</script><script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" }); + dom.window.onload = async () => { + const { Translator } = dom.window; + const file = "translation_test.json"; + + await Translator.load(mmm, file, true); + const json = require(path.join(__dirname, "..", "..", "..", "tests", "mocks", file)); + expect(Translator.translationsFallback[mmm.name]).toEqual(json); + done(); + }; + }); + + it("should not load translations, if module fallback exists", (done) => { + const dom = new JSDOM(`<script>var Log = {log: () => {}};</script><script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" }); + dom.window.onload = async () => { + const { Translator, XMLHttpRequest } = dom.window; + const file = "translation_test.json"; + + XMLHttpRequest.prototype.send = () => { + throw new Error("Shouldn't load files"); + }; + + Translator.translationsFallback[mmm.name] = { + Hello: "Hallo" + }; + + await Translator.load(mmm, file, false); + expect(Translator.translations[mmm.name]).toBe(undefined); + expect(Translator.translationsFallback[mmm.name]).toEqual({ + Hello: "Hallo" + }); + done(); + }; + }); + }); + + describe("loadCoreTranslations", () => { + it("should load core translations and fallback", (done) => { + const dom = new JSDOM( + `<script>var translations = {en: "http://localhost:3000/translations/translation_test.json"}; var Log = {log: () => {}};</script>\ + <script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, + { runScripts: "dangerously", resources: "usable" } + ); + dom.window.onload = async () => { + const { Translator } = dom.window; + await Translator.loadCoreTranslations("en"); + + const en = require(path.join(__dirname, "..", "..", "..", "tests", "mocks", "translation_test.json")); + setTimeout(() => { + expect(Translator.coreTranslations).toEqual(en); + expect(Translator.coreTranslationsFallback).toEqual(en); + done(); + }, 500); + }; + }); + + it("should load core fallback if language cannot be found", (done) => { + const dom = new JSDOM( + `<script>var translations = {en: "http://localhost:3000/translations/translation_test.json"}; var Log = {log: () => {}};</script>\ + <script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, + { runScripts: "dangerously", resources: "usable" } + ); + dom.window.onload = async () => { + const { Translator } = dom.window; + await Translator.loadCoreTranslations("MISSINGLANG"); + + const en = require(path.join(__dirname, "..", "..", "..", "tests", "mocks", "translation_test.json")); + setTimeout(() => { + expect(Translator.coreTranslations).toEqual({}); + expect(Translator.coreTranslationsFallback).toEqual(en); + done(); + }, 500); + }; + }); + }); + + describe("loadCoreTranslationsFallback", () => { + it("should load core translations fallback", (done) => { + const dom = new JSDOM( + `<script>var translations = {en: "http://localhost:3000/translations/translation_test.json"}; var Log = {log: () => {}};</script>\ + <script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, + { runScripts: "dangerously", resources: "usable" } + ); + dom.window.onload = async () => { + const { Translator } = dom.window; + await Translator.loadCoreTranslationsFallback(); + + const en = require(path.join(__dirname, "..", "..", "..", "tests", "mocks", "translation_test.json")); + setTimeout(() => { + expect(Translator.coreTranslationsFallback).toEqual(en); + done(); + }, 500); + }; + }); + + it("should load core fallback if language cannot be found", (done) => { + const dom = new JSDOM( + `<script>var translations = {}; var Log = {log: () => {}};</script>\ + <script src="file://${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, + { runScripts: "dangerously", resources: "usable" } + ); + dom.window.onload = async () => { + const { Translator } = dom.window; + await Translator.loadCoreTranslations(); + + setTimeout(() => { + expect(Translator.coreTranslationsFallback).toEqual({}); + done(); + }, 500); + }; + }); + }); +}); diff --git a/MagicMirror/tests/unit/classes/utils_spec.js b/MagicMirror/tests/unit/classes/utils_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..bf7a5c593a0f0ea594ace7bf4d3eb22e9be4451a --- /dev/null +++ b/MagicMirror/tests/unit/classes/utils_spec.js @@ -0,0 +1,38 @@ +const colors = require("colors/safe"); +const Utils = require("../../../js/utils"); + +describe("Utils", () => { + describe("colors", () => { + const colorsEnabled = colors.enabled; + + afterEach(() => { + colors.enabled = colorsEnabled; + }); + + it("should have info, warn and error properties", () => { + expect(Utils.colors).toHaveProperty("info"); + expect(Utils.colors).toHaveProperty("warn"); + expect(Utils.colors).toHaveProperty("error"); + }); + + it("properties should be functions", () => { + expect(typeof Utils.colors.info).toBe("function"); + expect(typeof Utils.colors.warn).toBe("function"); + expect(typeof Utils.colors.error).toBe("function"); + }); + + it("should print colored message in supported consoles", () => { + colors.enabled = true; + expect(Utils.colors.info("some informations")).toBe("\u001b[34msome informations\u001b[39m"); + expect(Utils.colors.warn("a warning")).toBe("\u001b[33ma warning\u001b[39m"); + expect(Utils.colors.error("ERROR!")).toBe("\u001b[31mERROR!\u001b[39m"); + }); + + it("should print message in unsupported consoles", () => { + colors.enabled = false; + expect(Utils.colors.info("some informations")).toBe("some informations"); + expect(Utils.colors.warn("a warning")).toBe("a warning"); + expect(Utils.colors.error("ERROR!")).toBe("ERROR!"); + }); + }); +}); diff --git a/MagicMirror/tests/unit/functions/__snapshots__/updatenotification_spec.js.snap b/MagicMirror/tests/unit/functions/__snapshots__/updatenotification_spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..adacd2509d5467b9c0e7c1ff0a9626bc5ba8af09 --- /dev/null +++ b/MagicMirror/tests/unit/functions/__snapshots__/updatenotification_spec.js.snap @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Updatenotification custom module returns status information without hash 1`] = ` +{ + "behind": 7, + "current": "master", + "hash": "", + "isBehindInStatus": false, + "module": "MMM-Fuel", + "tracking": "origin/master", +} +`; + +exports[`Updatenotification MagicMirror on develop returns status information 1`] = ` +{ + "behind": 5, + "current": "develop", + "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", + "isBehindInStatus": false, + "module": "MagicMirror", + "tracking": "origin/develop", +} +`; + +exports[`Updatenotification MagicMirror on develop returns status information early if isBehindInStatus 1`] = ` +{ + "behind": 5, + "current": "develop", + "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", + "isBehindInStatus": true, + "module": "MagicMirror", + "tracking": "origin/develop", +} +`; + +exports[`Updatenotification MagicMirror on master (empty taglist) returns status information 1`] = ` +{ + "behind": 5, + "current": "master", + "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", + "isBehindInStatus": false, + "module": "MagicMirror", + "tracking": "origin/master", +} +`; + +exports[`Updatenotification MagicMirror on master (empty taglist) returns status information early if isBehindInStatus 1`] = ` +{ + "behind": 5, + "current": "master", + "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", + "isBehindInStatus": true, + "module": "MagicMirror", + "tracking": "origin/master", +} +`; + +exports[`Updatenotification MagicMirror on master with match in taglist returns status information 1`] = ` +{ + "behind": 5, + "current": "master", + "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", + "isBehindInStatus": false, + "module": "MagicMirror", + "tracking": "origin/master", +} +`; + +exports[`Updatenotification MagicMirror on master with match in taglist returns status information early if isBehindInStatus 1`] = ` +{ + "behind": 5, + "current": "master", + "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", + "isBehindInStatus": true, + "module": "MagicMirror", + "tracking": "origin/master", +} +`; + +exports[`Updatenotification MagicMirror on master without match in taglist returns status information 1`] = ` +{ + "behind": 0, + "current": "master", + "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", + "isBehindInStatus": false, + "module": "MagicMirror", + "tracking": "origin/master", +} +`; + +exports[`Updatenotification MagicMirror on master without match in taglist returns status information early if isBehindInStatus 1`] = ` +{ + "behind": 0, + "current": "master", + "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", + "isBehindInStatus": true, + "module": "MagicMirror", + "tracking": "origin/master", +} +`; diff --git a/MagicMirror/tests/unit/functions/calendar_spec.js b/MagicMirror/tests/unit/functions/calendar_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..34e16dd36d71efb2681b183cb9f405f0ee10ce94 --- /dev/null +++ b/MagicMirror/tests/unit/functions/calendar_spec.js @@ -0,0 +1,128 @@ +global.moment = require("moment"); + +describe("Functions into modules/default/calendar/calendar.js", () => { + // Fake for use by calendar.js + Module = {}; + Module.definitions = {}; + Module.register = (name, moduleDefinition) => { + Module.definitions[name] = moduleDefinition; + }; + + beforeAll(() => { + // load calendar.js + require("../../../modules/default/calendar/calendar"); + }); + + describe("capFirst", () => { + const words = { + rodrigo: "Rodrigo", + "123m": "123m", + "magic mirror": "Magic mirror", + ",a": ",a", + ñandú: "Ñandú" + }; + + Object.keys(words).forEach((word) => { + it(`for '${word}' should return '${words[word]}'`, () => { + expect(Module.definitions.calendar.capFirst(word)).toBe(words[word]); + }); + }); + }); + + describe("getLocaleSpecification", () => { + it("should return a valid moment.LocaleSpecification for a 12-hour format", () => { + expect(Module.definitions.calendar.getLocaleSpecification(12)).toEqual({ longDateFormat: { LT: "h:mm A" } }); + }); + + it("should return a valid moment.LocaleSpecification for a 24-hour format", () => { + expect(Module.definitions.calendar.getLocaleSpecification(24)).toEqual({ longDateFormat: { LT: "HH:mm" } }); + }); + + it("should return the current system locale when called without timeFormat number", () => { + expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: moment.localeData().longDateFormat("LT") } }); + }); + + it("should return a 12-hour longDateFormat when using the 'en' locale", () => { + const localeBackup = moment.locale(); + moment.locale("en"); + expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "h:mm A" } }); + moment.locale(localeBackup); + }); + + it("should return a 12-hour longDateFormat when using the 'au' locale", () => { + const localeBackup = moment.locale(); + moment.locale("au"); + expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "h:mm A" } }); + moment.locale(localeBackup); + }); + + it("should return a 12-hour longDateFormat when using the 'eg' locale", () => { + const localeBackup = moment.locale(); + moment.locale("eg"); + expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "h:mm A" } }); + moment.locale(localeBackup); + }); + + it("should return a 24-hour longDateFormat when using the 'nl' locale", () => { + const localeBackup = moment.locale(); + moment.locale("nl"); + expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "HH:mm" } }); + moment.locale(localeBackup); + }); + + it("should return a 24-hour longDateFormat when using the 'fr' locale", () => { + const localeBackup = moment.locale(); + moment.locale("fr"); + expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "HH:mm" } }); + moment.locale(localeBackup); + }); + + it("should return a 24-hour longDateFormat when using the 'uk' locale", () => { + const localeBackup = moment.locale(); + moment.locale("uk"); + expect(Module.definitions.calendar.getLocaleSpecification()).toEqual({ longDateFormat: { LT: "HH:mm" } }); + moment.locale(localeBackup); + }); + }); + + describe("shorten", () => { + const strings = { + " String with whitespace at the beginning that needs trimming": { length: 16, return: "String with whit…" }, + "long string that needs shortening": { length: 16, return: "long string that…" }, + "short string": { length: 16, return: "short string" }, + "long string with no maxLength defined": { return: "long string with no maxLength defined" } + }; + + Object.keys(strings).forEach((string) => { + it(`for '${string}' should return '${strings[string].return}'`, () => { + expect(Module.definitions.calendar.shorten(string, strings[string].length)).toBe(strings[string].return); + }); + }); + + it("should return an empty string if shorten is called with a non-string", () => { + expect(Module.definitions.calendar.shorten(100)).toBe(""); + }); + + it("should not shorten the string if shorten is called with a non-number maxLength", () => { + expect(Module.definitions.calendar.shorten("This is a test string", "This is not a number")).toBe("This is a test string"); + }); + + it("should wrap the string instead of shorten it if shorten is called with wrapEvents = true (with maxLength defined as 20)", () => { + expect(Module.definitions.calendar.shorten("This is a wrapEvent test. Should wrap the string instead of shorten it if called with wrapEvent = true", 20, true)).toBe( + "This is a <br>wrapEvent test. Should wrap <br>the string instead of <br>shorten it if called with <br>wrapEvent = true" + ); + }); + + it("should wrap the string instead of shorten it if shorten is called with wrapEvents = true (without maxLength defined, default 25)", () => { + expect(Module.definitions.calendar.shorten("This is a wrapEvent test. Should wrap the string instead of shorten it if called with wrapEvent = true", undefined, true)).toBe( + "This is a wrapEvent <br>test. Should wrap the string <br>instead of shorten it if called <br>with wrapEvent = true" + ); + }); + + it("should wrap and shorten the string in the second line if called with wrapEvents = true and maxTitleLines = 2", () => { + expect(Module.definitions.calendar.shorten("This is a wrapEvent and maxTitleLines test. Should wrap and shorten the string in the second line if called with wrapEvents = true and maxTitleLines = 2", undefined, true, 2)).toBe( + "This is a wrapEvent and <br>maxTitleLines test. Should wrap and …" + ); + }); + }); +}); diff --git a/MagicMirror/tests/unit/functions/cmp_versions_spec.js b/MagicMirror/tests/unit/functions/cmp_versions_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..8367bec6f54b7e0c1f7ebf86a661abd975fe1d95 --- /dev/null +++ b/MagicMirror/tests/unit/functions/cmp_versions_spec.js @@ -0,0 +1,31 @@ +const path = require("path"); +const { JSDOM } = require("jsdom"); + +describe("Test function cmpVersions in js/module.js", () => { + let cmp; + + beforeAll((done) => { + const dom = new JSDOM( + `<script>var Class = {extend: () => { return {}; }};</script>\ + <script src="file://${path.join(__dirname, "..", "..", "..", "js", "module.js")}">`, + { runScripts: "dangerously", resources: "usable" } + ); + dom.window.onload = () => { + const { cmpVersions } = dom.window; + cmp = cmpVersions; + done(); + }; + }); + + it("should return -1 when comparing 2.1 to 2.2", () => { + expect(cmp("2.1", "2.2")).toBe(-1); + }); + + it("should be return 0 when comparing 2.2 to 2.2", () => { + expect(cmp("2.2", "2.2")).toBe(0); + }); + + it("should be return 1 when comparing 1.1 to 1.0", () => { + expect(cmp("1.1", "1.0")).toBe(1); + }); +}); diff --git a/MagicMirror/tests/unit/functions/server_functions_spec.js b/MagicMirror/tests/unit/functions/server_functions_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..3548e38a0e85b633d1230e2d8bf83b2d6fe8a96c --- /dev/null +++ b/MagicMirror/tests/unit/functions/server_functions_spec.js @@ -0,0 +1,149 @@ +const { cors } = require("../../../js/server_functions"); + +describe("server_functions tests", () => { + describe("The cors method", () => { + let fetchResponse; + let fetchResponseHeadersGet; + let fetchResponseHeadersText; + let corsResponse; + let request; + + jest.mock("node-fetch"); + let nodefetch = require("node-fetch"); + let fetchMock; + + beforeEach(() => { + nodefetch.mockReset(); + + fetchResponseHeadersGet = jest.fn(() => {}); + fetchResponseHeadersText = jest.fn(() => {}); + fetchResponse = { + headers: { + get: fetchResponseHeadersGet + }, + text: fetchResponseHeadersText + }; + jest.mock("node-fetch", () => jest.fn()); + nodefetch.mockImplementation(() => fetchResponse); + + fetchMock = nodefetch; + + corsResponse = { + set: jest.fn(() => {}), + send: jest.fn(() => {}) + }; + + request = { + url: `/cors?url=www.test.com` + }; + }); + + test("Calls correct URL once", async () => { + const urlToCall = "http://www.test.com/path?param1=value1"; + request.url = `/cors?url=${urlToCall}`; + + await cors(request, corsResponse); + + expect(fetchMock.mock.calls.length).toBe(1); + expect(fetchMock.mock.calls[0][0]).toBe(urlToCall); + }); + + test("Forewards Content-Type if json", async () => { + fetchResponseHeadersGet.mockImplementation(() => "json"); + + await cors(request, corsResponse); + + expect(fetchResponseHeadersGet.mock.calls.length).toBe(1); + expect(fetchResponseHeadersGet.mock.calls[0][0]).toBe("Content-Type"); + + expect(corsResponse.set.mock.calls.length).toBe(1); + expect(corsResponse.set.mock.calls[0][0]).toBe("Content-Type"); + expect(corsResponse.set.mock.calls[0][1]).toBe("json"); + }); + + test("Forewards Content-Type if xml", async () => { + fetchResponseHeadersGet.mockImplementation(() => "xml"); + + await cors(request, corsResponse); + + expect(fetchResponseHeadersGet.mock.calls.length).toBe(1); + expect(fetchResponseHeadersGet.mock.calls[0][0]).toBe("Content-Type"); + + expect(corsResponse.set.mock.calls.length).toBe(1); + expect(corsResponse.set.mock.calls[0][0]).toBe("Content-Type"); + expect(corsResponse.set.mock.calls[0][1]).toBe("xml"); + }); + + test("Sends correct data from response", async () => { + const responseData = "some data"; + fetchResponseHeadersText.mockImplementation(() => responseData); + + let sentData; + corsResponse.send = jest.fn((input) => { + sentData = input; + }); + + await cors(request, corsResponse); + + expect(fetchResponseHeadersText.mock.calls.length).toBe(1); + expect(sentData).toBe(responseData); + }); + + test("Sends error data from response", async () => { + const error = new Error("error data"); + fetchResponseHeadersText.mockImplementation(() => { + throw error; + }); + + let sentData; + corsResponse.send = jest.fn((input) => { + sentData = input; + }); + + await cors(request, corsResponse); + + expect(fetchResponseHeadersText.mock.calls.length).toBe(1); + expect(sentData).toBe(error); + }); + + test("Fetches with user agent by default", async () => { + await cors(request, corsResponse); + + expect(fetchMock.mock.calls.length).toBe(1); + expect(fetchMock.mock.calls[0][1]).toHaveProperty("headers"); + expect(fetchMock.mock.calls[0][1].headers).toHaveProperty("User-Agent"); + }); + + test("Fetches with specified headers", async () => { + const headersParam = "sendheaders=header1:value1,header2:value2"; + const urlParam = "http://www.test.com/path?param1=value1"; + request.url = `/cors?${headersParam}&url=${urlParam}`; + + await cors(request, corsResponse); + + expect(fetchMock.mock.calls.length).toBe(1); + expect(fetchMock.mock.calls[0][1]).toHaveProperty("headers"); + expect(fetchMock.mock.calls[0][1].headers).toHaveProperty("header1", "value1"); + expect(fetchMock.mock.calls[0][1].headers).toHaveProperty("header2", "value2"); + }); + + test("Sends specified headers", async () => { + fetchResponseHeadersGet.mockImplementation((input) => input.replace("header", "value")); + + const expectedheaders = "expectedheaders=header1,header2"; + const urlParam = "http://www.test.com/path?param1=value1"; + request.url = `/cors?${expectedheaders}&url=${urlParam}`; + + await cors(request, corsResponse); + + expect(fetchMock.mock.calls.length).toBe(1); + expect(fetchMock.mock.calls[0][1]).toHaveProperty("headers"); + expect(corsResponse.set.mock.calls.length).toBe(3); + expect(corsResponse.set.mock.calls[0][0]).toBe("Content-Type"); + expect(corsResponse.set.mock.calls[1][0]).toBe("header1"); + expect(corsResponse.set.mock.calls[1][1]).toBe("value1"); + expect(corsResponse.set.mock.calls[2][0]).toBe("header2"); + expect(corsResponse.set.mock.calls[2][1]).toBe("value2"); + }); + }); +}); diff --git a/MagicMirror/tests/unit/functions/updatenotification_spec.js b/MagicMirror/tests/unit/functions/updatenotification_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..426154ecdb3bd519b2926f7131f13e98da9dc028 --- /dev/null +++ b/MagicMirror/tests/unit/functions/updatenotification_spec.js @@ -0,0 +1,253 @@ +jest.mock("util", () => ({ + ...jest.requireActual("util"), + promisify: jest.fn() +})); + +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + statSync: jest.fn() +})); + +jest.mock("logger", () => ({ + ...jest.requireActual("logger"), + error: jest.fn(), + info: jest.fn() +})); + +describe("Updatenotification", () => { + const execMock = jest.fn(); + + let gitHelper; + + let gitRemoteOut; + let gitRevParseOut; + let gitStatusOut; + let gitFetchOut; + let gitRevListCountOut; + let gitRevListOut; + let gitFetchErr; + let gitTagListOut; + + beforeAll(async () => { + const { promisify } = require("util"); + promisify.mockReturnValue(execMock); + + const GitHelper = require(`../../../modules/default/updatenotification/git_helper`); + gitHelper = new GitHelper(); + }); + + beforeEach(() => { + gitRemoteOut = ""; + gitRevParseOut = ""; + gitStatusOut = ""; + gitFetchOut = ""; + gitRevListCountOut = ""; + gitRevListOut = ""; + gitFetchErr = ""; + gitTagListOut = ""; + + execMock.mockImplementation((command) => { + if (command.includes("git remote -v")) { + return { stdout: gitRemoteOut }; + } else if (command.includes("git rev-parse HEAD")) { + return { stdout: gitRevParseOut }; + } else if (command.includes("git status -sb")) { + return { stdout: gitStatusOut }; + } else if (command.includes("git fetch -n --dry-run")) { + return { stdout: gitFetchOut, stderr: gitFetchErr }; + } else if (command.includes("git rev-list --ancestry-path --count")) { + return { stdout: gitRevListCountOut }; + } else if (command.includes("git rev-list --ancestry-path")) { + return { stdout: gitRevListOut }; + } else if (command.includes("git ls-remote -q --tags --refs")) { + return { stdout: gitTagListOut }; + } + }); + }); + + afterEach(async () => { + gitHelper.gitRepos = []; + + jest.clearAllMocks(); + }); + + describe("MagicMirror on develop", () => { + const moduleName = "MagicMirror"; + + beforeEach(async () => { + gitRemoteOut = "origin\tgit@github.com:MichMich/MagicMirror.git (fetch)\norigin\tgit@github.com:MichMich/MagicMirror.git (push)\n"; + gitRevParseOut = "332e429a41f1a2339afd4f0ae96dd125da6beada"; + gitStatusOut = "## develop...origin/develop\n M tests/unit/functions/updatenotification_spec.js\n"; + gitFetchErr = "From github.com:MichMich/MagicMirror\n60e0377..332e429 develop -> origin/develop\n"; + gitRevListCountOut = "5"; + + await gitHelper.add(moduleName); + }); + + it("returns status information", async () => { + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(5); + }); + + it("returns status information early if isBehindInStatus", async () => { + gitStatusOut = "## develop...origin/develop [behind 5]"; + + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(3); + }); + + it("excludes repo if status can't be retrieved", async () => { + const errorMessage = "Failed to retrieve status"; + execMock.mockRejectedValueOnce(errorMessage); + + const repos = await gitHelper.getRepos(); + expect(repos.length).toBe(0); + + const { error } = require("logger"); + expect(error).toHaveBeenCalledWith(`Failed to retrieve repo info for ${moduleName}: Failed to retrieve status`); + }); + }); + + describe("MagicMirror on master (empty taglist)", () => { + const moduleName = "MagicMirror"; + + beforeEach(async () => { + gitRemoteOut = "origin\tgit@github.com:MichMich/MagicMirror.git (fetch)\norigin\tgit@github.com:MichMich/MagicMirror.git (push)\n"; + gitRevParseOut = "332e429a41f1a2339afd4f0ae96dd125da6beada"; + gitStatusOut = "## master...origin/master\n M tests/unit/functions/updatenotification_spec.js\n"; + gitFetchErr = "From github.com:MichMich/MagicMirror\n60e0377..332e429 master -> origin/master\n"; + gitRevListCountOut = "5"; + + await gitHelper.add(moduleName); + }); + + it("returns status information", async () => { + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(7); + }); + + it("returns status information early if isBehindInStatus", async () => { + gitStatusOut = "## master...origin/master [behind 5]"; + + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(7); + }); + + it("excludes repo if status can't be retrieved", async () => { + const errorMessage = "Failed to retrieve status"; + execMock.mockRejectedValueOnce(errorMessage); + + const repos = await gitHelper.getRepos(); + expect(repos.length).toBe(0); + + const { error } = require("logger"); + expect(error).toHaveBeenCalledWith(`Failed to retrieve repo info for ${moduleName}: Failed to retrieve status`); + }); + }); + + describe("MagicMirror on master with match in taglist", () => { + const moduleName = "MagicMirror"; + + beforeEach(async () => { + gitRemoteOut = "origin\tgit@github.com:MichMich/MagicMirror.git (fetch)\norigin\tgit@github.com:MichMich/MagicMirror.git (push)\n"; + gitRevParseOut = "332e429a41f1a2339afd4f0ae96dd125da6beada"; + gitStatusOut = "## master...origin/master\n M tests/unit/functions/updatenotification_spec.js\n"; + gitFetchErr = "From github.com:MichMich/MagicMirror\n60e0377..332e429 master -> origin/master\n"; + gitRevListCountOut = "5"; + gitTagListOut = "332e429a41f1a2339afd4f0ae96dd125da6beada...tag...\n"; + gitRevListOut = "332e429a41f1a2339afd4f0ae96dd125da6beada\n"; + + await gitHelper.add(moduleName); + }); + + it("returns status information", async () => { + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(7); + }); + + it("returns status information early if isBehindInStatus", async () => { + gitStatusOut = "## master...origin/master [behind 5]"; + + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(7); + }); + + it("excludes repo if status can't be retrieved", async () => { + const errorMessage = "Failed to retrieve status"; + execMock.mockRejectedValueOnce(errorMessage); + + const repos = await gitHelper.getRepos(); + expect(repos.length).toBe(0); + + const { error } = require("logger"); + expect(error).toHaveBeenCalledWith(`Failed to retrieve repo info for ${moduleName}: Failed to retrieve status`); + }); + }); + + describe("MagicMirror on master without match in taglist", () => { + const moduleName = "MagicMirror"; + + beforeEach(async () => { + gitRemoteOut = "origin\tgit@github.com:MichMich/MagicMirror.git (fetch)\norigin\tgit@github.com:MichMich/MagicMirror.git (push)\n"; + gitRevParseOut = "332e429a41f1a2339afd4f0ae96dd125da6beada"; + gitStatusOut = "## master...origin/master\n M tests/unit/functions/updatenotification_spec.js\n"; + gitFetchErr = "From github.com:MichMich/MagicMirror\n60e0377..332e429 master -> origin/master\n"; + gitRevListCountOut = "5"; + gitTagListOut = "xxxe429a41f1a2339afd4f0ae96dd125da6beada...tag...\n"; + gitRevListOut = "332e429a41f1a2339afd4f0ae96dd125da6beada\n"; + + await gitHelper.add(moduleName); + }); + + it("returns status information", async () => { + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(7); + }); + + it("returns status information early if isBehindInStatus", async () => { + gitStatusOut = "## master...origin/master [behind 5]"; + + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(7); + }); + + it("excludes repo if status can't be retrieved", async () => { + const errorMessage = "Failed to retrieve status"; + execMock.mockRejectedValueOnce(errorMessage); + + const repos = await gitHelper.getRepos(); + expect(repos.length).toBe(0); + + const { error } = require("logger"); + expect(error).toHaveBeenCalledWith(`Failed to retrieve repo info for ${moduleName}: Failed to retrieve status`); + }); + }); + + describe("custom module", () => { + const moduleName = "MMM-Fuel"; + + beforeEach(async () => { + gitRemoteOut = `origin\thttps://github.com/fewieden/${moduleName}.git (fetch)\norigin\thttps://github.com/fewieden/${moduleName}.git (push)\n`; + gitRevParseOut = "9d8310163da94441073a93cead711ba43e8888d0"; + gitStatusOut = "## master...origin/master"; + gitFetchErr = `From https://github.com/fewieden/${moduleName}\n19f7faf..9d83101 master -> origin/master`; + gitRevListCountOut = "7"; + + await gitHelper.add(moduleName); + }); + + it("returns status information without hash", async () => { + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(4); + }); + }); +}); diff --git a/MagicMirror/tests/unit/functions/weather_object_spec.js b/MagicMirror/tests/unit/functions/weather_object_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..5f9444e457ca43e68b44a7d9be8cdfcff6e066fb --- /dev/null +++ b/MagicMirror/tests/unit/functions/weather_object_spec.js @@ -0,0 +1,84 @@ +const WeatherObject = require("../../../modules/default/weather/weatherobject"); +const WeatherUtils = require("../../../modules/default/weather/weatherutils"); + +global.moment = require("moment-timezone"); +global.SunCalc = require("suncalc"); + +describe("WeatherObject", () => { + let originalTimeZone; + let weatherobject; + + beforeAll(() => { + originalTimeZone = moment.tz.guess(); + moment.tz.setDefault("Africa/Dar_es_Salaam"); + weatherobject = new WeatherObject(); + }); + + it("should return true for daytime at noon", () => { + weatherobject.date = moment("12:00", "HH:mm"); + weatherobject.updateSunTime(-6.774877582342688, 37.63345667023327); + expect(weatherobject.isDayTime()).toBe(true); + }); + + it("should return false for daytime at midnight", () => { + weatherobject.date = moment("00:00", "HH:mm"); + weatherobject.updateSunTime(-6.774877582342688, 37.63345667023327); + expect(weatherobject.isDayTime()).toBe(false); + }); + + it("should return sunrise as the next sunaction", () => { + weatherobject.updateSunTime(-6.774877582342688, 37.63345667023327); + let midnight = moment("00:00", "HH:mm"); + expect(weatherobject.nextSunAction(midnight)).toBe("sunrise"); + }); + + it("should return sunset as the next sunaction", () => { + weatherobject.updateSunTime(-6.774877582342688, 37.63345667023327); + let noon = moment(weatherobject.sunrise).hour(14); + expect(weatherobject.nextSunAction(noon)).toBe("sunset"); + }); + + it("should return an already defined feelsLike info", () => { + weatherobject.feelsLikeTemp = "feelsLikeTempValue"; + expect(weatherobject.feelsLike()).toBe("feelsLikeTempValue"); + }); + + afterAll(() => { + moment.tz.setDefault(originalTimeZone); + }); +}); + +describe("WeatherUtils", () => { + it("should convert windspeed correctly from mps to beaufort", () => { + expect(Math.round(WeatherUtils.convertWind(5, "beaufort"))).toBe(3); + expect(Math.round(WeatherUtils.convertWind(300, "beaufort"))).toBe(12); + }); + + it("should convert windspeed correctly from mps to kmh", () => { + expect(Math.round(WeatherUtils.convertWind(11.75, "kmh"))).toBe(42); + }); + + it("should convert windspeed correctly from mps to knots", () => { + expect(Math.round(WeatherUtils.convertWind(10, "knots"))).toBe(19); + }); + + it("should convert windspeed correctly from mph to mps", () => { + expect(Math.round(WeatherUtils.convertWindToMetric(93.951324266285))).toBe(42); + }); + + it("should convert windspeed correctly from kmh to mps", () => { + expect(Math.round(WeatherUtils.convertWindToMs(151.2))).toBe(42); + }); + + it("should convert wind direction correctly from cardinal to value", () => { + expect(WeatherUtils.convertWindDirection("SSE")).toBe(157); + }); + + it("should return a calculated feelsLike info", () => { + expect(WeatherUtils.calculateFeelsLike(0, 20, 40)).toBe(-9.444444444444445); + }); + + it("should return a calculated feelsLike info", () => { + expect(WeatherUtils.calculateFeelsLike(30, 0, 60)).toBe(32.8320322777777); + }); +}); diff --git a/MagicMirror/tests/unit/global_vars/defaults_modules_spec.js b/MagicMirror/tests/unit/global_vars/defaults_modules_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6a943bd004bdc8691bf1ea2c9a708b1e20c52536 --- /dev/null +++ b/MagicMirror/tests/unit/global_vars/defaults_modules_spec.js @@ -0,0 +1,14 @@ +const fs = require("fs"); +const path = require("path"); + +const root_path = path.join(__dirname, "../../.."); + +describe("Default modules set in modules/default/defaultmodules.js", () => { + const expectedDefaultModules = require("../../../modules/default/defaultmodules"); + + for (const defaultModule of expectedDefaultModules) { + it(`contains a folder for modules/default/${defaultModule}"`, () => { + expect(fs.existsSync(path.join(root_path, "modules/default", defaultModule))).toBe(true); + }); + } +}); diff --git a/MagicMirror/tests/unit/global_vars/root_path_spec.js b/MagicMirror/tests/unit/global_vars/root_path_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..78a96d091b362ea5ffd9f66e47841aacabaff2ad --- /dev/null +++ b/MagicMirror/tests/unit/global_vars/root_path_spec.js @@ -0,0 +1,28 @@ +const fs = require("fs"); +const path = require("path"); + +const root_path = path.join(__dirname, "../../.."); +const version = require(`${__dirname}/../../../package.json`).version; + +describe("'global.root_path' set in js/app.js", () => { + const expectedSubPaths = ["modules", "serveronly", "js", "js/app.js", "js/main.js", "js/electron.js", "config"]; + + expectedSubPaths.forEach((subpath) => { + it(`contains a file/folder "${subpath}"`, () => { + expect(fs.existsSync(path.join(root_path, subpath))).toBe(true); + }); + }); + + it("should not modify global.root_path for testing", () => { + expect(global.root_path).toBe(undefined); + }); + + it("should not modify global.version for testing", () => { + expect(global.version).toBe(undefined); + }); + + it("should expect the global.version equals package.json file", () => { + const versionPackage = JSON.parse(fs.readFileSync("package.json", "utf8")).version; + expect(version).toBe(versionPackage); + }); +}); diff --git a/MagicMirror/tests/unit/modules/default/utils_spec.js b/MagicMirror/tests/unit/modules/default/utils_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f9c9390ae551824aa8a9ac4493f37635f886689a --- /dev/null +++ b/MagicMirror/tests/unit/modules/default/utils_spec.js @@ -0,0 +1,112 @@ +const { performWebRequest } = require("../../../../modules/default/utils"); + +const nodeVersion = process.version.match(/^v(\d+)\.*/)[1]; + +describe("Utils tests", () => { + describe("The performWebRequest-method", () => { + if (nodeVersion > 18) { + const locationHost = "localhost:8080"; + const locationProtocol = "http"; + + let fetchResponse; + let fetchMock; + let url; + + beforeEach(() => { + fetchResponse = new Response(); + global.fetch = jest.fn(() => Promise.resolve(fetchResponse)); + fetchMock = global.fetch; + + url = "www.test.com"; + }); + + describe("When using cors proxy", () => { + Object.defineProperty(global, "location", { + value: { + host: locationHost, + protocol: locationProtocol + } + }); + + test("Calls correct URL once", async () => { + const urlToCall = "http://www.test.com/path?param1=value1"; + url = urlToCall; + + await performWebRequest(url, "json", true); + + expect(fetchMock.mock.calls.length).toBe(1); + expect(fetchMock.mock.calls[0][0]).toBe(`${locationProtocol}//${locationHost}/cors?url=${urlToCall}`); + }); + + test("Sends correct headers", async () => { + const urlToCall = "http://www.test.com/path?param1=value1"; + url = urlToCall; + const headers = [ + { name: "header1", value: "value1" }, + { name: "header2", value: "value2" } + ]; + + await performWebRequest(url, "json", true, headers); + + expect(fetchMock.mock.calls.length).toBe(1); + expect(fetchMock.mock.calls[0][0]).toBe(`${locationProtocol}//${locationHost}/cors?sendheaders=header1:value1,header2:value2&url=${urlToCall}`); + }); + }); + + describe("When not using cors proxy", () => { + test("Calls correct URL once", async () => { + const urlToCall = "http://www.test.com/path?param1=value1"; + url = urlToCall; + + await performWebRequest(url); + + expect(fetchMock.mock.calls.length).toBe(1); + expect(fetchMock.mock.calls[0][0]).toBe(urlToCall); + }); + + test("Sends correct headers", async () => { + const urlToCall = "http://www.test.com/path?param1=value1"; + url = urlToCall; + const headers = [ + { name: "header1", value: "value1" }, + { name: "header2", value: "value2" } + ]; + + await performWebRequest(url, "json", false, headers); + + const expectedHeaders = { headers: { header1: "value1", header2: "value2" } }; + expect(fetchMock.mock.calls.length).toBe(1); + expect(fetchMock.mock.calls[0][1]).toStrictEqual(expectedHeaders); + }); + }); + + describe("When receiving json format", () => { + test("Returns undefined when no data is received", async () => { + const response = await performWebRequest(url); + + expect(response).toBe(undefined); + }); + + test("Returns object when data is received", async () => { + fetchResponse = new Response('{"body": "some content"}'); + + const response = await performWebRequest(url); + + expect(response.body).toBe("some content"); + }); + + test("Returns expected headers when data is received", async () => { + fetchResponse = new Response('{"body": "some content"}', { headers: { header1: "value1", header2: "value2" } }); + + const response = await performWebRequest(url, "json", false, undefined, ["header1"]); + + expect(response.headers.length).toBe(1); + expect(response.headers[0].name).toBe("header1"); + expect(response.headers[0].value).toBe("value1"); + }); + }); + } else { + test("Always ok, need one test", () => {}); + } + }); +}); diff --git a/MagicMirror/tests/unit/modules/default/weather/weather_utils_spec.js b/MagicMirror/tests/unit/modules/default/weather/weather_utils_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..8aefd3c9fe3672f4148e0654ba7729941d20a1ce --- /dev/null +++ b/MagicMirror/tests/unit/modules/default/weather/weather_utils_spec.js @@ -0,0 +1,45 @@ +const weather = require("../../../../../modules/default/weather/weatherutils"); + +describe("Weather utils tests", () => { + describe("convertPrecipitationUnit tests", () => { + it("Should keep value and unit if outputUnit is undefined", () => { + const values = [1, 2]; + const units = ["mm", "cm"]; + + for (let i = 0; i < values.length; i++) { + var result = weather.convertPrecipitationUnit(values[i], units[i], undefined); + expect(result).toBe(`${values[i].toFixed(2)} ${units[i]}`); + } + }); + + it("Should keep value and unit if outputUnit is metric", () => { + const values = [1, 2]; + const units = ["mm", "cm"]; + + for (let i = 0; i < values.length; i++) { + var result = weather.convertPrecipitationUnit(values[i], units[i], "metric"); + expect(result).toBe(`${values[i].toFixed(2)} ${units[i]}`); + } + }); + + it("Should use mm unit if input unit is undefined", () => { + const values = [1, 2]; + + for (let i = 0; i < values.length; i++) { + var result = weather.convertPrecipitationUnit(values[i], undefined, "metric"); + expect(result).toBe(`${values[i].toFixed(2)} mm`); + } + }); + + it("Should convert value and unit if outputUnit is imperial", () => { + const values = [1, 2]; + const units = ["mm", "cm"]; + const expectedValues = [0.04, 0.79]; + + for (let i = 0; i < values.length; i++) { + var result = weather.convertPrecipitationUnit(values[i], units[i], "imperial"); + expect(result).toBe(`${expectedValues[i]} in`); + } + }); + }); +}); diff --git a/MagicMirror/tests/utils/test_sequencer.js b/MagicMirror/tests/utils/test_sequencer.js new file mode 100644 index 0000000000000000000000000000000000000000..ecb989ee166202baa6fe09f9a4fddaa631b1c532 --- /dev/null +++ b/MagicMirror/tests/utils/test_sequencer.js @@ -0,0 +1,30 @@ +const TestSequencer = require("@jest/test-sequencer").default; + +class CustomSequencer extends TestSequencer { + sort(tests) { + const orderPath = ["unit", "electron", "e2e"]; + return tests.sort((testA, testB) => { + let indexA = -1; + let indexB = -1; + const reg = ".*/tests/([^/]*).*"; + + // move calendar and newsfeed at the end + if (testA.path.includes("e2e/modules/calendar_spec") || testA.path.includes("e2e/modules/newsfeed_spec")) return 1; + if (testB.path.includes("e2e/modules/calendar_spec") || testB.path.includes("e2e/modules/newsfeed_spec")) return -1; + + let matchA = new RegExp(reg, "g").exec(testA.path); + if (matchA.length > 0) indexA = orderPath.indexOf(matchA[1]); + + let matchB = new RegExp(reg, "g").exec(testB.path); + if (matchB.length > 0) indexB = orderPath.indexOf(matchB[1]); + + if (indexA === indexB) return 0; + + if (indexA === -1) return 1; + if (indexB === -1) return -1; + return indexA < indexB ? -1 : 1; + }); + } +} + +module.exports = CustomSequencer; diff --git a/MagicMirror/tests/utils/weather_mocker.js b/MagicMirror/tests/utils/weather_mocker.js new file mode 100644 index 0000000000000000000000000000000000000000..81e912451e65989392cd2dc4c79a2683a779aef5 --- /dev/null +++ b/MagicMirror/tests/utils/weather_mocker.js @@ -0,0 +1,43 @@ +const fs = require("fs"); +const path = require("path"); +const _ = require("lodash"); + +/** + * @param {string} type what data to read, can be "current" "forecast" or "hourly + * @param {object} extendedData extra data to add to the default mock data + * @returns {string} mocked current weather data + */ +const readMockData = (type, extendedData = {}) => { + let fileName; + + switch (type) { + case "forecast": + fileName = "weather_forecast.json"; + break; + case "hourly": + fileName = "weather_hourly.json"; + break; + case "current": + default: + fileName = "weather_current.json"; + break; + } + + return JSON.stringify(_.merge({}, JSON.parse(fs.readFileSync(path.resolve(`${__dirname}/../mocks/${fileName}`)).toString()), extendedData)); +}; + +const injectMockData = (configFileName, extendedData = {}) => { + let mockWeather; + if (configFileName.includes("forecast")) { + mockWeather = readMockData("forecast", extendedData); + } else if (configFileName.includes("hourly")) { + mockWeather = readMockData("hourly", extendedData); + } else { + mockWeather = readMockData("current", extendedData); + } + let content = fs.readFileSync(path.resolve(`${__dirname}../../../${configFileName}`)).toString(); + content = content.replace("#####WEATHERDATA#####", mockWeather); + fs.writeFileSync(path.resolve(`${__dirname}../../../config/config.js`), content); +}; + +module.exports = { injectMockData }; diff --git a/MagicMirror/th.json b/MagicMirror/th.json new file mode 100644 index 0000000000000000000000000000000000000000..26f472b7b9e4a7f7bd1c37d597c4dd82b9e28e72 --- /dev/null +++ b/MagicMirror/th.json @@ -0,0 +1,43 @@ +{ + "LOADING": "à¸à¸³à¸¥à¸±à¸‡à¹‚หลด …", + + "TODAY": "วันนี้", + "TOMORROW": "พรุ่งนี้", + "RUNNING": "สิ้นสุดใน", + "EMPTY": "ไม่มีà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸—ี่à¸à¸³à¸¥à¸±à¸‡à¸ˆà¸°à¸¡à¸²à¸–ึง", + "WEEK": "สัปดาห์ที่ {weekNumber}", + + "N": "น", + "NNE": "น.ต.à¸.น.", + "NE": "ต.à¸.น.", + "ENE": "ต.à¸.ต.à¸.น.", + "E": "ต.à¸.", + "ESE": "ต.à¸.ต.à¸.ต.", + "SE": "ต.à¸.ต.", + "SSE": "ต.ต.à¸.ต.", + "S": "ต.", + "SSW": "ต.ต.ต.ต.", + "SW": "ต.ต.ต.", + "WSW": "ต.ต.ต.ต.ต.", + "W": "ต.ต.", + "WNW": "ต.ต.ต.ต.น.", + "NW": "ต.ต.น.", + "NNW": "น.ต.ต.น.", + + "PRECIP_POP": "ความà¹à¸¡à¹ˆà¸™à¸¢à¸³", + "PRECIP_AMOUNT": "ปริมาณน้ำà¸à¸™", + + "MODULE_CONFIG_CHANGED": "ตัวเลืà¸à¸à¸à¸²à¸£à¸à¸³à¸«à¸™à¸”ค่าสำหรับโมดูล {MODULE_NAME} มีà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡\nโปรดตรวจสà¸à¸šà¹€à¸à¸à¸ªà¸²à¸£à¸›à¸£à¸°à¸à¸à¸š", + "MODULE_CONFIG_ERROR": "เà¸à¸´à¸”ข้à¸à¸œà¸´à¸”พลาดในโมดูล {MODULE_NAME} {ERROR}", + "MODULE_ERROR_MALFORMED_URL": "URL ผิดรูปà¹à¸šà¸š", + "MODULE_ERROR_NO_CONNECTION": "ไม่มีà¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸à¸à¸´à¸™à¹€à¸—à¸à¸£à¹Œà¹€à¸™à¹‡à¸•.", + "MODULE_ERROR_UNAUTHORIZED": "à¸à¸²à¸£à¸à¸™à¸¸à¸à¸²à¸•ล้มเหลว", + "MODULE_ERROR_UNSPECIFIED": "ตรวจสà¸à¸šà¸šà¸±à¸™à¸—ึà¸à¸ªà¸³à¸«à¸£à¸±à¸šà¸£à¸²à¸¢à¸¥à¸°à¹€à¸à¸µà¸¢à¸”เพิ่มเติม", + + "NEWSFEED_NO_ITEMS": "ไม่มีข่าวในขณะนี้", + + "UPDATE_NOTIFICATION": "MagicMirror² มีà¸à¸²à¸£à¸à¸±à¸›à¹€à¸”ต", + "UPDATE_NOTIFICATION_MODULE": "มีà¸à¸²à¸£à¸à¸±à¸›à¹€à¸”ตสำหรับโมดูล {MODULE_NAME}", + "UPDATE_INFO_SINGLE": "à¸à¸²à¸£à¸•ิดตั้งปัจจุบันถูà¸à¸„à¸à¸¡à¸¡à¸´à¸— {COMMIT_COUNT} รายà¸à¸²à¸£à¹ƒà¸™à¸ªà¸²à¸‚า {BRANCH_NAME}", + "UPDATE_INFO_MULTIPLE": "à¸à¸²à¸£à¸•ิดตั้งปัจจุบันคืภ{COMMIT_COUNT} เป็นคà¸à¸¡à¸¡à¸´à¸—ที่à¸à¸¢à¸¹à¹ˆà¹€à¸šà¸·à¹‰à¸à¸‡à¸«à¸¥à¸±à¸‡à¹ƒà¸™à¸ªà¸²à¸‚า {BRANCH_NAME}" +} diff --git a/MagicMirror/translations/af.json b/MagicMirror/translations/af.json new file mode 100644 index 0000000000000000000000000000000000000000..7ef2bd558c83eb2453e2d1f2722b290b43ceb64c --- /dev/null +++ b/MagicMirror/translations/af.json @@ -0,0 +1,32 @@ +{ + "LOADING": "Besig om te laai …", + + "YESTERDAY": "Gister", + "TODAY": "Vandag", + "TOMORROW": "Môre", + "DAYAFTERTOMORROW": "Oormôre", + "RUNNING": "Eindig in", + "EMPTY": "Geen komende gebeurtenisse.", + + "N": "N", + "NNE": "NNO", + "NE": "NO", + "ENE": "ONO", + "E": "O", + "ESE": "OSO", + "SE": "SO", + "SSE": "SSO", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "UPDATE_NOTIFICATION": "MagicMirror² update beskikbaar.", + "UPDATE_NOTIFICATION_MODULE": "Update beskikbaar vir {MODULE_NAME} module.", + "UPDATE_INFO_SINGLE": "Die huidige installasie is {COMMIT_COUNT} commit agter op die {BRANCH_NAME} branch.", + "UPDATE_INFO_MULTIPLE": "Die huidige installasie is {COMMIT_COUNT} commits agter op die {BRANCH_NAME} branch." +} diff --git a/MagicMirror/translations/bg.json b/MagicMirror/translations/bg.json new file mode 100644 index 0000000000000000000000000000000000000000..cd0c01a2dffcec578784ca62232449dd40252079 --- /dev/null +++ b/MagicMirror/translations/bg.json @@ -0,0 +1,34 @@ +{ + "LOADING": "Зареждане на …", + + "DAYBEFOREYESTERDAY": "Завчера", + "YESTERDAY": "Вчера", + "TODAY": "ДнеÑ", + "TOMORROW": "Утре", + "DAYAFTERTOMORROW": "Вдругиден", + "RUNNING": "Свършва Ñлед", + "EMPTY": "ÐÑма предÑтоÑщи ÑъбитиÑ.", + "WEEK": "Седмица {weekNumber}", + + "N": "С", + "NNE": "ССИ", + "NE": "СИ", + "ENE": "ИСИ", + "E": "И", + "ESE": "ИЮИ", + "SE": "ЮИ", + "SSE": "ЮЮИ", + "S": "Ю", + "SSW": "ЮЮЗ", + "SW": "ЮЗ", + "WSW": "ЗЮЗ", + "W": "З", + "WNW": "ЗСЗ", + "NW": "СЗ", + "NNW": "ССЗ", + + "UPDATE_NOTIFICATION": "Ðалична е Ð°ÐºÑ‚ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð·Ð° MagicMirror².", + "UPDATE_NOTIFICATION_MODULE": "Ðалична е Ð°ÐºÑ‚ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð·Ð° модула „{MODULE_NAME}“.", + "UPDATE_INFO_SINGLE": "ИнÑталираната верÑÐ¸Ñ Ðµ Ñ {COMMIT_COUNT} Ñ€ÐµÐ²Ð¸Ð·Ð¸Ñ Ð½Ð°Ð·Ð°Ð´ от клона „{BRANCH_NAME}“.", + "UPDATE_INFO_MULTIPLE": "ИнÑталираната верÑÐ¸Ñ Ðµ Ñ {COMMIT_COUNT} ревизии назад от клона „{BRANCH_NAME}“." +} diff --git a/MagicMirror/translations/ca.json b/MagicMirror/translations/ca.json new file mode 100644 index 0000000000000000000000000000000000000000..73b8948c2976d64106b162c9090d804aab5048da --- /dev/null +++ b/MagicMirror/translations/ca.json @@ -0,0 +1,33 @@ +{ + "LOADING": "Carregant …", + + "YESTERDAY": "Ahir", + "TODAY": "Avui", + "TOMORROW": "Demà ", + "DAYAFTERTOMORROW": "Demà passat", + "RUNNING": "Acaba en", + "EMPTY": "No hi ha esdeveniments programats.", + "WEEK": "Setmana", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSO", + "SW": "SO", + "WSW": "OSO", + "W": "O", + "WNW": "ONO", + "NW": "NO", + "NNW": "NNO", + + "UPDATE_NOTIFICATION": "MagicMirror² actualizació disponible.", + "UPDATE_NOTIFICATION_MODULE": "Disponible una actualizació per al mòdul {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "La teva instal·lació actual està {COMMIT_COUNT} commit canvis darrere de la branca {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "La teva instal·lació actual està {COMMIT_COUNT} commits canvis darrere de la branca {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/cs.json b/MagicMirror/translations/cs.json new file mode 100644 index 0000000000000000000000000000000000000000..74a0896f0eb9da40d7d8c659d55868048aa2475e --- /dev/null +++ b/MagicMirror/translations/cs.json @@ -0,0 +1,39 @@ +{ + "LOADING": "NaÄÃtánà …", + + "DAYBEFOREYESTERDAY": "PÅ™edevÄÃrem", + "YESTERDAY": "VÄera", + "TODAY": "Dnes", + "TOMORROW": "ZÃtra", + "DAYAFTERTOMORROW": "PozÃtÅ™Ã", + "RUNNING": "KonÄà za", + "EMPTY": "Žádné nadcházejÃcà události.", + "WEEK": "{weekNumber}. týden", + + "N": "S", + "NNE": "SSV", + "NE": "SV", + "ENE": "VSV", + "E": "V", + "ESE": "VJV", + "SE": "JV", + "SSE": "JJV", + "S": "J", + "SSW": "JJZ", + "SW": "JZ", + "WSW": "ZJZ", + "W": "Z", + "WNW": "ZSZ", + "NW": "SZ", + "NNW": "SSZ", + + "FEELS": "PocitovÄ› {DEGREE}", + "PRECIP_POP": "PravdÄ›podobnost deÅ¡tÄ›", + + "NEWSFEED_NO_ITEMS": "Žádné zprávy.", + + "UPDATE_NOTIFICATION": "Dostupná aktualizace pro MagicMirror².", + "UPDATE_NOTIFICATION_MODULE": "Dostupná aktualizace pro modul {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "SouÄasná instalace je na vÄ›tvi {BRANCH_NAME} pozadu o {COMMIT_COUNT} commit.", + "UPDATE_INFO_MULTIPLE": "SouÄasná instalace je na vÄ›tvi {BRANCH_NAME} pozadu o {COMMIT_COUNT} commits." +} diff --git a/MagicMirror/translations/cv.json b/MagicMirror/translations/cv.json new file mode 100644 index 0000000000000000000000000000000000000000..34fc4fc2b55b1d08268610c58f484aea38d601cb --- /dev/null +++ b/MagicMirror/translations/cv.json @@ -0,0 +1,35 @@ +{ + "LOADING": "Тиенет …", + + "YESTERDAY": "Знон", + "TODAY": "ПаÑн", + "TOMORROW": "Ыран", + "DAYAFTERTOMORROW": "Виҫмине", + "RUNNING": "Хальхи", + "EMPTY": "ÐŸÑƒÐ»Ð°Ñ Ó—Ò« ҫук", + "WEEK": "{weekNumber} Ñрне", + + "N": "Òª", + "NNE": "ҪҪТ", + "NE": "ҪТ", + "ENE": "ТҪТ", + "E": "Т", + "ESE": "ТКТ", + "SE": "КТ", + "SSE": "ККТ", + "S": "К", + "SSW": "ККÐ", + "SW": "КÐ", + "WSW": "ÐКÐ", + "W": "Ð", + "WNW": "ÐÒªÐ", + "NW": "ÒªÐ", + "NNW": "ÒªÒªÐ", + + "FEELS": "Туйӑннӑ {DEGREE}", + + "UPDATE_NOTIFICATION": "MagicMirror² валли ҫӗнетӳ пур.", + "UPDATE_NOTIFICATION_MODULE": "{MODULE_NAME} модуль валли ҫӗнетӳ пур.", + "UPDATE_INFO_SINGLE": "Ҫак инÑталлÑци {BRANCH_NAME} commit турат {COMMIT_COUNT} коммитпа ÐºÐ°Ñ ÑƒÐ¹Ñ€Ó‘Ð»Ñа тӑрать.", + "UPDATE_INFO_MULTIPLE": "Ҫак инÑталлÑци {BRANCH_NAME} commit турат {COMMIT_COUNT} коммитпа ÐºÐ°Ñ ÑƒÐ¹Ñ€Ó‘Ð»Ñа тӑрать." +} diff --git a/MagicMirror/translations/cy.json b/MagicMirror/translations/cy.json new file mode 100644 index 0000000000000000000000000000000000000000..531d811633f49a8b6f82bc079e8f37f4e7001084 --- /dev/null +++ b/MagicMirror/translations/cy.json @@ -0,0 +1,33 @@ +{ + "LOADING": "Llwytho …", + + "YESTERDAY": "Ddoe", + "TODAY": "Heddiw", + "TOMORROW": "Yfory", + "DAYAFTERTOMORROW": "Drennydd", + "RUNNING": "Gorffen mewn", + "EMPTY": "Dim digwyddiadau.", + "WEEK": "Wythnos {weekNumber}", + + "N": "Go", + "NNE": "GoGoDw", + "NE": "GoDw", + "ENE": "DwGoDw", + "E": "Dw", + "ESE": "DwDeDw", + "SE": "DwDe", + "SSE": "DeDeDw", + "S": "De", + "SSW": "DeDeGr", + "SW": "DeGr", + "WSW": "GrDeGr", + "W": "Gr", + "WNW": "GrGoGr", + "NW": "GoGr", + "NNW": "GoGoGe", + + "UPDATE_NOTIFICATION": "MagicMirror² mwy diweddar yn barod.", + "UPDATE_NOTIFICATION_MODULE": "Mae diweddaraiad ar gyfer y modiwl {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "Mae'r fersiwn bresenol {COMMIT_COUNT} commit tu ôl i'r gangen {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "Mae'r fersiwn bresenol {COMMIT_COUNT} commit tu ôl i'r gangen {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/da.json b/MagicMirror/translations/da.json new file mode 100644 index 0000000000000000000000000000000000000000..ed32a142cd2b9c6c5eb6a08b3efd588ebb0b5e61 --- /dev/null +++ b/MagicMirror/translations/da.json @@ -0,0 +1,46 @@ +{ + "LOADING": "Indlæser …", + + "DAYBEFOREYESTERDAY": "ForgÃ¥rs", + "YESTERDAY": "I gÃ¥r", + "TODAY": "I dag", + "TOMORROW": "I morgen", + "DAYAFTERTOMORROW": "I overmorgen", + "RUNNING": "Slutter om", + "EMPTY": "Ingen kommende begivenheder.", + "WEEK": "Uge {weekNumber}", + + "N": "N", + "NNE": "NNØ", + "NE": "NØ", + "ENE": "ØNØ", + "E": "Ø", + "ESE": "ØSØ", + "SE": "SØ", + "SSE": "SSØ", + "S": "S", + "SSW": "SSV", + "SW": "SV", + "WSW": "VSV", + "W": "V", + "WNW": "VNV", + "NW": "NV", + "NNW": "NNV", + + "FEELS": "Føles som {DEGREE}", + "PRECIP_POP": "Sandsynlighed for nedbør", + + "MODULE_CONFIG_CHANGED": "Konfigurationsmulighederne for {MODULE_NAME} modulet er ændret.\nSe venligst dokumentationen.", + "MODULE_CONFIG_ERROR": "Fejl i {MODULE_NAME} modulet. {ERROR}", + "MODULE_ERROR_MALFORMED_URL": "Forkert url.", + "MODULE_ERROR_NO_CONNECTION": "Ingen internetforbindelse.", + "MODULE_ERROR_UNAUTHORIZED": "Godkendelse mislykkedes.", + "MODULE_ERROR_UNSPECIFIED": "Tjek logfiler for flere detaljer.", + + "NEWSFEED_NO_ITEMS": "Ingen nyheder i øjeblikket.", + + "UPDATE_NOTIFICATION": "MagicMirror² opdatering tilgængelig.", + "UPDATE_NOTIFICATION_MODULE": "Opdatering tilgængelig for {MODULE_NAME} modulet.", + "UPDATE_INFO_SINGLE": "Den nuværende installation er {COMMIT_COUNT} commit bagud pÃ¥ {BRANCH_NAME} branch'en.", + "UPDATE_INFO_MULTIPLE": "Den nuværende installation er {COMMIT_COUNT} commits bagud pÃ¥ {BRANCH_NAME} branch'en." +} diff --git a/MagicMirror/translations/de.json b/MagicMirror/translations/de.json new file mode 100644 index 0000000000000000000000000000000000000000..e0ea340e8767bbb5cd707de5f60d04905072ae49 --- /dev/null +++ b/MagicMirror/translations/de.json @@ -0,0 +1,47 @@ +{ + "LOADING": "Lade …", + + "DAYBEFOREYESTERDAY": "Vorgestern", + "YESTERDAY": "Gestern", + "TODAY": "Heute", + "TOMORROW": "Morgen", + "DAYAFTERTOMORROW": "Übermorgen", + "RUNNING": "noch", + "EMPTY": "Keine Termine.", + "WEEK": "{weekNumber}. Kalenderwoche", + + "N": "N", + "NNE": "NNO", + "NE": "NO", + "ENE": "ONO", + "E": "O", + "ESE": "OSO", + "SE": "SO", + "SSE": "SSO", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "FEELS": "Gefühlt {DEGREE}", + "PRECIP_POP": "Niederschlagswahrscheinlichkeit", + "PRECIP_AMOUNT": "Niederschlagsmenge", + + "MODULE_CONFIG_CHANGED": "Die Konfigurationsoptionen für das Modul „{MODULE_NAME}“ haben sich geändert. \nBitte überprüfen Sie die Dokumentation.", + "MODULE_CONFIG_ERROR": "Fehler im Modul „{MODULE_NAME}“. {ERROR}", + "MODULE_ERROR_MALFORMED_URL": "Fehlerhafte URL.", + "MODULE_ERROR_NO_CONNECTION": "Keine Internetverbindung.", + "MODULE_ERROR_UNAUTHORIZED": "Autorisierung fehlgeschlagen.", + "MODULE_ERROR_UNSPECIFIED": "Prüfe die Logdateien für weitere Details.", + + "NEWSFEED_NO_ITEMS": "Momentan keine Neuigkeiten.", + + "UPDATE_NOTIFICATION": "Aktualisierung für MagicMirror² verfügbar.", + "UPDATE_NOTIFICATION_MODULE": "Aktualisierung für das Modul „{MODULE_NAME}“ verfügbar.", + "UPDATE_INFO_SINGLE": "Die aktuelle Installation ist ein Commit hinter dem {BRANCH_NAME}-Branch.", + "UPDATE_INFO_MULTIPLE": "Die aktuelle Installation ist {COMMIT_COUNT} Commits hinter dem {BRANCH_NAME}-Branch." +} diff --git a/MagicMirror/translations/el.json b/MagicMirror/translations/el.json new file mode 100644 index 0000000000000000000000000000000000000000..cf10b32106037334ea76a1889133eaf35ad220e5 --- /dev/null +++ b/MagicMirror/translations/el.json @@ -0,0 +1,27 @@ +{ + "LOADING": "ΦόÏτωση …", + + "DAYBEFOREYESTERDAY": "Î ÏοχθÎÏ‚", + "YESTERDAY": "ΕχθÎÏ‚", + "TODAY": "ΣήμεÏα", + "TOMORROW": "ΑÏÏιο", + "RUNNING": "Λήγει σε", + "EMPTY": "Δεν υπάÏχουν Ï€Ïοσεχείς εκδηλώσεις.", + + "N": "B", + "NNE": "BBA", + "NE": "BA", + "ENE": "ABA", + "E": "A", + "ESE": "ANA", + "SE": "NA", + "SSE": "NNA", + "S": "N", + "SSW": "NNΔ", + "SW": "NΔ", + "WSW": "ΔNΔ", + "W": "Δ", + "WNW": "ΔΒΔ", + "NW": "ΒΔ", + "NNW": "ΒΒΔ" +} diff --git a/MagicMirror/translations/en.json b/MagicMirror/translations/en.json new file mode 100644 index 0000000000000000000000000000000000000000..eb9200c6c22dab1ee044445c9ecf03d530d1340e --- /dev/null +++ b/MagicMirror/translations/en.json @@ -0,0 +1,45 @@ +{ + "LOADING": "Loading …", + + "YESTERDAY": "Yesterday", + "TODAY": "Today", + "TOMORROW": "Tomorrow", + "RUNNING": "Ends in", + "EMPTY": "No upcoming events.", + "WEEK": "Week {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "FEELS": "Feels like {DEGREE}", + "PRECIP_POP": "PoP", + "PRECIP_AMOUNT": "Precipitation amount", + + "MODULE_CONFIG_CHANGED": "The configuration options for the {MODULE_NAME} module have changed.\nPlease check the documentation.", + "MODULE_CONFIG_ERROR": "Error in the {MODULE_NAME} module. {ERROR}", + "MODULE_ERROR_MALFORMED_URL": "Malformed url.", + "MODULE_ERROR_NO_CONNECTION": "No internet connection.", + "MODULE_ERROR_UNAUTHORIZED": "Authorization failed.", + "MODULE_ERROR_UNSPECIFIED": "Check logs for more details.", + + "NEWSFEED_NO_ITEMS": "No news at the moment.", + + "UPDATE_NOTIFICATION": "MagicMirror² update available.", + "UPDATE_NOTIFICATION_MODULE": "Update available for {MODULE_NAME} module.", + "UPDATE_INFO_SINGLE": "The current installation is {COMMIT_COUNT} commit behind on the {BRANCH_NAME} branch.", + "UPDATE_INFO_MULTIPLE": "The current installation is {COMMIT_COUNT} commits behind on the {BRANCH_NAME} branch." +} diff --git a/MagicMirror/translations/es.json b/MagicMirror/translations/es.json new file mode 100644 index 0000000000000000000000000000000000000000..454dcf404ee67cc624a5c00daa034bfbf18fa057 --- /dev/null +++ b/MagicMirror/translations/es.json @@ -0,0 +1,39 @@ +{ + "LOADING": "Cargando …", + + "DAYBEFOREYESTERDAY": "Anteayer", + "YESTERDAY": "Ayer", + "TODAY": "Hoy", + "TOMORROW": "Mañana", + "DAYAFTERTOMORROW": "Pasado mañana", + "RUNNING": "Termina en", + "EMPTY": "No hay eventos programados.", + "WEEK": "Semana {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSO", + "SW": "SO", + "WSW": "OSO", + "W": "O", + "WNW": "ONO", + "NW": "NO", + "NNW": "NNO", + + "FEELS": "Sensación térmica de {DEGREE}", + "PRECIP_POP": "Precipitación", + + "MODULE_CONFIG_CHANGED": "Las opciones de configuración para el módulo {MODULE_NAME} han cambiado. \nVerifique la documentación.", + + "UPDATE_NOTIFICATION": "MagicMirror² actualización disponible.", + "UPDATE_NOTIFICATION_MODULE": "Disponible una actualización para el módulo {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "Tu actual instalación está {COMMIT_COUNT} commit cambios detrás de la rama {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "Tu actual instalación está {COMMIT_COUNT} commits cambios detrás de la rama {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/et.json b/MagicMirror/translations/et.json new file mode 100644 index 0000000000000000000000000000000000000000..7e60043711b4ed1670f096b12ce74ec0170a2e34 --- /dev/null +++ b/MagicMirror/translations/et.json @@ -0,0 +1,33 @@ +{ + "LOADING": "Laen …", + + "DAYBEFOREYESTERDAY": "Üleeile", + "YESTERDAY": "Eile", + "TODAY": "Täna", + "TOMORROW": "Homme", + "DAYAFTERTOMORROW": "Ülehomme", + "RUNNING": "Teoksil", + "EMPTY": "Pole tulevasi sündmuseid.", + + "N": "Põhi", + "NNE": "Põhikirre", + "NE": "Kirre", + "ENE": "Idakirre", + "E": "Ida", + "ESE": "Idakagu", + "SE": "Kagu", + "SSE": "Lõunakagu", + "S": "Lõuna", + "SSW": "Lõunaedel", + "SW": "Edel", + "WSW": "Lääneedel", + "W": "Lääs", + "WNW": "Lääneloe", + "NW": "Loe", + "NNW": "Põhjaloe", + + "UPDATE_NOTIFICATION": "MagicMirror²´le uuendus saadaval.", + "UPDATE_NOTIFICATION_MODULE": "Uuendus saadaval {MODULE_NAME} moodulile.", + "UPDATE_INFO_SINGLE": "Praegune paigaldus on {COMMIT_COUNT} commit tagapool {BRANCH_NAME} harul.", + "UPDATE_INFO_MULTIPLE": "Praegune paigaldus on {COMMIT_COUNT} commits tagapool {BRANCH_NAME} harul." +} diff --git a/MagicMirror/translations/fi.json b/MagicMirror/translations/fi.json new file mode 100644 index 0000000000000000000000000000000000000000..87c0ef24a8d00928a11cf7ebb73cc1dcb0cb5aee --- /dev/null +++ b/MagicMirror/translations/fi.json @@ -0,0 +1,37 @@ +{ + "LOADING": "Lataa …", + + "DAYBEFOREYESTERDAY": "Toissapäivänä", + "YESTERDAY": "Eilen", + "TODAY": "Tänään", + "TOMORROW": "Huomenna", + "DAYAFTERTOMORROW": "Ylihuomenna", + "RUNNING": "Päättyy {timeUntilEnd} päästä", + "EMPTY": "Ei tulevia tapahtumia.", + "WEEK": "Viikko {weekNumber}", + + "N": "P", + "NNE": "PPI", + "NE": "PI", + "ENE": "IPI", + "E": "I", + "ESE": "IEI", + "SE": "EI", + "SSE": "EEI", + "S": "E", + "SSW": "EEL", + "SW": "EL", + "WSW": "LEL", + "W": "L", + "WNW": "LPL", + "NW": "PL", + "NNW": "PPL", + + "FEELS": "Tuntuu kuin {DEGREE}", + "PRECIP_POP": "Sateen todennäköisyys", + + "UPDATE_NOTIFICATION": "MagicMirror² päivitys saatavilla.", + "UPDATE_NOTIFICATION_MODULE": "Päivitys saatavilla moduulille {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "Nykyasennus on {COMMIT_COUNT} muutoksen jäljessä {BRANCH_NAME} haaraan nähden.", + "UPDATE_INFO_MULTIPLE": "Nykyasennus on {COMMIT_COUNT} muutosta jäljessä {BRANCH_NAME} haaraan nähden." +} diff --git a/MagicMirror/translations/fr.json b/MagicMirror/translations/fr.json new file mode 100644 index 0000000000000000000000000000000000000000..1e4a2946522624c0828722d6707bb7d0704f38e9 --- /dev/null +++ b/MagicMirror/translations/fr.json @@ -0,0 +1,40 @@ +{ + "LOADING": "Chargement…", + + "DAYBEFOREYESTERDAY": "Avant-hier", + "YESTERDAY": "Hier", + "TODAY": "Aujourd'hui", + "TOMORROW": "Demain", + "DAYAFTERTOMORROW": "Après-demain", + "RUNNING": "Se termine dans", + "EMPTY": "Aucun RDV à venir.", + "WEEK": "Semaine {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSO", + "SW": "SO", + "WSW": "OSO", + "W": "O", + "WNW": "ONO", + "NW": "NO", + "NNW": "NNO", + + "FEELS": "Ressenti {DEGREE}", + "PRECIP_POP": "Probabilité de précipitations", + + "MODULE_CONFIG_CHANGED": "Les options de configuration du module {MODULE_NAME} ont changé.\nVeuillez consulter la documentation.", + "MODULE_CONFIG_ERROR": "Erreur dans le module {MODULE_NAME}. {ERROR}", + + "UPDATE_NOTIFICATION": "Une mise à jour de MagicMirror² est disponible", + "UPDATE_NOTIFICATION_MODULE": "Une mise à jour est disponible pour le module {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "L'installation actuelle est {COMMIT_COUNT} commit en retard sur la branche {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "L'installation actuelle est {COMMIT_COUNT} commits en retard sur la branche {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/fy.json b/MagicMirror/translations/fy.json new file mode 100644 index 0000000000000000000000000000000000000000..ba1bc12c752c0088fcb7bb68f1c4cf49aceefbc5 --- /dev/null +++ b/MagicMirror/translations/fy.json @@ -0,0 +1,27 @@ +{ + "LOADING": "Bezich mei laden …", + + "YESTERDAY": "Juster", + "TODAY": "Hjoed", + "TOMORROW": "Moarn", + "DAYAFTERTOMORROW": "Oaremoarn", + "RUNNING": "Einigest oer", + "EMPTY": "Gjin plande ôfspraken.", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW" +} diff --git a/MagicMirror/translations/gl.json b/MagicMirror/translations/gl.json new file mode 100644 index 0000000000000000000000000000000000000000..1de91d628fdc7ea9b2068c25eed08dfee19c32ad --- /dev/null +++ b/MagicMirror/translations/gl.json @@ -0,0 +1,40 @@ +{ + "LOADING": "Cargando …", + + "YESTERDAY": "Onte", + "TODAY": "Hoxe", + "TOMORROW": "Mañá", + "DAYAFTERTOMORROW": "Pasado mañá", + "RUNNING": "Remata en", + "EMPTY": "Non hai próximos eventos.", + + "WEEK": "Semana {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "MODULE_CONFIG_CHANGED": "Cambiaron as opcións de configuración para o módulo {MODULE_NAME}.\nPor favor, verifique a documentación.", + "MODULE_CONFIG_ERROR": "Hai un erro no módulo {MODULE_NAME}. {ERROR}", + + "UPDATE_NOTIFICATION": "Actualización dispoñible para MagicMirror².", + "UPDATE_NOTIFICATION_MODULE": "Actualización dispoñible para o módulo {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "A instalación actual está {COMMIT_COUNT} commits detrás da rama {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "A instalación actual está {COMMIT_COUNT} commits detrás da rama {BRANCH_NAME}.", + + "FEELS": "Semella como {DEGREE}", + "PRECIP_POP": "Precipitacións" +} diff --git a/MagicMirror/translations/gu.json b/MagicMirror/translations/gu.json new file mode 100644 index 0000000000000000000000000000000000000000..015dc3d0bdf80b64fa3ebbd0352f5e2043af3617 --- /dev/null +++ b/MagicMirror/translations/gu.json @@ -0,0 +1,38 @@ +{ + "LOADING": "લોડ થઈ રહà«àª¯à«àª‚ છે …", + + "YESTERDAY": "ગઇકાલે", + "TODAY": "આજે", + "TOMORROW": "આવતી કાલે", + "DAYAFTERTOMORROW": "પરમ દિવસે", + "RUNNING": "માં સમાપà«àª¤ થાય છે", + "EMPTY": "કોઈ આગામી કારà«àª¯àª•à«àª°àª® નથી.", + "WEEK": "સપà«àª¤àª¾àª¹ {weekNumber}", + + "N": "ઉ", + "NNE": "ઉઉપà«", + "NE": "ઉપà«", + "ENE": "પà«àª‰àªªà«", + "E": "પà«", + "ESE": "પà«àª¦àªªà«", + "SE": "દપà«", + "SSE": "દદપà«", + "S": "દ", + "SSW": "દદપ", + "SW": "દપ", + "WSW": "પદપ", + "W": "પ", + "WNW": "પઉપ", + "NW": "ઉપ", + "NNW": "ઉઉપ", + + "FEELS": "{DEGREE} જેવà«àª‚ લાગશે", + "PRECIP_POP": "PoP", + + "MODULE_CONFIG_CHANGED": "{MODULE_NAME} મોડà«àª¯à«àª² માટે ગોઠવણી વિકલà«àªªà«‹ બદલાયા છે. \nકૃપા કરીને દસà«àª¤àª¾àªµà«‡àªœà«‹àª¨à«‡ તપાસો.", + + "UPDATE_NOTIFICATION": "MagicMirror² અપડેટ ઉપલબà«àª§ છે.", + "UPDATE_NOTIFICATION_MODULE": "{MODULE_NAME} મોડà«àª¯à«àª² માટે અપડેટ ઉપલબà«àª§ છે.", + "UPDATE_INFO_SINGLE": "વરà«àª¤àª®àª¾àª¨ ઇનà«àª¸à«àªŸà«‹àª²à«‡àª¶àª¨ ઠ{BRANCH_NAME} શાખા ની {COMMIT_COUNT} કમીટ પાછળ છે. ", + "UPDATE_INFO_MULTIPLE": "વરà«àª¤àª®àª¾àª¨ ઇનà«àª¸à«àªŸà«‹àª²à«‡àª¶àª¨ ઠ{BRANCH_NAME} શાખા ની {COMMIT_COUNT} કમીટ પાછળ છે. " +} diff --git a/MagicMirror/translations/he.json b/MagicMirror/translations/he.json new file mode 100644 index 0000000000000000000000000000000000000000..1661c9cc3fb8f01e90c44f6d2a67949da149df35 --- /dev/null +++ b/MagicMirror/translations/he.json @@ -0,0 +1,37 @@ +{ + "LOADING": "טוען...", + + "DAYBEFOREYESTERDAY": "שלשו×", + "YESTERDAY": "×תמול", + "TODAY": "היו×", + "TOMORROW": "מחר", + "DAYAFTERTOMORROW": "בעוד יומיי×", + "RUNNING": "×ž×¡×ª×™×™× ×‘", + "EMPTY": "×ין ×רועי×", + "WEEK": "{weekNumber} שבוע", + + "N": "צ", + "NNE": "צ-צ-מז", + "NE": "צ-מז", + "ENE": "מז-צ-מז", + "E": "מז", + "ESE": "מז-ד-מז", + "SE": "ד-מז", + "SSE": "ד-ד-מז", + "S": "ד", + "SSW": "ד-ד-מע", + "SW": "ד-מע", + "WSW": "מע-ד-מע", + "W": "מע", + "WNW": "מע-×–-מע", + "NW": "×–-מע", + "NNW": "צ-צ-מע", + + "FEELS": "מרגיש כמו {DEGREE}", + "PRECIP_POP": "משקעי×", + + "UPDATE_NOTIFICATION": "עדכון זמין ל-MagicMirror²", + "UPDATE_NOTIFICATION_MODULE": "עדכון זמין ב-{MODULE_NAME} מודול", + "UPDATE_INFO_SINGLE": "×”×”×ª×§× ×” ×”× ×•×›×—×™×ª × ×ž×¦×ת מ×חור ×”×¢× ×£ {BRANCH_NAME} ב-{COMMIT_COUNT} מופע", + "UPDATE_INFO_MULTIPLE": "×”×”×ª×§× ×” ×”× ×•×›×—×™×ª × ×ž×¦×ת מ×חור ×”×¢× ×£ {BRANCH_NAME} ב-{COMMIT_COUNT} מופעי×" +} diff --git a/MagicMirror/translations/hi.json b/MagicMirror/translations/hi.json new file mode 100644 index 0000000000000000000000000000000000000000..f93d2c25b88800faa9a41a270e7c8160b1a221a9 --- /dev/null +++ b/MagicMirror/translations/hi.json @@ -0,0 +1,39 @@ +{ + "LOADING": "लोड हो रहा है …", + + "DAYBEFOREYESTERDAY": "परसों", + "YESTERDAY": "कल", + "TODAY": "आज", + "TOMORROW": "आने वाला कल", + "DAYAFTERTOMORROW": "2 दिनों में", + "RUNNING": "में समापà¥à¤¤", + "EMPTY": "कोई आगामी कारà¥à¤¯à¤•à¥à¤°à¤® नहीं।", + "WEEK": "सपà¥à¤¤à¤¾à¤¹ {weekNumber}", + + "N": "उ", + "NNE": "उउपà¥", + "NE": "उपà¥", + "ENE": "पà¥à¤‰à¤ªà¥", + "E": "पà¥", + "ESE": "पà¥à¤¦à¤ªà¥", + "SE": "दपà¥", + "SSE": "ददपà¥", + "S": "द", + "SSW": "ददप", + "SW": "दप", + "WSW": "पदप", + "W": "प", + "WNW": "पउप", + "NW": "उप", + "NNW": "उउप", + + "FEELS": "{DEGREE} की तरह लगना", + "PRECIP_POP": "PoP", + + "MODULE_CONFIG_CHANGED": "{MODULE_NAME} मॉडà¥à¤¯à¥‚ल के लिठकॉनà¥à¤«à¤¼à¤¿à¤—रेशन विकलà¥à¤ª बदल गठहैं। n कृपया दसà¥à¤¤à¤¾à¤µà¥‡à¤œà¤¼ देखें।", + + "UPDATE_NOTIFICATION": "MagicMirror² अपडेट उपलबà¥à¤§à¥¤", + "UPDATE_NOTIFICATION_MODULE": "{MODULE_NAME} मॉडà¥à¤¯à¥‚ल के लिठउपलबà¥à¤§ अदà¥à¤¯à¤¤à¤¨à¥¤", + "UPDATE_INFO_SINGLE": "वरà¥à¤¤à¤®à¤¾à¤¨ सà¥à¤¥à¤¾à¤ªà¤¨à¤¾ {COMMIT_COUNT} {BRANCH_NAME} शाखा के पीछे है।", + "UPDATE_INFO_MULTIPLE": "वरà¥à¤¤à¤®à¤¾à¤¨ सà¥à¤¥à¤¾à¤ªà¤¨à¤¾ {COMMIT_COUNT} पीछे {BRANCH_NAME} शाखा पर है।" +} diff --git a/MagicMirror/translations/hr.json b/MagicMirror/translations/hr.json new file mode 100644 index 0000000000000000000000000000000000000000..27b835fd7d264129c1b4c35d128a3c3f50c84774 --- /dev/null +++ b/MagicMirror/translations/hr.json @@ -0,0 +1,36 @@ +{ + "LOADING": "UÄitavanje …", + + "DAYBEFOREYESTERDAY": "PrekjuÄer", + "YESTERDAY": "JuÄer", + "TODAY": "Danas", + "TOMORROW": "Sutra", + "DAYAFTERTOMORROW": "Prekosutra", + "RUNNING": "ZavrÅ¡ava za", + "EMPTY": "Nema nadolazećih dogaÄ‘aja.", + "WEEK": "Tjedan {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "FEELS": "Osjećaj {DEGREE}", + + "UPDATE_NOTIFICATION": "Dostupna je aktualizacija MagicMirror².", + "UPDATE_NOTIFICATION_MODULE": "Dostupna je aktualizacija modula {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "Instalirana verzija {COMMIT_COUNT} commit kasni za branch-om {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "Instalirana verzija {COMMIT_COUNT} commit-ova kasni za branch-om {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/hu.json b/MagicMirror/translations/hu.json new file mode 100644 index 0000000000000000000000000000000000000000..7a8b3d7f764a5f98b3e2f714c1346f4e21d23003 --- /dev/null +++ b/MagicMirror/translations/hu.json @@ -0,0 +1,34 @@ +{ + "LOADING": "Betöltés …", + + "TODAY": "Ma", + "TOMORROW": "Holnap", + "DAYAFTERTOMORROW": "Holnapután", + "RUNNING": "Vége lesz", + "EMPTY": "Nincs közelgÅ‘ esemény.", + "WEEK": "{weekNumber}. hét", + + "N": "É", + "NNE": "ÉÉK", + "NE": "ÉK", + "ENE": "KÉK", + "E": "K", + "ESE": "KDK", + "SE": "DK", + "SSE": "DDK", + "S": "D", + "SSW": "DDNy", + "SW": "DNy", + "WSW": "NyDNy", + "W": "Ny", + "WNW": "NyÉNy", + "NW": "ÉNy", + "NNW": "ÉÉNy", + + "FEELS": "Érzet {DEGREE}", + + "UPDATE_NOTIFICATION": "MagicMirror²-hoz frissÃtés érhetÅ‘ el.", + "UPDATE_NOTIFICATION_MODULE": "A {MODULE_NAME} modulhoz frissÃtés érhetÅ‘ el.", + "UPDATE_INFO_SINGLE": "A jelenlegi telepÃtés óta {COMMIT_COUNT} új commit jelent meg a {BRANCH_NAME} ágon.", + "UPDATE_INFO_MULTIPLE": "A jelenlegi telepÃtés óta {COMMIT_COUNT} új commit jelent meg a {BRANCH_NAME} ágon." +} diff --git a/MagicMirror/translations/id.json b/MagicMirror/translations/id.json new file mode 100644 index 0000000000000000000000000000000000000000..439d2151f8f151f7d1f57f95264cfa0ad4ecc001 --- /dev/null +++ b/MagicMirror/translations/id.json @@ -0,0 +1,33 @@ +{ + "LOADING": "Memuat …", + + "YESTERDAY": "Kemarin", + "TODAY": "Hari ini", + "TOMORROW": "Besok", + "DAYAFTERTOMORROW": "Lusa", + "RUNNING": "Berakhir dalam", + "EMPTY": "Tidak ada agenda", + "WEEK": "Pekan", + + "N": "U", + "NNE": "UTL", + "NE": "TL", + "ENE": "TTL", + "E": "T", + "ESE": "TMg", + "SE": "TG", + "SSE": "SMg", + "S": "S", + "SSW": "SBD", + "SW": "BD", + "WSW": "BBD", + "W": "B", + "WNW": "BBL", + "NW": "BL", + "NNW": "UBL", + + "UPDATE_NOTIFICATION": "Memperbarui MagicMirror² tersedia.", + "UPDATE_NOTIFICATION_MODULE": "Memperbarui tersedia untuk modul {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "Instalasi saat ini tertinggal {COMMIT_COUNT} commit pada cabang {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "Instalasi saat ini tertinggal {COMMIT_COUNT} commits pada cabang {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/is.json b/MagicMirror/translations/is.json new file mode 100644 index 0000000000000000000000000000000000000000..c4da887cf74d98e0e31244291b2f9c9cd022e71c --- /dev/null +++ b/MagicMirror/translations/is.json @@ -0,0 +1,33 @@ +{ + "LOADING": "Hleð upp …", + + "DAYBEFOREYESTERDAY": "à fyrradag", + "YESTERDAY": "à gær", + "TODAY": "à dag", + "TOMORROW": "à morgun", + "DAYAFTERTOMORROW": "Ekki á morgun, heldur hinn", + "RUNNING": "Endar eftir", + "EMPTY": "Ekkert framundan.", + + "N": "N", + "NNE": "NNA", + "NE": "NA", + "ENE": "ANA", + "E": "A", + "ESE": "ASA", + "SE": "SA", + "SSE": "SSA", + "S": "S", + "SSW": "SSV", + "SW": "SV", + "WSW": "VSV", + "W": "V", + "WNW": "VNV", + "NW": "NV", + "NNW": "NNV", + + "UPDATE_NOTIFICATION": "MagicMirror² uppfærsla à boði.", + "UPDATE_NOTIFICATION_MODULE": "Uppfærsla à boði fyrir {MODULE_NAME} module.", + "UPDATE_INFO_SINGLE": "Núverandi kerfi er {COMMIT_COUNT} commit á eftir {BRANCH_NAME} branchinu.", + "UPDATE_INFO_MULTIPLE": "Núverandi kerfi er {COMMIT_COUNT} commits á eftir {BRANCH_NAME} branchinu." +} diff --git a/MagicMirror/translations/it.json b/MagicMirror/translations/it.json new file mode 100644 index 0000000000000000000000000000000000000000..79199f1a47f481145e7cf3e36658c452982f1149 --- /dev/null +++ b/MagicMirror/translations/it.json @@ -0,0 +1,35 @@ +{ + "LOADING": "Caricamento in corso …", + + "YESTERDAY": "Ieri", + "TODAY": "Oggi", + "TOMORROW": "Domani", + "DAYAFTERTOMORROW": "Dopodomani", + "RUNNING": "Termina entro", + "EMPTY": "Nessun evento imminente.", + "WEEK": "Settimana {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSO", + "SW": "SO", + "WSW": "OSO", + "W": "O", + "WNW": "ONO", + "NW": "NO", + "NNW": "NNO", + + "FEELS": "Percepiti {DEGREE}", + + "UPDATE_NOTIFICATION": "E' disponibile un aggiornamento di MagicMirror².", + "UPDATE_NOTIFICATION_MODULE": "E' disponibile un aggiornamento del modulo {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "L'installazione è {COMMIT_COUNT} commit indietro rispetto all'attuale branch {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "L'installazione è {COMMIT_COUNT} commits indietro rispetto all'attuale branch {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/ja.json b/MagicMirror/translations/ja.json new file mode 100644 index 0000000000000000000000000000000000000000..a59a82bfc9c0b462a98d5c2581c1af2f21fff5e1 --- /dev/null +++ b/MagicMirror/translations/ja.json @@ -0,0 +1,27 @@ +{ + "LOADING": "ãƒãƒ¼ãƒ‡ã‚£ãƒ³ã‚° …", + + "DAYBEFOREYESTERDAY": "ãŠã¨ã¨ã„", + "YESTERDAY": "昨日", + "TODAY": "今日", + "TOMORROW": "明日", + "RUNNING": "ã§çµ‚ã‚りã¾ã™", + "EMPTY": "ç›´è¿‘ã®ã‚¤ãƒ™ãƒ³ãƒˆã¯ã‚りã¾ã›ã‚“", + + "N": "北", + "NNE": "北北æ±", + "NE": "北æ±", + "ENE": "æ±åŒ—æ±", + "E": "æ±", + "ESE": "æ±å—æ±", + "SE": "å—æ±", + "SSE": "å—å—æ±", + "S": "å—", + "SSW": "å—å—西", + "SW": "å—西", + "WSW": "西å—西", + "W": "西", + "WNW": "西北西", + "NW": "北西", + "NNW": "北北西" +} diff --git a/MagicMirror/translations/ko.json b/MagicMirror/translations/ko.json new file mode 100644 index 0000000000000000000000000000000000000000..7fc8432889786f8e1f78a4108f8ad2fc9f26177e --- /dev/null +++ b/MagicMirror/translations/ko.json @@ -0,0 +1,43 @@ +{ + "LOADING": "로드 중 …", + + "YESTERDAY": "ì–´ì œ", + "TODAY": "오늘", + "TOMORROW": "ë‚´ì¼", + "DAYAFTERTOMORROW": "ëª¨ë ˆ", + "RUNNING": "종료 ì¼", + "EMPTY": "ì˜ˆì •ëœ ì´ë²¤íŠ¸ê°€ 없습니다.", + "WEEK": "{weekNumber}주차", + + "N": "ë¶í’", + "NNE": "ë¶ë¶ë™í’", + "NE": "ë¶ë™í’", + "ENE": "ë™ë¶ë™í’", + "E": "ë™í’", + "ESE": "ë™ë‚¨ë™í’", + "SE": "남ë™í’", + "SSE": "남남ë™í’", + "S": "남í’", + "SSW": "남남서í’", + "SW": "남서í’", + "WSW": "서남서í’", + "W": "서í’", + "WNW": "서ë¶ì„œí’", + "NW": "ë¶ì„œí’", + "NNW": "ë¶ë¶ì„œí’", + + "FEELS": "ì²´ê°ì˜¨ë„ {DEGREE}", + "PRECIP_POP": "PoP", + + "MODULE_CONFIG_CHANGED": "모듈 {MODULE_NAME}ì˜ ì„¤ì •ê°’ì´ ë°”ë€Œì—ˆìŠµë‹ˆë‹¤.\në§¤ë‰´ì–¼ì„ ì°¸ê³ í•˜ì„¸ìš”.", + "MODULE_CONFIG_ERROR": "ì—러 : {MODULE_NAME} - {ERROR}", + "MODULE_ERROR_MALFORMED_URL": "ìž˜ëª»ëœ URL 형ì‹ìž…니다.", + "MODULE_ERROR_NO_CONNECTION": "ì¸í„°ë„·ì´ ì—°ê²°ë˜ì§€ 않았습니다.", + "MODULE_ERROR_UNAUTHORIZED": "ì¸ì¦ì´ 실패했습니다.", + "MODULE_ERROR_UNSPECIFIED": "ìƒì„¸ ë‚´ìš©ì€ ë¡œê·¸ë¥¼ 확ì¸í•˜ì„¸ìš”.", + + "UPDATE_NOTIFICATION": "새로운 MagicMirror² ì—…ë°ì´íŠ¸ê°€ 있습니다.", + "UPDATE_NOTIFICATION_MODULE": "{MODULE_NAME} 모듈ì—서 사용 가능한 ì—…ë°ì´íЏ 입니다.", + "UPDATE_INFO_SINGLE": "ì„¤ì¹˜í• {COMMIT_COUNT} commit 는 {BRANCH_NAME} ë¶„ê¸°ì— í•´ë‹¹ë©ë‹ˆë‹¤.", + "UPDATE_INFO_MULTIPLE": "ì„¤ì¹˜í• {COMMIT_COUNT} commits 는 {BRANCH_NAME} ë¶„ê¸°ì— í•´ë‹¹ë©ë‹ˆë‹¤." +} diff --git a/MagicMirror/translations/lt.json b/MagicMirror/translations/lt.json new file mode 100644 index 0000000000000000000000000000000000000000..dc7a23af1a14887175224410c645a97103ac29f1 --- /dev/null +++ b/MagicMirror/translations/lt.json @@ -0,0 +1,37 @@ +{ + "LOADING": "Kraunasi …", + + "DAYBEFOREYESTERDAY": "Užvakar", + "YESTERDAY": "Vakar", + "TODAY": "Å iandien", + "TOMORROW": "Rytoj", + "DAYAFTERTOMORROW": "Už 2 dienų", + "RUNNING": "Pasibaigs už", + "EMPTY": "NÄ—ra artimų įvykių.", + "WEEK": "{weekNumber} savaitÄ—", + + "N": "Å ", + "NNE": "Å Å R", + "NE": "Å R", + "ENE": "RÅ R", + "E": "R", + "ESE": "RPR", + "SE": "PR", + "SSE": "PPR", + "S": "P", + "SSW": "PPV", + "SW": "PV", + "WSW": "VPV", + "W": "V", + "WNW": "VÅ V", + "NW": "Å V", + "NNW": "Å Å V", + + "FEELS": "JutiminÄ— temp. {DEGREE}", + "PRECIP_POP": "Krituliai", + + "UPDATE_NOTIFICATION": "Galimas MagicMirror² naujinimas.", + "UPDATE_NOTIFICATION_MODULE": "Galimas {MODULE_NAME} naujinimas.", + "UPDATE_INFO_SINGLE": "Å is įdiegimas atsilieka {COMMIT_COUNT} įsipareigojimu {BRANCH_NAME} Å¡akoje.", + "UPDATE_INFO_MULTIPLE": "Å is įdiegimas atsilieka {COMMIT_COUNT} įsipareigojimais {BRANCH_NAME} Å¡akoje." +} diff --git a/MagicMirror/translations/ms-my.json b/MagicMirror/translations/ms-my.json new file mode 100644 index 0000000000000000000000000000000000000000..07d0bb996c185f5b4f1e55ea84744cb58e1a5f22 --- /dev/null +++ b/MagicMirror/translations/ms-my.json @@ -0,0 +1,33 @@ +{ + "LOADING": "Tunggu Sebentar …", + + "YESTERDAY": "Semalam", + "TODAY": "Hari ini", + "TOMORROW": "Esok", + "DAYAFTERTOMORROW": "Lusa", + "RUNNING": "Berakhir dalam", + "EMPTY": "Tidak ada agenda", + "WEEK": "Minggu ke-{weekNumber}", + + "N": "U", + "NNE": "UUT", + "NE": "TL", + "ENE": "TTL", + "E": "T", + "ESE": "TT", + "SE": "T", + "SSE": "ST", + "S": "S", + "SSW": "SBD", + "SW": "BD", + "WSW": "BBD", + "W": "B", + "WNW": "BBL", + "NW": "BL", + "NNW": "UBL", + + "UPDATE_NOTIFICATION": "MagicMirror² mempunyai update terkini.", + "UPDATE_NOTIFICATION_MODULE": "Modul {MODULE_NAME} mempunyai update terkini.", + "UPDATE_INFO_SINGLE": "Pemasangan MagicMirror² ini mempunyai {COMMIT_COUNT} commit terkebelakang dari branch {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "Pemasangan MagicMirror² ini mempunyai {COMMIT_COUNT} commit terkebelakang dari branch {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/nb.json b/MagicMirror/translations/nb.json new file mode 100644 index 0000000000000000000000000000000000000000..669b2d265a72cf08d5de262a6286a12adef24cbf --- /dev/null +++ b/MagicMirror/translations/nb.json @@ -0,0 +1,38 @@ +{ + "LOADING": "Laster …", + + "DAYBEFOREYESTERDAY": "I forgÃ¥rs", + "YESTERDAY": "I gÃ¥r", + "TODAY": "I dag", + "TOMORROW": "I morgen", + "DAYAFTERTOMORROW": "I overmorgen", + "RUNNING": "Slutter om", + "EMPTY": "Ingen kommende arrangementer.", + "WEEK": "Uke {weekNumber}", + + "N": "N", + "NNE": "NNØ", + "NE": "NØ", + "ENE": "ØNØ", + "E": "Ø", + "ESE": "ØSØ", + "SE": "SØ", + "SSE": "SSØ", + "S": "S", + "SSW": "SSV", + "SW": "SV", + "WSW": "VSV", + "W": "V", + "WNW": "VNV", + "NW": "NV", + "NNW": "NNV", + + "FEELS": "Føles som {DEGREE}", + "PRECIP_POP": "Sannsynlighet for nedbør", + "PRECIP_AMOUNT": "Nedbørsmengde", + + "UPDATE_NOTIFICATION": "MagicMirror²-oppdatering er tilgjengelig.", + "UPDATE_NOTIFICATION_MODULE": "Oppdatering tilgjengelig for modulen {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "NÃ¥værende installasjon er {COMMIT_COUNT} commit bak {BRANCH_NAME} grenen.", + "UPDATE_INFO_MULTIPLE": "NÃ¥værende installasjon er {COMMIT_COUNT} commits bak {BRANCH_NAME} grenen." +} diff --git a/MagicMirror/translations/nl.json b/MagicMirror/translations/nl.json new file mode 100644 index 0000000000000000000000000000000000000000..8e2fbb34ebe34206b2a2cde153dcbd71e80dfdaf --- /dev/null +++ b/MagicMirror/translations/nl.json @@ -0,0 +1,46 @@ +{ + "LOADING": "Bezig met laden …", + + "DAYBEFOREYESTERDAY": "Eergisteren", + "YESTERDAY": "Gisteren", + "TODAY": "Vandaag", + "TOMORROW": "Morgen", + "DAYAFTERTOMORROW": "Overmorgen", + "RUNNING": "Eindigt over", + "EMPTY": "Geen geplande afspraken.", + "WEEK": "Week {weekNumber}", + + "N": "N", + "NNE": "NNO", + "NE": "NO", + "ENE": "ONO", + "E": "O", + "ESE": "OZO", + "SE": "ZO", + "SSE": "ZZO", + "S": "Z", + "SSW": "ZZW", + "SW": "ZW", + "WSW": "WZW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "FEELS": "Voelt als {DEGREE}", + "PRECIP_POP": "Neerslagkans", + + "MODULE_CONFIG_CHANGED": "De configuratie opties voor de module {MODULE_NAME} zijn gewijzigd.\nControleer de documentatie.", + "MODULE_CONFIG_ERROR": "Fout in de {MODULE_NAME} module. {ERROR}", + "MODULE_ERROR_MALFORMED_URL": "Ongeldige url.", + "MODULE_ERROR_NO_CONNECTION": "Geen internet verbinding.", + "MODULE_ERROR_UNAUTHORIZED": "Authenticatie mislukt.", + "MODULE_ERROR_UNSPECIFIED": "Bekijk de logs voor meer informatie.", + + "NEWSFEED_NO_ITEMS": "Geen nieuws op dit moment.", + + "UPDATE_NOTIFICATION": "MagicMirror² update beschikbaar.", + "UPDATE_NOTIFICATION_MODULE": "Update beschikbaar voor {MODULE_NAME} module.", + "UPDATE_INFO_SINGLE": "De huidige installatie loopt {COMMIT_COUNT} commit achter op de {BRANCH_NAME} branch.", + "UPDATE_INFO_MULTIPLE": "De huidige installatie loopt {COMMIT_COUNT} commits achter op de {BRANCH_NAME} branch." +} diff --git a/MagicMirror/translations/nn.json b/MagicMirror/translations/nn.json new file mode 100644 index 0000000000000000000000000000000000000000..609d8fc64675ec5ed21048c3d0374f6bc5c33b28 --- /dev/null +++ b/MagicMirror/translations/nn.json @@ -0,0 +1,35 @@ +{ + "LOADING": "Lastar …", + + "DAYBEFOREYESTERDAY": "I forgÃ¥rs", + "YESTERDAY": "I gÃ¥r", + "TODAY": "I dag", + "TOMORROW": "I morgon", + "DAYAFTERTOMORROW": "I overmorgon", + "RUNNING": "Sluttar om", + "EMPTY": "Ingen komande hendingar.", + + "N": "N", + "NNE": "NNA", + "NE": "NA", + "ENE": "ANA", + "E": "A", + "ESE": "ASA", + "SE": "SA", + "SSE": "SSA", + "S": "S", + "SSW": "SSV", + "SW": "SV", + "WSW": "VSV", + "W": "V", + "WNW": "VNV", + "NW": "NV", + "NNW": "NNV", + + "FEELS": "Kjenst som {DEGREE}", + + "UPDATE_NOTIFICATION": "MagicMirror² oppdatering er tilgjengeleg.", + "UPDATE_NOTIFICATION_MODULE": "Oppdatering tilgjengeleg for modulen {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "noverande installasjon er {COMMIT_COUNT} commit bak {BRANCH_NAME} greinen.", + "UPDATE_INFO_MULTIPLE": "noverande installasjon er {COMMIT_COUNT} commits bak {BRANCH_NAME} greinen." +} diff --git a/MagicMirror/translations/pl.json b/MagicMirror/translations/pl.json new file mode 100644 index 0000000000000000000000000000000000000000..04fd0ca1947dfda42718b8b40fdc22c9c5c4db68 --- /dev/null +++ b/MagicMirror/translations/pl.json @@ -0,0 +1,37 @@ +{ + "LOADING": "Åadowanie …", + + "DAYBEFOREYESTERDAY": "Wczoraj", + "YESTERDAY": "Przedwczoraj", + "TODAY": "DziÅ›", + "TOMORROW": "Jutro", + "DAYAFTERTOMORROW": "Pojutrze", + "RUNNING": "Koniec za", + "EMPTY": "Brak wydarzeÅ„.", + "WEEK": "TydzieÅ„ {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "FEELS": "Odczuwalna {DEGREE}", + "PRECIP_POP": "Szansa opadów", + + "UPDATE_NOTIFICATION": "DostÄ™pna jest aktualizacja MagicMirror².", + "UPDATE_NOTIFICATION_MODULE": "DostÄ™pna jest aktualizacja moduÅ‚u {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "Zainstalowana wersja odbiega o {COMMIT_COUNT} commit od gałęzi {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "Zainstalowana wersja odbiega o {COMMIT_COUNT} commitów od gałęzi {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/ps.json b/MagicMirror/translations/ps.json new file mode 100644 index 0000000000000000000000000000000000000000..701c30e82ab0eb05d747e9dd22f408dcd3258505 --- /dev/null +++ b/MagicMirror/translations/ps.json @@ -0,0 +1,38 @@ +{ + "LOADING": "Ù¾ÛŒÙ„ÛØ¯Ù„", + + "DAYBEFOREYESTERDAY": "پرون ورÚ", + "YESTERDAY": "پرون", + "TODAY": "نن", + "TOMORROW": "سبا", + "DAYAFTERTOMORROW": "بل سبا", + "RUNNING": "روان", + "EMPTY": "تش", + "WEEK": "{weekNumber}. اونÛ", + + "N": "N", + "NNE": "NNO", + "NE": "NO", + "ENE": "ONO", + "E": "O", + "ESE": "OSO", + "SE": "SO", + "SSE": "SSO", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "FEELS": "ØØ³ Ú©ÛÚ–ÛŒ {DEGREE}", + + "MODULE_CONFIG_CHANGED": "د {MODULE_NAME} بڼی تغیر Ú©Ú“ÛŒ دی. \n هیله دی Ú†ÛŒ اسناد Ùˆ ګوری!", + + "UPDATE_NOTIFICATION": "د MagicMirror² Ù†ÙˆÛ Ù†Ø³Ø®Ù‡ سته ", + "UPDATE_NOTIFICATION_MODULE": "د {MODULE_NAME} نوی نسخه سته", + "UPDATE_INFO_SINGLE": "اوسنی برخه {COMMIT_COUNT} د {BRANCH_NAME} څخه وروسته پاته ده", + "UPDATE_INFO_MULTIPLE": "اوسنی برخه {COMMIT_COUNT} د {BRANCH_NAME} څخه وروسته پاته ده" +} diff --git a/MagicMirror/translations/pt-br.json b/MagicMirror/translations/pt-br.json new file mode 100644 index 0000000000000000000000000000000000000000..4c1a49061a7632acd04d8476bbe9d7f74336ff94 --- /dev/null +++ b/MagicMirror/translations/pt-br.json @@ -0,0 +1,35 @@ +{ + "LOADING": "Carregando …", + + "DAYBEFOREYESTERDAY": "Anteontem", + "YESTERDAY": "Ontem", + "TODAY": "Hoje", + "TOMORROW": "Amanhã", + "RUNNING": "Acaba em", + "EMPTY": "Nenhum evento novo.", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSO", + "SW": "SO", + "WSW": "OSO", + "W": "O", + "WNW": "ONO", + "NW": "NO", + "NNW": "NNO", + + "FEELS": "Percebida {DEGREE}", + "PRECIP_POP": "PoP", + + "UPDATE_NOTIFICATION": "Nova atualização para MagicMirror² disponÃvel.", + "UPDATE_NOTIFICATION_MODULE": "Atualização para o módulo {MODULE_NAME} disponÃvel.", + "UPDATE_INFO_SINGLE": "Sua versão atual é a {COMMIT_COUNT} commit dentro do seguinte branch {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "Sua versão atual é a {COMMIT_COUNT} commits dentro do seguinte branch {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/pt.json b/MagicMirror/translations/pt.json new file mode 100644 index 0000000000000000000000000000000000000000..2b1e1cf0ff69fded41f3bf21c8ef8974bf07e5ba --- /dev/null +++ b/MagicMirror/translations/pt.json @@ -0,0 +1,39 @@ +{ + "LOADING": "A carregar …", + + "DAYBEFOREYESTERDAY": "Anteontem", + "YESTERDAY": "Ontem", + "TODAY": "Hoje", + "TOMORROW": "Amanhã", + "DAYAFTERTOMORROW": "Depois de amanhã", + "RUNNING": "Termina em", + "EMPTY": "Sem eventos programados.", + "WEEK": "Semana {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSO", + "SW": "SO", + "WSW": "OSO", + "W": "O", + "WNW": "ONO", + "NW": "NO", + "NNW": "NNO", + + "FEELS": "Sentida {DEGREE}", + "PRECIP_POP": "Prob. Precipitação", + + "MODULE_CONFIG_CHANGED": "As opções na configuração do módulo {MODULE_NAME} foram alteradas.\nPor favor, verifica a documentação.", + + "UPDATE_NOTIFICATION": "Atualização do MagicMirror² disponÃvel.", + "UPDATE_NOTIFICATION_MODULE": "Atualização para o módulo {MODULE_NAME} disponÃvel.", + "UPDATE_INFO_SINGLE": "A instalação atual está {COMMIT_COUNT} commit atrasada no branch {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "A instalação atual está {COMMIT_COUNT} commits atrasada no branch {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/ro.json b/MagicMirror/translations/ro.json new file mode 100644 index 0000000000000000000000000000000000000000..ce04e337a29ad72bc38cb392a2df5e77699a2d45 --- /dev/null +++ b/MagicMirror/translations/ro.json @@ -0,0 +1,36 @@ +{ + "LOADING": "Se încarcă …", + + "DAYBEFOREYESTERDAY": "Alaltaieri", + "YESTERDAY": "Ieri", + "TODAY": "Astăzi", + "TOMORROW": "Mâine", + "DAYAFTERTOMORROW": "Poimâine", + "RUNNING": "Se termină în", + "EMPTY": "Nici un eveniment.", + "WEEK": "Săptămâna {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "FEELS": "Se simte ca fiind {DEGREE}", + + "UPDATE_NOTIFICATION": "Un update este disponibil pentru MagicMirror².", + "UPDATE_NOTIFICATION_MODULE": "Un update este disponibil pentru modulul {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "Există {COMMIT_COUNT} commit-uri noi pe branch-ul {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "Există {COMMIT_COUNT} commit-uri noi pe branch-ul {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/ru.json b/MagicMirror/translations/ru.json new file mode 100644 index 0000000000000000000000000000000000000000..eb4c2f77beef13959d746dd788c85a53f56c610c --- /dev/null +++ b/MagicMirror/translations/ru.json @@ -0,0 +1,36 @@ +{ + "LOADING": "Загрузка …", + + "DAYBEFOREYESTERDAY": "Позавчера", + "YESTERDAY": "Вчера", + "TODAY": "СегоднÑ", + "TOMORROW": "Завтра", + "DAYAFTERTOMORROW": "ПоÑлезавтра", + "RUNNING": "ЗаканчиваетÑÑ Ñ‡ÐµÑ€ÐµÐ·", + "EMPTY": "Ðет предÑтоÑщих Ñобытий", + "WEEK": "ÐÐµÐ´ÐµÐ»Ñ {weekNumber}", + + "N": "С", + "NNE": "ССВ", + "NE": "СВ", + "ENE": "ВСВ", + "E": "Ð’", + "ESE": "ВЮВ", + "SE": "ЮВ", + "SSE": "ЮЮВ", + "S": "Ю", + "SSW": "ЮЮЗ", + "SW": "ЮЗ", + "WSW": "ЗЮЗ", + "W": "З", + "WNW": "ЗСЗ", + "NW": "СЗ", + "NNW": "ССЗ", + + "FEELS": "По ощущению {DEGREE}", + + "UPDATE_NOTIFICATION": "ЕÑть обновление Ð´Ð»Ñ MagicMirror².", + "UPDATE_NOTIFICATION_MODULE": "ЕÑть обновление Ð´Ð»Ñ {MODULE_NAME} модулÑ.", + "UPDATE_INFO_SINGLE": "Ð”Ð°Ð½Ð½Ð°Ñ Ð¸Ð½ÑталлÑÑ†Ð¸Ñ Ð¿Ð¾Ð·Ð°Ð´Ð¸ {BRANCH_NAME} commit ветки на {COMMIT_COUNT} коммитов.", + "UPDATE_INFO_MULTIPLE": "Ð”Ð°Ð½Ð½Ð°Ñ Ð¸Ð½ÑталлÑÑ†Ð¸Ñ Ð¿Ð¾Ð·Ð°Ð´Ð¸ {BRANCH_NAME} commits ветки на {COMMIT_COUNT} коммитов." +} diff --git a/MagicMirror/translations/sk.json b/MagicMirror/translations/sk.json new file mode 100644 index 0000000000000000000000000000000000000000..ef1757b85b52cdd933e13a2da479cc7f4f555dd6 --- /dev/null +++ b/MagicMirror/translations/sk.json @@ -0,0 +1,34 @@ +{ + "LOADING": "NaÄÃtanie …", + + "DAYBEFOREYESTERDAY": "PredvÄerom", + "YESTERDAY": "VÄera", + "TODAY": "Dnes", + "TOMORROW": "Zajtra", + "DAYAFTERTOMORROW": "Pozajtra", + "RUNNING": "KonÄà o", + "EMPTY": "Žiadne nadchádzajúce udalosti.", + "WEEK": "{weekNumber}. týždeň", + + "N": "S", + "NNE": "SSV", + "NE": "SV", + "ENE": "VSV", + "E": "V", + "ESE": "VJV", + "SE": "JV", + "SSE": "JJV", + "S": "J", + "SSW": "JJZ", + "SW": "JZ", + "WSW": "ZJZ", + "W": "Z", + "WNW": "ZSZ", + "NW": "SZ", + "NNW": "SSZ", + + "UPDATE_NOTIFICATION": "Dostupná aktualizácia pre MagicMirror².", + "UPDATE_NOTIFICATION_MODULE": "Dostupná aktualizácia pre modul {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "SúÄasná inÅ¡talácia je na vetve {BRANCH_NAME} pozadu o {COMMIT_COUNT} commit.", + "UPDATE_INFO_MULTIPLE": "SúÄasná inÅ¡talácia je na vetve {BRANCH_NAME} pozadu o {COMMIT_COUNT} commitov." +} diff --git a/MagicMirror/translations/sv.json b/MagicMirror/translations/sv.json new file mode 100644 index 0000000000000000000000000000000000000000..0d37a1bba6c9ad86c63ef79100891d649a638ddc --- /dev/null +++ b/MagicMirror/translations/sv.json @@ -0,0 +1,36 @@ +{ + "LOADING": "Laddar …", + + "DAYBEFOREYESTERDAY": "", + "YESTERDAY": "I gÃ¥r", + "TODAY": "I dag", + "TOMORROW": "I morgon", + "DAYAFTERTOMORROW": "I övermorgon", + "RUNNING": "Slutar", + "EMPTY": "Inga kommande händelser.", + "WEEK": "Vecka {weekNumber}", + + "N": "N", + "NNE": "NNO", + "NE": "NO", + "ENE": "ONO", + "E": "Ö", + "ESE": "OSO", + "SE": "SO", + "SSE": "SSO", + "S": "S", + "SSW": "SSV", + "SW": "SV", + "WSW": "VSV", + "W": "V", + "WNW": "VNV", + "NW": "NV", + "NNW": "NNV", + + "FEELS": "Känns som {DEGREE}", + + "UPDATE_NOTIFICATION": "MagicMirror² uppdatering finns tillgänglig.", + "UPDATE_NOTIFICATION_MODULE": "Uppdatering finns tillgänglig av {MODULE_NAME} modulen.", + "UPDATE_INFO_SINGLE": "Denna installation ligger {COMMIT_COUNT} commit steg bakom {BRANCH_NAME} grenen.", + "UPDATE_INFO_MULTIPLE": "Denna installation ligger {COMMIT_COUNT} commits steg bakom {BRANCH_NAME} grenen." +} diff --git a/MagicMirror/translations/th.json b/MagicMirror/translations/th.json new file mode 100644 index 0000000000000000000000000000000000000000..26f472b7b9e4a7f7bd1c37d597c4dd82b9e28e72 --- /dev/null +++ b/MagicMirror/translations/th.json @@ -0,0 +1,43 @@ +{ + "LOADING": "à¸à¸³à¸¥à¸±à¸‡à¹‚หลด …", + + "TODAY": "วันนี้", + "TOMORROW": "พรุ่งนี้", + "RUNNING": "สิ้นสุดใน", + "EMPTY": "ไม่มีà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸—ี่à¸à¸³à¸¥à¸±à¸‡à¸ˆà¸°à¸¡à¸²à¸–ึง", + "WEEK": "สัปดาห์ที่ {weekNumber}", + + "N": "น", + "NNE": "น.ต.à¸.น.", + "NE": "ต.à¸.น.", + "ENE": "ต.à¸.ต.à¸.น.", + "E": "ต.à¸.", + "ESE": "ต.à¸.ต.à¸.ต.", + "SE": "ต.à¸.ต.", + "SSE": "ต.ต.à¸.ต.", + "S": "ต.", + "SSW": "ต.ต.ต.ต.", + "SW": "ต.ต.ต.", + "WSW": "ต.ต.ต.ต.ต.", + "W": "ต.ต.", + "WNW": "ต.ต.ต.ต.น.", + "NW": "ต.ต.น.", + "NNW": "น.ต.ต.น.", + + "PRECIP_POP": "ความà¹à¸¡à¹ˆà¸™à¸¢à¸³", + "PRECIP_AMOUNT": "ปริมาณน้ำà¸à¸™", + + "MODULE_CONFIG_CHANGED": "ตัวเลืà¸à¸à¸à¸²à¸£à¸à¸³à¸«à¸™à¸”ค่าสำหรับโมดูล {MODULE_NAME} มีà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡\nโปรดตรวจสà¸à¸šà¹€à¸à¸à¸ªà¸²à¸£à¸›à¸£à¸°à¸à¸à¸š", + "MODULE_CONFIG_ERROR": "เà¸à¸´à¸”ข้à¸à¸œà¸´à¸”พลาดในโมดูล {MODULE_NAME} {ERROR}", + "MODULE_ERROR_MALFORMED_URL": "URL ผิดรูปà¹à¸šà¸š", + "MODULE_ERROR_NO_CONNECTION": "ไม่มีà¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸à¸à¸´à¸™à¹€à¸—à¸à¸£à¹Œà¹€à¸™à¹‡à¸•.", + "MODULE_ERROR_UNAUTHORIZED": "à¸à¸²à¸£à¸à¸™à¸¸à¸à¸²à¸•ล้มเหลว", + "MODULE_ERROR_UNSPECIFIED": "ตรวจสà¸à¸šà¸šà¸±à¸™à¸—ึà¸à¸ªà¸³à¸«à¸£à¸±à¸šà¸£à¸²à¸¢à¸¥à¸°à¹€à¸à¸µà¸¢à¸”เพิ่มเติม", + + "NEWSFEED_NO_ITEMS": "ไม่มีข่าวในขณะนี้", + + "UPDATE_NOTIFICATION": "MagicMirror² มีà¸à¸²à¸£à¸à¸±à¸›à¹€à¸”ต", + "UPDATE_NOTIFICATION_MODULE": "มีà¸à¸²à¸£à¸à¸±à¸›à¹€à¸”ตสำหรับโมดูล {MODULE_NAME}", + "UPDATE_INFO_SINGLE": "à¸à¸²à¸£à¸•ิดตั้งปัจจุบันถูà¸à¸„à¸à¸¡à¸¡à¸´à¸— {COMMIT_COUNT} รายà¸à¸²à¸£à¹ƒà¸™à¸ªà¸²à¸‚า {BRANCH_NAME}", + "UPDATE_INFO_MULTIPLE": "à¸à¸²à¸£à¸•ิดตั้งปัจจุบันคืภ{COMMIT_COUNT} เป็นคà¸à¸¡à¸¡à¸´à¸—ที่à¸à¸¢à¸¹à¹ˆà¹€à¸šà¸·à¹‰à¸à¸‡à¸«à¸¥à¸±à¸‡à¹ƒà¸™à¸ªà¸²à¸‚า {BRANCH_NAME}" +} diff --git a/MagicMirror/translations/tlh.json b/MagicMirror/translations/tlh.json new file mode 100644 index 0000000000000000000000000000000000000000..6bcefefa55ba04be1c7553880299bf1227bae957 --- /dev/null +++ b/MagicMirror/translations/tlh.json @@ -0,0 +1,36 @@ +{ + "LOADING": "loS …", + + "DAYBEFOREYESTERDAY": "cha'Hu'", + "YESTERDAY": "wa'Hu'", + "TODAY": "DaHjaj", + "TOMORROW": "wa'leS", + "DAYAFTERTOMORROW": "cha'leS", + "RUNNING": "Dor", + "EMPTY": "Sumbe' wanI'.", + "WEEK": "Hogh (terran) {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "chan", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "tIng", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "'ev", + "NNW": "NNW", + + "FEELS": "jem {DEGREE}", + + "UPDATE_NOTIFICATION": " De'chu' MagicMirror² lI'laH.", + "UPDATE_NOTIFICATION_MODULE": "bobcho' {MODULE_NAME} lI'laH De'chu.", + "UPDATE_INFO_SINGLE": "The current installation is {COMMIT_COUNT} commit behind on the {BRANCH_NAME} branch.", + "UPDATE_INFO_MULTIPLE": "The current installation is {COMMIT_COUNT} commits behind on the {BRANCH_NAME} branch." +} diff --git a/MagicMirror/translations/tr.json b/MagicMirror/translations/tr.json new file mode 100644 index 0000000000000000000000000000000000000000..3c03f2954c6e080f92a531f95741c1917770ff52 --- /dev/null +++ b/MagicMirror/translations/tr.json @@ -0,0 +1,36 @@ +{ + "LOADING": "Yükleniyor …", + + "YESTERDAY": "Dün", + "TODAY": "Bugün", + "TOMORROW": "Yarın", + "DAYAFTERTOMORROW": "İki gün içinde", + "RUNNING": "Biten", + "EMPTY": "Yakında etkinlik yok.", + "WEEK": "Hafta {weekNumber}", + + "N": "K", + "NNE": "KKD", + "NE": "KD", + "ENE": "DKD", + "E": "D", + "ESE": "DGD", + "SE": "GD", + "SSE": "GGD", + "S": "G", + "SSW": "GGB", + "SW": "GB", + "WSW": "BGB", + "W": "B", + "WNW": "BKB", + "NW": "KB", + "NNW": "KKB", + + "FEELS": "Hissedilen {DEGREE}", + "PRECIP_POP": "Yağış", + + "UPDATE_NOTIFICATION": "MagicMirror² güncellemesi mevcut.", + "UPDATE_NOTIFICATION_MODULE": "{MODULE_NAME} modulü için güncelleme mevcut.", + "UPDATE_INFO_SINGLE": "Sahip olduÄŸunuz kurulum {BRANCH_NAME} branchinden {COMMIT_COUNT} commit geridedir.", + "UPDATE_INFO_MULTIPLE": "Sahip olduÄŸunuz kurulum {BRANCH_NAME} branchinden {COMMIT_COUNT} commit geridedir." +} diff --git a/MagicMirror/translations/translations.js b/MagicMirror/translations/translations.js new file mode 100644 index 0000000000000000000000000000000000000000..f3b2d2c14a20e7a24fd5879044f038768db3634d --- /dev/null +++ b/MagicMirror/translations/translations.js @@ -0,0 +1,57 @@ +/* MagicMirror² + * Translation Definition + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ + +let translations = { + en: "translations/en.json", // English + nl: "translations/nl.json", // Dutch + de: "translations/de.json", // German + fi: "translations/fi.json", // Suomi + fr: "translations/fr.json", // French + fy: "translations/fy.json", // Frysk + es: "translations/es.json", // Spanish + ca: "translations/ca.json", // Catalan + cv: "translations/cv.json", // Chuvash + nb: "translations/nb.json", // Norsk bokmÃ¥l + nn: "translations/nn.json", // Norsk nynorsk + pt: "translations/pt.json", // Português + "pt-br": "translations/pt-br.json", // Português Brasileiro + sv: "translations/sv.json", // Svenska + id: "translations/id.json", // Indonesian + it: "translations/it.json", // Italian + "zh-cn": "translations/zh-cn.json", // Simplified Chinese + "zh-tw": "translations/zh-tw.json", // Traditional Chinese + ja: "translations/ja.json", // Japanese + pl: "translations/pl.json", // Polish + el: "translations/el.json", // Greek + da: "translations/da.json", // Danish + tr: "translations/tr.json", // Turkish + ru: "translations/ru.json", // Russian + af: "translations/af.json", // Afrikaans + hu: "translations/hu.json", // Hungarian + is: "translations/is.json", // Icelandic + et: "translations/et.json", // Estonian + ko: "translations/ko.json", // Korean + ro: "translations/ro.json", // Romanian + cy: "translations/cy.json", // Welsh (Cymraeg) + bg: "translations/bg.json", // Bulgarian + cs: "translations/cs.json", // Czech + hr: "translations/hr.json", // Croatian + sk: "translations/sk.json", // Slovak + tlh: "translations/tlh.json", // Klingon + "ms-my": "translations/ms-my.json", // Malay + he: "translations/he.json", // Hebrew + uk: "translations/uk.json", // Ukrainian + hi: "translations/hi.json", // Hindi + gu: "translations/gu.json", // Gujarati + gl: "translations/gl.json", // Galego + lt: "translations/lt.json", // Lithuanian + th: "translations/th.json" // Thai +}; + +if (typeof module !== "undefined") { + module.exports = translations; +} diff --git a/MagicMirror/translations/uk.json b/MagicMirror/translations/uk.json new file mode 100644 index 0000000000000000000000000000000000000000..3bb4284e0d26dc72dc8b923f70ab9f17c8a983de --- /dev/null +++ b/MagicMirror/translations/uk.json @@ -0,0 +1,37 @@ +{ + "LOADING": "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ â€¦", + + "DAYBEFOREYESTERDAY": "Позавчора", + "YESTERDAY": "вчора", + "TODAY": "Сьогодні", + "TOMORROW": "Завтра", + "DAYAFTERTOMORROW": "ПіÑлÑзавтра", + "RUNNING": "ЗакінчуєтьÑÑ Ñ‡ÐµÑ€ÐµÐ·", + "EMPTY": "Ðемає найближчих подій", + "WEEK": "Тиждень {weekNumber}", + + "N": "Пн", + "NNE": "ПнПнСх", + "NE": "ПнСх", + "ENE": "СхПнСх", + "E": "Сх", + "ESE": "СхПдСх", + "SE": "СхПд", + "SSE": "СхСхПд", + "S": "Пд", + "SSW": "ПдПдЗх", + "SW": "ПдЗх", + "WSW": "ЗхПдЗх", + "W": "Зх", + "WNW": "ЗхПнЗх", + "NW": "ПнЗх", + "NNW": "ПнПнЗх", + + "FEELS": "ВідчуваєтьÑÑ Ñк {DEGREE}", + "PRECIP_POP": "Опади", + + "UPDATE_NOTIFICATION": "Є Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ MagicMirror².", + "UPDATE_NOTIFICATION_MODULE": "Є Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð¼Ð¾Ð´ÑƒÐ»Ñ {MODULE_NAME}.", + "UPDATE_INFO_SINGLE": "Поточна верÑÑ–Ñ Ð½Ð° {COMMIT_COUNT} комміт позаду від гілки {BRANCH_NAME}.", + "UPDATE_INFO_MULTIPLE": "Поточна інÑталÑÑ†Ñ–Ñ Ð½Ð° {COMMIT_COUNT} комітів позаду від гілки {BRANCH_NAME}." +} diff --git a/MagicMirror/translations/zh-cn.json b/MagicMirror/translations/zh-cn.json new file mode 100644 index 0000000000000000000000000000000000000000..f4ee2562f70c5517515dbf04c7c210e78af26850 --- /dev/null +++ b/MagicMirror/translations/zh-cn.json @@ -0,0 +1,36 @@ +{ + "LOADING": "æ£åœ¨åŠ è½½ …", + + "DAYBEFOREYESTERDAY": "å‰å¤©", + "YESTERDAY": "昨天", + "TODAY": "今天", + "TOMORROW": "明天", + "DAYAFTERTOMORROW": "åŽå¤©", + "RUNNING": "ç»“æŸæ—¥æœŸ", + "EMPTY": "æ— æ—¥ç¨‹å®‰æŽ’ã€‚", + "WEEK": "第{weekNumber}周", + + "N": "北风", + "NNE": "北å东风", + "NE": "东北风", + "ENE": "东å北风", + "E": "东风", + "ESE": "东åå—风", + "SE": "东å—风", + "SSE": "å—å东风", + "S": "å—风", + "SSW": "å—å西风", + "SW": "西å—风", + "WSW": "西åå—风", + "W": "西风", + "WNW": "西å北风", + "NW": "西北风", + "NNW": "北å西风", + + "FEELS": "体感 {DEGREE}", + + "UPDATE_NOTIFICATION": "MagicMirror²有新的版本。", + "UPDATE_NOTIFICATION_MODULE": "{MODULE_NAME}模å—坿›´æ–°ã€‚", + "UPDATE_INFO_SINGLE": "当å‰å·²å®‰è£…版本比{BRANCH_NAME}分支è½åŽ{COMMIT_COUNT}æ¬¡ä»£ç æ›´æ–°ã€‚", + "UPDATE_INFO_MULTIPLE": "当å‰å·²å®‰è£…版本比{BRANCH_NAME}分支è½åŽ{COMMIT_COUNT}æ¬¡ä»£ç æ›´æ–°ã€‚" +} diff --git a/MagicMirror/translations/zh-tw.json b/MagicMirror/translations/zh-tw.json new file mode 100644 index 0000000000000000000000000000000000000000..6bdd95dcb5baa23495299fa06b86c9e0a004f82b --- /dev/null +++ b/MagicMirror/translations/zh-tw.json @@ -0,0 +1,46 @@ +{ + "LOADING": "æ£åœ¨è¼‰å…¥ …", + + "DAYBEFOREYESTERDAY": "å‰å¤©", + "YESTERDAY": "昨天", + "TODAY": "今天", + "TOMORROW": "明天", + "DAYAFTERTOMORROW": "後天", + "RUNNING": "çµæŸæ—¥æœŸ", + "EMPTY": "沒有更多的活動。", + "WEEK": "第 {weekNumber} 週", + + "N": "北風", + "NNE": "åŒ—åæ±é¢¨", + "NE": "æ±åŒ—風", + "ENE": "æ±å北風", + "E": "æ±é¢¨", + "ESE": "æ±åå—風", + "SE": "æ±å—風", + "SSE": "å—忱颍", + "S": "å—風", + "SSW": "å—å西風", + "SW": "西å—風", + "WSW": "西åå—風", + "W": "西風", + "WNW": "西å北風", + "NW": "西北風", + "NNW": "北å西風", + + "FEELS": "體感溫度 {DEGREE}", + "PRECIP_POP": "é™é›¨æ©Ÿçއ", + + "MODULE_CONFIG_CHANGED": "模組 {MODULE_NAME} çš„è¨å®šæª”é¸é …已更改。\nè«‹åƒè¦‹èªªæ˜Žæ–‡ä»¶ã€‚", + "MODULE_CONFIG_ERROR": "{MODULE_NAME} 模組發生錯誤。{ERROR}", + "MODULE_ERROR_MALFORMED_URL": "ç¶²å€æ ¼å¼éŒ¯èª¤ã€‚", + "MODULE_ERROR_NO_CONNECTION": "無網路連線。", + "MODULE_ERROR_UNAUTHORIZED": "授權失敗。", + "MODULE_ERROR_UNSPECIFIED": "查看日誌以了解詳情。", + + "NEWSFEED_NO_ITEMS": "ç›®å‰æ²’有新èžã€‚", + + "UPDATE_NOTIFICATION": "MagicMirror² 有å¯ç”¨æ›´æ–°ã€‚", + "UPDATE_NOTIFICATION_MODULE": "{MODULE_NAME} 模組有å¯ç”¨æ›´æ–°ã€‚", + "UPDATE_INFO_SINGLE": "ç›®å‰ç‰ˆæœ¬åœ¨ {BRANCH_NAME} 分支上已è½å¾Œäº† {COMMIT_COUNT} 次 commit。", + "UPDATE_INFO_MULTIPLE": "ç›®å‰ç‰ˆæœ¬åœ¨ {BRANCH_NAME} 分支上已è½å¾Œäº† {COMMIT_COUNT} 次 commit。" +} diff --git a/MagicMirror/vendor/css/font-awesome.css b/MagicMirror/vendor/css/font-awesome.css new file mode 100644 index 0000000000000000000000000000000000000000..0c52aa670854bb8ed4dbc648699dde0c9a1f3826 --- /dev/null +++ b/MagicMirror/vendor/css/font-awesome.css @@ -0,0 +1,2 @@ +@import url("../node_modules/@fortawesome/fontawesome-free/css/all.min.css"); +@import url("../node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css"); diff --git a/MagicMirror/vendor/package-lock.json b/MagicMirror/vendor/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..3ee7a27516b8fd03cf252a43626449f21735cbe5 --- /dev/null +++ b/MagicMirror/vendor/package-lock.json @@ -0,0 +1,154 @@ +{ + "name": "magicmirror-vendors", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "magicmirror-vendors", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-free": "^6.3.0", + "moment": "^2.29.4", + "moment-timezone": "^0.5.41", + "nunjucks": "^3.2.3", + "suncalc": "^1.9.0", + "weathericons": "^2.1.0" + } + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.3.0.tgz", + "integrity": "sha512-qVtd5i1Cc7cdrqnTWqTObKQHjPWAiRwjUPaXObaeNPcy7+WKxJumGBx66rfSFgK6LNpIasVKkEgW8oyf0tmPLA==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.41", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.41.tgz", + "integrity": "sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nunjucks": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", + "integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==", + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/suncalc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", + "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==" + }, + "node_modules/weathericons": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/weathericons/-/weathericons-2.1.0.tgz", + "integrity": "sha512-V45viuyuQQOuoePTqzxvP/wBpYALWkD695fkFvqpn+BiMyo64fFlyDbP2A8umZyFyz1cXFPNw1pWqeaSaQqJlQ==" + } + }, + "dependencies": { + "@fortawesome/fontawesome-free": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.3.0.tgz", + "integrity": "sha512-qVtd5i1Cc7cdrqnTWqTObKQHjPWAiRwjUPaXObaeNPcy7+WKxJumGBx66rfSFgK6LNpIasVKkEgW8oyf0tmPLA==" + }, + "a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" + }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, + "moment-timezone": { + "version": "0.5.41", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.41.tgz", + "integrity": "sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg==", + "requires": { + "moment": "^2.29.4" + } + }, + "nunjucks": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", + "integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==", + "requires": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + } + }, + "suncalc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", + "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==" + }, + "weathericons": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/weathericons/-/weathericons-2.1.0.tgz", + "integrity": "sha512-V45viuyuQQOuoePTqzxvP/wBpYALWkD695fkFvqpn+BiMyo64fFlyDbP2A8umZyFyz1cXFPNw1pWqeaSaQqJlQ==" + } + } +} diff --git a/MagicMirror/vendor/package.json b/MagicMirror/vendor/package.json new file mode 100644 index 0000000000000000000000000000000000000000..b83dd916c54144dc2a88830f922049e0e80a43f8 --- /dev/null +++ b/MagicMirror/vendor/package.json @@ -0,0 +1,20 @@ +{ + "name": "magicmirror-vendors", + "description": "Package for vendors use by MagicMirror² Core.", + "repository": { + "type": "git", + "url": "git+https://github.com/MichMich/MagicMirror.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/MichMich/MagicMirror/issues" + }, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.3.0", + "moment": "^2.29.4", + "moment-timezone": "^0.5.41", + "nunjucks": "^3.2.3", + "suncalc": "^1.9.0", + "weathericons": "^2.1.0" + } +} diff --git a/MagicMirror/vendor/vendor.js b/MagicMirror/vendor/vendor.js new file mode 100644 index 0000000000000000000000000000000000000000..7d7a2a8ee838cca1cdcf7e197387d7de34d8c3d3 --- /dev/null +++ b/MagicMirror/vendor/vendor.js @@ -0,0 +1,19 @@ +/* MagicMirror² + * Vendor File Definition + * + * By Michael Teeuw https://michaelteeuw.nl + * MIT Licensed. + */ +const vendor = { + "moment.js": "node_modules/moment/min/moment-with-locales.js", + "moment-timezone.js": "node_modules/moment-timezone/builds/moment-timezone-with-data.js", + "weather-icons.css": "node_modules/weathericons/css/weather-icons.css", + "weather-icons-wind.css": "node_modules/weathericons/css/weather-icons-wind.css", + "font-awesome.css": "css/font-awesome.css", + "nunjucks.js": "node_modules/nunjucks/browser/nunjucks.min.js", + "suncalc.js": "node_modules/suncalc/suncalc.js" +}; + +if (typeof module !== "undefined") { + module.exports = vendor; +}