├── .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 | ![](images/termiwidget.png) | 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 | } --------------------------------------------------------------------------------