├── .gitignore
├── LICENSE
├── README.md
├── dist
├── MLB.js
└── TermiWidget.js
├── images
├── mlb-expanded-0.jpg
├── mlb-expanded-1.jpg
└── termiwidget.png
├── package-lock.json
├── package.json
├── src
├── MLB.js
├── TermiWidget.js
└── lib
│ ├── cache.js
│ ├── http.js
│ └── updater.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/node
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node
4 |
5 | ### Node ###
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 |
14 | # Diagnostic reports (https://nodejs.org/api/report.html)
15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 | *.lcov
29 |
30 | # nyc test coverage
31 | .nyc_output
32 |
33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34 | .grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | bower_components
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directories
46 | node_modules/
47 | jspm_packages/
48 |
49 | # TypeScript v1 declaration files
50 | typings/
51 |
52 | # TypeScript cache
53 | *.tsbuildinfo
54 |
55 | # Optional npm cache directory
56 | .npm
57 |
58 | # Optional eslint cache
59 | .eslintcache
60 |
61 | # Microbundle cache
62 | .rpt2_cache/
63 | .rts2_cache_cjs/
64 | .rts2_cache_es/
65 | .rts2_cache_umd/
66 |
67 | # Optional REPL history
68 | .node_repl_history
69 |
70 | # Output of 'npm pack'
71 | *.tgz
72 |
73 | # Yarn Integrity file
74 | .yarn-integrity
75 |
76 | # dotenv environment variables file
77 | .env
78 | .env.test
79 | .env*.local
80 |
81 | # parcel-bundler cache (https://parceljs.org/)
82 | .cache
83 | .parcel-cache
84 |
85 | # Next.js build output
86 | .next
87 |
88 | # Nuxt.js build / generate output
89 | .nuxt
90 |
91 | # Gatsby files
92 | .cache/
93 | # Comment in the public line in if your project uses Gatsby and not Next.js
94 | # https://nextjs.org/blog/next-9-1#public-directory-support
95 | # public
96 |
97 | # vuepress build output
98 | .vuepress/dist
99 |
100 | # Serverless directories
101 | .serverless/
102 |
103 | # FuseBox cache
104 | .fusebox/
105 |
106 | # DynamoDB Local files
107 | .dynamodb/
108 |
109 | # TernJS port file
110 | .tern-port
111 |
112 | # Stores VSCode versions used for testing VSCode extensions
113 | .vscode-test
114 |
115 | # End of https://www.toptal.com/developers/gitignore/api/node
116 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Evan Coleman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scriptable
2 |
3 | A collection of my scriptable.app scripts
4 |
5 | ## Scripts
6 |
7 | ### Modified TermiWidget
8 |
9 | Original by @spencerwooo: https://gist.github.com/spencerwooo/7955aefc4ffa5bc8ae7c83d85d05e7a4
10 |
11 | Copy both src/TermiWidget.js and src/cache.js into your Scriptable iCloud Drive folder and modify the values at the top of TermiWidget.js
12 |
13 | ### MLB Scores
14 |
15 | Copy dist/MLB.js into your Scriptable iCloud Drive folder (or copy & paste into a new script in the Scriptable app).
16 |
17 | ## Images
18 |
19 | TermiWidget | MLB
20 | :-------------------------:|:-------------------------:
21 |  | 
22 |
23 |
--------------------------------------------------------------------------------
/dist/MLB.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: deep-blue; icon-glyph: baseball-ball;
4 |
5 | /////////////////////////////////////////
6 | //
7 | // Configuration - PLEASE READ
8 | //
9 | /////////////////////////////////////////
10 |
11 | // PLEASE READ - To set your team:
12 | // Long-press on the widget on your homescreen, then tap "Edit Widget"
13 | // Input your team abbreviation in the "Parameter" field.
14 | // Set "When Interacting" to "Run Script" if you want taps to route to the MLB app.
15 | // Find team abbreviation here: https://en.wikipedia.org/wiki/Wikipedia:WikiProject_Baseball/Team_abbreviations
16 |
17 | /////////////////////////
18 |
19 | const TEAM = args.widgetParameter || 'NYY';
20 |
21 | // simple, expanded
22 | const LAYOUT = "expanded";
23 |
24 | /////////////////////////////////////////
25 | //
26 | // Do not edit below this line!
27 | //
28 | /////////////////////////////////////////
29 |
30 | /******/
31 | /******/ (() => { // webpackBootstrap
32 | /******/ "use strict";
33 | /******/ var __webpack_modules__ = ({
34 |
35 | /***/ 208:
36 | /***/ ((module, __unused_webpack___webpack_exports__, __webpack_require__) => {
37 |
38 | __webpack_require__.a(module, async (__webpack_handle_async_dependencies__) => {
39 | /* harmony import */ var _lib_cache__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(59);
40 | /* harmony import */ var _lib_updater__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(331);
41 | /* harmony import */ var _lib_http__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(630);
42 |
43 |
44 |
45 |
46 |
47 |
48 | const scriptVersion = 16;
49 | const sourceRepo = "evandcoleman/scriptable";
50 | const scriptName = "MLB";
51 |
52 | /////////////////////////////////////////
53 | //
54 | // Script
55 | //
56 | /////////////////////////////////////////
57 |
58 | const cache = new _lib_cache__WEBPACK_IMPORTED_MODULE_0__/* .default */ .Z("mlbWidgetCache", 2);
59 | const updater = new _lib_updater__WEBPACK_IMPORTED_MODULE_1__/* .default */ .Z(sourceRepo);
60 |
61 | try {
62 | const widget = await (async (layout) => {
63 | switch (layout) {
64 | case 'simple':
65 | return createSimpleWidget();
66 | case 'expanded':
67 | return createExpandedWidget();
68 | default:
69 | throw new Error(`Invalid layout type ${layout}`);
70 | }
71 | })(LAYOUT);
72 | widget.url = "mlbatbat://"
73 | Script.setWidget(widget);
74 | } catch (error) {
75 | console.log(`${error.line}: ${error.message}`);
76 | }
77 |
78 | try {
79 | await updater.checkForUpdate(scriptName, scriptVersion);
80 | } catch (error) {
81 | console.log(`${error.line}: ${error.message}`);
82 | }
83 |
84 | Script.complete();
85 |
86 | async function createExpandedWidget() {
87 | const w = new ListWidget()
88 | w.backgroundColor = new Color("#0F1011");
89 | w.setPadding(20, 15, 15, 15)
90 |
91 | const mainStack = w.addStack();
92 | mainStack.layoutVertically();
93 |
94 | const { game, team } = await fetchTeam(TEAM);
95 | const awayLogo = await fetchTeamLogo(game.teams.away.team.abbreviation);
96 | const homeLogo = await fetchTeamLogo(game.teams.home.team.abbreviation);
97 | const { gameStatus, isPlaying, isPreGame, isPostGame, isPPD } = getFormattedStatus(game);
98 |
99 | const upperStack = mainStack.addStack();
100 |
101 | if (!isPreGame && !isPPD) {
102 | upperStack.layoutHorizontally();
103 | const scoreStack = upperStack.addStack();
104 | scoreStack.layoutVertically();
105 |
106 | const awayStack = scoreStack.addStack();
107 | awayStack.centerAlignContent();
108 | const awayLogoImage = awayStack.addImage(awayLogo);
109 | awayLogoImage.imageSize = new Size(32, 32);
110 | awayStack.addSpacer(6);
111 | if (game.linescore) {
112 | const awayRuns = awayStack.addText(`${game.linescore.teams.away.runs || 0}`);
113 | awayRuns.font = Font.boldSystemFont(28);
114 | awayRuns.textColor = Color.white();
115 | }
116 |
117 | const spacer = scoreStack.addSpacer();
118 | spacer.length = 6;
119 |
120 | const homeStack = scoreStack.addStack();
121 | homeStack.centerAlignContent();
122 | const homeLogoImage = homeStack.addImage(homeLogo);
123 | homeLogoImage.imageSize = new Size(32, 32);
124 | homeStack.addSpacer(6);
125 | if (game.linescore) {
126 | const homeRuns = homeStack.addText(`${game.linescore.teams.home.runs || 0}`);
127 | homeRuns.font = Font.boldSystemFont(28);
128 | homeRuns.textColor = Color.white();
129 | }
130 | } else {
131 | upperStack.layoutVertically();
132 |
133 | const logoStack = upperStack.addStack();
134 | logoStack.layoutHorizontally();
135 | logoStack.bottomAlignContent();
136 | const awayLogoImage = logoStack.addImage(awayLogo);
137 | awayLogoImage.imageSize = new Size(38, 38);
138 | logoStack.addSpacer();
139 | const vsText = logoStack.addText('vs.');
140 | vsText.textColor = Color.lightGray();
141 | vsText.font = Font.regularSystemFont(14);
142 | logoStack.addSpacer();
143 | const homeLogoImage = logoStack.addImage(homeLogo);
144 | homeLogoImage.imageSize = new Size(38, 38);
145 | }
146 |
147 | upperStack.addSpacer();
148 | const statusStack = upperStack.addStack();
149 | statusStack.layoutVertically();
150 |
151 | const inningStack = statusStack.addStack();
152 | inningStack.layoutHorizontally();
153 | inningStack.centerAlignContent();
154 |
155 | if (isPlaying) {
156 | inningStack.addSpacer(12);
157 | const arrowText = inningStack.addText(game.linescore.isTopInning ? '▲' : '▼');
158 | arrowText.font = Font.regularSystemFont(10);
159 | arrowText.textColor = Color.lightGray();
160 | inningStack.addSpacer(4);
161 | const statusText = inningStack.addText(game.linescore.currentInning.toString());
162 | statusText.font = Font.mediumSystemFont(22);
163 | statusText.textColor = Color.white();
164 |
165 | const basesStack = statusStack.addStack();
166 | basesStack.layoutHorizontally();
167 | const bases = getBasesImage(game);
168 | const basesWidgetImage = basesStack.addImage(bases);
169 | basesWidgetImage.rightAlignImage();
170 | basesWidgetImage.imageSize = new Size(42, 42);
171 |
172 | const outsStack = statusStack.addStack();
173 | outsStack.layoutHorizontally();
174 | const outImages = getOutsImages(game);
175 | for (let index in outImages) {
176 | if (index > 0) {
177 | outsStack.addSpacer(index == 0 ? null : index === 2 ? 0 : 12);
178 | }
179 | const widgetImage = outsStack.addImage(outImages[index]);
180 | widgetImage.imageSize = new Size(6, 6);
181 | }
182 | } else if (isPreGame || isPPD) {
183 | inningStack.addSpacer();
184 | const statusText = inningStack.addText(gameStatus);
185 | statusText.font = Font.regularSystemFont(11);
186 | statusText.textColor = Color.lightGray();
187 | inningStack.addSpacer();
188 | } else {
189 | const statusText = inningStack.addText(gameStatus);
190 | statusText.font = Font.caption1();
191 | statusText.textColor = Color.lightGray();
192 | }
193 |
194 | mainStack.addSpacer();
195 |
196 | const lowerStack = mainStack.addStack();
197 | lowerStack.layoutVertically();
198 |
199 | if (isPlaying) {
200 | const abTitleText = lowerStack.addText("At Bat:")
201 | abTitleText.font = Font.mediumSystemFont(11);
202 | abTitleText.textColor = Color.lightGray();
203 | const nameCountStack = lowerStack.addStack();
204 | nameCountStack.layoutHorizontally();
205 | nameCountStack.centerAlignContent();
206 | const playerNameText = nameCountStack.addText(game.linescore.offense.batter.fullName);
207 | playerNameText.font = Font.regularSystemFont(12);
208 | playerNameText.textColor = Color.white();
209 | // playerNameText.minimumScaleFactor = 0.9;
210 | nameCountStack.addSpacer(4);
211 | const countText = nameCountStack.addText(`(${game.linescore.balls}-${game.linescore.strikes})`);
212 | countText.font = Font.regularSystemFont(10);
213 | countText.textColor = Color.lightGray();
214 | nameCountStack.addSpacer();
215 |
216 | const pitcherTitleText = lowerStack.addText("Pitching:")
217 | pitcherTitleText.font = Font.mediumSystemFont(11);
218 | pitcherTitleText.textColor = Color.lightGray();
219 | const namePitchesStack = lowerStack.addStack();
220 | namePitchesStack.layoutHorizontally();
221 | namePitchesStack.centerAlignContent();
222 | const pitcherNameText = namePitchesStack.addText(game.linescore.defense.pitcher.fullName);
223 | pitcherNameText.font = Font.regularSystemFont(12);
224 | pitcherNameText.textColor = Color.white();
225 | // pitcherNameText.minimumScaleFactor = 0.9;
226 | namePitchesStack.addSpacer(4);
227 | const pitchesThrown = game.linescore.defense.pitcher.stats.filter(stat => stat.type.displayName === 'gameLog' && stat.group.displayName === 'pitching')[0].stats.pitchesThrown;
228 | const pitchesThrownText = namePitchesStack.addText(`(P ${pitchesThrown})`);
229 | pitchesThrownText.font = Font.regularSystemFont(10);
230 | pitchesThrownText.textColor = Color.lightGray();
231 | namePitchesStack.addSpacer();
232 | } else if (isPreGame || isPPD) {
233 | const abTitleText = lowerStack.addText("Away Pitcher:")
234 | abTitleText.font = Font.mediumSystemFont(11);
235 | abTitleText.textColor = Color.lightGray();
236 | const nameCountStack = lowerStack.addStack();
237 | nameCountStack.layoutHorizontally();
238 | nameCountStack.centerAlignContent();
239 | const playerNameText = nameCountStack.addText(game.teams.away.probablePitcher?.fullName || 'TBD');
240 | playerNameText.font = Font.regularSystemFont(12);
241 | playerNameText.textColor = Color.white();
242 | // playerNameText.minimumScaleFactor = 0.9;
243 | if (game.teams.away.probablePitcher) {
244 | nameCountStack.addSpacer(4);
245 | if (game.teams.away.probablePitcher.stats) {
246 | const winnerStats = game.teams.away.probablePitcher.stats.filter(stat => stat.type.displayName === 'statsSingleSeason' && stat.group.displayName === 'pitching')[0].stats;
247 | const countText = nameCountStack.addText(`(${winnerStats.wins}-${winnerStats.losses})`);
248 | countText.font = Font.regularSystemFont(10);
249 | countText.textColor = Color.lightGray();
250 | }
251 | }
252 | nameCountStack.addSpacer();
253 |
254 | const pitcherTitleText = lowerStack.addText("Home Pitcher:")
255 | pitcherTitleText.font = Font.mediumSystemFont(11);
256 | pitcherTitleText.textColor = Color.lightGray();
257 | const namePitchesStack = lowerStack.addStack();
258 | namePitchesStack.layoutHorizontally();
259 | namePitchesStack.centerAlignContent();
260 | const pitcherNameText = namePitchesStack.addText(game.teams.home.probablePitcher?.fullName || 'TBD');
261 | pitcherNameText.font = Font.regularSystemFont(12);
262 | pitcherNameText.textColor = Color.white();
263 | // pitcherNameText.minimumScaleFactor = 0.9;
264 | if (game.teams.home.probablePitcher) {
265 | namePitchesStack.addSpacer(4);
266 | if (game.teams.home.probablePitcher.stats) {
267 | const loserStats = game.teams.home.probablePitcher.stats.filter(stat => stat.type.displayName === 'statsSingleSeason' && stat.group.displayName === 'pitching')[0].stats;
268 | const pitchesThrownText = namePitchesStack.addText(`(${loserStats.wins}-${loserStats.losses})`);
269 | pitchesThrownText.font = Font.regularSystemFont(10);
270 | pitchesThrownText.textColor = Color.lightGray();
271 | }
272 | }
273 | namePitchesStack.addSpacer();
274 | } else if (isPostGame && game.decisions) {
275 | const abTitleText = lowerStack.addText("Winning Pitcher:")
276 | abTitleText.font = Font.mediumSystemFont(11);
277 | abTitleText.textColor = Color.lightGray();
278 | const nameCountStack = lowerStack.addStack();
279 | nameCountStack.layoutHorizontally();
280 | nameCountStack.centerAlignContent();
281 | const playerNameText = nameCountStack.addText(game.decisions.winner?.fullName || "N/A");
282 | playerNameText.font = Font.regularSystemFont(12);
283 | playerNameText.textColor = Color.white();
284 | // playerNameText.minimumScaleFactor = 0.9;
285 | nameCountStack.addSpacer(4);
286 | if (game.decisions.winner && game.decisions.winner.stats) {
287 | const winnerStats = game.decisions.winner.stats.filter(stat => stat.type.displayName === 'statsSingleSeason' && stat.group.displayName === 'pitching')[0].stats;
288 | const countText = nameCountStack.addText(`(${winnerStats.wins}-${winnerStats.losses})`);
289 | countText.font = Font.regularSystemFont(10);
290 | countText.textColor = Color.lightGray();
291 | }
292 | nameCountStack.addSpacer();
293 |
294 | const pitcherTitleText = lowerStack.addText("Losing Pitcher:")
295 | pitcherTitleText.font = Font.mediumSystemFont(11);
296 | pitcherTitleText.textColor = Color.lightGray();
297 | const namePitchesStack = lowerStack.addStack();
298 | namePitchesStack.layoutHorizontally();
299 | namePitchesStack.centerAlignContent();
300 | const pitcherNameText = namePitchesStack.addText(game.decisions.loser?.fullName || "N/A");
301 | pitcherNameText.font = Font.regularSystemFont(12);
302 | pitcherNameText.textColor = Color.white();
303 | // pitcherNameText.minimumScaleFactor = 0.9;
304 | namePitchesStack.addSpacer(4);
305 | if (game.decisions.loser && game.decisions.loser.stats) {
306 | const loserStats = game.decisions.loser.stats.filter(stat => stat.type.displayName === 'statsSingleSeason' && stat.group.displayName === 'pitching')[0].stats;
307 | const pitchesThrownText = namePitchesStack.addText(`(${loserStats.wins}-${loserStats.losses})`);
308 | pitchesThrownText.font = Font.regularSystemFont(10);
309 | pitchesThrownText.textColor = Color.lightGray();
310 | }
311 | namePitchesStack.addSpacer();
312 | }
313 |
314 | lowerStack.addSpacer();
315 |
316 | return w
317 | }
318 |
319 | async function createSimpleWidget() {
320 | const w = new ListWidget()
321 | w.backgroundColor = new Color("#0F1011");
322 | w.setPadding(15, 10, 15, 15)
323 |
324 | const mainStack = w.addStack();
325 | mainStack.layoutVertically();
326 |
327 | const { game, team } = await fetchTeam(TEAM);
328 | const awayLogo = await fetchTeamLogo(game.teams.away.team.abbreviation);
329 | const homeLogo = await fetchTeamLogo(game.teams.home.team.abbreviation);
330 | const { gameStatus, isPlaying, isPreGame, isPostGame } = getFormattedStatus(game);
331 |
332 | const scoreStack = mainStack.addStack();
333 | scoreStack.layoutVertically();
334 |
335 | const awayStack = scoreStack.addStack();
336 | awayStack.centerAlignContent();
337 | const awayLogoImage = awayStack.addImage(awayLogo);
338 | awayLogoImage.imageSize = new Size(42, 42);
339 | awayStack.addSpacer();
340 | const awayName = awayStack.addText(game.teams.away.team.abbreviation);
341 | awayName.font = Font.title2();
342 | awayName.textColor = Color.white();
343 | awayStack.addSpacer();
344 | if (!isPreGame) {
345 | const awayRuns = awayStack.addText(`${game.linescore.teams.away.runs || 0}`);
346 | awayRuns.font = Font.title2();
347 | awayRuns.textColor = Color.white();
348 | }
349 |
350 | const spacer = scoreStack.addSpacer();
351 | spacer.length = 6;
352 |
353 | const homeStack = scoreStack.addStack();
354 | homeStack.centerAlignContent();
355 | const homeLogoImage = homeStack.addImage(homeLogo);
356 | homeLogoImage.imageSize = new Size(42, 42);
357 | homeStack.addSpacer();
358 | const homeName = homeStack.addText(game.teams.home.team.abbreviation);
359 | homeName.font = Font.title2();
360 | homeName.textColor = Color.white();
361 | homeStack.addSpacer();
362 | if (!isPreGame) {
363 | const homeRuns = homeStack.addText(`${game.linescore.teams.home.runs || 0}`);
364 | homeRuns.font = Font.title2();
365 | homeRuns.textColor = Color.white();
366 | }
367 |
368 | mainStack.addSpacer();
369 | const statusStack = mainStack.addStack();
370 | statusStack.layoutHorizontally();
371 | statusStack.addSpacer();
372 |
373 | const statusText = statusStack.addText(gameStatus);
374 | statusText.font = Font.callout();
375 | statusText.textColor = Color.lightGray();
376 |
377 | return w
378 | }
379 |
380 | function getFormattedStatus(game, opts) {
381 | const options = opts || {};
382 | const status = game.status.abstractGameState;
383 | const shortStatus = game.status.abstractGameCode;
384 | const innings = (game.linescore || { innings: [] }).innings.length;
385 | const short = options.short || false;
386 |
387 | let statusText;
388 | let isPlaying = false;
389 | let isPreGame = false;
390 | let isPostGame = false;
391 | let isPPD = false;
392 | switch (status) {
393 | case "Final":
394 | case "Completed Early":
395 | case "Game Over":
396 | isPostGame = true;
397 | isPPD = game.status.detailedState === "Postponed";
398 | if (innings !== 9) {
399 | statusText = `${short ? shortStatus : status}/${innings}`;
400 | } else {
401 | statusText = short ? shortStatus : status;
402 | }
403 | if (isPPD) {
404 | statusText = game.status.detailedState;
405 | }
406 | break;
407 | case "Delayed":
408 | isPlaying = true;
409 | statusText = `${short ? shortStatus : status}/${innings}`;
410 | break;
411 | case "Suspended":
412 | isPostGame = true;
413 | statusText = `${short ? shortStatus : status}/${innings}`;
414 | break;
415 | case "In Progress":
416 | case "Live":
417 | isPlaying = true;
418 | if (!short) {
419 | statusText = `${game.linescore.inningState} ${game.linescore.currentInningOrdinal}`;
420 | } else {
421 | statusText = `${game.linescore.isTopInning ? 'Top' : 'Bot'} ${game.linescore.currentInning}`;
422 | }
423 | break;
424 | case "Preview":
425 | case "Pre-Game":
426 | isPreGame = true;
427 | const df = new DateFormatter();
428 | df.useShortTimeStyle();
429 | const now = new Date();
430 | const tomorrow = new Date(now.getTime() + 86400000);
431 | if ((new Date(game.gameDate)).setHours(0, 0, 0, 0) === now.setHours(0, 0, 0, 0)) {
432 | df.useNoDateStyle();
433 | statusText = df.string(new Date(game.gameDate));
434 | } else if ((new Date(game.gameDate)).setHours(0, 0, 0, 0) === tomorrow.setHours(0, 0, 0, 0)) {
435 | df.useNoDateStyle();
436 | const rdtf = new RelativeDateTimeFormatter();
437 | rdtf.useNamedDateTimeStyle();
438 | statusText = rdtf.string(new Date(game.gameDate), now) + ' at ' + df.string(new Date(game.gameDate));
439 | } else {
440 | df.dateFormat = "E M/d 'at' h:mm a";
441 | statusText = df.string(new Date(game.gameDate));
442 | }
443 | break;
444 | default:
445 | statusText = short ? shortStatus : status;
446 | break;
447 | }
448 |
449 | return {
450 | gameStatus: statusText,
451 | isPlaying,
452 | isPreGame,
453 | isPostGame,
454 | isPPD,
455 | }
456 | }
457 |
458 | function getBasesImage(game) {
459 | const side = 80;
460 | const space = 6;
461 | const onFirst = 'first' in game.linescore.offense;
462 | const onSecond = 'second' in game.linescore.offense;
463 | const onThird = 'third' in game.linescore.offense;
464 |
465 | const baseSide = (Math.sqrt(2 * Math.pow(side / 2, 2)) / 2) - space;
466 | const baseHyp = Math.sqrt(2 * Math.pow(baseSide, 2));
467 | const spaceX = Math.sqrt(Math.pow(space, 2) / 2) * 2;
468 |
469 | const ctx = new DrawContext();
470 | ctx.opaque = false;
471 | ctx.size = new Size(side, side);
472 | ctx.setStrokeColor(Color.lightGray());
473 | ctx.setFillColor(new Color("#FFA500"));
474 | ctx.setLineWidth(2);
475 |
476 | const thirdBasePath = new Path();
477 | thirdBasePath.addLines([
478 | new Point(0, side / 2),
479 | new Point(baseHyp / 2, (side / 2) + (baseHyp / 2)),
480 | new Point(baseHyp, side / 2),
481 | new Point(baseHyp / 2, (side / 2) - (baseHyp / 2))
482 | ]);
483 | thirdBasePath.closeSubpath();
484 | ctx.addPath(thirdBasePath);
485 | ctx.strokePath();
486 | if (onThird) {
487 | ctx.addPath(thirdBasePath);
488 | ctx.fillPath();
489 | }
490 |
491 | const secondBasePath = new Path();
492 | secondBasePath.addLines([
493 | new Point((baseHyp / 2) + spaceX, baseHyp / 2),
494 | new Point(baseHyp + spaceX, 0),
495 | new Point(baseHyp + spaceX + (baseHyp / 2), baseHyp / 2),
496 | new Point(baseHyp + spaceX, baseHyp)
497 | ]);
498 | secondBasePath.closeSubpath();
499 | ctx.addPath(secondBasePath);
500 | ctx.strokePath();
501 | if (onSecond) {
502 | ctx.addPath(secondBasePath);
503 | ctx.fillPath();
504 | }
505 |
506 | const firstBasePath = new Path();
507 | firstBasePath.addLines([
508 | new Point((side / 2) + spaceX, side / 2),
509 | new Point(((side / 2) + spaceX) + (baseHyp / 2), (side / 2) + (baseHyp / 2)),
510 | new Point(((side / 2) + spaceX) + baseHyp, side / 2),
511 | new Point(((side / 2) + spaceX) + (baseHyp / 2), (side / 2) - (baseHyp / 2))
512 | ]);
513 | firstBasePath.closeSubpath();
514 | ctx.addPath(firstBasePath);
515 | ctx.strokePath();
516 | if (onFirst) {
517 | ctx.addPath(firstBasePath);
518 | ctx.fillPath();
519 | }
520 |
521 | const image = ctx.getImage();
522 |
523 | return image;
524 | }
525 |
526 | function getOutsImages(game) {
527 | const radius = 8;
528 |
529 | const ctx = new DrawContext();
530 | ctx.opaque = false;
531 | ctx.size = new Size(radius * 2, radius * 2);
532 | ctx.setFillColor(Color.lightGray());
533 |
534 | const outs = game.linescore.outs;
535 |
536 | for (let i = 0; i < 3; i += 1) {
537 | ctx.fillEllipse(new Rect(0, 0, radius * 2, radius * 2));
538 | }
539 |
540 | const offImage = ctx.getImage();
541 | ctx.setFillColor(new Color("#FFA500"));
542 |
543 | for (let i = 1; i <= 3; i += 1) {
544 | ctx.fillEllipse(new Rect(0, 0, radius * 2, radius * 2));
545 | }
546 |
547 | const onImage = ctx.getImage();
548 |
549 | return [
550 | outs > 0 ? onImage : offImage,
551 | outs > 1 ? onImage : offImage,
552 | outs > 2 ? onImage : offImage,
553 | ];
554 | }
555 |
556 | async function fetchTeam(team) {
557 | let game;
558 |
559 | // Find a game within 14 days for the provided team
560 | game = await fetchGameWithinDays(14, { team });
561 |
562 | // If the provided team has no upcoming games, pick the first game
563 | // that's currently in-progress
564 | if (!game) {
565 | game = await fetchGameWithinDays(14, { inProgress: true });
566 | }
567 |
568 | // Just get the first game in the list
569 | if (!game) {
570 | game = await fetchGameWithinDays(14);
571 | }
572 |
573 | // Get the last game of the provided team
574 | if (!game) {
575 | game = await fetchGameWithinDays(180, { team, backwards: true });
576 | }
577 |
578 | const isHome = game.teams.home.team.abbreviation === team;
579 |
580 | return {
581 | game,
582 | team: isHome ? game.teams.home.team : game.teams.away.team,
583 | };
584 | }
585 |
586 | async function fetchGameWithinDays(maxDays, options) {
587 | var game = null;
588 | let days = options?.backwards == true ? maxDays - 1 : 0;
589 |
590 | while (!game && days < maxDays && days >= 0) {
591 | let scoreboard = await fetchScoreboard(days);
592 | var games = [];
593 |
594 | if (options?.team) {
595 | games = scoreboard.filter(game => {
596 | const away = game.teams.away.team.abbreviation;
597 | const home = game.teams.home.team.abbreviation;
598 |
599 | return options.team === away || options.team === home;
600 | });
601 | } else if (options?.inProgress) {
602 | games = scoreboard.filter(game => {
603 | const { isPlaying } = getFormattedStatus(game);
604 |
605 | return isPlaying;
606 | });
607 | } else if (scoreboard.length > 0) {
608 | games = scoreboard;
609 | }
610 |
611 | game = games[0];
612 |
613 |
614 | if (options?.backwards == true) {
615 | days -= 1;
616 | } else {
617 | days += 1;
618 | }
619 | }
620 |
621 | return game;
622 | }
623 |
624 | async function fetchScoreboard(inDays) {
625 | const df = new DateFormatter();
626 | df.dateFormat = "yyyy-MM-dd";
627 | const now = new Date();
628 | const date = now.getHours() < 5 ? new Date(now.getTime() - 43200000) : new Date(now.getTime() + (86400000 * (inDays || 0)));
629 | const dateString = df.string(date);
630 | const url = `https://statsapi.mlb.com/api/v1/schedule?date=${dateString}&language=en&hydrate=team(league),venue(location,timezone),linescore(matchup,runners,positions),decisions,homeRuns,probablePitcher,flags,review,seriesStatus,person,stats,broadcasts(all)&sportId=1`;
631 | const data = await _lib_http__WEBPACK_IMPORTED_MODULE_2__/* .fetchJson */ .r({
632 | cache,
633 | url,
634 | cacheKey: `mlb_scores_${TEAM}_${inDays}`,
635 | });
636 |
637 | return data.dates[0]?.games || [];
638 | }
639 |
640 | async function fetchTeamLogo(team) {
641 | const req = new Request(`https://a.espncdn.com/i/teamlogos/mlb/500/${team.toLowerCase()}.png`);
642 | return req.loadImage();
643 | }
644 |
645 | __webpack_handle_async_dependencies__();
646 | }, 1);
647 |
648 | /***/ }),
649 |
650 | /***/ 59:
651 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
652 |
653 | /* harmony export */ __webpack_require__.d(__webpack_exports__, {
654 | /* harmony export */ "Z": () => (/* binding */ Cache)
655 | /* harmony export */ });
656 | class Cache {
657 | constructor(name, expirationMinutes) {
658 | this.fm = FileManager.iCloud();
659 | this.cachePath = this.fm.joinPath(this.fm.documentsDirectory(), name);
660 | this.expirationMinutes = expirationMinutes;
661 |
662 | if (!this.fm.fileExists(this.cachePath)) {
663 | this.fm.createDirectory(this.cachePath)
664 | }
665 | }
666 |
667 | async read(key, expirationMinutes) {
668 | try {
669 | const path = this.fm.joinPath(this.cachePath, key);
670 | await this.fm.downloadFileFromiCloud(path);
671 | const createdAt = this.fm.creationDate(path);
672 |
673 | if (this.expirationMinutes || expirationMinutes) {
674 | if ((new Date()) - createdAt > ((this.expirationMinutes || expirationMinutes) * 60000)) {
675 | this.fm.remove(path);
676 | return null;
677 | }
678 | }
679 |
680 | const value = this.fm.readString(path);
681 |
682 | try {
683 | return JSON.parse(value);
684 | } catch(error) {
685 | return value;
686 | }
687 | } catch(error) {
688 | return null;
689 | }
690 | };
691 |
692 | write(key, value) {
693 | const path = this.fm.joinPath(this.cachePath, key.replace('/', '-'));
694 | console.log(`Caching to ${path}...`);
695 |
696 | if (typeof value === 'string' || value instanceof String) {
697 | this.fm.writeString(path, value);
698 | } else {
699 | this.fm.writeString(path, JSON.stringify(value));
700 | }
701 | }
702 | }
703 |
704 |
705 | /***/ }),
706 |
707 | /***/ 630:
708 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
709 |
710 | /* harmony export */ __webpack_require__.d(__webpack_exports__, {
711 | /* harmony export */ "r": () => (/* binding */ fetchJson)
712 | /* harmony export */ });
713 | async function fetchJson({ url, headers, cache, cacheKey, cacheExpiration }) {
714 | if (cache && cacheKey) {
715 | const cached = await cache.read(cacheKey, cacheExpiration);
716 | if (cached) {
717 | return cached;
718 | }
719 | }
720 |
721 | try {
722 | console.log(`Fetching url: ${url}`);
723 | const req = new Request(url);
724 | if (headers) {
725 | req.headers = headers;
726 | }
727 | const resp = await req.loadJSON();
728 | if (cache && cacheKey) {
729 | cache.write(cacheKey, resp);
730 | }
731 | return resp;
732 | } catch (error) {
733 | if (cache && cacheKey) {
734 | try {
735 | return cache.read(cacheKey, cacheTimeout || 1);
736 | } catch (error) {
737 | console.log(`Couldn't fetch ${url}`);
738 | }
739 | } else {
740 | console.log(error);
741 | }
742 | }
743 | }
744 |
745 |
746 | /***/ }),
747 |
748 | /***/ 331:
749 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
750 |
751 | /* harmony export */ __webpack_require__.d(__webpack_exports__, {
752 | /* harmony export */ "Z": () => (/* binding */ Updater)
753 | /* harmony export */ });
754 | /* harmony import */ var _cache__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(59);
755 | /* harmony import */ var _http__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(630);
756 |
757 |
758 |
759 | class Updater {
760 | constructor(repo) {
761 | this.repo = repo;
762 | this.fm = FileManager.iCloud();
763 | this.cache = new _cache__WEBPACK_IMPORTED_MODULE_0__/* .default */ .Z("edcWidgetUpdaterCache", 15);
764 | }
765 |
766 | async checkForUpdate(name, version) {
767 | const latestVersion = await this.getLatestVersion(name);
768 |
769 | if (latestVersion > version) {
770 | console.log(`Version ${latestVersion} is greater than ${version}. Updating...`);
771 | await this.updateScript(name, latestVersion);
772 |
773 | return true;
774 | }
775 |
776 | console.log(`Version ${version} is not newer than ${latestVersion}. Skipping update.`);
777 |
778 | return false;
779 | }
780 |
781 | async getLatestVersion(name) {
782 | const url = `https://api.github.com/repos/${this.repo}/releases`;
783 | const data = await _http__WEBPACK_IMPORTED_MODULE_1__/* .fetchJson */ .r({
784 | url,
785 | cache: this.cache,
786 | cacheKey: name
787 | });
788 |
789 | if (!data || data.length === 0) {
790 | return null;
791 | }
792 |
793 | const matches = data
794 | .filter(x => x.tag_name.startsWith(`${name}-`) && !x.draft && !x.prerelease)
795 | .sort((a, b) => new Date(b.published_at) - new Date(a.published_at));
796 |
797 | if (!matches|| matches.length === 0) {
798 | return null;
799 | }
800 |
801 | const release = matches[0];
802 | const version = release.tag_name.split('-').slice(-1)[0];
803 |
804 | return parseInt(version, 10);
805 | }
806 |
807 | async updateScript(name, version) {
808 | const url = `https://raw.githubusercontent.com/${this.repo}/${name}-${version}/dist/${name}.js`;
809 | const req = new Request(url);
810 | const content = await req.loadString();
811 |
812 | const path = this.fm.joinPath(this.fm.documentsDirectory(), name + '.js');
813 |
814 | this.fm.writeString(path, content);
815 | }
816 | }
817 |
818 | /***/ })
819 |
820 | /******/ });
821 | /************************************************************************/
822 | /******/ // The module cache
823 | /******/ var __webpack_module_cache__ = {};
824 | /******/
825 | /******/ // The require function
826 | /******/ function __webpack_require__(moduleId) {
827 | /******/ // Check if module is in cache
828 | /******/ if(__webpack_module_cache__[moduleId]) {
829 | /******/ return __webpack_module_cache__[moduleId].exports;
830 | /******/ }
831 | /******/ // Create a new module (and put it into the cache)
832 | /******/ var module = __webpack_module_cache__[moduleId] = {
833 | /******/ // no module.id needed
834 | /******/ // no module.loaded needed
835 | /******/ exports: {}
836 | /******/ };
837 | /******/
838 | /******/ // Execute the module function
839 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
840 | /******/
841 | /******/ // Return the exports of the module
842 | /******/ return module.exports;
843 | /******/ }
844 | /******/
845 | /************************************************************************/
846 | /******/ /* webpack/runtime/async module */
847 | /******/ (() => {
848 | /******/ var webpackThen = typeof Symbol === "function" ? Symbol("webpack then") : "__webpack_then__";
849 | /******/ var webpackExports = typeof Symbol === "function" ? Symbol("webpack exports") : "__webpack_exports__";
850 | /******/ var completeQueue = (queue) => {
851 | /******/ if(queue) {
852 | /******/ queue.forEach(fn => fn.r--);
853 | /******/ queue.forEach(fn => fn.r-- ? fn.r++ : fn());
854 | /******/ }
855 | /******/ }
856 | /******/ var completeFunction = fn => !--fn.r && fn();
857 | /******/ var queueFunction = (queue, fn) => queue ? queue.push(fn) : completeFunction(fn);
858 | /******/ var wrapDeps = (deps) => (deps.map((dep) => {
859 | /******/ if(dep !== null && typeof dep === "object") {
860 | /******/ if(dep[webpackThen]) return dep;
861 | /******/ if(dep.then) {
862 | /******/ var queue = [], result;
863 | /******/ dep.then((r) => {
864 | /******/ obj[webpackExports] = r;
865 | /******/ completeQueue(queue);
866 | /******/ queue = 0;
867 | /******/ });
868 | /******/ var obj = { [webpackThen]: (fn, reject) => { queueFunction(queue, fn); dep.catch(reject); } };
869 | /******/ return obj;
870 | /******/ }
871 | /******/ }
872 | /******/ return { [webpackThen]: (fn) => { completeFunction(fn); }, [webpackExports]: dep };
873 | /******/ }));
874 | /******/ __webpack_require__.a = (module, body, hasAwait) => {
875 | /******/ var queue = hasAwait && [];
876 | /******/ var exports = module.exports;
877 | /******/ var currentDeps;
878 | /******/ var outerResolve;
879 | /******/ var reject;
880 | /******/ var isEvaluating = true;
881 | /******/ var nested = false;
882 | /******/ var whenAll = (deps, onResolve, onReject) => {
883 | /******/ if (nested) return;
884 | /******/ nested = true;
885 | /******/ onResolve.r += deps.length;
886 | /******/ deps.map((dep, i) => {
887 | /******/ dep[webpackThen](onResolve, onReject);
888 | /******/ });
889 | /******/ nested = false;
890 | /******/ };
891 | /******/ var promise = new Promise((resolve, rej) => {
892 | /******/ reject = rej;
893 | /******/ outerResolve = () => {
894 | /******/ resolve(exports);
895 | /******/ completeQueue(queue);
896 | /******/ queue = 0;
897 | /******/ };
898 | /******/ });
899 | /******/ promise[webpackExports] = exports;
900 | /******/ promise[webpackThen] = (fn, rejectFn) => {
901 | /******/ if (isEvaluating) { return completeFunction(fn); }
902 | /******/ if (currentDeps) whenAll(currentDeps, fn, rejectFn);
903 | /******/ queueFunction(queue, fn);
904 | /******/ promise.catch(rejectFn);
905 | /******/ };
906 | /******/ module.exports = promise;
907 | /******/ body((deps) => {
908 | /******/ if(!deps) return outerResolve();
909 | /******/ currentDeps = wrapDeps(deps);
910 | /******/ var fn, result;
911 | /******/ var promise = new Promise((resolve, reject) => {
912 | /******/ fn = () => (resolve(result = currentDeps.map(d => d[webpackExports])))
913 | /******/ fn.r = 0;
914 | /******/ whenAll(currentDeps, fn, reject);
915 | /******/ });
916 | /******/ return fn.r ? promise : result;
917 | /******/ }).then(outerResolve, reject);
918 | /******/ isEvaluating = false;
919 | /******/ };
920 | /******/ })();
921 | /******/
922 | /******/ /* webpack/runtime/define property getters */
923 | /******/ (() => {
924 | /******/ // define getter functions for harmony exports
925 | /******/ __webpack_require__.d = (exports, definition) => {
926 | /******/ for(var key in definition) {
927 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
928 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
929 | /******/ }
930 | /******/ }
931 | /******/ };
932 | /******/ })();
933 | /******/
934 | /******/ /* webpack/runtime/hasOwnProperty shorthand */
935 | /******/ (() => {
936 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
937 | /******/ })();
938 | /******/
939 | /************************************************************************/
940 | /******/
941 | /******/ // startup
942 | /******/ // Load entry module and return exports
943 | /******/ var __webpack_exports__ = __webpack_require__(208);
944 | /******/ // This entry module used 'module' so it can't be inlined
945 | /******/
946 | /******/ })()
947 | ;
--------------------------------------------------------------------------------
/dist/TermiWidget.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: deep-gray; icon-glyph: magic;
4 |
5 | // Change these to your usernames!
6 | const user = "evan";
7 |
8 | // API PARAMETERS !important
9 | // WEATHER_API_KEY, you need an Open Weather API Key
10 | // You can get one for free at: https://home.openweathermap.org/api_keys (account needed).
11 | const WEATHER_API_KEY = "";
12 | const DEFAULT_LOCATION = {
13 | latitude: 0,
14 | longitude: 0
15 | };
16 | const TAUTULLI_API_BASE = "";
17 | const TAUTULLI_API_KEY = "";
18 | const HOME_ASSISTANT_API_BASE = "";
19 | const HOME_ASSISTANT_API_KEY = "";
20 | const UPCOMING_SAT_PASS_URL = "";
21 |
22 | /******/
23 | /******/ (() => { // webpackBootstrap
24 | /******/ "use strict";
25 | /******/ var __webpack_modules__ = ({
26 |
27 | /***/ 679:
28 | /***/ ((module, __unused_webpack___webpack_exports__, __webpack_require__) => {
29 |
30 | __webpack_require__.a(module, async (__webpack_handle_async_dependencies__) => {
31 | /* harmony import */ var _lib_cache__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(59);
32 |
33 |
34 |
35 |
36 | const cache = new _lib_cache__WEBPACK_IMPORTED_MODULE_0__/* .default */ .Z("termiWidgetCache");
37 | const data = await fetchData();
38 | const widget = createWidget(data);
39 | Script.setWidget(widget);
40 | Script.complete();
41 |
42 | function createWidget(data) {
43 | console.log(data)
44 | const w = new ListWidget()
45 | const bgColor = new LinearGradient()
46 | bgColor.colors = [new Color("#29323c"), new Color("#1c1c1c")]
47 | bgColor.locations = [0.0, 1.0]
48 | w.backgroundGradient = bgColor
49 | w.setPadding(12, 15, 15, 12)
50 |
51 | const stack = w.addStack();
52 | stack.layoutHorizontally();
53 |
54 | const leftStack = stack.addStack();
55 | leftStack.layoutVertically();
56 | leftStack.spacing = 6;
57 | leftStack.size = new Size(200, 0);
58 |
59 | const time = new Date()
60 | const dfTime = new DateFormatter()
61 | dfTime.locale = "en"
62 | dfTime.useMediumDateStyle()
63 | dfTime.useNoTimeStyle()
64 |
65 | const firstLine = leftStack.addText(`[] ${user} ~$ now`)
66 | firstLine.textColor = Color.white()
67 | firstLine.textOpacity = 0.7
68 | firstLine.font = new Font("Menlo", 11)
69 |
70 | const timeLine = leftStack.addText(`[🗓] ${dfTime.string(time)}`)
71 | timeLine.textColor = Color.white()
72 | timeLine.font = new Font("Menlo", 11)
73 |
74 | const batteryLine = leftStack.addText(`[🔋] ${renderBattery()}`)
75 | batteryLine.textColor = new Color("#6ef2ae")
76 | batteryLine.font = new Font("Menlo", 11)
77 |
78 | const locationLine = leftStack.addText(`[️️📍] Location: ${data.weather.location}`)
79 | locationLine.textColor = new Color("#7dbbae")
80 | locationLine.font = new Font("Menlo", 11)
81 |
82 | const homeLine = leftStack.addText(`[🏠] ${data.home.mode}, ${data.home.temperature}°, Lights ${data.home.lights ? "On" : "Off"}`);
83 | homeLine.textColor = new Color("#ff9468")
84 | homeLine.font = new Font("Menlo", 11)
85 |
86 | let plexText = `[🍿] Plex: ${data.plex.streams} stream${data.plex.streams == 1 ? '' : 's'}`;
87 | // if (data.plex.streams > 0) {
88 | // plexText += `, ${data.plex.transcodes} transcode${data.plex.transcodes == 1 ? '' : 's'}`;
89 | // }
90 | const plexLine = leftStack.addText(plexText);
91 | plexLine.textColor = new Color("#ffa7d3")
92 | plexLine.font = new Font("Menlo", 11)
93 |
94 | const satLine = leftStack.addText(`[🛰] ${data.satPass}`);
95 | satLine.textColor = new Color("#ffcc66")
96 | satLine.font = new Font("Menlo", 11)
97 |
98 | stack.addSpacer();
99 | const rightStack = stack.addStack();
100 | rightStack.spacing = 2;
101 | rightStack.layoutVertically();
102 | rightStack.bottomAlignContent();
103 |
104 | addWeatherLine(rightStack, data.weather.icon, 32);
105 | addWeatherLine(rightStack, `${data.weather.description}, ${data.weather.temperature}°`, 12, true);
106 | addWeatherLine(rightStack, `High: ${data.weather.high}°`);
107 | addWeatherLine(rightStack, `Low: ${data.weather.low}°`);
108 | addWeatherLine(rightStack, `Wind: ${data.weather.wind} mph`);
109 |
110 | return w
111 | }
112 |
113 | function addWeatherLine(w, text, size, bold) {
114 | const stack = w.addStack();
115 | stack.setPadding(0, 0, 0, 0);
116 | stack.layoutHorizontally();
117 | stack.addSpacer();
118 | const line = stack.addText(text);
119 | line.textColor = new Color("#ffcc66");
120 | line.font = new Font("Menlo" + (bold ? "-Bold" : ""), size || 11);
121 | }
122 |
123 | async function fetchData() {
124 | const weather = await fetchWeather();
125 | const plex = await fetchPlex();
126 | const home = await fetchHome();
127 | const satPass = await fetchNextSatPass();
128 |
129 | return {
130 | weather,
131 | plex,
132 | home,
133 | satPass,
134 | }
135 | }
136 |
137 | function renderBattery() {
138 | const batteryLevel = Device.batteryLevel()
139 | const juice = "#".repeat(Math.floor(batteryLevel * 8))
140 | const used = ".".repeat(8 - juice.length)
141 | const batteryAscii = `[${juice}${used}] ${Math.round(batteryLevel * 100)}%`
142 | return batteryAscii
143 | }
144 |
145 | async function fetchWeather() {
146 | let location = await cache.read('location');
147 | if (!location) {
148 | try {
149 | Location.setAccuracyToThreeKilometers();
150 | location = await Location.current();
151 | } catch(error) {
152 | location = await cache.read('location');
153 | }
154 | }
155 | if (!location) {
156 | location = DEFAULT_LOCATION;
157 | }
158 | const address = await Location.reverseGeocode(location.latitude, location.longitude);
159 | const url = "https://api.openweathermap.org/data/2.5/onecall?lat=" + location.latitude + "&lon=" + location.longitude + "&exclude=minutely,hourly,alerts&units=imperial&lang=en&appid=" + WEATHER_API_KEY;
160 | const data = await fetchJson(`weather_${address[0].locality}`, url);
161 |
162 | return {
163 | location: address[0].locality,
164 | icon: getWeatherEmoji(data.current.weather[0].id, ((new Date()).getTime() / 1000) >= data.current.sunset),
165 | description: data.current.weather[0].main,
166 | temperature: Math.round(data.current.temp),
167 | wind: Math.round(data.current.wind_speed),
168 | high: Math.round(data.daily[0].temp.max),
169 | low: Math.round(data.daily[0].temp.min),
170 | }
171 | }
172 |
173 | async function fetchPlex() {
174 | const url = `${TAUTULLI_API_BASE}/api/v2?apikey=${TAUTULLI_API_KEY}&cmd=get_activity`;
175 | const data = await fetchJson(`plex`, url);
176 |
177 | return {
178 | streams: data.response.data.stream_count,
179 | transcodes: data.response.data.stream_count_transcode,
180 | };
181 | }
182 |
183 | async function fetchHome() {
184 | const mode = await fetchHomeAssistant('states/input_select.mode');
185 | const temp = await fetchHomeAssistant('states/sensor.hallway_temperature');
186 | const livingRoomLight = (await fetchHomeAssistant('states/light.living_room')).state == "on";
187 | const bedRoomLight = (await fetchHomeAssistant('states/light.bedroom')).state == "on";
188 | const hallwayLight = (await fetchHomeAssistant('states/light.hallway')).state == "on";
189 | const bathroomLight = (await fetchHomeAssistant('states/light.bathroom')).state == "on";
190 |
191 | return {
192 | mode: mode.state,
193 | temperature: Math.round(parseFloat(temp.state)),
194 | lights: livingRoomLight || bedRoomLight || hallwayLight || bathroomLight,
195 | };
196 | }
197 |
198 | async function fetchHomeAssistant(path) {
199 | return fetchJson(path, `${HOME_ASSISTANT_API_BASE}/api/${path}`, {
200 | 'Authorization': `Bearer ${HOME_ASSISTANT_API_KEY}`,
201 | 'Content-Type': 'application/json',
202 | });
203 | }
204 |
205 | async function fetchNextSatPass() {
206 | const passes = await fetchJson('upcoming-passes.json', UPCOMING_SAT_PASS_URL);
207 | const now = new Date();
208 | const nextPass = passes
209 | .filter((p) => now.getTime() < p.end)[0];
210 |
211 | if (!nextPass) {
212 | return 'No more passes today';
213 | }
214 |
215 | if (nextPass.start > now.getTime()) {
216 | const minutes = Math.round(((nextPass.start - now.getTime()) / 1000) / 60);
217 | const hours = Math.round((((nextPass.start - now.getTime()) / 1000) / 60) / 60);
218 |
219 | if (minutes > 59) {
220 | return `${nextPass.satellite} in ${hours}h, ${Math.round(nextPass.elevation)}°`;
221 | } else {
222 | return `${nextPass.satellite} in ${minutes}m, ${Math.round(nextPass.elevation)}°`;
223 | }
224 | } else {
225 | return `${nextPass.satellite} for ${Math.round(((nextPass.end - now.getTime()) / 1000) / 60)}m, ${Math.round(nextPass.elevation)}°`;
226 | }
227 | }
228 |
229 | async function fetchJson(key, url, headers) {
230 | const cached = await cache.read(key, 5);
231 | if (cached) {
232 | return cached;
233 | }
234 |
235 | try {
236 | console.log(`Fetching url: ${url}`);
237 | const req = new Request(url);
238 | req.headers = headers;
239 | const resp = await req.loadJSON();
240 | cache.write(key, resp);
241 | return resp;
242 | } catch (error) {
243 | try {
244 | return cache.read(key, 5);
245 | } catch (error) {
246 | console.log(`Couldn't fetch ${url}`);
247 | }
248 | }
249 | }
250 |
251 | function getWeatherEmoji(code, isNight) {
252 | if (code >= 200 && code < 300 || code == 960 || code == 961) {
253 | return "⛈"
254 | } else if ((code >= 300 && code < 600) || code == 701) {
255 | return "🌧"
256 | } else if (code >= 600 && code < 700) {
257 | return "❄️"
258 | } else if (code == 711) {
259 | return "🔥"
260 | } else if (code == 800) {
261 | return isNight ? "🌕" : "☀️"
262 | } else if (code == 801) {
263 | return isNight ? "☁️" : "🌤"
264 | } else if (code == 802) {
265 | return isNight ? "☁️" : "⛅️"
266 | } else if (code == 803) {
267 | return isNight ? "☁️" : "🌥"
268 | } else if (code == 804) {
269 | return "☁️"
270 | } else if (code == 900 || code == 962 || code == 781) {
271 | return "🌪"
272 | } else if (code >= 700 && code < 800) {
273 | return "🌫"
274 | } else if (code == 903) {
275 | return "🥶"
276 | } else if (code == 904) {
277 | return "🥵"
278 | } else if (code == 905 || code == 957) {
279 | return "💨"
280 | } else if (code == 906 || code == 958 || code == 959) {
281 | return "🧊"
282 | } else {
283 | return "❓"
284 | }
285 | }
286 |
287 | __webpack_handle_async_dependencies__();
288 | }, 1);
289 |
290 | /***/ }),
291 |
292 | /***/ 59:
293 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
294 |
295 | /* harmony export */ __webpack_require__.d(__webpack_exports__, {
296 | /* harmony export */ "Z": () => (/* binding */ Cache)
297 | /* harmony export */ });
298 | class Cache {
299 | constructor(name, expirationMinutes) {
300 | this.fm = FileManager.iCloud();
301 | this.cachePath = this.fm.joinPath(this.fm.documentsDirectory(), name);
302 | this.expirationMinutes = expirationMinutes;
303 |
304 | if (!this.fm.fileExists(this.cachePath)) {
305 | this.fm.createDirectory(this.cachePath)
306 | }
307 | }
308 |
309 | async read(key, expirationMinutes) {
310 | try {
311 | const path = this.fm.joinPath(this.cachePath, key);
312 | await this.fm.downloadFileFromiCloud(path);
313 | const createdAt = this.fm.creationDate(path);
314 |
315 | if (this.expirationMinutes || expirationMinutes) {
316 | if ((new Date()) - createdAt > ((this.expirationMinutes || expirationMinutes) * 60000)) {
317 | this.fm.remove(path);
318 | return null;
319 | }
320 | }
321 |
322 | const value = this.fm.readString(path);
323 |
324 | try {
325 | return JSON.parse(value);
326 | } catch(error) {
327 | return value;
328 | }
329 | } catch(error) {
330 | return null;
331 | }
332 | };
333 |
334 | write(key, value) {
335 | const path = this.fm.joinPath(this.cachePath, key.replace('/', '-'));
336 | console.log(`Caching to ${path}...`);
337 |
338 | if (typeof value === 'string' || value instanceof String) {
339 | this.fm.writeString(path, value);
340 | } else {
341 | this.fm.writeString(path, JSON.stringify(value));
342 | }
343 | }
344 | }
345 |
346 |
347 | /***/ })
348 |
349 | /******/ });
350 | /************************************************************************/
351 | /******/ // The module cache
352 | /******/ var __webpack_module_cache__ = {};
353 | /******/
354 | /******/ // The require function
355 | /******/ function __webpack_require__(moduleId) {
356 | /******/ // Check if module is in cache
357 | /******/ if(__webpack_module_cache__[moduleId]) {
358 | /******/ return __webpack_module_cache__[moduleId].exports;
359 | /******/ }
360 | /******/ // Create a new module (and put it into the cache)
361 | /******/ var module = __webpack_module_cache__[moduleId] = {
362 | /******/ // no module.id needed
363 | /******/ // no module.loaded needed
364 | /******/ exports: {}
365 | /******/ };
366 | /******/
367 | /******/ // Execute the module function
368 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
369 | /******/
370 | /******/ // Return the exports of the module
371 | /******/ return module.exports;
372 | /******/ }
373 | /******/
374 | /************************************************************************/
375 | /******/ /* webpack/runtime/async module */
376 | /******/ (() => {
377 | /******/ var webpackThen = typeof Symbol === "function" ? Symbol("webpack then") : "__webpack_then__";
378 | /******/ var webpackExports = typeof Symbol === "function" ? Symbol("webpack exports") : "__webpack_exports__";
379 | /******/ var completeQueue = (queue) => {
380 | /******/ if(queue) {
381 | /******/ queue.forEach(fn => fn.r--);
382 | /******/ queue.forEach(fn => fn.r-- ? fn.r++ : fn());
383 | /******/ }
384 | /******/ }
385 | /******/ var completeFunction = fn => !--fn.r && fn();
386 | /******/ var queueFunction = (queue, fn) => queue ? queue.push(fn) : completeFunction(fn);
387 | /******/ var wrapDeps = (deps) => (deps.map((dep) => {
388 | /******/ if(dep !== null && typeof dep === "object") {
389 | /******/ if(dep[webpackThen]) return dep;
390 | /******/ if(dep.then) {
391 | /******/ var queue = [], result;
392 | /******/ dep.then((r) => {
393 | /******/ obj[webpackExports] = r;
394 | /******/ completeQueue(queue);
395 | /******/ queue = 0;
396 | /******/ });
397 | /******/ var obj = { [webpackThen]: (fn, reject) => { queueFunction(queue, fn); dep.catch(reject); } };
398 | /******/ return obj;
399 | /******/ }
400 | /******/ }
401 | /******/ return { [webpackThen]: (fn) => { completeFunction(fn); }, [webpackExports]: dep };
402 | /******/ }));
403 | /******/ __webpack_require__.a = (module, body, hasAwait) => {
404 | /******/ var queue = hasAwait && [];
405 | /******/ var exports = module.exports;
406 | /******/ var currentDeps;
407 | /******/ var outerResolve;
408 | /******/ var reject;
409 | /******/ var isEvaluating = true;
410 | /******/ var nested = false;
411 | /******/ var whenAll = (deps, onResolve, onReject) => {
412 | /******/ if (nested) return;
413 | /******/ nested = true;
414 | /******/ onResolve.r += deps.length;
415 | /******/ deps.map((dep, i) => {
416 | /******/ dep[webpackThen](onResolve, onReject);
417 | /******/ });
418 | /******/ nested = false;
419 | /******/ };
420 | /******/ var promise = new Promise((resolve, rej) => {
421 | /******/ reject = rej;
422 | /******/ outerResolve = () => {
423 | /******/ resolve(exports);
424 | /******/ completeQueue(queue);
425 | /******/ queue = 0;
426 | /******/ };
427 | /******/ });
428 | /******/ promise[webpackExports] = exports;
429 | /******/ promise[webpackThen] = (fn, rejectFn) => {
430 | /******/ if (isEvaluating) { return completeFunction(fn); }
431 | /******/ if (currentDeps) whenAll(currentDeps, fn, rejectFn);
432 | /******/ queueFunction(queue, fn);
433 | /******/ promise.catch(rejectFn);
434 | /******/ };
435 | /******/ module.exports = promise;
436 | /******/ body((deps) => {
437 | /******/ if(!deps) return outerResolve();
438 | /******/ currentDeps = wrapDeps(deps);
439 | /******/ var fn, result;
440 | /******/ var promise = new Promise((resolve, reject) => {
441 | /******/ fn = () => (resolve(result = currentDeps.map(d => d[webpackExports])))
442 | /******/ fn.r = 0;
443 | /******/ whenAll(currentDeps, fn, reject);
444 | /******/ });
445 | /******/ return fn.r ? promise : result;
446 | /******/ }).then(outerResolve, reject);
447 | /******/ isEvaluating = false;
448 | /******/ };
449 | /******/ })();
450 | /******/
451 | /******/ /* webpack/runtime/define property getters */
452 | /******/ (() => {
453 | /******/ // define getter functions for harmony exports
454 | /******/ __webpack_require__.d = (exports, definition) => {
455 | /******/ for(var key in definition) {
456 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
457 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
458 | /******/ }
459 | /******/ }
460 | /******/ };
461 | /******/ })();
462 | /******/
463 | /******/ /* webpack/runtime/hasOwnProperty shorthand */
464 | /******/ (() => {
465 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
466 | /******/ })();
467 | /******/
468 | /************************************************************************/
469 | /******/
470 | /******/ // startup
471 | /******/ // Load entry module and return exports
472 | /******/ var __webpack_exports__ = __webpack_require__(679);
473 | /******/ // This entry module used 'module' so it can't be inlined
474 | /******/
475 | /******/ })()
476 | ;
--------------------------------------------------------------------------------
/images/mlb-expanded-0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evandcoleman/scriptable/5de94c6f8eb174735862f69cbcd9699623fb17bd/images/mlb-expanded-0.jpg
--------------------------------------------------------------------------------
/images/mlb-expanded-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evandcoleman/scriptable/5de94c6f8eb174735862f69cbcd9699623fb17bd/images/mlb-expanded-1.jpg
--------------------------------------------------------------------------------
/images/termiwidget.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evandcoleman/scriptable/5de94c6f8eb174735862f69cbcd9699623fb17bd/images/termiwidget.png
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scriptable-scripts",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@discoveryjs/json-ext": {
8 | "version": "0.5.2",
9 | "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz",
10 | "integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==",
11 | "dev": true
12 | },
13 | "@types/eslint": {
14 | "version": "7.2.7",
15 | "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.7.tgz",
16 | "integrity": "sha512-EHXbc1z2GoQRqHaAT7+grxlTJ3WE2YNeD6jlpPoRc83cCoThRY+NUWjCUZaYmk51OICkPXn2hhphcWcWXgNW0Q==",
17 | "dev": true,
18 | "requires": {
19 | "@types/estree": "*",
20 | "@types/json-schema": "*"
21 | }
22 | },
23 | "@types/eslint-scope": {
24 | "version": "3.7.0",
25 | "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz",
26 | "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==",
27 | "dev": true,
28 | "requires": {
29 | "@types/eslint": "*",
30 | "@types/estree": "*"
31 | }
32 | },
33 | "@types/estree": {
34 | "version": "0.0.46",
35 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
36 | "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==",
37 | "dev": true
38 | },
39 | "@types/json-schema": {
40 | "version": "7.0.7",
41 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
42 | "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
43 | "dev": true
44 | },
45 | "@types/node": {
46 | "version": "14.14.33",
47 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.33.tgz",
48 | "integrity": "sha512-oJqcTrgPUF29oUP8AsUqbXGJNuPutsetaa9kTQAQce5Lx5dTYWV02ScBiT/k1BX/Z7pKeqedmvp39Wu4zR7N7g==",
49 | "dev": true
50 | },
51 | "@webassemblyjs/ast": {
52 | "version": "1.11.0",
53 | "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz",
54 | "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==",
55 | "dev": true,
56 | "requires": {
57 | "@webassemblyjs/helper-numbers": "1.11.0",
58 | "@webassemblyjs/helper-wasm-bytecode": "1.11.0"
59 | }
60 | },
61 | "@webassemblyjs/floating-point-hex-parser": {
62 | "version": "1.11.0",
63 | "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz",
64 | "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==",
65 | "dev": true
66 | },
67 | "@webassemblyjs/helper-api-error": {
68 | "version": "1.11.0",
69 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz",
70 | "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==",
71 | "dev": true
72 | },
73 | "@webassemblyjs/helper-buffer": {
74 | "version": "1.11.0",
75 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz",
76 | "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==",
77 | "dev": true
78 | },
79 | "@webassemblyjs/helper-numbers": {
80 | "version": "1.11.0",
81 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz",
82 | "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==",
83 | "dev": true,
84 | "requires": {
85 | "@webassemblyjs/floating-point-hex-parser": "1.11.0",
86 | "@webassemblyjs/helper-api-error": "1.11.0",
87 | "@xtuc/long": "4.2.2"
88 | }
89 | },
90 | "@webassemblyjs/helper-wasm-bytecode": {
91 | "version": "1.11.0",
92 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz",
93 | "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==",
94 | "dev": true
95 | },
96 | "@webassemblyjs/helper-wasm-section": {
97 | "version": "1.11.0",
98 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz",
99 | "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==",
100 | "dev": true,
101 | "requires": {
102 | "@webassemblyjs/ast": "1.11.0",
103 | "@webassemblyjs/helper-buffer": "1.11.0",
104 | "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
105 | "@webassemblyjs/wasm-gen": "1.11.0"
106 | }
107 | },
108 | "@webassemblyjs/ieee754": {
109 | "version": "1.11.0",
110 | "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz",
111 | "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==",
112 | "dev": true,
113 | "requires": {
114 | "@xtuc/ieee754": "^1.2.0"
115 | }
116 | },
117 | "@webassemblyjs/leb128": {
118 | "version": "1.11.0",
119 | "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz",
120 | "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==",
121 | "dev": true,
122 | "requires": {
123 | "@xtuc/long": "4.2.2"
124 | }
125 | },
126 | "@webassemblyjs/utf8": {
127 | "version": "1.11.0",
128 | "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz",
129 | "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==",
130 | "dev": true
131 | },
132 | "@webassemblyjs/wasm-edit": {
133 | "version": "1.11.0",
134 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz",
135 | "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==",
136 | "dev": true,
137 | "requires": {
138 | "@webassemblyjs/ast": "1.11.0",
139 | "@webassemblyjs/helper-buffer": "1.11.0",
140 | "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
141 | "@webassemblyjs/helper-wasm-section": "1.11.0",
142 | "@webassemblyjs/wasm-gen": "1.11.0",
143 | "@webassemblyjs/wasm-opt": "1.11.0",
144 | "@webassemblyjs/wasm-parser": "1.11.0",
145 | "@webassemblyjs/wast-printer": "1.11.0"
146 | }
147 | },
148 | "@webassemblyjs/wasm-gen": {
149 | "version": "1.11.0",
150 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz",
151 | "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==",
152 | "dev": true,
153 | "requires": {
154 | "@webassemblyjs/ast": "1.11.0",
155 | "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
156 | "@webassemblyjs/ieee754": "1.11.0",
157 | "@webassemblyjs/leb128": "1.11.0",
158 | "@webassemblyjs/utf8": "1.11.0"
159 | }
160 | },
161 | "@webassemblyjs/wasm-opt": {
162 | "version": "1.11.0",
163 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz",
164 | "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==",
165 | "dev": true,
166 | "requires": {
167 | "@webassemblyjs/ast": "1.11.0",
168 | "@webassemblyjs/helper-buffer": "1.11.0",
169 | "@webassemblyjs/wasm-gen": "1.11.0",
170 | "@webassemblyjs/wasm-parser": "1.11.0"
171 | }
172 | },
173 | "@webassemblyjs/wasm-parser": {
174 | "version": "1.11.0",
175 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz",
176 | "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==",
177 | "dev": true,
178 | "requires": {
179 | "@webassemblyjs/ast": "1.11.0",
180 | "@webassemblyjs/helper-api-error": "1.11.0",
181 | "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
182 | "@webassemblyjs/ieee754": "1.11.0",
183 | "@webassemblyjs/leb128": "1.11.0",
184 | "@webassemblyjs/utf8": "1.11.0"
185 | }
186 | },
187 | "@webassemblyjs/wast-printer": {
188 | "version": "1.11.0",
189 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz",
190 | "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==",
191 | "dev": true,
192 | "requires": {
193 | "@webassemblyjs/ast": "1.11.0",
194 | "@xtuc/long": "4.2.2"
195 | }
196 | },
197 | "@webpack-cli/configtest": {
198 | "version": "1.0.1",
199 | "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.1.tgz",
200 | "integrity": "sha512-B+4uBUYhpzDXmwuo3V9yBH6cISwxEI4J+NO5ggDaGEEHb0osY/R7MzeKc0bHURXQuZjMM4qD+bSJCKIuI3eNBQ==",
201 | "dev": true
202 | },
203 | "@webpack-cli/info": {
204 | "version": "1.2.2",
205 | "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.2.tgz",
206 | "integrity": "sha512-5U9kUJHnwU+FhKH4PWGZuBC1hTEPYyxGSL5jjoBI96Gx8qcYJGOikpiIpFoTq8mmgX3im2zAo2wanv/alD74KQ==",
207 | "dev": true,
208 | "requires": {
209 | "envinfo": "^7.7.3"
210 | }
211 | },
212 | "@webpack-cli/serve": {
213 | "version": "1.3.0",
214 | "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.0.tgz",
215 | "integrity": "sha512-k2p2VrONcYVX1wRRrf0f3X2VGltLWcv+JzXRBDmvCxGlCeESx4OXw91TsWeKOkp784uNoVQo313vxJFHXPPwfw==",
216 | "dev": true
217 | },
218 | "@xtuc/ieee754": {
219 | "version": "1.2.0",
220 | "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
221 | "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
222 | "dev": true
223 | },
224 | "@xtuc/long": {
225 | "version": "4.2.2",
226 | "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
227 | "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
228 | "dev": true
229 | },
230 | "acorn": {
231 | "version": "8.1.0",
232 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.0.tgz",
233 | "integrity": "sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==",
234 | "dev": true
235 | },
236 | "ajv": {
237 | "version": "6.12.6",
238 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
239 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
240 | "dev": true,
241 | "requires": {
242 | "fast-deep-equal": "^3.1.1",
243 | "fast-json-stable-stringify": "^2.0.0",
244 | "json-schema-traverse": "^0.4.1",
245 | "uri-js": "^4.2.2"
246 | }
247 | },
248 | "ajv-keywords": {
249 | "version": "3.5.2",
250 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
251 | "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
252 | "dev": true
253 | },
254 | "ansi-colors": {
255 | "version": "4.1.1",
256 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
257 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
258 | "dev": true
259 | },
260 | "browserslist": {
261 | "version": "4.16.3",
262 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz",
263 | "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==",
264 | "dev": true,
265 | "requires": {
266 | "caniuse-lite": "^1.0.30001181",
267 | "colorette": "^1.2.1",
268 | "electron-to-chromium": "^1.3.649",
269 | "escalade": "^3.1.1",
270 | "node-releases": "^1.1.70"
271 | }
272 | },
273 | "buffer-from": {
274 | "version": "1.1.1",
275 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
276 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
277 | "dev": true
278 | },
279 | "caniuse-lite": {
280 | "version": "1.0.30001198",
281 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001198.tgz",
282 | "integrity": "sha512-r5GGgESqOPZzwvdLVER374FpQu2WluCF1Z2DSiFJ89KSmGjT0LVKjgv4NcAqHmGWF9ihNpqRI9KXO9Ex4sKsgA==",
283 | "dev": true
284 | },
285 | "chrome-trace-event": {
286 | "version": "1.0.2",
287 | "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
288 | "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
289 | "dev": true,
290 | "requires": {
291 | "tslib": "^1.9.0"
292 | }
293 | },
294 | "clone-deep": {
295 | "version": "4.0.1",
296 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
297 | "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
298 | "dev": true,
299 | "requires": {
300 | "is-plain-object": "^2.0.4",
301 | "kind-of": "^6.0.2",
302 | "shallow-clone": "^3.0.0"
303 | }
304 | },
305 | "colorette": {
306 | "version": "1.2.2",
307 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
308 | "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
309 | "dev": true
310 | },
311 | "commander": {
312 | "version": "2.20.3",
313 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
314 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
315 | "dev": true
316 | },
317 | "cross-spawn": {
318 | "version": "7.0.3",
319 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
320 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
321 | "dev": true,
322 | "requires": {
323 | "path-key": "^3.1.0",
324 | "shebang-command": "^2.0.0",
325 | "which": "^2.0.1"
326 | }
327 | },
328 | "electron-to-chromium": {
329 | "version": "1.3.684",
330 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.684.tgz",
331 | "integrity": "sha512-GV/vz2EmmtRSvfGSQ5A0Lucic//IRSDijgL15IgzbBEEnp4rfbxeUSZSlBfmsj7BQvE4sBdgfsvPzLCnp6L21w==",
332 | "dev": true
333 | },
334 | "enhanced-resolve": {
335 | "version": "5.7.0",
336 | "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz",
337 | "integrity": "sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==",
338 | "dev": true,
339 | "requires": {
340 | "graceful-fs": "^4.2.4",
341 | "tapable": "^2.2.0"
342 | }
343 | },
344 | "enquirer": {
345 | "version": "2.3.6",
346 | "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
347 | "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
348 | "dev": true,
349 | "requires": {
350 | "ansi-colors": "^4.1.1"
351 | }
352 | },
353 | "envinfo": {
354 | "version": "7.7.4",
355 | "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz",
356 | "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==",
357 | "dev": true
358 | },
359 | "es-module-lexer": {
360 | "version": "0.4.1",
361 | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz",
362 | "integrity": "sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==",
363 | "dev": true
364 | },
365 | "escalade": {
366 | "version": "3.1.1",
367 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
368 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
369 | "dev": true
370 | },
371 | "eslint-scope": {
372 | "version": "5.1.1",
373 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
374 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
375 | "dev": true,
376 | "requires": {
377 | "esrecurse": "^4.3.0",
378 | "estraverse": "^4.1.1"
379 | }
380 | },
381 | "esrecurse": {
382 | "version": "4.3.0",
383 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
384 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
385 | "dev": true,
386 | "requires": {
387 | "estraverse": "^5.2.0"
388 | },
389 | "dependencies": {
390 | "estraverse": {
391 | "version": "5.2.0",
392 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
393 | "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
394 | "dev": true
395 | }
396 | }
397 | },
398 | "estraverse": {
399 | "version": "4.3.0",
400 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
401 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
402 | "dev": true
403 | },
404 | "events": {
405 | "version": "3.3.0",
406 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
407 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
408 | "dev": true
409 | },
410 | "execa": {
411 | "version": "5.0.0",
412 | "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz",
413 | "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==",
414 | "dev": true,
415 | "requires": {
416 | "cross-spawn": "^7.0.3",
417 | "get-stream": "^6.0.0",
418 | "human-signals": "^2.1.0",
419 | "is-stream": "^2.0.0",
420 | "merge-stream": "^2.0.0",
421 | "npm-run-path": "^4.0.1",
422 | "onetime": "^5.1.2",
423 | "signal-exit": "^3.0.3",
424 | "strip-final-newline": "^2.0.0"
425 | }
426 | },
427 | "fast-deep-equal": {
428 | "version": "3.1.3",
429 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
430 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
431 | "dev": true
432 | },
433 | "fast-json-stable-stringify": {
434 | "version": "2.1.0",
435 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
436 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
437 | "dev": true
438 | },
439 | "fastest-levenshtein": {
440 | "version": "1.0.12",
441 | "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz",
442 | "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==",
443 | "dev": true
444 | },
445 | "find-up": {
446 | "version": "4.1.0",
447 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
448 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
449 | "dev": true,
450 | "requires": {
451 | "locate-path": "^5.0.0",
452 | "path-exists": "^4.0.0"
453 | }
454 | },
455 | "function-bind": {
456 | "version": "1.1.1",
457 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
458 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
459 | "dev": true
460 | },
461 | "get-stream": {
462 | "version": "6.0.0",
463 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz",
464 | "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==",
465 | "dev": true
466 | },
467 | "glob-to-regexp": {
468 | "version": "0.4.1",
469 | "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
470 | "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
471 | "dev": true
472 | },
473 | "graceful-fs": {
474 | "version": "4.2.6",
475 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
476 | "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
477 | "dev": true
478 | },
479 | "has": {
480 | "version": "1.0.3",
481 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
482 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
483 | "dev": true,
484 | "requires": {
485 | "function-bind": "^1.1.1"
486 | }
487 | },
488 | "has-flag": {
489 | "version": "4.0.0",
490 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
491 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
492 | "dev": true
493 | },
494 | "human-signals": {
495 | "version": "2.1.0",
496 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
497 | "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
498 | "dev": true
499 | },
500 | "import-local": {
501 | "version": "3.0.2",
502 | "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz",
503 | "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==",
504 | "dev": true,
505 | "requires": {
506 | "pkg-dir": "^4.2.0",
507 | "resolve-cwd": "^3.0.0"
508 | }
509 | },
510 | "interpret": {
511 | "version": "2.2.0",
512 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
513 | "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
514 | "dev": true
515 | },
516 | "is-core-module": {
517 | "version": "2.2.0",
518 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
519 | "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
520 | "dev": true,
521 | "requires": {
522 | "has": "^1.0.3"
523 | }
524 | },
525 | "is-plain-object": {
526 | "version": "2.0.4",
527 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
528 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
529 | "dev": true,
530 | "requires": {
531 | "isobject": "^3.0.1"
532 | }
533 | },
534 | "is-stream": {
535 | "version": "2.0.0",
536 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
537 | "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
538 | "dev": true
539 | },
540 | "isexe": {
541 | "version": "2.0.0",
542 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
543 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
544 | "dev": true
545 | },
546 | "isobject": {
547 | "version": "3.0.1",
548 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
549 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
550 | "dev": true
551 | },
552 | "jest-worker": {
553 | "version": "26.6.2",
554 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
555 | "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
556 | "dev": true,
557 | "requires": {
558 | "@types/node": "*",
559 | "merge-stream": "^2.0.0",
560 | "supports-color": "^7.0.0"
561 | }
562 | },
563 | "json-parse-better-errors": {
564 | "version": "1.0.2",
565 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
566 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
567 | "dev": true
568 | },
569 | "json-schema-traverse": {
570 | "version": "0.4.1",
571 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
572 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
573 | "dev": true
574 | },
575 | "kind-of": {
576 | "version": "6.0.3",
577 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
578 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
579 | "dev": true
580 | },
581 | "loader-runner": {
582 | "version": "4.2.0",
583 | "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
584 | "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==",
585 | "dev": true
586 | },
587 | "locate-path": {
588 | "version": "5.0.0",
589 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
590 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
591 | "dev": true,
592 | "requires": {
593 | "p-locate": "^4.1.0"
594 | }
595 | },
596 | "merge-stream": {
597 | "version": "2.0.0",
598 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
599 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
600 | "dev": true
601 | },
602 | "mime-db": {
603 | "version": "1.46.0",
604 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz",
605 | "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==",
606 | "dev": true
607 | },
608 | "mime-types": {
609 | "version": "2.1.29",
610 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz",
611 | "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==",
612 | "dev": true,
613 | "requires": {
614 | "mime-db": "1.46.0"
615 | }
616 | },
617 | "mimic-fn": {
618 | "version": "2.1.0",
619 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
620 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
621 | "dev": true
622 | },
623 | "neo-async": {
624 | "version": "2.6.2",
625 | "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
626 | "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
627 | "dev": true
628 | },
629 | "node-releases": {
630 | "version": "1.1.71",
631 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz",
632 | "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==",
633 | "dev": true
634 | },
635 | "npm-run-path": {
636 | "version": "4.0.1",
637 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
638 | "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
639 | "dev": true,
640 | "requires": {
641 | "path-key": "^3.0.0"
642 | }
643 | },
644 | "onetime": {
645 | "version": "5.1.2",
646 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
647 | "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
648 | "dev": true,
649 | "requires": {
650 | "mimic-fn": "^2.1.0"
651 | }
652 | },
653 | "p-limit": {
654 | "version": "3.1.0",
655 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
656 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
657 | "dev": true,
658 | "requires": {
659 | "yocto-queue": "^0.1.0"
660 | }
661 | },
662 | "p-locate": {
663 | "version": "4.1.0",
664 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
665 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
666 | "dev": true,
667 | "requires": {
668 | "p-limit": "^2.2.0"
669 | },
670 | "dependencies": {
671 | "p-limit": {
672 | "version": "2.3.0",
673 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
674 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
675 | "dev": true,
676 | "requires": {
677 | "p-try": "^2.0.0"
678 | }
679 | }
680 | }
681 | },
682 | "p-try": {
683 | "version": "2.2.0",
684 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
685 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
686 | "dev": true
687 | },
688 | "path-exists": {
689 | "version": "4.0.0",
690 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
691 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
692 | "dev": true
693 | },
694 | "path-key": {
695 | "version": "3.1.1",
696 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
697 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
698 | "dev": true
699 | },
700 | "path-parse": {
701 | "version": "1.0.6",
702 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
703 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
704 | "dev": true
705 | },
706 | "pkg-dir": {
707 | "version": "4.2.0",
708 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
709 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
710 | "dev": true,
711 | "requires": {
712 | "find-up": "^4.0.0"
713 | }
714 | },
715 | "punycode": {
716 | "version": "2.1.1",
717 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
718 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
719 | "dev": true
720 | },
721 | "randombytes": {
722 | "version": "2.1.0",
723 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
724 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
725 | "dev": true,
726 | "requires": {
727 | "safe-buffer": "^5.1.0"
728 | }
729 | },
730 | "rechoir": {
731 | "version": "0.7.0",
732 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz",
733 | "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==",
734 | "dev": true,
735 | "requires": {
736 | "resolve": "^1.9.0"
737 | }
738 | },
739 | "resolve": {
740 | "version": "1.20.0",
741 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
742 | "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
743 | "dev": true,
744 | "requires": {
745 | "is-core-module": "^2.2.0",
746 | "path-parse": "^1.0.6"
747 | }
748 | },
749 | "resolve-cwd": {
750 | "version": "3.0.0",
751 | "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
752 | "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
753 | "dev": true,
754 | "requires": {
755 | "resolve-from": "^5.0.0"
756 | }
757 | },
758 | "resolve-from": {
759 | "version": "5.0.0",
760 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
761 | "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
762 | "dev": true
763 | },
764 | "safe-buffer": {
765 | "version": "5.2.1",
766 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
767 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
768 | "dev": true
769 | },
770 | "schema-utils": {
771 | "version": "3.0.0",
772 | "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
773 | "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
774 | "dev": true,
775 | "requires": {
776 | "@types/json-schema": "^7.0.6",
777 | "ajv": "^6.12.5",
778 | "ajv-keywords": "^3.5.2"
779 | }
780 | },
781 | "serialize-javascript": {
782 | "version": "5.0.1",
783 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
784 | "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
785 | "dev": true,
786 | "requires": {
787 | "randombytes": "^2.1.0"
788 | }
789 | },
790 | "shallow-clone": {
791 | "version": "3.0.1",
792 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
793 | "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
794 | "dev": true,
795 | "requires": {
796 | "kind-of": "^6.0.2"
797 | }
798 | },
799 | "shebang-command": {
800 | "version": "2.0.0",
801 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
802 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
803 | "dev": true,
804 | "requires": {
805 | "shebang-regex": "^3.0.0"
806 | }
807 | },
808 | "shebang-regex": {
809 | "version": "3.0.0",
810 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
811 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
812 | "dev": true
813 | },
814 | "signal-exit": {
815 | "version": "3.0.3",
816 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
817 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
818 | "dev": true
819 | },
820 | "source-list-map": {
821 | "version": "2.0.1",
822 | "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
823 | "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
824 | "dev": true
825 | },
826 | "source-map": {
827 | "version": "0.6.1",
828 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
829 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
830 | "dev": true
831 | },
832 | "source-map-support": {
833 | "version": "0.5.19",
834 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
835 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
836 | "dev": true,
837 | "requires": {
838 | "buffer-from": "^1.0.0",
839 | "source-map": "^0.6.0"
840 | }
841 | },
842 | "strip-final-newline": {
843 | "version": "2.0.0",
844 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
845 | "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
846 | "dev": true
847 | },
848 | "supports-color": {
849 | "version": "7.2.0",
850 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
851 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
852 | "dev": true,
853 | "requires": {
854 | "has-flag": "^4.0.0"
855 | }
856 | },
857 | "tapable": {
858 | "version": "2.2.0",
859 | "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz",
860 | "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==",
861 | "dev": true
862 | },
863 | "terser": {
864 | "version": "5.6.0",
865 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.0.tgz",
866 | "integrity": "sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA==",
867 | "dev": true,
868 | "requires": {
869 | "commander": "^2.20.0",
870 | "source-map": "~0.7.2",
871 | "source-map-support": "~0.5.19"
872 | },
873 | "dependencies": {
874 | "source-map": {
875 | "version": "0.7.3",
876 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
877 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
878 | "dev": true
879 | }
880 | }
881 | },
882 | "terser-webpack-plugin": {
883 | "version": "5.1.1",
884 | "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz",
885 | "integrity": "sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q==",
886 | "dev": true,
887 | "requires": {
888 | "jest-worker": "^26.6.2",
889 | "p-limit": "^3.1.0",
890 | "schema-utils": "^3.0.0",
891 | "serialize-javascript": "^5.0.1",
892 | "source-map": "^0.6.1",
893 | "terser": "^5.5.1"
894 | }
895 | },
896 | "tslib": {
897 | "version": "1.14.1",
898 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
899 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
900 | "dev": true
901 | },
902 | "uri-js": {
903 | "version": "4.4.1",
904 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
905 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
906 | "dev": true,
907 | "requires": {
908 | "punycode": "^2.1.0"
909 | }
910 | },
911 | "v8-compile-cache": {
912 | "version": "2.3.0",
913 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
914 | "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
915 | "dev": true
916 | },
917 | "watchpack": {
918 | "version": "2.1.1",
919 | "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz",
920 | "integrity": "sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==",
921 | "dev": true,
922 | "requires": {
923 | "glob-to-regexp": "^0.4.1",
924 | "graceful-fs": "^4.1.2"
925 | }
926 | },
927 | "webpack": {
928 | "version": "5.24.4",
929 | "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.24.4.tgz",
930 | "integrity": "sha512-RXOdxF9hFFFhg47BryCgyFrEyyu7Y/75/uiI2DoUiTMqysK+WczVSTppvkR47oZcmI/DPaXCiCiaXBP8QjkNpA==",
931 | "dev": true,
932 | "requires": {
933 | "@types/eslint-scope": "^3.7.0",
934 | "@types/estree": "^0.0.46",
935 | "@webassemblyjs/ast": "1.11.0",
936 | "@webassemblyjs/wasm-edit": "1.11.0",
937 | "@webassemblyjs/wasm-parser": "1.11.0",
938 | "acorn": "^8.0.4",
939 | "browserslist": "^4.14.5",
940 | "chrome-trace-event": "^1.0.2",
941 | "enhanced-resolve": "^5.7.0",
942 | "es-module-lexer": "^0.4.0",
943 | "eslint-scope": "^5.1.1",
944 | "events": "^3.2.0",
945 | "glob-to-regexp": "^0.4.1",
946 | "graceful-fs": "^4.2.4",
947 | "json-parse-better-errors": "^1.0.2",
948 | "loader-runner": "^4.2.0",
949 | "mime-types": "^2.1.27",
950 | "neo-async": "^2.6.2",
951 | "schema-utils": "^3.0.0",
952 | "tapable": "^2.1.1",
953 | "terser-webpack-plugin": "^5.1.1",
954 | "watchpack": "^2.0.0",
955 | "webpack-sources": "^2.1.1"
956 | }
957 | },
958 | "webpack-cli": {
959 | "version": "4.5.0",
960 | "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.5.0.tgz",
961 | "integrity": "sha512-wXg/ef6Ibstl2f50mnkcHblRPN/P9J4Nlod5Hg9HGFgSeF8rsqDGHJeVe4aR26q9l62TUJi6vmvC2Qz96YJw1Q==",
962 | "dev": true,
963 | "requires": {
964 | "@discoveryjs/json-ext": "^0.5.0",
965 | "@webpack-cli/configtest": "^1.0.1",
966 | "@webpack-cli/info": "^1.2.2",
967 | "@webpack-cli/serve": "^1.3.0",
968 | "colorette": "^1.2.1",
969 | "commander": "^7.0.0",
970 | "enquirer": "^2.3.6",
971 | "execa": "^5.0.0",
972 | "fastest-levenshtein": "^1.0.12",
973 | "import-local": "^3.0.2",
974 | "interpret": "^2.2.0",
975 | "rechoir": "^0.7.0",
976 | "v8-compile-cache": "^2.2.0",
977 | "webpack-merge": "^5.7.3"
978 | },
979 | "dependencies": {
980 | "commander": {
981 | "version": "7.1.0",
982 | "resolved": "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz",
983 | "integrity": "sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==",
984 | "dev": true
985 | }
986 | }
987 | },
988 | "webpack-merge": {
989 | "version": "5.7.3",
990 | "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz",
991 | "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==",
992 | "dev": true,
993 | "requires": {
994 | "clone-deep": "^4.0.1",
995 | "wildcard": "^2.0.0"
996 | }
997 | },
998 | "webpack-sources": {
999 | "version": "2.2.0",
1000 | "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz",
1001 | "integrity": "sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==",
1002 | "dev": true,
1003 | "requires": {
1004 | "source-list-map": "^2.0.1",
1005 | "source-map": "^0.6.1"
1006 | }
1007 | },
1008 | "which": {
1009 | "version": "2.0.2",
1010 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1011 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1012 | "dev": true,
1013 | "requires": {
1014 | "isexe": "^2.0.0"
1015 | }
1016 | },
1017 | "wildcard": {
1018 | "version": "2.0.0",
1019 | "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
1020 | "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
1021 | "dev": true
1022 | },
1023 | "yocto-queue": {
1024 | "version": "0.1.0",
1025 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
1026 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
1027 | "dev": true
1028 | }
1029 | }
1030 | }
1031 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scriptable-scripts",
3 | "version": "1.0.0",
4 | "description": "A collection of Scriptable.app scripts'",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/evandcoleman/scriptable.git"
12 | },
13 | "author": "Evan Coleman (http://edc.me)",
14 | "license": "MIT",
15 | "bugs": {
16 | "url": "https://github.com/evandcoleman/scriptable/issues"
17 | },
18 | "homepage": "https://github.com/evandcoleman/scriptable#readme",
19 | "devDependencies": {
20 | "terser-webpack-plugin": "^5.1.1",
21 | "webpack": "^5.24.4",
22 | "webpack-cli": "^4.5.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/MLB.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: deep-blue; icon-glyph: baseball-ball;
4 |
5 | /////////////////////////////////////////
6 | //
7 | // Configuration - PLEASE READ
8 | //
9 | /////////////////////////////////////////
10 |
11 | // PLEASE READ - To set your team:
12 | // Long-press on the widget on your homescreen, then tap "Edit Widget"
13 | // Input your team abbreviation in the "Parameter" field.
14 | // Set "When Interacting" to "Run Script" if you want taps to route to the MLB app.
15 | // Find team abbreviation here: https://en.wikipedia.org/wiki/Wikipedia:WikiProject_Baseball/Team_abbreviations
16 |
17 | /////////////////////////
18 |
19 | const TEAM = args.widgetParameter || 'NYY';
20 |
21 | // simple, expanded
22 | const LAYOUT = "expanded";
23 |
24 | /////////////////////////////////////////
25 | //
26 | // Do not edit below this line!
27 | //
28 | /////////////////////////////////////////
29 |
30 | /******/
31 |
32 | import Cache from './lib/cache';
33 | import Updater from './lib/updater';
34 | import * as http from './lib/http';
35 |
36 | const scriptVersion = 16;
37 | const sourceRepo = "evandcoleman/scriptable";
38 | const scriptName = "MLB";
39 |
40 | /////////////////////////////////////////
41 | //
42 | // Script
43 | //
44 | /////////////////////////////////////////
45 |
46 | const cache = new Cache("mlbWidgetCache", 2);
47 | const updater = new Updater(sourceRepo);
48 |
49 | try {
50 | const widget = await (async (layout) => {
51 | switch (layout) {
52 | case 'simple':
53 | return createSimpleWidget();
54 | case 'expanded':
55 | return createExpandedWidget();
56 | default:
57 | throw new Error(`Invalid layout type ${layout}`);
58 | }
59 | })(LAYOUT);
60 | widget.url = "mlbatbat://"
61 | Script.setWidget(widget);
62 | } catch (error) {
63 | console.log(`${error.line}: ${error.message}`);
64 | }
65 |
66 | try {
67 | await updater.checkForUpdate(scriptName, scriptVersion);
68 | } catch (error) {
69 | console.log(`${error.line}: ${error.message}`);
70 | }
71 |
72 | Script.complete();
73 |
74 | async function createExpandedWidget() {
75 | const w = new ListWidget()
76 | w.backgroundColor = new Color("#0F1011");
77 | w.setPadding(20, 15, 15, 15)
78 |
79 | const mainStack = w.addStack();
80 | mainStack.layoutVertically();
81 |
82 | const { game, team } = await fetchTeam(TEAM);
83 | const awayLogo = await fetchTeamLogo(game.teams.away.team.abbreviation);
84 | const homeLogo = await fetchTeamLogo(game.teams.home.team.abbreviation);
85 | const { gameStatus, isPlaying, isPreGame, isPostGame, isPPD } = getFormattedStatus(game);
86 |
87 | const upperStack = mainStack.addStack();
88 |
89 | if (!isPreGame && !isPPD) {
90 | upperStack.layoutHorizontally();
91 | const scoreStack = upperStack.addStack();
92 | scoreStack.layoutVertically();
93 |
94 | const awayStack = scoreStack.addStack();
95 | awayStack.centerAlignContent();
96 | const awayLogoImage = awayStack.addImage(awayLogo);
97 | awayLogoImage.imageSize = new Size(32, 32);
98 | awayStack.addSpacer(6);
99 | if (game.linescore) {
100 | const awayRuns = awayStack.addText(`${game.linescore.teams.away.runs || 0}`);
101 | awayRuns.font = Font.boldSystemFont(28);
102 | awayRuns.textColor = Color.white();
103 | }
104 |
105 | const spacer = scoreStack.addSpacer();
106 | spacer.length = 6;
107 |
108 | const homeStack = scoreStack.addStack();
109 | homeStack.centerAlignContent();
110 | const homeLogoImage = homeStack.addImage(homeLogo);
111 | homeLogoImage.imageSize = new Size(32, 32);
112 | homeStack.addSpacer(6);
113 | if (game.linescore) {
114 | const homeRuns = homeStack.addText(`${game.linescore.teams.home.runs || 0}`);
115 | homeRuns.font = Font.boldSystemFont(28);
116 | homeRuns.textColor = Color.white();
117 | }
118 | } else {
119 | upperStack.layoutVertically();
120 |
121 | const logoStack = upperStack.addStack();
122 | logoStack.layoutHorizontally();
123 | logoStack.bottomAlignContent();
124 | const awayLogoImage = logoStack.addImage(awayLogo);
125 | awayLogoImage.imageSize = new Size(38, 38);
126 | logoStack.addSpacer();
127 | const vsText = logoStack.addText('vs.');
128 | vsText.textColor = Color.lightGray();
129 | vsText.font = Font.regularSystemFont(14);
130 | logoStack.addSpacer();
131 | const homeLogoImage = logoStack.addImage(homeLogo);
132 | homeLogoImage.imageSize = new Size(38, 38);
133 | }
134 |
135 | upperStack.addSpacer();
136 | const statusStack = upperStack.addStack();
137 | statusStack.layoutVertically();
138 |
139 | const inningStack = statusStack.addStack();
140 | inningStack.layoutHorizontally();
141 | inningStack.centerAlignContent();
142 |
143 | if (isPlaying) {
144 | inningStack.addSpacer(12);
145 | const arrowText = inningStack.addText(game.linescore.isTopInning ? '▲' : '▼');
146 | arrowText.font = Font.regularSystemFont(10);
147 | arrowText.textColor = Color.lightGray();
148 | inningStack.addSpacer(4);
149 | const statusText = inningStack.addText(game.linescore.currentInning.toString());
150 | statusText.font = Font.mediumSystemFont(22);
151 | statusText.textColor = Color.white();
152 |
153 | const basesStack = statusStack.addStack();
154 | basesStack.layoutHorizontally();
155 | const bases = getBasesImage(game);
156 | const basesWidgetImage = basesStack.addImage(bases);
157 | basesWidgetImage.rightAlignImage();
158 | basesWidgetImage.imageSize = new Size(42, 42);
159 |
160 | const outsStack = statusStack.addStack();
161 | outsStack.layoutHorizontally();
162 | const outImages = getOutsImages(game);
163 | for (let index in outImages) {
164 | if (index > 0) {
165 | outsStack.addSpacer(index == 0 ? null : index === 2 ? 0 : 12);
166 | }
167 | const widgetImage = outsStack.addImage(outImages[index]);
168 | widgetImage.imageSize = new Size(6, 6);
169 | }
170 | } else if (isPreGame || isPPD) {
171 | inningStack.addSpacer();
172 | const statusText = inningStack.addText(gameStatus);
173 | statusText.font = Font.regularSystemFont(11);
174 | statusText.textColor = Color.lightGray();
175 | inningStack.addSpacer();
176 | } else {
177 | const statusText = inningStack.addText(gameStatus);
178 | statusText.font = Font.caption1();
179 | statusText.textColor = Color.lightGray();
180 | }
181 |
182 | mainStack.addSpacer();
183 |
184 | const lowerStack = mainStack.addStack();
185 | lowerStack.layoutVertically();
186 |
187 | if (isPlaying) {
188 | const abTitleText = lowerStack.addText("At Bat:")
189 | abTitleText.font = Font.mediumSystemFont(11);
190 | abTitleText.textColor = Color.lightGray();
191 | const nameCountStack = lowerStack.addStack();
192 | nameCountStack.layoutHorizontally();
193 | nameCountStack.centerAlignContent();
194 | const playerNameText = nameCountStack.addText(game.linescore.offense.batter.fullName);
195 | playerNameText.font = Font.regularSystemFont(12);
196 | playerNameText.textColor = Color.white();
197 | // playerNameText.minimumScaleFactor = 0.9;
198 | nameCountStack.addSpacer(4);
199 | const countText = nameCountStack.addText(`(${game.linescore.balls}-${game.linescore.strikes})`);
200 | countText.font = Font.regularSystemFont(10);
201 | countText.textColor = Color.lightGray();
202 | nameCountStack.addSpacer();
203 |
204 | const pitcherTitleText = lowerStack.addText("Pitching:")
205 | pitcherTitleText.font = Font.mediumSystemFont(11);
206 | pitcherTitleText.textColor = Color.lightGray();
207 | const namePitchesStack = lowerStack.addStack();
208 | namePitchesStack.layoutHorizontally();
209 | namePitchesStack.centerAlignContent();
210 | const pitcherNameText = namePitchesStack.addText(game.linescore.defense.pitcher.fullName);
211 | pitcherNameText.font = Font.regularSystemFont(12);
212 | pitcherNameText.textColor = Color.white();
213 | // pitcherNameText.minimumScaleFactor = 0.9;
214 | namePitchesStack.addSpacer(4);
215 | const pitchesThrown = game.linescore.defense.pitcher.stats.filter(stat => stat.type.displayName === 'gameLog' && stat.group.displayName === 'pitching')[0].stats.pitchesThrown;
216 | const pitchesThrownText = namePitchesStack.addText(`(P ${pitchesThrown})`);
217 | pitchesThrownText.font = Font.regularSystemFont(10);
218 | pitchesThrownText.textColor = Color.lightGray();
219 | namePitchesStack.addSpacer();
220 | } else if (isPreGame || isPPD) {
221 | const abTitleText = lowerStack.addText("Away Pitcher:")
222 | abTitleText.font = Font.mediumSystemFont(11);
223 | abTitleText.textColor = Color.lightGray();
224 | const nameCountStack = lowerStack.addStack();
225 | nameCountStack.layoutHorizontally();
226 | nameCountStack.centerAlignContent();
227 | const playerNameText = nameCountStack.addText(game.teams.away.probablePitcher?.fullName || 'TBD');
228 | playerNameText.font = Font.regularSystemFont(12);
229 | playerNameText.textColor = Color.white();
230 | // playerNameText.minimumScaleFactor = 0.9;
231 | if (game.teams.away.probablePitcher) {
232 | nameCountStack.addSpacer(4);
233 | if (game.teams.away.probablePitcher.stats) {
234 | const winnerStats = game.teams.away.probablePitcher.stats.filter(stat => stat.type.displayName === 'statsSingleSeason' && stat.group.displayName === 'pitching')[0].stats;
235 | const countText = nameCountStack.addText(`(${winnerStats.wins}-${winnerStats.losses})`);
236 | countText.font = Font.regularSystemFont(10);
237 | countText.textColor = Color.lightGray();
238 | }
239 | }
240 | nameCountStack.addSpacer();
241 |
242 | const pitcherTitleText = lowerStack.addText("Home Pitcher:")
243 | pitcherTitleText.font = Font.mediumSystemFont(11);
244 | pitcherTitleText.textColor = Color.lightGray();
245 | const namePitchesStack = lowerStack.addStack();
246 | namePitchesStack.layoutHorizontally();
247 | namePitchesStack.centerAlignContent();
248 | const pitcherNameText = namePitchesStack.addText(game.teams.home.probablePitcher?.fullName || 'TBD');
249 | pitcherNameText.font = Font.regularSystemFont(12);
250 | pitcherNameText.textColor = Color.white();
251 | // pitcherNameText.minimumScaleFactor = 0.9;
252 | if (game.teams.home.probablePitcher) {
253 | namePitchesStack.addSpacer(4);
254 | if (game.teams.home.probablePitcher.stats) {
255 | const loserStats = game.teams.home.probablePitcher.stats.filter(stat => stat.type.displayName === 'statsSingleSeason' && stat.group.displayName === 'pitching')[0].stats;
256 | const pitchesThrownText = namePitchesStack.addText(`(${loserStats.wins}-${loserStats.losses})`);
257 | pitchesThrownText.font = Font.regularSystemFont(10);
258 | pitchesThrownText.textColor = Color.lightGray();
259 | }
260 | }
261 | namePitchesStack.addSpacer();
262 | } else if (isPostGame && game.decisions) {
263 | const abTitleText = lowerStack.addText("Winning Pitcher:")
264 | abTitleText.font = Font.mediumSystemFont(11);
265 | abTitleText.textColor = Color.lightGray();
266 | const nameCountStack = lowerStack.addStack();
267 | nameCountStack.layoutHorizontally();
268 | nameCountStack.centerAlignContent();
269 | const playerNameText = nameCountStack.addText(game.decisions.winner?.fullName || "N/A");
270 | playerNameText.font = Font.regularSystemFont(12);
271 | playerNameText.textColor = Color.white();
272 | // playerNameText.minimumScaleFactor = 0.9;
273 | nameCountStack.addSpacer(4);
274 | if (game.decisions.winner && game.decisions.winner.stats) {
275 | const winnerStats = game.decisions.winner.stats.filter(stat => stat.type.displayName === 'statsSingleSeason' && stat.group.displayName === 'pitching')[0].stats;
276 | const countText = nameCountStack.addText(`(${winnerStats.wins}-${winnerStats.losses})`);
277 | countText.font = Font.regularSystemFont(10);
278 | countText.textColor = Color.lightGray();
279 | }
280 | nameCountStack.addSpacer();
281 |
282 | const pitcherTitleText = lowerStack.addText("Losing Pitcher:")
283 | pitcherTitleText.font = Font.mediumSystemFont(11);
284 | pitcherTitleText.textColor = Color.lightGray();
285 | const namePitchesStack = lowerStack.addStack();
286 | namePitchesStack.layoutHorizontally();
287 | namePitchesStack.centerAlignContent();
288 | const pitcherNameText = namePitchesStack.addText(game.decisions.loser?.fullName || "N/A");
289 | pitcherNameText.font = Font.regularSystemFont(12);
290 | pitcherNameText.textColor = Color.white();
291 | // pitcherNameText.minimumScaleFactor = 0.9;
292 | namePitchesStack.addSpacer(4);
293 | if (game.decisions.loser && game.decisions.loser.stats) {
294 | const loserStats = game.decisions.loser.stats.filter(stat => stat.type.displayName === 'statsSingleSeason' && stat.group.displayName === 'pitching')[0].stats;
295 | const pitchesThrownText = namePitchesStack.addText(`(${loserStats.wins}-${loserStats.losses})`);
296 | pitchesThrownText.font = Font.regularSystemFont(10);
297 | pitchesThrownText.textColor = Color.lightGray();
298 | }
299 | namePitchesStack.addSpacer();
300 | }
301 |
302 | lowerStack.addSpacer();
303 |
304 | return w
305 | }
306 |
307 | async function createSimpleWidget() {
308 | const w = new ListWidget()
309 | w.backgroundColor = new Color("#0F1011");
310 | w.setPadding(15, 10, 15, 15)
311 |
312 | const mainStack = w.addStack();
313 | mainStack.layoutVertically();
314 |
315 | const { game, team } = await fetchTeam(TEAM);
316 | const awayLogo = await fetchTeamLogo(game.teams.away.team.abbreviation);
317 | const homeLogo = await fetchTeamLogo(game.teams.home.team.abbreviation);
318 | const { gameStatus, isPlaying, isPreGame, isPostGame } = getFormattedStatus(game);
319 |
320 | const scoreStack = mainStack.addStack();
321 | scoreStack.layoutVertically();
322 |
323 | const awayStack = scoreStack.addStack();
324 | awayStack.centerAlignContent();
325 | const awayLogoImage = awayStack.addImage(awayLogo);
326 | awayLogoImage.imageSize = new Size(42, 42);
327 | awayStack.addSpacer();
328 | const awayName = awayStack.addText(game.teams.away.team.abbreviation);
329 | awayName.font = Font.title2();
330 | awayName.textColor = Color.white();
331 | awayStack.addSpacer();
332 | if (!isPreGame) {
333 | const awayRuns = awayStack.addText(`${game.linescore.teams.away.runs || 0}`);
334 | awayRuns.font = Font.title2();
335 | awayRuns.textColor = Color.white();
336 | }
337 |
338 | const spacer = scoreStack.addSpacer();
339 | spacer.length = 6;
340 |
341 | const homeStack = scoreStack.addStack();
342 | homeStack.centerAlignContent();
343 | const homeLogoImage = homeStack.addImage(homeLogo);
344 | homeLogoImage.imageSize = new Size(42, 42);
345 | homeStack.addSpacer();
346 | const homeName = homeStack.addText(game.teams.home.team.abbreviation);
347 | homeName.font = Font.title2();
348 | homeName.textColor = Color.white();
349 | homeStack.addSpacer();
350 | if (!isPreGame) {
351 | const homeRuns = homeStack.addText(`${game.linescore.teams.home.runs || 0}`);
352 | homeRuns.font = Font.title2();
353 | homeRuns.textColor = Color.white();
354 | }
355 |
356 | mainStack.addSpacer();
357 | const statusStack = mainStack.addStack();
358 | statusStack.layoutHorizontally();
359 | statusStack.addSpacer();
360 |
361 | const statusText = statusStack.addText(gameStatus);
362 | statusText.font = Font.callout();
363 | statusText.textColor = Color.lightGray();
364 |
365 | return w
366 | }
367 |
368 | function getFormattedStatus(game, opts) {
369 | const options = opts || {};
370 | const status = game.status.abstractGameState;
371 | const shortStatus = game.status.abstractGameCode;
372 | const innings = (game.linescore || { innings: [] }).innings.length;
373 | const short = options.short || false;
374 |
375 | let statusText;
376 | let isPlaying = false;
377 | let isPreGame = false;
378 | let isPostGame = false;
379 | let isPPD = false;
380 | switch (status) {
381 | case "Final":
382 | case "Completed Early":
383 | case "Game Over":
384 | isPostGame = true;
385 | isPPD = game.status.detailedState === "Postponed";
386 | if (innings !== 9) {
387 | statusText = `${short ? shortStatus : status}/${innings}`;
388 | } else {
389 | statusText = short ? shortStatus : status;
390 | }
391 | if (isPPD) {
392 | statusText = game.status.detailedState;
393 | }
394 | break;
395 | case "Delayed":
396 | isPlaying = true;
397 | statusText = `${short ? shortStatus : status}/${innings}`;
398 | break;
399 | case "Suspended":
400 | isPostGame = true;
401 | statusText = `${short ? shortStatus : status}/${innings}`;
402 | break;
403 | case "In Progress":
404 | case "Live":
405 | isPlaying = true;
406 | if (!short) {
407 | statusText = `${game.linescore.inningState} ${game.linescore.currentInningOrdinal}`;
408 | } else {
409 | statusText = `${game.linescore.isTopInning ? 'Top' : 'Bot'} ${game.linescore.currentInning}`;
410 | }
411 | break;
412 | case "Preview":
413 | case "Pre-Game":
414 | isPreGame = true;
415 | const df = new DateFormatter();
416 | df.useShortTimeStyle();
417 | const now = new Date();
418 | const tomorrow = new Date(now.getTime() + 86400000);
419 | if ((new Date(game.gameDate)).setHours(0, 0, 0, 0) === now.setHours(0, 0, 0, 0)) {
420 | df.useNoDateStyle();
421 | statusText = df.string(new Date(game.gameDate));
422 | } else if ((new Date(game.gameDate)).setHours(0, 0, 0, 0) === tomorrow.setHours(0, 0, 0, 0)) {
423 | df.useNoDateStyle();
424 | const rdtf = new RelativeDateTimeFormatter();
425 | rdtf.useNamedDateTimeStyle();
426 | statusText = rdtf.string(new Date(game.gameDate), now) + ' at ' + df.string(new Date(game.gameDate));
427 | } else {
428 | df.dateFormat = "E M/d 'at' h:mm a";
429 | statusText = df.string(new Date(game.gameDate));
430 | }
431 | break;
432 | default:
433 | statusText = short ? shortStatus : status;
434 | break;
435 | }
436 |
437 | return {
438 | gameStatus: statusText,
439 | isPlaying,
440 | isPreGame,
441 | isPostGame,
442 | isPPD,
443 | }
444 | }
445 |
446 | function getBasesImage(game) {
447 | const side = 80;
448 | const space = 6;
449 | const onFirst = 'first' in game.linescore.offense;
450 | const onSecond = 'second' in game.linescore.offense;
451 | const onThird = 'third' in game.linescore.offense;
452 |
453 | const baseSide = (Math.sqrt(2 * Math.pow(side / 2, 2)) / 2) - space;
454 | const baseHyp = Math.sqrt(2 * Math.pow(baseSide, 2));
455 | const spaceX = Math.sqrt(Math.pow(space, 2) / 2) * 2;
456 |
457 | const ctx = new DrawContext();
458 | ctx.opaque = false;
459 | ctx.size = new Size(side, side);
460 | ctx.setStrokeColor(Color.lightGray());
461 | ctx.setFillColor(new Color("#FFA500"));
462 | ctx.setLineWidth(2);
463 |
464 | const thirdBasePath = new Path();
465 | thirdBasePath.addLines([
466 | new Point(0, side / 2),
467 | new Point(baseHyp / 2, (side / 2) + (baseHyp / 2)),
468 | new Point(baseHyp, side / 2),
469 | new Point(baseHyp / 2, (side / 2) - (baseHyp / 2))
470 | ]);
471 | thirdBasePath.closeSubpath();
472 | ctx.addPath(thirdBasePath);
473 | ctx.strokePath();
474 | if (onThird) {
475 | ctx.addPath(thirdBasePath);
476 | ctx.fillPath();
477 | }
478 |
479 | const secondBasePath = new Path();
480 | secondBasePath.addLines([
481 | new Point((baseHyp / 2) + spaceX, baseHyp / 2),
482 | new Point(baseHyp + spaceX, 0),
483 | new Point(baseHyp + spaceX + (baseHyp / 2), baseHyp / 2),
484 | new Point(baseHyp + spaceX, baseHyp)
485 | ]);
486 | secondBasePath.closeSubpath();
487 | ctx.addPath(secondBasePath);
488 | ctx.strokePath();
489 | if (onSecond) {
490 | ctx.addPath(secondBasePath);
491 | ctx.fillPath();
492 | }
493 |
494 | const firstBasePath = new Path();
495 | firstBasePath.addLines([
496 | new Point((side / 2) + spaceX, side / 2),
497 | new Point(((side / 2) + spaceX) + (baseHyp / 2), (side / 2) + (baseHyp / 2)),
498 | new Point(((side / 2) + spaceX) + baseHyp, side / 2),
499 | new Point(((side / 2) + spaceX) + (baseHyp / 2), (side / 2) - (baseHyp / 2))
500 | ]);
501 | firstBasePath.closeSubpath();
502 | ctx.addPath(firstBasePath);
503 | ctx.strokePath();
504 | if (onFirst) {
505 | ctx.addPath(firstBasePath);
506 | ctx.fillPath();
507 | }
508 |
509 | const image = ctx.getImage();
510 |
511 | return image;
512 | }
513 |
514 | function getOutsImages(game) {
515 | const radius = 8;
516 |
517 | const ctx = new DrawContext();
518 | ctx.opaque = false;
519 | ctx.size = new Size(radius * 2, radius * 2);
520 | ctx.setFillColor(Color.lightGray());
521 |
522 | const outs = game.linescore.outs;
523 |
524 | for (let i = 0; i < 3; i += 1) {
525 | ctx.fillEllipse(new Rect(0, 0, radius * 2, radius * 2));
526 | }
527 |
528 | const offImage = ctx.getImage();
529 | ctx.setFillColor(new Color("#FFA500"));
530 |
531 | for (let i = 1; i <= 3; i += 1) {
532 | ctx.fillEllipse(new Rect(0, 0, radius * 2, radius * 2));
533 | }
534 |
535 | const onImage = ctx.getImage();
536 |
537 | return [
538 | outs > 0 ? onImage : offImage,
539 | outs > 1 ? onImage : offImage,
540 | outs > 2 ? onImage : offImage,
541 | ];
542 | }
543 |
544 | async function fetchTeam(team) {
545 | let game;
546 |
547 | // Find a game within 14 days for the provided team
548 | game = await fetchGameWithinDays(14, { team });
549 |
550 | // If the provided team has no upcoming games, pick the first game
551 | // that's currently in-progress
552 | if (!game) {
553 | game = await fetchGameWithinDays(14, { inProgress: true });
554 | }
555 |
556 | // Just get the first game in the list
557 | if (!game) {
558 | game = await fetchGameWithinDays(14);
559 | }
560 |
561 | // Get the last game of the provided team
562 | if (!game) {
563 | game = await fetchGameWithinDays(180, { team, backwards: true });
564 | }
565 |
566 | const isHome = game.teams.home.team.abbreviation === team;
567 |
568 | return {
569 | game,
570 | team: isHome ? game.teams.home.team : game.teams.away.team,
571 | };
572 | }
573 |
574 | async function fetchGameWithinDays(maxDays, options) {
575 | var game = null;
576 | let days = options?.backwards == true ? maxDays - 1 : 0;
577 |
578 | while (!game && days < maxDays && days >= 0) {
579 | let scoreboard = await fetchScoreboard(days);
580 | var games = [];
581 |
582 | if (options?.team) {
583 | games = scoreboard.filter(game => {
584 | const away = game.teams.away.team.abbreviation;
585 | const home = game.teams.home.team.abbreviation;
586 |
587 | return options.team === away || options.team === home;
588 | });
589 | } else if (options?.inProgress) {
590 | games = scoreboard.filter(game => {
591 | const { isPlaying } = getFormattedStatus(game);
592 |
593 | return isPlaying;
594 | });
595 | } else if (scoreboard.length > 0) {
596 | games = scoreboard;
597 | }
598 |
599 | game = games[0];
600 |
601 |
602 | if (options?.backwards == true) {
603 | days -= 1;
604 | } else {
605 | days += 1;
606 | }
607 | }
608 |
609 | return game;
610 | }
611 |
612 | async function fetchScoreboard(inDays) {
613 | const df = new DateFormatter();
614 | df.dateFormat = "yyyy-MM-dd";
615 | const now = new Date();
616 | const date = now.getHours() < 5 ? new Date(now.getTime() - 43200000) : new Date(now.getTime() + (86400000 * (inDays || 0)));
617 | const dateString = df.string(date);
618 | const url = `https://statsapi.mlb.com/api/v1/schedule?date=${dateString}&language=en&hydrate=team(league),venue(location,timezone),linescore(matchup,runners,positions),decisions,homeRuns,probablePitcher,flags,review,seriesStatus,person,stats,broadcasts(all)&sportId=1`;
619 | const data = await http.fetchJson({
620 | cache,
621 | url,
622 | cacheKey: `mlb_scores_${TEAM}_${inDays}`,
623 | });
624 |
625 | return data.dates[0]?.games || [];
626 | }
627 |
628 | async function fetchTeamLogo(team) {
629 | const req = new Request(`https://a.espncdn.com/i/teamlogos/mlb/500/${team.toLowerCase()}.png`);
630 | return req.loadImage();
631 | }
632 |
--------------------------------------------------------------------------------
/src/TermiWidget.js:
--------------------------------------------------------------------------------
1 | // Variables used by Scriptable.
2 | // These must be at the very top of the file. Do not edit.
3 | // icon-color: deep-gray; icon-glyph: magic;
4 |
5 | // Change these to your usernames!
6 | const user = "evan";
7 |
8 | // API PARAMETERS !important
9 | // WEATHER_API_KEY, you need an Open Weather API Key
10 | // You can get one for free at: https://home.openweathermap.org/api_keys (account needed).
11 | const WEATHER_API_KEY = "";
12 | const DEFAULT_LOCATION = {
13 | latitude: 0,
14 | longitude: 0
15 | };
16 | const TAUTULLI_API_BASE = "";
17 | const TAUTULLI_API_KEY = "";
18 | const HOME_ASSISTANT_API_BASE = "";
19 | const HOME_ASSISTANT_API_KEY = "";
20 | const UPCOMING_SAT_PASS_URL = "";
21 |
22 | /******/
23 |
24 | import Cache from './lib/cache';
25 |
26 | const cache = new Cache("termiWidgetCache");
27 | const data = await fetchData();
28 | const widget = createWidget(data);
29 | Script.setWidget(widget);
30 | Script.complete();
31 |
32 | function createWidget(data) {
33 | console.log(data)
34 | const w = new ListWidget()
35 | const bgColor = new LinearGradient()
36 | bgColor.colors = [new Color("#29323c"), new Color("#1c1c1c")]
37 | bgColor.locations = [0.0, 1.0]
38 | w.backgroundGradient = bgColor
39 | w.setPadding(12, 15, 15, 12)
40 |
41 | const stack = w.addStack();
42 | stack.layoutHorizontally();
43 |
44 | const leftStack = stack.addStack();
45 | leftStack.layoutVertically();
46 | leftStack.spacing = 6;
47 | leftStack.size = new Size(200, 0);
48 |
49 | const time = new Date()
50 | const dfTime = new DateFormatter()
51 | dfTime.locale = "en"
52 | dfTime.useMediumDateStyle()
53 | dfTime.useNoTimeStyle()
54 |
55 | const firstLine = leftStack.addText(`[] ${user} ~$ now`)
56 | firstLine.textColor = Color.white()
57 | firstLine.textOpacity = 0.7
58 | firstLine.font = new Font("Menlo", 11)
59 |
60 | const timeLine = leftStack.addText(`[🗓] ${dfTime.string(time)}`)
61 | timeLine.textColor = Color.white()
62 | timeLine.font = new Font("Menlo", 11)
63 |
64 | const batteryLine = leftStack.addText(`[🔋] ${renderBattery()}`)
65 | batteryLine.textColor = new Color("#6ef2ae")
66 | batteryLine.font = new Font("Menlo", 11)
67 |
68 | const locationLine = leftStack.addText(`[️️📍] Location: ${data.weather.location}`)
69 | locationLine.textColor = new Color("#7dbbae")
70 | locationLine.font = new Font("Menlo", 11)
71 |
72 | const homeLine = leftStack.addText(`[🏠] ${data.home.mode}, ${data.home.temperature}°, Lights ${data.home.lights ? "On" : "Off"}`);
73 | homeLine.textColor = new Color("#ff9468")
74 | homeLine.font = new Font("Menlo", 11)
75 |
76 | let plexText = `[🍿] Plex: ${data.plex.streams} stream${data.plex.streams == 1 ? '' : 's'}`;
77 | // if (data.plex.streams > 0) {
78 | // plexText += `, ${data.plex.transcodes} transcode${data.plex.transcodes == 1 ? '' : 's'}`;
79 | // }
80 | const plexLine = leftStack.addText(plexText);
81 | plexLine.textColor = new Color("#ffa7d3")
82 | plexLine.font = new Font("Menlo", 11)
83 |
84 | const satLine = leftStack.addText(`[🛰] ${data.satPass}`);
85 | satLine.textColor = new Color("#ffcc66")
86 | satLine.font = new Font("Menlo", 11)
87 |
88 | stack.addSpacer();
89 | const rightStack = stack.addStack();
90 | rightStack.spacing = 2;
91 | rightStack.layoutVertically();
92 | rightStack.bottomAlignContent();
93 |
94 | addWeatherLine(rightStack, data.weather.icon, 32);
95 | addWeatherLine(rightStack, `${data.weather.description}, ${data.weather.temperature}°`, 12, true);
96 | addWeatherLine(rightStack, `High: ${data.weather.high}°`);
97 | addWeatherLine(rightStack, `Low: ${data.weather.low}°`);
98 | addWeatherLine(rightStack, `Wind: ${data.weather.wind} mph`);
99 |
100 | return w
101 | }
102 |
103 | function addWeatherLine(w, text, size, bold) {
104 | const stack = w.addStack();
105 | stack.setPadding(0, 0, 0, 0);
106 | stack.layoutHorizontally();
107 | stack.addSpacer();
108 | const line = stack.addText(text);
109 | line.textColor = new Color("#ffcc66");
110 | line.font = new Font("Menlo" + (bold ? "-Bold" : ""), size || 11);
111 | }
112 |
113 | async function fetchData() {
114 | const weather = await fetchWeather();
115 | const plex = await fetchPlex();
116 | const home = await fetchHome();
117 | const satPass = await fetchNextSatPass();
118 |
119 | return {
120 | weather,
121 | plex,
122 | home,
123 | satPass,
124 | }
125 | }
126 |
127 | function renderBattery() {
128 | const batteryLevel = Device.batteryLevel()
129 | const juice = "#".repeat(Math.floor(batteryLevel * 8))
130 | const used = ".".repeat(8 - juice.length)
131 | const batteryAscii = `[${juice}${used}] ${Math.round(batteryLevel * 100)}%`
132 | return batteryAscii
133 | }
134 |
135 | async function fetchWeather() {
136 | let location = await cache.read('location');
137 | if (!location) {
138 | try {
139 | Location.setAccuracyToThreeKilometers();
140 | location = await Location.current();
141 | } catch(error) {
142 | location = await cache.read('location');
143 | }
144 | }
145 | if (!location) {
146 | location = DEFAULT_LOCATION;
147 | }
148 | const address = await Location.reverseGeocode(location.latitude, location.longitude);
149 | const url = "https://api.openweathermap.org/data/2.5/onecall?lat=" + location.latitude + "&lon=" + location.longitude + "&exclude=minutely,hourly,alerts&units=imperial&lang=en&appid=" + WEATHER_API_KEY;
150 | const data = await fetchJson(`weather_${address[0].locality}`, url);
151 |
152 | return {
153 | location: address[0].locality,
154 | icon: getWeatherEmoji(data.current.weather[0].id, ((new Date()).getTime() / 1000) >= data.current.sunset),
155 | description: data.current.weather[0].main,
156 | temperature: Math.round(data.current.temp),
157 | wind: Math.round(data.current.wind_speed),
158 | high: Math.round(data.daily[0].temp.max),
159 | low: Math.round(data.daily[0].temp.min),
160 | }
161 | }
162 |
163 | async function fetchPlex() {
164 | const url = `${TAUTULLI_API_BASE}/api/v2?apikey=${TAUTULLI_API_KEY}&cmd=get_activity`;
165 | const data = await fetchJson(`plex`, url);
166 |
167 | return {
168 | streams: data.response.data.stream_count,
169 | transcodes: data.response.data.stream_count_transcode,
170 | };
171 | }
172 |
173 | async function fetchHome() {
174 | const mode = await fetchHomeAssistant('states/input_select.mode');
175 | const temp = await fetchHomeAssistant('states/sensor.hallway_temperature');
176 | const livingRoomLight = (await fetchHomeAssistant('states/light.living_room')).state == "on";
177 | const bedRoomLight = (await fetchHomeAssistant('states/light.bedroom')).state == "on";
178 | const hallwayLight = (await fetchHomeAssistant('states/light.hallway')).state == "on";
179 | const bathroomLight = (await fetchHomeAssistant('states/light.bathroom')).state == "on";
180 |
181 | return {
182 | mode: mode.state,
183 | temperature: Math.round(parseFloat(temp.state)),
184 | lights: livingRoomLight || bedRoomLight || hallwayLight || bathroomLight,
185 | };
186 | }
187 |
188 | async function fetchHomeAssistant(path) {
189 | return fetchJson(path, `${HOME_ASSISTANT_API_BASE}/api/${path}`, {
190 | 'Authorization': `Bearer ${HOME_ASSISTANT_API_KEY}`,
191 | 'Content-Type': 'application/json',
192 | });
193 | }
194 |
195 | async function fetchNextSatPass() {
196 | const passes = await fetchJson('upcoming-passes.json', UPCOMING_SAT_PASS_URL);
197 | const now = new Date();
198 | const nextPass = passes
199 | .filter((p) => now.getTime() < p.end)[0];
200 |
201 | if (!nextPass) {
202 | return 'No more passes today';
203 | }
204 |
205 | if (nextPass.start > now.getTime()) {
206 | const minutes = Math.round(((nextPass.start - now.getTime()) / 1000) / 60);
207 | const hours = Math.round((((nextPass.start - now.getTime()) / 1000) / 60) / 60);
208 |
209 | if (minutes > 59) {
210 | return `${nextPass.satellite} in ${hours}h, ${Math.round(nextPass.elevation)}°`;
211 | } else {
212 | return `${nextPass.satellite} in ${minutes}m, ${Math.round(nextPass.elevation)}°`;
213 | }
214 | } else {
215 | return `${nextPass.satellite} for ${Math.round(((nextPass.end - now.getTime()) / 1000) / 60)}m, ${Math.round(nextPass.elevation)}°`;
216 | }
217 | }
218 |
219 | async function fetchJson(key, url, headers) {
220 | const cached = await cache.read(key, 5);
221 | if (cached) {
222 | return cached;
223 | }
224 |
225 | try {
226 | console.log(`Fetching url: ${url}`);
227 | const req = new Request(url);
228 | req.headers = headers;
229 | const resp = await req.loadJSON();
230 | cache.write(key, resp);
231 | return resp;
232 | } catch (error) {
233 | try {
234 | return cache.read(key, 5);
235 | } catch (error) {
236 | console.log(`Couldn't fetch ${url}`);
237 | }
238 | }
239 | }
240 |
241 | function getWeatherEmoji(code, isNight) {
242 | if (code >= 200 && code < 300 || code == 960 || code == 961) {
243 | return "⛈"
244 | } else if ((code >= 300 && code < 600) || code == 701) {
245 | return "🌧"
246 | } else if (code >= 600 && code < 700) {
247 | return "❄️"
248 | } else if (code == 711) {
249 | return "🔥"
250 | } else if (code == 800) {
251 | return isNight ? "🌕" : "☀️"
252 | } else if (code == 801) {
253 | return isNight ? "☁️" : "🌤"
254 | } else if (code == 802) {
255 | return isNight ? "☁️" : "⛅️"
256 | } else if (code == 803) {
257 | return isNight ? "☁️" : "🌥"
258 | } else if (code == 804) {
259 | return "☁️"
260 | } else if (code == 900 || code == 962 || code == 781) {
261 | return "🌪"
262 | } else if (code >= 700 && code < 800) {
263 | return "🌫"
264 | } else if (code == 903) {
265 | return "🥶"
266 | } else if (code == 904) {
267 | return "🥵"
268 | } else if (code == 905 || code == 957) {
269 | return "💨"
270 | } else if (code == 906 || code == 958 || code == 959) {
271 | return "🧊"
272 | } else {
273 | return "❓"
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/src/lib/cache.js:
--------------------------------------------------------------------------------
1 | export default class Cache {
2 | constructor(name, expirationMinutes) {
3 | this.fm = FileManager.iCloud();
4 | this.cachePath = this.fm.joinPath(this.fm.documentsDirectory(), name);
5 | this.expirationMinutes = expirationMinutes;
6 |
7 | if (!this.fm.fileExists(this.cachePath)) {
8 | this.fm.createDirectory(this.cachePath)
9 | }
10 | }
11 |
12 | async read(key, expirationMinutes) {
13 | try {
14 | const path = this.fm.joinPath(this.cachePath, key);
15 | await this.fm.downloadFileFromiCloud(path);
16 | const createdAt = this.fm.creationDate(path);
17 |
18 | if (this.expirationMinutes || expirationMinutes) {
19 | if ((new Date()) - createdAt > ((this.expirationMinutes || expirationMinutes) * 60000)) {
20 | this.fm.remove(path);
21 | return null;
22 | }
23 | }
24 |
25 | const value = this.fm.readString(path);
26 |
27 | try {
28 | return JSON.parse(value);
29 | } catch(error) {
30 | return value;
31 | }
32 | } catch(error) {
33 | return null;
34 | }
35 | };
36 |
37 | write(key, value) {
38 | const path = this.fm.joinPath(this.cachePath, key.replace('/', '-'));
39 | console.log(`Caching to ${path}...`);
40 |
41 | if (typeof value === 'string' || value instanceof String) {
42 | this.fm.writeString(path, value);
43 | } else {
44 | this.fm.writeString(path, JSON.stringify(value));
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/lib/http.js:
--------------------------------------------------------------------------------
1 | export async function fetchJson({ url, headers, cache, cacheKey, cacheExpiration }) {
2 | if (cache && cacheKey) {
3 | const cached = await cache.read(cacheKey, cacheExpiration);
4 | if (cached) {
5 | return cached;
6 | }
7 | }
8 |
9 | try {
10 | console.log(`Fetching url: ${url}`);
11 | const req = new Request(url);
12 | if (headers) {
13 | req.headers = headers;
14 | }
15 | const resp = await req.loadJSON();
16 | if (cache && cacheKey) {
17 | cache.write(cacheKey, resp);
18 | }
19 | return resp;
20 | } catch (error) {
21 | if (cache && cacheKey) {
22 | try {
23 | return cache.read(cacheKey, cacheTimeout || 1);
24 | } catch (error) {
25 | console.log(`Couldn't fetch ${url}`);
26 | }
27 | } else {
28 | console.log(error);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/lib/updater.js:
--------------------------------------------------------------------------------
1 | import Cache from './cache';
2 | import * as http from './http';
3 |
4 | export default class Updater {
5 | constructor(repo) {
6 | this.repo = repo;
7 | this.fm = FileManager.iCloud();
8 | this.cache = new Cache("edcWidgetUpdaterCache", 15);
9 | }
10 |
11 | async checkForUpdate(name, version) {
12 | const latestVersion = await this.getLatestVersion(name);
13 |
14 | if (latestVersion > version) {
15 | console.log(`Version ${latestVersion} is greater than ${version}. Updating...`);
16 | await this.updateScript(name, latestVersion);
17 |
18 | return true;
19 | }
20 |
21 | console.log(`Version ${version} is not newer than ${latestVersion}. Skipping update.`);
22 |
23 | return false;
24 | }
25 |
26 | async getLatestVersion(name) {
27 | const url = `https://api.github.com/repos/${this.repo}/releases`;
28 | const data = await http.fetchJson({
29 | url,
30 | cache: this.cache,
31 | cacheKey: name
32 | });
33 |
34 | if (!data || data.length === 0) {
35 | return null;
36 | }
37 |
38 | const matches = data
39 | .filter(x => x.tag_name.startsWith(`${name}-`) && !x.draft && !x.prerelease)
40 | .sort((a, b) => new Date(b.published_at) - new Date(a.published_at));
41 |
42 | if (!matches|| matches.length === 0) {
43 | return null;
44 | }
45 |
46 | const release = matches[0];
47 | const version = release.tag_name.split('-').slice(-1)[0];
48 |
49 | return parseInt(version, 10);
50 | }
51 |
52 | async updateScript(name, version) {
53 | const url = `https://raw.githubusercontent.com/${this.repo}/${name}-${version}/dist/${name}.js`;
54 | const req = new Request(url);
55 | const content = await req.loadString();
56 |
57 | const path = this.fm.joinPath(this.fm.documentsDirectory(), name + '.js');
58 |
59 | this.fm.writeString(path, content);
60 | }
61 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const TerserPlugin = require("terser-webpack-plugin");
4 |
5 | module.exports = {
6 | mode: "production",
7 | entry: {
8 | TermiWidget: './src/TermiWidget.js',
9 | MLB: './src/MLB.js',
10 | },
11 | output: {
12 | path:path.resolve(__dirname, "dist"),
13 | },
14 | experiments: {
15 | topLevelAwait: true,
16 | },
17 | optimization: {
18 | minimize: true,
19 | minimizer: [
20 | new TerserPlugin({
21 | minify: (file, map, minimizerOptions) => {
22 | let code = file[Object.keys(file)[0]];
23 | const pattern = /\/\/ Variables used by Scriptable([^\0]*?)\/[\*]+\//;
24 | const matches = code.match(pattern)
25 | code = matches[0] + '\n' + code.replace(pattern, '');
26 |
27 | return { map, code };
28 | },
29 | }),
30 | ],
31 | },
32 | // plugins: [
33 | // new webpack.BannerPlugin({
34 | // banner: (va) => {
35 | // console.log(Object.keys(va.chunk));
36 | // console.log(va.chunk.files)
37 | // return 'hello world';
38 | // }
39 | // })
40 | // ]
41 | }
--------------------------------------------------------------------------------