├── .eslintrc.js ├── .github └── screenshots │ └── tiny-dashboard │ ├── anamnisar.png │ ├── ash.png │ ├── cloud.png │ ├── dull.png │ ├── midnight.png │ ├── nord.png │ ├── nordlight.png │ ├── pacific.png │ ├── preview.png │ ├── royal.png │ ├── sandblue.png │ ├── seablue.png │ └── sin.png ├── .gitignore ├── README.md ├── babel-plugin └── babel-transform-import-scriptable.js ├── babel.config.json ├── build ├── demo.js ├── mono-monthly-small.js ├── monobank.js ├── tiny-charts.js ├── tiny-dashboard.js ├── update-code.js └── utils.js ├── package-lock.json ├── package.json ├── src ├── demo.ts ├── mono-monthly-small.ts ├── monobank.ts ├── monobank.types.ts ├── tiny-charts.ts ├── tiny-dashboard.ts ├── update-code.ts ├── utils.ts └── utils.types.ts └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-magic-numbers */ 2 | 3 | module.exports = { 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | '@scriptable-ios', 8 | ], 9 | 10 | rules: { 11 | 'no-extra-parens': [ 12 | 'error', 13 | 'all', 14 | { 15 | conditionalAssign: false, 16 | nestedBinaryExpressions: false, 17 | returnAssign: false, 18 | enforceForArrowConditionals: false, 19 | enforceForSequenceExpressions: false, 20 | enforceForNewInMemberExpressions: false, 21 | enforceForFunctionPrototypeMethods: false, 22 | }, 23 | ], 24 | // https://eslint.org/docs/rules/no-invalid-regexp 25 | 'no-invalid-regexp': 'error', 26 | 27 | // https://eslint.org/docs/rules/no-unreachable 28 | 'no-unreachable': 'error', 29 | 30 | // https://eslint.org/docs/rules/no-unreachable-loop 31 | 'no-unreachable-loop': 'error', 32 | 33 | // https://eslint.org/docs/rules/valid-typeof 34 | 'valid-typeof': [ 35 | 'error', 36 | { 37 | requireStringLiterals: true, 38 | }, 39 | ], 40 | 41 | // https://eslint.org/docs/rules/dot-location 42 | 'dot-location': ['error', 'property'], 43 | 44 | // https://eslint.org/docs/rules/dot-notation 45 | 'dot-notation': [ 46 | 'error', 47 | { 48 | allowKeywords: true, 49 | }, 50 | ], 51 | 52 | // https://eslint.org/docs/rules/no-alert 53 | 'no-alert': 'error', 54 | 55 | // https://eslint.org/docs/rules/no-else-return 56 | 'no-else-return': [ 57 | 'error', 58 | { 59 | allowElseIf: false, 60 | }, 61 | ], 62 | 63 | // https://eslint.org/docs/rules/no-eval 64 | 'no-eval': 'error', 65 | 66 | // https://eslint.org/docs/rules/no-global-assign 67 | 'no-global-assign': [ 68 | 'error', 69 | { 70 | exceptions: [], 71 | }, 72 | ], 73 | // https://eslint.org/docs/rules/no-multi-spaces 74 | 'no-multi-spaces': 'error', 75 | 76 | // https://eslint.org/docs/rules/no-multi-str 77 | 'no-multi-str': 'error', 78 | 79 | // https://eslint.org/docs/rules/no-restricted-properties 80 | 'no-restricted-properties': [ 81 | 'error', 82 | { 83 | object: 'arguments', 84 | property: 'callee', 85 | message: 'arguments.callee is deprecated', 86 | }, 87 | { 88 | object: 'global', 89 | property: 'isFinite', 90 | message: 'Use Number.isFinite instead', 91 | }, 92 | { 93 | object: 'window', 94 | property: 'isFinite', 95 | message: 'Use Number.isFinite instead', 96 | }, 97 | { 98 | object: 'global', 99 | property: 'isNaN', 100 | message: 'Use Number.isNaN instead', 101 | }, 102 | { 103 | object: 'window', 104 | property: 'isNaN', 105 | message: 'Use Number.isNaN instead', 106 | }, 107 | { 108 | property: '__defineGetter__', 109 | message: 'Use Object.defineProperty instead', 110 | }, 111 | { 112 | property: '__defineSetter__', 113 | message: 'Use Object.defineProperty instead', 114 | }, 115 | { 116 | object: 'require', 117 | message: 'Please call require() directly.', 118 | }, 119 | ], 120 | 121 | // https://eslint.org/docs/rules/no-useless-return 122 | 'no-useless-return': 'error', 123 | 124 | // https://eslint.org/docs/rules/require-await 125 | 'require-await': 'error', 126 | 127 | // https://eslint.org/docs/rules/vars-on-top 128 | 'vars-on-top': 'error', 129 | 130 | // https://eslint.org/docs/rules/no-compare-neg-zero 131 | 'no-compare-neg-zero': 'error', 132 | 133 | // https://eslint.org/docs/rules/no-cond-assign 134 | 'no-cond-assign': 'error', 135 | 136 | // https://eslint.org/docs/rules/no-console 137 | 'no-console': 'warn', 138 | 139 | // https://eslint.org/docs/rules/no-constant-condition 140 | 'no-constant-condition': 'error', 141 | 142 | // https://eslint.org/docs/rules/no-control-regex 143 | 'no-control-regex': 'error', 144 | 145 | // https://eslint.org/docs/rules/no-debugger 146 | 'no-debugger': 'error', 147 | 148 | // https://eslint.org/docs/rules/no-undefined 149 | 'no-undefined': 'off', 150 | 151 | 'eol-last': ['error', 'always'], 152 | 'no-mixed-spaces-and-tabs': 'error', 153 | 154 | // https://eslint.org/docs/rules/jsx-quotes 155 | 'jsx-quotes': ['error', 'prefer-double'], 156 | 157 | // https://eslint.org/docs/rules/quotes 158 | quotes: ['error', 'single'], 159 | 160 | // https://eslint.org/docs/rules/block-spacing 161 | 'block-spacing': ['error', 'always'], 162 | 163 | // https://eslint.org/docs/rules/array-bracket-spacing 164 | 'array-bracket-spacing': ['error', 'never'], 165 | 166 | // https://eslint.org/docs/rules/no-whitespace-before-property 167 | 'no-whitespace-before-property': 'error', 168 | 169 | // https://eslint.org/docs/rules/linebreak-style 170 | 'linebreak-style': ['error', 'unix'], 171 | 172 | // https://eslint.org/docs/rules/multiline-comment-style 173 | 'comma-spacing': [ 174 | 'error', 175 | { 176 | before: false, 177 | after: true, 178 | }, 179 | ], 180 | 181 | // https://eslint.org/docs/rules/brace-style 182 | 'brace-style': [ 183 | 'error', 184 | '1tbs', 185 | { 186 | allowSingleLine: true, 187 | }, 188 | ], 189 | 190 | // https://eslint.org/docs/rules/no-multiple-empty-lines 191 | 'no-multiple-empty-lines': [ 192 | 'error', 193 | { 194 | max: 2, 195 | maxEOF: 0, 196 | maxBOF: 0, 197 | }, 198 | ], 199 | 200 | // https://eslint.org/docs/rules/key-spacing 201 | 'key-spacing': [ 202 | 'error', 203 | { 204 | beforeColon: false, 205 | afterColon: true, 206 | mode: 'strict', 207 | }, 208 | ], 209 | 210 | // https://eslint.org/docs/rules/no-trailing-spaces 211 | 'no-trailing-spaces': [ 212 | 'error', 213 | { 214 | skipBlankLines: false, 215 | ignoreComments: false, 216 | }, 217 | ], 218 | 219 | // https://eslint.org/docs/rules/no-underscore-dangle 220 | 'no-underscore-dangle': [ 221 | 'error', 222 | { 223 | allow: ['__', '__typename'], 224 | allowAfterThis: false, 225 | allowAfterSuper: false, 226 | enforceInMethodNames: false, 227 | }, 228 | ], 229 | // https://eslint.org/docs/rules/object-curly-spacing 230 | 'object-curly-spacing': [ 231 | 'error', 232 | 'always', 233 | { 234 | arraysInObjects: true, 235 | objectsInObjects: true, 236 | }, 237 | ], 238 | 239 | // https://eslint.org/docs/rules/padding-line-between-statements 240 | 'padding-line-between-statements': [ 241 | 'error', 242 | { 243 | blankLine: 'always', 244 | prev: 'directive', 245 | next: '*', 246 | }, 247 | { 248 | blankLine: 'any', 249 | prev: 'directive', 250 | next: 'directive', 251 | }, 252 | 253 | { 254 | blankLine: 'always', 255 | prev: ['const', 'let', 'var'], 256 | next: '*', 257 | }, 258 | { 259 | blankLine: 'any', 260 | prev: ['const', 'let', 'var'], 261 | next: ['const', 'let', 'var'], 262 | }, 263 | 264 | { 265 | blankLine: 'always', 266 | prev: '*', 267 | next: 'return', 268 | }, 269 | ], 270 | 271 | '@typescript-eslint/explicit-function-return-type': 'off', 272 | '@typescript-eslint/ban-ts-comment': 'off', 273 | '@typescript-eslint/no-var-requires': 'off', 274 | // note you must disable the base rule as it can report incorrect errors 275 | 'comma-dangle': 'off', 276 | '@typescript-eslint/comma-dangle': ['error', 'always-multiline'], 277 | // note you must disable the base rule as it can report incorrect errors 278 | 'no-unused-vars': 'off', 279 | '@typescript-eslint/no-unused-vars': 'warn', 280 | 281 | // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md 282 | '@typescript-eslint/member-delimiter-style': [ 283 | 'error', 284 | { 285 | multiline: { 286 | delimiter: 'comma', 287 | requireLast: true, 288 | }, 289 | singleline: { 290 | delimiter: 'comma', 291 | requireLast: false, 292 | }, 293 | }, 294 | ], 295 | 296 | // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-as-const.md 297 | '@typescript-eslint/prefer-as-const': 'warn', 298 | 299 | // note you must disable the base rule as it can report incorrect errors 300 | indent: 'off', 301 | '@typescript-eslint/indent': [ 302 | 'error', 303 | 2, 304 | { 305 | SwitchCase: 1, 306 | VariableDeclarator: 1, 307 | outerIIFEBody: 1, 308 | MemberExpression: 0, 309 | FunctionDeclaration: { 310 | parameters: 1, 311 | body: 1, 312 | }, 313 | FunctionExpression: { 314 | parameters: 1, 315 | body: 1, 316 | }, 317 | CallExpression: { 318 | arguments: 1, 319 | }, 320 | ArrayExpression: 1, 321 | ObjectExpression: 1, 322 | ImportDeclaration: 1, 323 | flatTernaryExpressions: false, 324 | offsetTernaryExpressions: false, 325 | ignoredNodes: [], 326 | ignoreComments: false, 327 | }, 328 | ], 329 | // note you must disable the base rule as it can report incorrect errors 330 | semi: 'off', 331 | '@typescript-eslint/semi': ['error', 'never'], 332 | '@typescript-eslint/naming-convention': [ 333 | 'error', 334 | { 335 | selector: 'typeAlias', 336 | format: ['PascalCase'], 337 | suffix: ['T'], 338 | }, 339 | { 340 | selector: 'interface', 341 | format: ['PascalCase'], 342 | suffix: ['I'], 343 | }, 344 | ], 345 | // note you must disable the base rule as it can report incorrect errors 346 | 'no-magic-numbers': 'off', 347 | '@typescript-eslint/no-magic-numbers': [ 348 | 'error', 349 | { 350 | ignore: [-1, 2, 0, 1, 1e2, 1e3], 351 | ignoreArrayIndexes: true, 352 | enforceConst: true, 353 | detectObjects: false, 354 | ignoreNumericLiteralTypes: true, 355 | }, 356 | ], 357 | // note you must disable the base rule as it can report incorrect errors 358 | 'keyword-spacing': 'off', 359 | '@typescript-eslint/keyword-spacing': [ 360 | 'error', 361 | { 362 | before: true, 363 | after: true, 364 | overrides: { 365 | return: { 366 | after: true, 367 | }, 368 | throw: { 369 | after: true, 370 | }, 371 | case: { 372 | after: true, 373 | }, 374 | }, 375 | }, 376 | ], 377 | }, 378 | } 379 | -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/anamnisar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/anamnisar.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/ash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/ash.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/cloud.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/dull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/dull.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/midnight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/midnight.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/nord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/nord.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/nordlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/nordlight.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/pacific.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/pacific.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/preview.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/royal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/royal.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/sandblue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/sandblue.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/seablue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/seablue.png -------------------------------------------------------------------------------- /.github/screenshots/tiny-dashboard/sin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nodman/scriptables/19bfc56c86c8bbf3ddf1efe32bb0323641b95924/.github/screenshots/tiny-dashboard/sin.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | log 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Dependency directories 10 | node_modules/ 11 | 12 | # MAC 13 | .DS_Store 14 | 15 | # VScode 16 | .vscode 17 | jsconfig.json 18 | 19 | # automation tests 20 | allure-results 21 | 22 | # Yarn lock file 23 | yarn.lock 24 | 25 | #IntelliJ *.orig files 26 | *.orig 27 | 28 | # Vim 29 | .vim/ 30 | *.vim 31 | 32 | # releases 33 | *.zip 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scriptables 2 | my small collection of scripts to be used with [scriptable.app](https://scriptable.app) 3 | 4 | ### [tiny-dashboard](https://github.com/Nodman/scripables/blob/main/src/tiny-dashboard.ts): 5 | 6 | 7 | 8 | Small-size widget with several text fields, graph and icon, supports dynamic dark / light mode and has 12 builtin themes. 9 | 10 | Themes can be configured through widget params: ***dark=sandblue;light=royal*** 11 | 12 |
13 | themes preview (expandable) 14 | 15 | *NOTE:* 16 | 17 | 18 | *most of the colors were taken from https://uigradients.com* 19 | *nord colors were taken from https://www.nordtheme.com* 20 | 21 | 22 | * seablue 23 | 24 | ![tiny-dashboard seablue](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/seablue.png?raw=true) 25 | 26 | * cloud 27 | 28 | ![tiny-dashboard cloud](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/cloud.png?raw=true) 29 | 30 | * midnight 31 | 32 | ![tiny-dashboard midnight](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/midnight.png?raw=true) 33 | 34 | * royal 35 | 36 | ![tiny-dashboard royal](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/royal.png?raw=true) 37 | 38 | * dull 39 | 40 | ![tiny-dashboard dull](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/dull.png?raw=true) 41 | 42 | * anamnisar 43 | 44 | ![tiny-dashboard anamnisar](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/anamnisar.png?raw=true) 45 | 46 | * ash 47 | 48 | ![tiny-dashboard ash](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/ash.png?raw=true) 49 | 50 | * pacific 51 | 52 | ![tiny-dashboard pacific](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/pacific.png?raw=true) 53 | 54 | * sin 55 | 56 | ![tiny-dashboard sin](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/sin.png?raw=true) 57 | 58 | * sandblue 59 | 60 | ![tiny-dashboard sandblue](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/sandblue.png?raw=true) 61 | 62 | * nord 63 | 64 | ![tiny-dashboard nord](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/nord.png?raw=true) 65 | 66 | * nordlight 67 | 68 | ![tiny-dashboard nordlight](https://github.com/Nodman/scripables/blob/main/.github/screenshots/tiny-dashboard/nordlight.png?raw=true) 69 | 70 |
71 | 72 | 73 | 74 | ### [tiny-charts](https://github.com/Nodman/scripables/blob/main/src/tiny-charts.ts): 75 | 76 | Script for drawing charts with context, for now supports only area-type chart as shown on screenshot above 77 | 78 | 79 | 80 | ### [monobank](https://github.com/Nodman/scripables/blob/main/src/monobank.ts): 81 | 82 | *(WIP, expect breaking changes)* 83 | 84 | Monobank api client: fetch, record, filter and edit statement for current month, cache history for previous monthes. 85 | 86 | Provides UI for setting api-key, settting account per widget and and managing statement filters. Uses keychain to store api-key. 87 | 88 | ### [mono-monthly-small](https://github.com/Nodman/scripables/blob/main/src/mono-monthly-small.ts): 89 | 90 | Monobank script + tiny dashboard (as shown on the frist screenshot) 91 | 92 | ### [utils](https://github.com/Nodman/scripables/blob/main/src/utils.ts): 93 | 94 | Some common utils that are used across other scripts 95 | 96 | ### [update-code](https://github.com/Nodman/scripables/blob/main/src/update-code.ts): 97 | 98 | Update code of scripts from this repo 99 | 100 | --- 101 | 102 | this repos also contains custom bable plugin that transpiles es imprts to scriptable ***importModule*** api 103 | -------------------------------------------------------------------------------- /babel-plugin/babel-transform-import-scriptable.js: -------------------------------------------------------------------------------- 1 | // transform ESModule imports to scriptable 'importModule' api call 2 | 3 | const syntaxErrorMessage = 4 | "Unsupported syntax error. Don't mix default and named imports"; 5 | 6 | function transform({ types }) { 7 | return { 8 | visitor: { 9 | ImportDeclaration(path) { 10 | let variableDeclarator; 11 | 12 | const { specifiers, source } = path.node; 13 | const callExpression = types.callExpression( 14 | types.identifier("importModule"), 15 | [types.stringLiteral(source.value)] 16 | ); 17 | 18 | if (specifiers[0].type === "ImportSpecifier") { 19 | const objectProperties = specifiers.map((item) => { 20 | if (item.type === "ImportDefaultSpecifier") { 21 | throw path.buildCodeFrameError(syntaxErrorMessage); 22 | } 23 | 24 | const identifier = types.identifier(item.local.name); 25 | return types.objectProperty(identifier, identifier, false, true); 26 | }); 27 | 28 | variableDeclarator = types.variableDeclarator( 29 | types.objectPattern(objectProperties), 30 | callExpression 31 | ); 32 | } else { 33 | if (specifiers.length > 1) { 34 | throw path.buildCodeFrameError(syntaxErrorMessage); 35 | } 36 | variableDeclarator = types.variableDeclarator( 37 | types.identifier(specifiers[0].local.name), 38 | callExpression 39 | ); 40 | } 41 | 42 | path.replaceWith( 43 | types.variableDeclaration("const", [variableDeclarator]) 44 | ); 45 | }, 46 | }, 47 | }; 48 | } 49 | 50 | module.exports = transform; 51 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "./babel-plugin/babel-transform-import-scriptable.js", 4 | "@babel/plugin-transform-modules-commonjs" 5 | ], 6 | "presets": ["@babel/preset-typescript"], 7 | "ignore": ["./src/*.types.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /build/demo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* eslint-disable @typescript-eslint/no-magic-numbers */ 4 | const { 5 | createWidget 6 | } = importModule("./tiny-dashboard"); 7 | const { 8 | numberFormatterShort 9 | } = importModule("./utils"); 10 | const dateFormatter = new DateFormatter(); 11 | dateFormatter.useShortTimeStyle(); 12 | 13 | function exec() { 14 | const widget = createWidget({ 15 | chartData: [89, 100, 69, 190, 59, 22, 40], 16 | subtitle1: `${numberFormatterShort(1400)} DAILY / ${numberFormatterShort(12780)} MONTHLY`, 17 | subtitle2: `UPDATED: ${dateFormatter.string(new Date())}`, 18 | value: numberFormatterShort(2888), 19 | subValue: `/ ${numberFormatterShort(190)}`, 20 | headerSymbol: 'hryvniasign.circle', 21 | header: 'CURRENT MONTH:' 22 | }, { 23 | dark: 'midnight' 24 | }); 25 | widget.presentSmall(); 26 | } 27 | 28 | exec(); 29 | Script.complete(); -------------------------------------------------------------------------------- /build/mono-monthly-small.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: blue; icon-glyph: hand-holding-usd; 4 | 5 | /* 6 | * author: https://github.com/Nodman 7 | * track expanses from monobank account with small widget 8 | */ 9 | 'use strict'; 10 | 11 | const { 12 | createWidget 13 | } = importModule("./tiny-dashboard"); 14 | const { 15 | numberFormatterShort, 16 | parseWidgetParams 17 | } = importModule("./utils"); 18 | const { 19 | Monobank 20 | } = importModule("./monobank"); 21 | const SCRIPT_NAME = 'mono-monthly-small'; 22 | const dateFormatter = new DateFormatter(); 23 | dateFormatter.useShortTimeStyle(); 24 | 25 | async function exec() { 26 | const { 27 | name = SCRIPT_NAME 28 | } = parseWidgetParams(args.widgetParameter); 29 | const monobank = new Monobank(name); 30 | await monobank.setup(); 31 | const { 32 | currentPeriod, 33 | history 34 | } = await monobank.fetchLatestStatement(); 35 | const todaysValue = Monobank.getTodaysExpanses({ 36 | currentPeriod 37 | }); 38 | const { 39 | monthlyHistory, 40 | daily, 41 | monthly 42 | } = Monobank.getStatsForPeriod({ 43 | history 44 | }); 45 | monthlyHistory.push(currentPeriod.total); 46 | const widget = createWidget({ 47 | chartData: monthlyHistory, 48 | subtitle1: `${numberFormatterShort(daily / 100)} DAILY / ${numberFormatterShort(monthly / 100)} MONTHLY`, 49 | subtitle2: `UPDATED: ${dateFormatter.string(new Date())}`, 50 | value: numberFormatterShort(currentPeriod.total / 100), 51 | subValue: `/ ${numberFormatterShort(todaysValue / 100)}`, 52 | headerSymbol: 'hryvniasign.circle', 53 | header: 'CURRENT MONTH:' 54 | }); 55 | Script.setWidget(widget); 56 | return widget; 57 | } 58 | 59 | if (config.runsInApp) { 60 | const widget = await exec(); 61 | await widget.presentSmall(); 62 | } else { 63 | await exec(); 64 | } 65 | 66 | Script.complete(); -------------------------------------------------------------------------------- /build/monobank.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: university; 4 | 5 | /* 6 | * author: https://github.com/Nodman 7 | * Monobank api module 8 | */ 9 | // NOTE: This script uses the Cache script (https://github.com/yaylinda/scriptable/blob/main/Cache.js) 10 | 'use strict'; 11 | 12 | Object.defineProperty(exports, "__esModule", { 13 | value: true 14 | }); 15 | exports.Monobank = void 0; 16 | const { 17 | fetchJson, 18 | createAlert, 19 | getDaysInMonth, 20 | Logger 21 | } = importModule("./utils"); 22 | const Cache = importModule('cache'); 23 | const DEFAULT_PERIOD = 12; 24 | const ACCOUNT_MASK_LEN = 4; 25 | const SCRIPT_NAME = 'monobank'; 26 | const CACHE_KEY_SETTINGS = 'settings.json'; 27 | const MONOBANK_TOKEN_KEY = 'monobank_token@scriptable'; 28 | const MONOBANK_API_URL = 'https://api.monobank.ua'; 29 | const API_PATH = { 30 | PERSONAL_INFO: '/personal/client-info', 31 | STATEMENT: '/personal/statement', 32 | CURRENCIES: '/bank/currency' 33 | }; 34 | const CURRENCIES = { 35 | 978: { 36 | id: 978, 37 | name: 'EUR', 38 | sign: '€' 39 | }, 40 | 980: { 41 | id: 980, 42 | name: 'UAH', 43 | sign: '₴' 44 | }, 45 | 840: { 46 | id: 840, 47 | name: 'USD', 48 | sign: '$' 49 | } 50 | }; 51 | const cache = new Cache(SCRIPT_NAME); 52 | const logger = new Logger('Mono'); 53 | 54 | class Monobank { 55 | constructor(parentScriptName) { 56 | this.parentScriptName = parentScriptName; 57 | this.readSettings(); 58 | } 59 | 60 | static getTodaysExpanses({ 61 | currentPeriod 62 | }) { 63 | if (!currentPeriod || !currentPeriod.statement) { 64 | return 0; 65 | } 66 | 67 | const date = new Date().getDate(); 68 | const todaysStatement = currentPeriod.statement[date]; 69 | 70 | if (!todaysStatement || !todaysStatement.length) { 71 | return 0; 72 | } 73 | 74 | return todaysStatement.reduce((acc, item) => { 75 | return acc - (item?.amount ?? 0); 76 | }, 0); 77 | } 78 | 79 | static getStatsForPeriod({ 80 | history 81 | }, period = DEFAULT_PERIOD) { 82 | const yeatsAvailable = Object.keys(history).map(Number).sort((a, b) => a - b); 83 | const monthlyHistory = []; 84 | const dailyHistory = []; 85 | 86 | while (monthlyHistory.length < period && yeatsAvailable.length) { 87 | const [year] = yeatsAvailable.splice(-1); 88 | const yearPeriod = history[year] ?? {}; 89 | Object.keys(yearPeriod).map(Number).sort((a, b) => b - a).slice(-period + monthlyHistory.length).forEach(month => { 90 | const daysInMonth = getDaysInMonth(year, month); 91 | const monthValue = yearPeriod[month]; 92 | monthlyHistory.unshift(monthValue); 93 | dailyHistory.unshift(monthValue / daysInMonth); 94 | }); 95 | } 96 | 97 | return { 98 | monthlyHistory, 99 | dailyHistory, 100 | monthly: monthlyHistory.reduce((acc, item) => acc + item, 0) / (monthlyHistory.length || 1), 101 | daily: dailyHistory.reduce((acc, item) => acc + item, 0) / (dailyHistory.length || 1) 102 | }; 103 | } 104 | 105 | async getStatementItemsFilter() { 106 | const settings = await this.settings; 107 | const filter = this.parentScriptName && settings ? settings[this.parentScriptName]?.filter : undefined; 108 | return statementItem => { 109 | const isExpanse = statementItem.amount < 0 || statementItem.description.toLowerCase().startsWith('скасування'); 110 | const isExcluded = filter ? filter.split(';').some(filterString => statementItem.description.match(filterString)) : false; 111 | return isExpanse && !isExcluded; 112 | }; 113 | } 114 | 115 | async readSettings() { 116 | this.settings = cache.read(CACHE_KEY_SETTINGS) ?? {}; 117 | return await this.settings; 118 | } 119 | 120 | async writeSettings(nextSettings) { 121 | const prevSettings = await this.readSettings(); 122 | const settings = { ...prevSettings, 123 | ...nextSettings 124 | }; 125 | cache.write(CACHE_KEY_SETTINGS, settings); 126 | this.settings = Promise.resolve(settings); 127 | } 128 | 129 | get requestHeaders() { 130 | const token = Keychain.get(MONOBANK_TOKEN_KEY); 131 | 132 | if (!token) { 133 | throw new Error('No token was found in keychain'); 134 | } 135 | 136 | return { 137 | 'X-Token': token 138 | }; 139 | } 140 | 141 | async getAccountId(parentScriptName) { 142 | const parentScript = parentScriptName ?? this.parentScriptName; 143 | const settings = await this.settings; 144 | let accountId = parentScript && settings ? settings[parentScript]?.accountId : undefined; 145 | 146 | if (!accountId) { 147 | accountId = await this.setupAccount(); 148 | } 149 | 150 | if (!accountId) { 151 | throw new Error('Account ID is required'); 152 | } 153 | 154 | return accountId; 155 | } 156 | 157 | fetchData(path) { 158 | if (this.isTokenProvided()) { 159 | const url = `${MONOBANK_API_URL}${path}`; 160 | return fetchJson(url, this.requestHeaders); 161 | } 162 | 163 | throw new Error('No token provided'); 164 | } 165 | 166 | isTokenProvided() { 167 | return Keychain.contains(MONOBANK_TOKEN_KEY); 168 | } 169 | 170 | async getAccountCacheKey(parentScriptName) { 171 | const accountId = await this.getAccountId(parentScriptName); 172 | return `mono-${accountId}.json`; 173 | } 174 | 175 | async setupToken() { 176 | if (this.isTokenProvided()) { 177 | const tokenExistsAlert = createAlert('Monobank api token', 'Token already exists in your Keychain. Do you wish to change it?', 'Cancel'); 178 | tokenExistsAlert.addAction('Change token'); 179 | tokenExistsAlert.addDestructiveAction('Delete token'); 180 | const actionIndex = await tokenExistsAlert.presentSheet(); 181 | 182 | switch (actionIndex) { 183 | case -1: 184 | return; 185 | 186 | case 1: 187 | { 188 | Keychain.remove(MONOBANK_TOKEN_KEY); 189 | await createAlert('Token removed', '', 'Ok').present(); 190 | return; 191 | } 192 | } 193 | } 194 | 195 | const setupAlert = createAlert('Monobank API token is required', `In order to fetch data from Monobank you need to provide your generated API token. 196 | If you don't have one - please visit https://api.monobank.ua and follow the instructions.`, 'Cancel'); 197 | setupAlert.addAction('I have token'); 198 | setupAlert.addAction('https://api.monobank.ua'); 199 | const setupActionIndex = await setupAlert.presentSheet(); 200 | 201 | const presentInputAlert = async () => { 202 | const inputAlert = createAlert('Insert token', 'Your token will be saved in Keychain', 'Cancel'); 203 | inputAlert.addAction('Save'); 204 | inputAlert.addSecureTextField('token:'); 205 | const inputActionIndex = await inputAlert.present(); 206 | const tokenValue = inputAlert.textFieldValue(0); 207 | 208 | if (inputActionIndex === -1) { 209 | return; 210 | } 211 | 212 | if (tokenValue) { 213 | Keychain.set(MONOBANK_TOKEN_KEY, tokenValue); 214 | await createAlert('Token saved', '', 'Ok').present(); 215 | } else { 216 | await createAlert('Invalid token value', '', 'Ok').present(); 217 | } 218 | }; 219 | 220 | switch (setupActionIndex) { 221 | case 0: 222 | { 223 | await presentInputAlert(); 224 | break; 225 | } 226 | 227 | case 1: 228 | await Safari.openInApp('https://api.monobank.ua'); 229 | await presentInputAlert(); 230 | break; 231 | } 232 | } 233 | 234 | async setupFilters(parentScriptName) { 235 | const scriptName = parentScriptName ?? this.parentScriptName; 236 | 237 | if (!scriptName) { 238 | return; 239 | } 240 | 241 | const settings = (await this.settings) ?? {}; 242 | const setupFiltersAlert = createAlert('Set statement items filter', 'Enter semicolon separated values, e.g. "rent;netflix;spotify". Items with one of those words in description will be excluded from accounting', 'Cancel'); 243 | setupFiltersAlert.addAction('Save'); 244 | const filter = settings ? settings[scriptName]?.filter : undefined; 245 | setupFiltersAlert.addTextField('filter:', filter ?? ''); 246 | const actionIndex = await setupFiltersAlert.present(); 247 | 248 | if (actionIndex === -1) { 249 | return; 250 | } 251 | 252 | const nextFilter = setupFiltersAlert.textFieldValue(0); 253 | settings[scriptName] = settings[scriptName] ?? {}; 254 | settings[scriptName].filter = nextFilter; 255 | await this.writeSettings(settings); 256 | await createAlert('Filters saved', '', 'Ok').present(); 257 | } 258 | 259 | async setupAccount(parentScriptName) { 260 | const scriptName = parentScriptName ?? this.parentScriptName; 261 | const setupAccountsAlert = createAlert('Select account', 'ID of selected account will be copied to clipboard and used for this script.', 'Cancel'); 262 | const accounts = await this.fetchAccounts(); 263 | accounts.forEach(item => { 264 | const { 265 | name, 266 | type 267 | } = item; 268 | setupAccountsAlert.addAction(`[${type}] ****${name}`); 269 | }); 270 | const actionIndex = await setupAccountsAlert.presentSheet(); 271 | 272 | if (actionIndex === -1) { 273 | return; 274 | } 275 | 276 | const id = accounts[actionIndex].id; 277 | Pasteboard.copy(id); 278 | 279 | if (scriptName) { 280 | await this.writeSettings({ 281 | [scriptName]: { 282 | accountId: id 283 | } 284 | }); 285 | } 286 | 287 | return id; 288 | } 289 | 290 | async fetchAccounts() { 291 | const response = await this.fetchData(API_PATH.PERSONAL_INFO); 292 | const accounts = response.accounts.map(item => { 293 | const name = (item.maskedPan[0] ?? item.iban).slice(-ACCOUNT_MASK_LEN); 294 | return { 295 | id: item.id, 296 | type: item.type, 297 | name 298 | }; 299 | }); 300 | return accounts; 301 | } 302 | 303 | async fetchAccountStatement(fromArg, toArg) { 304 | const from = Math.round(Number(fromArg) / 1e3); 305 | const to = Math.round(Number(toArg ?? new Date()) / 1e3); 306 | const accountId = await this.getAccountId(); 307 | const url = `${API_PATH.STATEMENT}/${accountId}/${from}/${to}`; 308 | return this.fetchData(url); 309 | } 310 | 311 | fetchTodaysStatement() { 312 | const to = new Date(); 313 | const from = new Date(); 314 | from.setHours(0, 0, 0, 0); 315 | return this.fetchAccountStatement(from, to); 316 | } 317 | 318 | async fetchLatestStatement() { 319 | const accountCacheKey = await this.getAccountCacheKey(); 320 | const { 321 | currentPeriod, 322 | history = {} 323 | } = (await cache.read(accountCacheKey)) ?? {}; 324 | 325 | if (!currentPeriod?.lastUpdatedAt) { 326 | return this.fetchInitialPeriod(); 327 | } 328 | 329 | const { 330 | lastUpdatedAt, 331 | lastOperationId 332 | } = currentPeriod; 333 | const to = new Date(); 334 | const response = await this.fetchAccountStatement(new Date(lastUpdatedAt), to); 335 | 336 | if (!response.length || response[0].id === lastOperationId) { 337 | logger.log('nothing to process, skipping...'); 338 | return { 339 | currentPeriod, 340 | history 341 | }; 342 | } 343 | 344 | const filter = await this.getStatementItemsFilter(); 345 | 346 | for (let index = response.length - 1; index >= 0; index--) { 347 | const item = response[index]; 348 | 349 | if (filter(item)) { 350 | const { 351 | id, 352 | time, 353 | amount, 354 | description, 355 | mcc, 356 | cashbackAmount 357 | } = item; 358 | const opDate = new Date(item.time * 1e3); 359 | const month = opDate.getMonth() + 1; 360 | const year = opDate.getFullYear(); 361 | const date = opDate.getDate(); 362 | 363 | if (month > currentPeriod.month) { 364 | history[currentPeriod.year] = history[currentPeriod.year] ?? {}; 365 | history[currentPeriod.year][currentPeriod.month] = currentPeriod.total; 366 | currentPeriod.year = year; 367 | currentPeriod.month = month; 368 | currentPeriod.total = 0; 369 | currentPeriod.statement = [null]; 370 | } else if (month < currentPeriod.month) { 371 | continue; 372 | } 373 | 374 | currentPeriod.statement[date] = currentPeriod.statement[date] ?? []; // @ts-ignore 375 | 376 | currentPeriod.statement[date].push({ 377 | id, 378 | time, 379 | amount, 380 | description, 381 | mcc, 382 | cashbackAmount 383 | }); 384 | currentPeriod.total = currentPeriod.total += -1 * amount; 385 | } 386 | } 387 | 388 | currentPeriod.lastUpdatedAt = Number(to); 389 | currentPeriod.lastOperationId = response[0].id; 390 | cache.write(accountCacheKey, { 391 | currentPeriod, 392 | history 393 | }); 394 | return { 395 | currentPeriod, 396 | history 397 | }; 398 | } 399 | 400 | async fetchInitialPeriod() { 401 | logger.log('fetching initial period...'); 402 | const to = new Date(); 403 | const from = new Date(); 404 | const currentMonth = from.getMonth() + 1; 405 | const currentYear = from.getFullYear(); 406 | from.setDate(1); 407 | from.setHours(0, 0, 0, 0); 408 | const response = await this.fetchAccountStatement(from, to); 409 | const filter = await this.getStatementItemsFilter(); 410 | const currentPeriod = response.reduce((acc, item, index) => { 411 | if (filter(item)) { 412 | const { 413 | id, 414 | time, 415 | amount, 416 | description, 417 | mcc, 418 | cashbackAmount 419 | } = item; 420 | const date = new Date(item.time * 1e3).getDate(); 421 | const currentValue = acc.statement[date] ?? []; 422 | acc.statement[date] = [{ 423 | id, 424 | time, 425 | amount, 426 | description, 427 | mcc, 428 | cashbackAmount 429 | }, ...currentValue]; 430 | acc.total += -1 * amount; 431 | 432 | if (!index) { 433 | acc.lastOperationId = id; 434 | } 435 | } 436 | 437 | return acc; 438 | }, { 439 | month: currentMonth, 440 | year: currentYear, 441 | total: 0, 442 | statement: [], 443 | lastOperationId: '', 444 | lastUpdatedAt: Number(to) 445 | }); 446 | const statement = { 447 | history: {}, 448 | currentPeriod 449 | }; 450 | const accountCacheKey = await this.getAccountCacheKey(); 451 | await cache.write(accountCacheKey, statement); 452 | return statement; 453 | } 454 | 455 | async editOP({ 456 | date, 457 | day, 458 | amount: editAmount 459 | }, parentScriptName) { 460 | const accountCacheKey = await this.getAccountCacheKey(parentScriptName); 461 | const statement = await cache.read(accountCacheKey); 462 | const item = statement.currentPeriod.statement?.[date]?.[day]; 463 | 464 | if (item == null) { 465 | throw new Error(`OP not found: ${date}-${day}`); 466 | } 467 | 468 | if (editAmount === null) { 469 | item.amount = item.originalAmount ?? item.amount; 470 | item.originalAmount = undefined; 471 | } else { 472 | item.originalAmount = item.amount; 473 | item.amount = item.amount + editAmount; 474 | } 475 | 476 | const nextTotal = statement.currentPeriod.statement.reduce((total, day) => { 477 | return day ? total + day.reduce((acc, item) => item ? -1 * item.amount + acc : acc, 0) : total; 478 | }, 0); 479 | statement.currentPeriod.total = nextTotal; 480 | await cache.write(accountCacheKey, statement); 481 | await createAlert('Successfuly updated', '', 'Ok').present(); 482 | } 483 | 484 | async editStatementItem(parentScriptName) { 485 | const accountCacheKey = await this.getAccountCacheKey(parentScriptName); 486 | const { 487 | currentPeriod: { 488 | statement 489 | } 490 | } = await cache.read(accountCacheKey); 491 | const editAlert = createAlert('Select operation to edit', '', 'Cancel'); 492 | const actions = []; 493 | 494 | for (let dateIndex = statement.length - 1; dateIndex > 0; dateIndex--) { 495 | const date = statement[dateIndex]; 496 | 497 | if (date) { 498 | for (let opIndex = date.length - 1; opIndex >= 0; opIndex--) { 499 | const opItem = date[opIndex]; 500 | 501 | if (!opItem) { 502 | continue; 503 | } 504 | 505 | const { 506 | amount, 507 | description, 508 | originalAmount 509 | } = opItem; 510 | const summary = `${amount / 100}${CURRENCIES[980].sign}${originalAmount ? '[E]' : ''}: "${description.replace('\n', '_')}"`; 511 | editAlert.addAction(summary); 512 | actions.push({ 513 | summary, 514 | dateIndex, 515 | opIndex, 516 | originalAmount, 517 | amount 518 | }); 519 | } 520 | } 521 | } 522 | 523 | const opActionIndex = await editAlert.presentSheet(); 524 | 525 | if (opActionIndex === -1) { 526 | return; 527 | } 528 | 529 | let editActionIndex = -1; 530 | let amount = 0; 531 | const { 532 | originalAmount, 533 | amount: currentAmount 534 | } = actions[opActionIndex]; 535 | const valueAlert = createAlert('Edit operation', `Current amount is: ${currentAmount / 1e2}.\nEnter either positive or negative value to add:`, 'Cancel'); 536 | valueAlert.addTextField('Amount:'); 537 | valueAlert.addAction('Submit'); 538 | 539 | if (originalAmount) { 540 | valueAlert.addDestructiveAction('Restore this operation'); 541 | } else { 542 | valueAlert.addDestructiveAction('Delete this operation'); 543 | } 544 | 545 | const valueActionIndex = await valueAlert.present(); 546 | editActionIndex = valueActionIndex; 547 | 548 | switch (valueActionIndex) { 549 | case -1: 550 | break; 551 | 552 | case 0: 553 | amount = Number(valueAlert.textFieldValue(0)) * 1e2; 554 | break; 555 | 556 | case 1: 557 | amount = originalAmount ? null : -1 * currentAmount; 558 | break; 559 | } 560 | 561 | if (editActionIndex === -1) { 562 | return; 563 | } 564 | 565 | const { 566 | dateIndex, 567 | opIndex 568 | } = actions[opActionIndex]; 569 | const actionSummary = amount === null ? `restored to ${(originalAmount ?? 0) / 1e2}` : `edited for ${amount / 1e2}.\nNew value: ${currentAmount / 1e2 + amount / 1e2}`; 570 | const confirmationAlert = createAlert('Are you sure?', `${actions[opActionIndex].summary} will be ${actionSummary}.`, 'Cancel'); 571 | confirmationAlert.addDestructiveAction('Submit'); 572 | const confirmationIndex = await confirmationAlert.presentSheet(); 573 | 574 | if (confirmationIndex === -1) { 575 | return; 576 | } 577 | 578 | await this.editOP({ 579 | date: dateIndex, 580 | day: opIndex, 581 | amount 582 | }, parentScriptName); 583 | } 584 | 585 | async setup() { 586 | if (!this.parentScriptName) { 587 | return; 588 | } 589 | 590 | if (!this.isTokenProvided()) { 591 | await this.setupToken(); 592 | } 593 | 594 | const settings = (await this.readSettings()) ?? {}; 595 | const scriptSettings = settings[this.parentScriptName]; 596 | 597 | if (!scriptSettings || !scriptSettings.accountId) { 598 | await this.setupAccount(); 599 | await this.setupFilters(); 600 | } 601 | } 602 | 603 | async pickScript() { 604 | if (this.parentScriptName) { 605 | return; 606 | } 607 | 608 | const settngs = await this.readSettings(); 609 | const keys = Object.keys(settngs ?? {}); 610 | const selectScriptAlert = createAlert('Select script to modify', '', 'Cancel'); 611 | 612 | if (!keys.length) { 613 | return; 614 | } 615 | 616 | keys.forEach(item => { 617 | selectScriptAlert.addAction(item); 618 | }); 619 | const keyIndex = await selectScriptAlert.presentSheet(); 620 | return keys[keyIndex]; 621 | } 622 | 623 | async showSettings() { 624 | const settingsAlert = createAlert('Monobank settngs', '', 'Cancel'); 625 | settingsAlert.addAction('Manage auth token'); 626 | settingsAlert.addAction('Change account number'); 627 | settingsAlert.addAction('Set filters'); 628 | settingsAlert.addAction('Edit statement'); 629 | const actionIndex = await settingsAlert.present(); 630 | 631 | switch (actionIndex) { 632 | case -1: 633 | break; 634 | 635 | case 0: 636 | await this.setupToken(); 637 | break; 638 | 639 | case 1: 640 | { 641 | const scriptName = await this.pickScript(); 642 | 643 | if (scriptName) { 644 | await this.setupAccount(scriptName); 645 | } 646 | 647 | break; 648 | } 649 | 650 | case 2: 651 | { 652 | const scriptName = await this.pickScript(); 653 | 654 | if (scriptName) { 655 | await this.setupFilters(scriptName); 656 | } 657 | 658 | break; 659 | } 660 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers 661 | 662 | case 3: 663 | { 664 | const scriptName = await this.pickScript(); 665 | 666 | if (scriptName) { 667 | await this.editStatementItem(scriptName); 668 | } 669 | 670 | break; 671 | } 672 | } 673 | } 674 | 675 | } 676 | 677 | exports.Monobank = Monobank; 678 | 679 | if (config.runsInApp && Script.name() === SCRIPT_NAME) { 680 | const monobank = new Monobank(); 681 | monobank.showSettings(); 682 | } -------------------------------------------------------------------------------- /build/tiny-charts.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: teal; icon-glyph: chart-line; 4 | 5 | /* 6 | * author: https://github.com/Nodman 7 | * small chart, for now supports only area chart 8 | */ 9 | 'use strict'; 10 | 11 | Object.defineProperty(exports, "__esModule", { 12 | value: true 13 | }); 14 | exports.TinyCharts = void 0; 15 | const SCRIPT_NAME = 'tiny-charts'; 16 | 17 | class TinyCharts { 18 | constructor(width, height) { 19 | this.width = width; 20 | this.height = height; 21 | } 22 | 23 | createContext() { 24 | this.context = new DrawContext(); 25 | this.context.size = new Size(this.width, this.height); 26 | this.context.opaque = false; 27 | this.context.respectScreenScale = true; 28 | this.context.setLineWidth(1); 29 | } 30 | 31 | normalizeData(data) { 32 | const maxValue = Math.max(...data); 33 | const normalized = data.map(value => value / (maxValue / 1e2)); 34 | return normalized; 35 | } 36 | 37 | getCoordinates(data, index) { 38 | const y = this.height - this.height * data[index] / 100; 39 | const x = index * (this.width / (data.length - 1)); 40 | return { 41 | x, 42 | y 43 | }; 44 | } 45 | 46 | setFillColor(color) { 47 | this.fillColor = color; 48 | } 49 | 50 | setStrokeColor(color) { 51 | this.strokeColor = color; 52 | } 53 | 54 | drawAreaChart(rawData) { 55 | this.createContext(); 56 | const data = this.normalizeData(rawData); 57 | const startingPoint = this.getCoordinates(data, 0); 58 | const path = new Path(); 59 | path.move(new Point(startingPoint.x, startingPoint.y)); 60 | 61 | for (let index = 0; index < data.length - 1; index++) { 62 | const point = this.getCoordinates(data, index); 63 | const nextPoint = this.getCoordinates(data, index + 1); 64 | const pointX = (point.x + nextPoint.x) / 2; 65 | const pointY = (point.y + nextPoint.y) / 2; 66 | const controlx1 = (pointX + point.x) / 2; 67 | const controlx2 = (pointX + nextPoint.x) / 2; 68 | path.addQuadCurve(new Point(pointX, pointY), new Point(controlx1, point.y)); 69 | path.addQuadCurve(new Point(nextPoint.x, nextPoint.y), new Point(controlx2, nextPoint.y)); 70 | } 71 | 72 | path.addLine(new Point(this.width, this.height)); 73 | path.addLine(new Point(0, this.height)); 74 | path.closeSubpath(); 75 | 76 | if (this.strokeColor) { 77 | this.context.setStrokeColor(this.strokeColor); 78 | this.context.addPath(path); 79 | this.context.strokePath(); 80 | } 81 | 82 | if (this.fillColor) { 83 | this.context.setFillColor(this.fillColor); 84 | this.context.addPath(path); 85 | this.context.fillPath(); 86 | } 87 | } 88 | 89 | getContext() { 90 | return this.context; 91 | } 92 | 93 | getImage() { 94 | return this.context.getImage(); 95 | } 96 | 97 | } 98 | 99 | exports.TinyCharts = TinyCharts; 100 | Script.complete(); -------------------------------------------------------------------------------- /build/tiny-dashboard.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: blue; icon-glyph: columns; 4 | 5 | /* 6 | * author: https://github.com/Nodman 7 | * small widget layout with graph 8 | */ 9 | 'use strict'; 10 | 11 | Object.defineProperty(exports, "__esModule", { 12 | value: true 13 | }); 14 | exports.PALETTES = void 0; 15 | exports.createWidget = createWidget; 16 | const { 17 | getDynamicGradient, 18 | getDynamicColor, 19 | parseWidgetParams, 20 | getDeviceAppearance 21 | } = importModule("./utils"); 22 | const { 23 | TinyCharts 24 | } = importModule("./tiny-charts"); 25 | const SCRIPT_NAME = 'tiny-dashboard'; 26 | const GAP = 10; 27 | const SYMBOL_SIZE = 16; 28 | const GRAPH_OPACITY = 0.3; 29 | const GRAPH_WIDTH = 510; 30 | const GRAPH_HEIGHT = 150; 31 | /* 32 | * most of the colors taken from https://uigradients.com/ 33 | */ 34 | 35 | const PALETTES = { 36 | // darker ? palettes 37 | seablue: { 38 | bgStart: '#2b5876', 39 | bgEnd: '#4e4376', 40 | primary: '#ece9e6', 41 | secondary: '#7d7795' 42 | }, 43 | cloud: { 44 | bgStart: '#141E30', 45 | bgEnd: '#243B55', 46 | primary: '#d0d0da', 47 | secondary: '#141E30' 48 | }, 49 | midnight: { 50 | bgStart: '#232526', 51 | bgEnd: '#414345', 52 | primary: '#ece9e6', 53 | secondary: '#232526' 54 | }, 55 | // lighter palettes, kinda. I am bad with colors 56 | royal: { 57 | bgStart: '#ece9e6', 58 | bgEnd: '#ffffff', 59 | primary: '#141E30', 60 | secondary: '#c2bbb5' 61 | }, 62 | dull: { 63 | bgStart: '#c9d6ff', 64 | bgEnd: '#e2e2e2', 65 | primary: '#000c40', 66 | secondary: '#99b1fe' 67 | }, 68 | anamnisar: { 69 | bgStart: '#9796f0', 70 | bgEnd: '#fbc7d4', 71 | primary: '#1D2B64', 72 | secondary: '#9796f0' 73 | }, 74 | ash: { 75 | bgStart: '#606c88', 76 | bgEnd: '#3f4c6b', 77 | primary: '#c9d6ff', 78 | secondary: '#c9d6ff' 79 | }, 80 | // other color presets 81 | pacific: { 82 | bgStart: '#0f3443', 83 | bgEnd: '#34e89e', 84 | primary: '#BDFFF3', 85 | secondary: '#0f3443' 86 | }, 87 | sin: { 88 | bgStart: '#93291E', 89 | bgEnd: '#ED213A', 90 | primary: '#340707', 91 | secondary: '#333333' 92 | }, 93 | sandblue: { 94 | bgStart: '#DECBA4', 95 | bgEnd: '#3E5151', 96 | primary: '#243737', 97 | secondary: '#3E5151' 98 | }, 99 | // nord colors taken from https://www.nordtheme.com 100 | nord: { 101 | bgStart: '#2E3440', 102 | bgEnd: '#2E3440', 103 | primary: '#81a1c1', 104 | accent: '#d8dee9', 105 | secondary: '#d8dee9' 106 | }, 107 | nordlight: { 108 | bgStart: '#d8dee9', 109 | bgEnd: '#d8dee9', 110 | primary: '#4c566a', 111 | accent: '#5e81ac', 112 | secondary: '#81a1c1' 113 | } 114 | }; 115 | exports.PALETTES = PALETTES; 116 | const TYPOGRAPHY = { 117 | title: 40, 118 | subtitle: 15, 119 | body: 10, 120 | caption: 8 121 | }; 122 | 123 | function getPalette(palette = {}) { 124 | const { 125 | light, 126 | dark 127 | } = parseWidgetParams(args.widgetParameter); 128 | return { 129 | light: PALETTES[palette.light ?? light] ?? PALETTES.dull, 130 | dark: PALETTES[palette.dark ?? dark] ?? PALETTES.sandblue 131 | }; 132 | } 133 | 134 | function createWidget(args, theme) { 135 | const { 136 | value, 137 | chartData, 138 | subtitle1, 139 | subtitle2, 140 | headerSymbol: headerSymbolProp, 141 | header, 142 | subValue 143 | } = args; 144 | const appearence = getDeviceAppearance(); 145 | const palette = getPalette(theme); 146 | const listWidget = new ListWidget(); 147 | const textColor = getDynamicColor(palette, 'primary'); 148 | const titleColor = getDynamicColor(palette, 'accent'); 149 | const gradient = getDynamicGradient(palette); 150 | const opacity = palette[appearence].bgStart === palette[appearence].bgEnd ? 1 : GRAPH_OPACITY; 151 | const fillColor = getDynamicColor(palette, 'secondary', opacity); 152 | listWidget.setPadding(GAP, 0, 0, 0); 153 | gradient.locations = [0.0, 1]; 154 | listWidget.backgroundGradient = gradient; // HEADER 155 | 156 | const headerStack = listWidget.addStack(); 157 | const headerText = headerStack.addText(header); 158 | headerStack.setPadding(0, GAP, 0, GAP); 159 | headerText.textColor = textColor; 160 | headerText.font = Font.regularSystemFont(TYPOGRAPHY.body); 161 | headerText.minimumScaleFactor = 0.50; 162 | 163 | if (headerSymbolProp) { 164 | const headerSymbol = SFSymbol.named(headerSymbolProp); 165 | headerSymbol.applyFont(Font.systemFont(SYMBOL_SIZE)); 166 | headerSymbol.applySemiboldWeight; 167 | headerStack.addSpacer(); 168 | const currencySymbolImg = headerStack.addImage(headerSymbol.image); 169 | currencySymbolImg.resizable = false; 170 | currencySymbolImg.tintColor = textColor; 171 | currencySymbolImg.imageSize = new Size(SYMBOL_SIZE, SYMBOL_SIZE); 172 | } 173 | 174 | listWidget.addSpacer(); // VALUE 175 | 176 | const valueStack = listWidget.addStack(); 177 | valueStack.setPadding(0, GAP, 1, GAP); 178 | valueStack.bottomAlignContent(); 179 | valueStack.spacing = 2; 180 | const valueText = valueStack.addText(value); 181 | valueText.textColor = titleColor; 182 | valueText.font = Font.boldSystemFont(TYPOGRAPHY.title); 183 | valueText.minimumScaleFactor = 0.50; 184 | 185 | if (subValue) { 186 | const subValueStack = valueStack.addStack(); 187 | const subValueText = subValueStack.addText(subValue); // hack to proper align text 188 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers 189 | 190 | subValueStack.setPadding(0, 0, 6, 0); 191 | subValueText.textColor = titleColor; 192 | subValueText.font = Font.boldSystemFont(TYPOGRAPHY.body); 193 | subValueText.minimumScaleFactor = 0.50; 194 | } // FOOTER 195 | 196 | 197 | const footerStack = listWidget.addStack(); 198 | const footerStackBottomPadding = (2 - [subtitle1, subtitle2].reduce((acc, item) => acc + (item ? 1 : 0), 0)) * GAP; 199 | footerStack.setPadding(0, GAP, footerStackBottomPadding, 0); 200 | footerStack.layoutVertically(); 201 | footerStack.spacing = 2; 202 | 203 | if (subtitle1) { 204 | const subtitle1Text = footerStack.addText(subtitle1); 205 | subtitle1Text.textColor = textColor; 206 | subtitle1Text.font = Font.lightSystemFont(TYPOGRAPHY.caption); 207 | } 208 | 209 | if (subtitle2) { 210 | const subtitle2Text = footerStack.addText(subtitle2); 211 | subtitle2Text.textColor = textColor; 212 | subtitle2Text.font = Font.lightSystemFont(TYPOGRAPHY.caption); 213 | } 214 | 215 | listWidget.addSpacer(GAP / 2); // GRAPH 216 | 217 | const graphStack = listWidget.addStack(); 218 | graphStack.setPadding(0, 0, 0, 0); 219 | graphStack.backgroundColor = Color.clear(); 220 | 221 | if (chartData) { 222 | const chart = new TinyCharts(GRAPH_WIDTH, GRAPH_HEIGHT); 223 | chart.setFillColor(fillColor); 224 | chart.drawAreaChart(chartData); 225 | graphStack.addImage(chart.getImage()); 226 | } else { 227 | listWidget.addSpacer(GAP); 228 | listWidget.addSpacer(); 229 | } 230 | 231 | return listWidget; 232 | } 233 | 234 | Script.complete(); -------------------------------------------------------------------------------- /build/update-code.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: red; icon-glyph: check-circle; 4 | 'use strict'; 5 | 6 | Object.defineProperty(exports, "__esModule", { 7 | value: true 8 | }); 9 | const SCRIPTS = ['utils', 'tiny-dashboard', 'tiny-charts', 'mono-monthly-small', 'update-code', 'monobank']; 10 | const URL = 'https://raw.githubusercontent.com/Nodman/scriptables/main/build'; // taken from Max Zeryck blur script 11 | 12 | const updateCode = async scriptName => { 13 | const alert = new Alert(); 14 | const moduleName = `${scriptName}.js`; 15 | const url = `${URL}/${moduleName}`; 16 | const path = [...module.filename.split('/').slice(0, -1), moduleName].join('/'); 17 | alert.title = `Update "${moduleName}" code?`; 18 | alert.message = 'This will overwrite any of your local changes!'; 19 | alert.addCancelAction('Nope'); 20 | alert.addDestructiveAction('Yesh'); 21 | const actionIndex = await alert.present(); 22 | 23 | if (actionIndex === -1) { 24 | return; 25 | } // Determine if the user is using iCloud. 26 | 27 | 28 | let files = FileManager.local(); 29 | const iCloudInUse = files.isFileStoredIniCloud(path); // If so, use an iCloud file manager. 30 | 31 | files = iCloudInUse ? FileManager.iCloud() : files; 32 | let message = ''; // Try to download the file. 33 | 34 | try { 35 | const req = new Request(url); 36 | const codeString = await req.loadString(); 37 | files.writeString(path, codeString); 38 | message = 'The code has been updated. If the script is open, close it for the change to take effect.'; 39 | } catch { 40 | message = 'The update failed. Please try again later.'; 41 | } 42 | 43 | const confirmAlert = new Alert(); 44 | confirmAlert.title = 'Update code'; 45 | confirmAlert.message = message; 46 | confirmAlert.addCancelAction('Ok'); 47 | await confirmAlert.present(); 48 | }; 49 | 50 | const selectScriptAlert = new Alert(); 51 | selectScriptAlert.title = 'Select script to update'; 52 | selectScriptAlert.addCancelAction('Cancel'); 53 | SCRIPTS.forEach(item => { 54 | selectScriptAlert.addAction(item); 55 | }); 56 | const actionIndex = await selectScriptAlert.presentSheet(); 57 | 58 | if (actionIndex !== -1) { 59 | await updateCode(SCRIPTS[actionIndex]); 60 | } -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-purple; icon-glyph: wrench; 4 | 5 | /* 6 | * author: https://github.com/Nodman 7 | * few common utils I use across scriptable widgets 8 | */ 9 | 'use strict'; 10 | 11 | Object.defineProperty(exports, "__esModule", { 12 | value: true 13 | }); 14 | exports.currencyFormatter = exports.createAlert = exports.Logger = void 0; 15 | exports.fetchJson = fetchJson; 16 | exports.sendNotification = exports.sendErrorNotification = exports.parseWidgetParams = exports.numberFormatterShort = exports.getDynamicGradient = exports.getDynamicColor = exports.getDeviceAppearance = exports.getDaysInMonth = void 0; 17 | const SCRIPT_NAME = 'utils'; 18 | const DEBUG = true; 19 | const ERROR_NOTIFICATION_ID = '_error'; 20 | 21 | class Logger { 22 | constructor(prefix) { 23 | this.prefix = prefix ?? ''; 24 | this.separator = prefix ? ': ' : ''; 25 | } 26 | 27 | print(method, message) { 28 | if (!DEBUG) { 29 | return; 30 | } // eslint-disable-next-line no-console 31 | 32 | 33 | console[method](`${this.prefix}${this.separator}${message}`); 34 | } 35 | 36 | log(message) { 37 | this.print('log', message); 38 | } 39 | 40 | warn(message) { 41 | this.print('warn', message); 42 | } 43 | 44 | error(message) { 45 | this.print('error', message); 46 | } 47 | 48 | } 49 | 50 | exports.Logger = Logger; 51 | const requestLogger = new Logger('Request'); 52 | 53 | async function fetchJson(url, headers) { 54 | try { 55 | const req = new Request(url); 56 | req.headers = headers ?? {}; 57 | requestLogger.log(url); 58 | return await req.loadJSON(); 59 | } catch (error) { 60 | requestLogger.error(JSON.stringify(error)); 61 | throw new Error(error); 62 | } 63 | } 64 | /** 65 | * Send notification 66 | * 67 | * @param {string} title Notification title 68 | * @param {string} message Notification body 69 | * @param {string} id Notification id 70 | * @param {*} userInfo Notification userInfo object 71 | * @param {Date} date Schedule date, if not provided - will be scheduled asap 72 | * @param {*} actions Array of actions, [[title, action]] 73 | * @param {string} scriptName Explicit scriptName 74 | * @param {string} openURL url to open when click on notification 75 | */ 76 | 77 | 78 | const sendNotification = ({ 79 | title, 80 | message, 81 | id = 'notification', 82 | userInfo, 83 | date, 84 | actions = [], 85 | scriptName, 86 | openURL 87 | }) => { 88 | const notification = new Notification(); 89 | notification.title = title; 90 | notification.identifier = `${Script.name()}${id}`; 91 | notification.threadIdentifier = `${Script.name()}${id}_thread`; 92 | 93 | if (userInfo) { 94 | notification.userInfo = { 95 | error: true 96 | }; 97 | } 98 | 99 | if (date) { 100 | notification.setTriggerDate(date); 101 | } 102 | 103 | if (openURL) { 104 | notification.openURL = openURL; 105 | } 106 | 107 | actions.forEach(action => { 108 | notification.addAction(...action); 109 | }); 110 | notification.scriptName = scriptName || Script.name(); 111 | notification.body = message; 112 | notification.schedule(); 113 | }; 114 | 115 | exports.sendNotification = sendNotification; 116 | 117 | const sendErrorNotification = (err, params) => { 118 | sendNotification({ ...params, 119 | title: `Something went wrong with ${Script.name()}`, 120 | id: ERROR_NOTIFICATION_ID, 121 | message: err.message 122 | }); 123 | }; 124 | 125 | exports.sendErrorNotification = sendErrorNotification; 126 | 127 | const createAlert = (title, message, cancelAction) => { 128 | const alert = new Alert(); 129 | alert.message = message; 130 | alert.title = title; 131 | 132 | if (cancelAction) { 133 | alert.addCancelAction(cancelAction); 134 | } 135 | 136 | return alert; 137 | }; // starts from 1 138 | 139 | 140 | exports.createAlert = createAlert; 141 | 142 | const getDaysInMonth = (year, month) => { 143 | return new Date(year, month, 0).getDate(); 144 | }; 145 | 146 | exports.getDaysInMonth = getDaysInMonth; 147 | 148 | const getDynamicColor = ({ 149 | light, 150 | dark 151 | }, key, opacity) => { 152 | const lightColor = light[key] ?? light.primary; 153 | const darkColor = dark[key] ?? dark.primary; 154 | return Color.dynamic(new Color(lightColor, opacity), new Color(darkColor, opacity)); 155 | }; 156 | 157 | exports.getDynamicColor = getDynamicColor; 158 | 159 | const getDynamicGradient = ({ 160 | dark, 161 | light 162 | }) => { 163 | const startColor = Color.dynamic(new Color(light.bgStart), new Color(dark.bgStart)); 164 | const endColor = Color.dynamic(new Color(light.bgEnd), new Color(dark.bgEnd)); 165 | const gradient = new LinearGradient(); 166 | gradient.colors = [startColor, endColor]; 167 | return gradient; 168 | }; 169 | 170 | exports.getDynamicGradient = getDynamicGradient; 171 | 172 | const parseWidgetParams = params => { 173 | if (typeof params !== 'string') { 174 | return {}; 175 | } 176 | 177 | return params.split(';').reduce((acc, item) => { 178 | const [key, value = ''] = item.split('='); 179 | return { ...acc, 180 | [key.trim()]: value.trim() ?? true 181 | }; 182 | }, {}); 183 | }; 184 | 185 | exports.parseWidgetParams = parseWidgetParams; 186 | const numberFormatterShort = new Intl.NumberFormat('ua', { 187 | notation: 'compact', 188 | compactDisplay: 'short', 189 | maximumFractionDigits: 1, 190 | minimumFractionDigits: 0 191 | }).format; 192 | exports.numberFormatterShort = numberFormatterShort; 193 | const currencyFormatter = new Intl.NumberFormat('uk-UA', { 194 | minimumFractionDigits: 2 195 | }).format; 196 | exports.currencyFormatter = currencyFormatter; 197 | 198 | const getDeviceAppearance = () => { 199 | return Device.isUsingDarkAppearance() ? 'dark' : 'light'; 200 | }; 201 | 202 | exports.getDeviceAppearance = getDeviceAppearance; 203 | Script.complete(); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mono-widgets", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/cli": { 8 | "version": "7.16.8", 9 | "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.16.8.tgz", 10 | "integrity": "sha512-FTKBbxyk5TclXOGmwYyqelqP5IF6hMxaeJskd85jbR5jBfYlwqgwAbJwnixi1ZBbTqKfFuAA95mdmUFeSRwyJA==", 11 | "dev": true, 12 | "requires": { 13 | "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", 14 | "chokidar": "^3.4.0", 15 | "commander": "^4.0.1", 16 | "convert-source-map": "^1.1.0", 17 | "fs-readdir-recursive": "^1.1.0", 18 | "glob": "^7.0.0", 19 | "make-dir": "^2.1.0", 20 | "slash": "^2.0.0", 21 | "source-map": "^0.5.0" 22 | } 23 | }, 24 | "@babel/code-frame": { 25 | "version": "7.16.7", 26 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", 27 | "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", 28 | "dev": true, 29 | "requires": { 30 | "@babel/highlight": "^7.16.7" 31 | } 32 | }, 33 | "@babel/compat-data": { 34 | "version": "7.16.8", 35 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", 36 | "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", 37 | "dev": true 38 | }, 39 | "@babel/core": { 40 | "version": "7.16.12", 41 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", 42 | "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", 43 | "dev": true, 44 | "requires": { 45 | "@babel/code-frame": "^7.16.7", 46 | "@babel/generator": "^7.16.8", 47 | "@babel/helper-compilation-targets": "^7.16.7", 48 | "@babel/helper-module-transforms": "^7.16.7", 49 | "@babel/helpers": "^7.16.7", 50 | "@babel/parser": "^7.16.12", 51 | "@babel/template": "^7.16.7", 52 | "@babel/traverse": "^7.16.10", 53 | "@babel/types": "^7.16.8", 54 | "convert-source-map": "^1.7.0", 55 | "debug": "^4.1.0", 56 | "gensync": "^1.0.0-beta.2", 57 | "json5": "^2.1.2", 58 | "semver": "^6.3.0", 59 | "source-map": "^0.5.0" 60 | }, 61 | "dependencies": { 62 | "semver": { 63 | "version": "6.3.0", 64 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 65 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 66 | "dev": true 67 | } 68 | } 69 | }, 70 | "@babel/generator": { 71 | "version": "7.16.8", 72 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", 73 | "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", 74 | "dev": true, 75 | "requires": { 76 | "@babel/types": "^7.16.8", 77 | "jsesc": "^2.5.1", 78 | "source-map": "^0.5.0" 79 | } 80 | }, 81 | "@babel/helper-annotate-as-pure": { 82 | "version": "7.16.7", 83 | "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", 84 | "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", 85 | "dev": true, 86 | "requires": { 87 | "@babel/types": "^7.16.7" 88 | } 89 | }, 90 | "@babel/helper-compilation-targets": { 91 | "version": "7.16.7", 92 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", 93 | "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", 94 | "dev": true, 95 | "requires": { 96 | "@babel/compat-data": "^7.16.4", 97 | "@babel/helper-validator-option": "^7.16.7", 98 | "browserslist": "^4.17.5", 99 | "semver": "^6.3.0" 100 | }, 101 | "dependencies": { 102 | "semver": { 103 | "version": "6.3.0", 104 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 105 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 106 | "dev": true 107 | } 108 | } 109 | }, 110 | "@babel/helper-create-class-features-plugin": { 111 | "version": "7.16.10", 112 | "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.10.tgz", 113 | "integrity": "sha512-wDeej0pu3WN/ffTxMNCPW5UCiOav8IcLRxSIyp/9+IF2xJUM9h/OYjg0IJLHaL6F8oU8kqMz9nc1vryXhMsgXg==", 114 | "dev": true, 115 | "requires": { 116 | "@babel/helper-annotate-as-pure": "^7.16.7", 117 | "@babel/helper-environment-visitor": "^7.16.7", 118 | "@babel/helper-function-name": "^7.16.7", 119 | "@babel/helper-member-expression-to-functions": "^7.16.7", 120 | "@babel/helper-optimise-call-expression": "^7.16.7", 121 | "@babel/helper-replace-supers": "^7.16.7", 122 | "@babel/helper-split-export-declaration": "^7.16.7" 123 | } 124 | }, 125 | "@babel/helper-environment-visitor": { 126 | "version": "7.16.7", 127 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", 128 | "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", 129 | "dev": true, 130 | "requires": { 131 | "@babel/types": "^7.16.7" 132 | } 133 | }, 134 | "@babel/helper-function-name": { 135 | "version": "7.16.7", 136 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", 137 | "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", 138 | "dev": true, 139 | "requires": { 140 | "@babel/helper-get-function-arity": "^7.16.7", 141 | "@babel/template": "^7.16.7", 142 | "@babel/types": "^7.16.7" 143 | } 144 | }, 145 | "@babel/helper-get-function-arity": { 146 | "version": "7.16.7", 147 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", 148 | "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", 149 | "dev": true, 150 | "requires": { 151 | "@babel/types": "^7.16.7" 152 | } 153 | }, 154 | "@babel/helper-hoist-variables": { 155 | "version": "7.16.7", 156 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", 157 | "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", 158 | "dev": true, 159 | "requires": { 160 | "@babel/types": "^7.16.7" 161 | } 162 | }, 163 | "@babel/helper-member-expression-to-functions": { 164 | "version": "7.16.7", 165 | "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", 166 | "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", 167 | "dev": true, 168 | "requires": { 169 | "@babel/types": "^7.16.7" 170 | } 171 | }, 172 | "@babel/helper-module-imports": { 173 | "version": "7.16.7", 174 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", 175 | "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", 176 | "dev": true, 177 | "requires": { 178 | "@babel/types": "^7.16.7" 179 | } 180 | }, 181 | "@babel/helper-module-transforms": { 182 | "version": "7.16.7", 183 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", 184 | "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", 185 | "dev": true, 186 | "requires": { 187 | "@babel/helper-environment-visitor": "^7.16.7", 188 | "@babel/helper-module-imports": "^7.16.7", 189 | "@babel/helper-simple-access": "^7.16.7", 190 | "@babel/helper-split-export-declaration": "^7.16.7", 191 | "@babel/helper-validator-identifier": "^7.16.7", 192 | "@babel/template": "^7.16.7", 193 | "@babel/traverse": "^7.16.7", 194 | "@babel/types": "^7.16.7" 195 | } 196 | }, 197 | "@babel/helper-optimise-call-expression": { 198 | "version": "7.16.7", 199 | "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", 200 | "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", 201 | "dev": true, 202 | "requires": { 203 | "@babel/types": "^7.16.7" 204 | } 205 | }, 206 | "@babel/helper-plugin-utils": { 207 | "version": "7.16.7", 208 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", 209 | "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", 210 | "dev": true 211 | }, 212 | "@babel/helper-replace-supers": { 213 | "version": "7.16.7", 214 | "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", 215 | "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", 216 | "dev": true, 217 | "requires": { 218 | "@babel/helper-environment-visitor": "^7.16.7", 219 | "@babel/helper-member-expression-to-functions": "^7.16.7", 220 | "@babel/helper-optimise-call-expression": "^7.16.7", 221 | "@babel/traverse": "^7.16.7", 222 | "@babel/types": "^7.16.7" 223 | } 224 | }, 225 | "@babel/helper-simple-access": { 226 | "version": "7.16.7", 227 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", 228 | "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", 229 | "dev": true, 230 | "requires": { 231 | "@babel/types": "^7.16.7" 232 | } 233 | }, 234 | "@babel/helper-split-export-declaration": { 235 | "version": "7.16.7", 236 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", 237 | "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", 238 | "dev": true, 239 | "requires": { 240 | "@babel/types": "^7.16.7" 241 | } 242 | }, 243 | "@babel/helper-validator-identifier": { 244 | "version": "7.16.7", 245 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", 246 | "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", 247 | "dev": true 248 | }, 249 | "@babel/helper-validator-option": { 250 | "version": "7.16.7", 251 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", 252 | "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", 253 | "dev": true 254 | }, 255 | "@babel/helpers": { 256 | "version": "7.16.7", 257 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", 258 | "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", 259 | "dev": true, 260 | "requires": { 261 | "@babel/template": "^7.16.7", 262 | "@babel/traverse": "^7.16.7", 263 | "@babel/types": "^7.16.7" 264 | } 265 | }, 266 | "@babel/highlight": { 267 | "version": "7.16.10", 268 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", 269 | "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", 270 | "dev": true, 271 | "requires": { 272 | "@babel/helper-validator-identifier": "^7.16.7", 273 | "chalk": "^2.0.0", 274 | "js-tokens": "^4.0.0" 275 | } 276 | }, 277 | "@babel/parser": { 278 | "version": "7.16.12", 279 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz", 280 | "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==", 281 | "dev": true 282 | }, 283 | "@babel/plugin-syntax-typescript": { 284 | "version": "7.16.7", 285 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", 286 | "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", 287 | "dev": true, 288 | "requires": { 289 | "@babel/helper-plugin-utils": "^7.16.7" 290 | } 291 | }, 292 | "@babel/plugin-transform-modules-commonjs": { 293 | "version": "7.16.8", 294 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", 295 | "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", 296 | "dev": true, 297 | "requires": { 298 | "@babel/helper-module-transforms": "^7.16.7", 299 | "@babel/helper-plugin-utils": "^7.16.7", 300 | "@babel/helper-simple-access": "^7.16.7", 301 | "babel-plugin-dynamic-import-node": "^2.3.3" 302 | } 303 | }, 304 | "@babel/plugin-transform-typescript": { 305 | "version": "7.16.8", 306 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz", 307 | "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==", 308 | "dev": true, 309 | "requires": { 310 | "@babel/helper-create-class-features-plugin": "^7.16.7", 311 | "@babel/helper-plugin-utils": "^7.16.7", 312 | "@babel/plugin-syntax-typescript": "^7.16.7" 313 | } 314 | }, 315 | "@babel/preset-typescript": { 316 | "version": "7.16.7", 317 | "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz", 318 | "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==", 319 | "dev": true, 320 | "requires": { 321 | "@babel/helper-plugin-utils": "^7.16.7", 322 | "@babel/helper-validator-option": "^7.16.7", 323 | "@babel/plugin-transform-typescript": "^7.16.7" 324 | } 325 | }, 326 | "@babel/template": { 327 | "version": "7.16.7", 328 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", 329 | "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", 330 | "dev": true, 331 | "requires": { 332 | "@babel/code-frame": "^7.16.7", 333 | "@babel/parser": "^7.16.7", 334 | "@babel/types": "^7.16.7" 335 | } 336 | }, 337 | "@babel/traverse": { 338 | "version": "7.16.10", 339 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz", 340 | "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==", 341 | "dev": true, 342 | "requires": { 343 | "@babel/code-frame": "^7.16.7", 344 | "@babel/generator": "^7.16.8", 345 | "@babel/helper-environment-visitor": "^7.16.7", 346 | "@babel/helper-function-name": "^7.16.7", 347 | "@babel/helper-hoist-variables": "^7.16.7", 348 | "@babel/helper-split-export-declaration": "^7.16.7", 349 | "@babel/parser": "^7.16.10", 350 | "@babel/types": "^7.16.8", 351 | "debug": "^4.1.0", 352 | "globals": "^11.1.0" 353 | } 354 | }, 355 | "@babel/types": { 356 | "version": "7.16.8", 357 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", 358 | "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", 359 | "dev": true, 360 | "requires": { 361 | "@babel/helper-validator-identifier": "^7.16.7", 362 | "to-fast-properties": "^2.0.0" 363 | } 364 | }, 365 | "@eslint/eslintrc": { 366 | "version": "1.0.5", 367 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz", 368 | "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==", 369 | "dev": true, 370 | "requires": { 371 | "ajv": "^6.12.4", 372 | "debug": "^4.3.2", 373 | "espree": "^9.2.0", 374 | "globals": "^13.9.0", 375 | "ignore": "^4.0.6", 376 | "import-fresh": "^3.2.1", 377 | "js-yaml": "^4.1.0", 378 | "minimatch": "^3.0.4", 379 | "strip-json-comments": "^3.1.1" 380 | }, 381 | "dependencies": { 382 | "globals": { 383 | "version": "13.12.0", 384 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", 385 | "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", 386 | "dev": true, 387 | "requires": { 388 | "type-fest": "^0.20.2" 389 | } 390 | }, 391 | "ignore": { 392 | "version": "4.0.6", 393 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 394 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 395 | "dev": true 396 | } 397 | } 398 | }, 399 | "@humanwhocodes/config-array": { 400 | "version": "0.9.3", 401 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", 402 | "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==", 403 | "dev": true, 404 | "requires": { 405 | "@humanwhocodes/object-schema": "^1.2.1", 406 | "debug": "^4.1.1", 407 | "minimatch": "^3.0.4" 408 | } 409 | }, 410 | "@humanwhocodes/object-schema": { 411 | "version": "1.2.1", 412 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", 413 | "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", 414 | "dev": true 415 | }, 416 | "@nicolo-ribaudo/chokidar-2": { 417 | "version": "2.1.8-no-fsevents.3", 418 | "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", 419 | "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", 420 | "dev": true, 421 | "optional": true 422 | }, 423 | "@nodelib/fs.scandir": { 424 | "version": "2.1.5", 425 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 426 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 427 | "dev": true, 428 | "requires": { 429 | "@nodelib/fs.stat": "2.0.5", 430 | "run-parallel": "^1.1.9" 431 | } 432 | }, 433 | "@nodelib/fs.stat": { 434 | "version": "2.0.5", 435 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 436 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 437 | "dev": true 438 | }, 439 | "@nodelib/fs.walk": { 440 | "version": "1.2.8", 441 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 442 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 443 | "dev": true, 444 | "requires": { 445 | "@nodelib/fs.scandir": "2.1.5", 446 | "fastq": "^1.6.0" 447 | } 448 | }, 449 | "@scriptable-ios/eslint-config": { 450 | "version": "1.6.4", 451 | "resolved": "https://registry.npmjs.org/@scriptable-ios/eslint-config/-/eslint-config-1.6.4.tgz", 452 | "integrity": "sha512-7cCSZvL/WUyeBNsjVjYrN5Uc+tB17goEm3z2e8TUW3oWVSJ8Z3pUnllwY0HOkHaOQiZyxhuRNDLbQYimOlwh/Q==", 453 | "dev": true 454 | }, 455 | "@types/json-schema": { 456 | "version": "7.0.9", 457 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", 458 | "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", 459 | "dev": true 460 | }, 461 | "@types/scriptable-ios": { 462 | "version": "1.6.5", 463 | "resolved": "https://registry.npmjs.org/@types/scriptable-ios/-/scriptable-ios-1.6.5.tgz", 464 | "integrity": "sha512-8HvyHVIwmCdXt509c7xIvcccVnIstRMJ9DXVU5SEVjF3EJl3T9/BEF4lNjrJ+uyZPLIOacLeJ2nMgohl8NLk8A==", 465 | "dev": true 466 | }, 467 | "@typescript-eslint/eslint-plugin": { 468 | "version": "5.11.0", 469 | "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.11.0.tgz", 470 | "integrity": "sha512-HJh33bgzXe6jGRocOj4FmefD7hRY4itgjzOrSs3JPrTNXsX7j5+nQPciAUj/1nZtwo2kAc3C75jZO+T23gzSGw==", 471 | "dev": true, 472 | "requires": { 473 | "@typescript-eslint/scope-manager": "5.11.0", 474 | "@typescript-eslint/type-utils": "5.11.0", 475 | "@typescript-eslint/utils": "5.11.0", 476 | "debug": "^4.3.2", 477 | "functional-red-black-tree": "^1.0.1", 478 | "ignore": "^5.1.8", 479 | "regexpp": "^3.2.0", 480 | "semver": "^7.3.5", 481 | "tsutils": "^3.21.0" 482 | }, 483 | "dependencies": { 484 | "semver": { 485 | "version": "7.3.5", 486 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 487 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 488 | "dev": true, 489 | "requires": { 490 | "lru-cache": "^6.0.0" 491 | } 492 | } 493 | } 494 | }, 495 | "@typescript-eslint/parser": { 496 | "version": "5.11.0", 497 | "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.11.0.tgz", 498 | "integrity": "sha512-x0DCjetHZYBRovJdr3U0zG9OOdNXUaFLJ82ehr1AlkArljJuwEsgnud+Q7umlGDFLFrs8tU8ybQDFocp/eX8mQ==", 499 | "dev": true, 500 | "requires": { 501 | "@typescript-eslint/scope-manager": "5.11.0", 502 | "@typescript-eslint/types": "5.11.0", 503 | "@typescript-eslint/typescript-estree": "5.11.0", 504 | "debug": "^4.3.2" 505 | } 506 | }, 507 | "@typescript-eslint/scope-manager": { 508 | "version": "5.11.0", 509 | "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.11.0.tgz", 510 | "integrity": "sha512-z+K4LlahDFVMww20t/0zcA7gq/NgOawaLuxgqGRVKS0PiZlCTIUtX0EJbC0BK1JtR4CelmkPK67zuCgpdlF4EA==", 511 | "dev": true, 512 | "requires": { 513 | "@typescript-eslint/types": "5.11.0", 514 | "@typescript-eslint/visitor-keys": "5.11.0" 515 | } 516 | }, 517 | "@typescript-eslint/type-utils": { 518 | "version": "5.11.0", 519 | "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.11.0.tgz", 520 | "integrity": "sha512-wDqdsYO6ofLaD4DsGZ0jGwxp4HrzD2YKulpEZXmgN3xo4BHJwf7kq49JTRpV0Gx6bxkSUmc9s0EIK1xPbFFpIA==", 521 | "dev": true, 522 | "requires": { 523 | "@typescript-eslint/utils": "5.11.0", 524 | "debug": "^4.3.2", 525 | "tsutils": "^3.21.0" 526 | } 527 | }, 528 | "@typescript-eslint/types": { 529 | "version": "5.11.0", 530 | "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.11.0.tgz", 531 | "integrity": "sha512-cxgBFGSRCoBEhvSVLkKw39+kMzUKHlJGVwwMbPcTZX3qEhuXhrjwaZXWMxVfxDgyMm+b5Q5b29Llo2yow8Y7xQ==", 532 | "dev": true 533 | }, 534 | "@typescript-eslint/typescript-estree": { 535 | "version": "5.11.0", 536 | "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.11.0.tgz", 537 | "integrity": "sha512-yVH9hKIv3ZN3lw8m/Jy5I4oXO4ZBMqijcXCdA4mY8ull6TPTAoQnKKrcZ0HDXg7Bsl0Unwwx7jcXMuNZc0m4lg==", 538 | "dev": true, 539 | "requires": { 540 | "@typescript-eslint/types": "5.11.0", 541 | "@typescript-eslint/visitor-keys": "5.11.0", 542 | "debug": "^4.3.2", 543 | "globby": "^11.0.4", 544 | "is-glob": "^4.0.3", 545 | "semver": "^7.3.5", 546 | "tsutils": "^3.21.0" 547 | }, 548 | "dependencies": { 549 | "semver": { 550 | "version": "7.3.5", 551 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 552 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 553 | "dev": true, 554 | "requires": { 555 | "lru-cache": "^6.0.0" 556 | } 557 | } 558 | } 559 | }, 560 | "@typescript-eslint/utils": { 561 | "version": "5.11.0", 562 | "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.11.0.tgz", 563 | "integrity": "sha512-g2I480tFE1iYRDyMhxPAtLQ9HAn0jjBtipgTCZmd9I9s11OV8CTsG+YfFciuNDcHqm4csbAgC2aVZCHzLxMSUw==", 564 | "dev": true, 565 | "requires": { 566 | "@types/json-schema": "^7.0.9", 567 | "@typescript-eslint/scope-manager": "5.11.0", 568 | "@typescript-eslint/types": "5.11.0", 569 | "@typescript-eslint/typescript-estree": "5.11.0", 570 | "eslint-scope": "^5.1.1", 571 | "eslint-utils": "^3.0.0" 572 | }, 573 | "dependencies": { 574 | "eslint-scope": { 575 | "version": "5.1.1", 576 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", 577 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", 578 | "dev": true, 579 | "requires": { 580 | "esrecurse": "^4.3.0", 581 | "estraverse": "^4.1.1" 582 | } 583 | }, 584 | "estraverse": { 585 | "version": "4.3.0", 586 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 587 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", 588 | "dev": true 589 | } 590 | } 591 | }, 592 | "@typescript-eslint/visitor-keys": { 593 | "version": "5.11.0", 594 | "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.11.0.tgz", 595 | "integrity": "sha512-E8w/vJReMGuloGxJDkpPlGwhxocxOpSVgSvjiLO5IxZPmxZF30weOeJYyPSEACwM+X4NziYS9q+WkN/2DHYQwA==", 596 | "dev": true, 597 | "requires": { 598 | "@typescript-eslint/types": "5.11.0", 599 | "eslint-visitor-keys": "^3.0.0" 600 | } 601 | }, 602 | "acorn": { 603 | "version": "8.7.0", 604 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", 605 | "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", 606 | "dev": true 607 | }, 608 | "acorn-jsx": { 609 | "version": "5.3.2", 610 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 611 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 612 | "dev": true 613 | }, 614 | "ajv": { 615 | "version": "6.12.6", 616 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 617 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 618 | "dev": true, 619 | "requires": { 620 | "fast-deep-equal": "^3.1.1", 621 | "fast-json-stable-stringify": "^2.0.0", 622 | "json-schema-traverse": "^0.4.1", 623 | "uri-js": "^4.2.2" 624 | } 625 | }, 626 | "ansi-regex": { 627 | "version": "5.0.1", 628 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 629 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 630 | "dev": true 631 | }, 632 | "ansi-styles": { 633 | "version": "3.2.1", 634 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 635 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 636 | "dev": true, 637 | "requires": { 638 | "color-convert": "^1.9.0" 639 | } 640 | }, 641 | "anymatch": { 642 | "version": "3.1.2", 643 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 644 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 645 | "dev": true, 646 | "optional": true, 647 | "requires": { 648 | "normalize-path": "^3.0.0", 649 | "picomatch": "^2.0.4" 650 | } 651 | }, 652 | "argparse": { 653 | "version": "2.0.1", 654 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 655 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 656 | "dev": true 657 | }, 658 | "array-union": { 659 | "version": "2.1.0", 660 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", 661 | "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", 662 | "dev": true 663 | }, 664 | "babel-plugin-dynamic-import-node": { 665 | "version": "2.3.3", 666 | "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", 667 | "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", 668 | "dev": true, 669 | "requires": { 670 | "object.assign": "^4.1.0" 671 | } 672 | }, 673 | "balanced-match": { 674 | "version": "1.0.2", 675 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 676 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 677 | "dev": true 678 | }, 679 | "binary-extensions": { 680 | "version": "2.2.0", 681 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 682 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 683 | "dev": true, 684 | "optional": true 685 | }, 686 | "brace-expansion": { 687 | "version": "1.1.11", 688 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 689 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 690 | "dev": true, 691 | "requires": { 692 | "balanced-match": "^1.0.0", 693 | "concat-map": "0.0.1" 694 | } 695 | }, 696 | "braces": { 697 | "version": "3.0.2", 698 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 699 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 700 | "dev": true, 701 | "requires": { 702 | "fill-range": "^7.0.1" 703 | } 704 | }, 705 | "browserslist": { 706 | "version": "4.19.1", 707 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", 708 | "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", 709 | "dev": true, 710 | "requires": { 711 | "caniuse-lite": "^1.0.30001286", 712 | "electron-to-chromium": "^1.4.17", 713 | "escalade": "^3.1.1", 714 | "node-releases": "^2.0.1", 715 | "picocolors": "^1.0.0" 716 | } 717 | }, 718 | "call-bind": { 719 | "version": "1.0.2", 720 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 721 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 722 | "dev": true, 723 | "requires": { 724 | "function-bind": "^1.1.1", 725 | "get-intrinsic": "^1.0.2" 726 | } 727 | }, 728 | "callsites": { 729 | "version": "3.1.0", 730 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 731 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 732 | "dev": true 733 | }, 734 | "caniuse-lite": { 735 | "version": "1.0.30001303", 736 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001303.tgz", 737 | "integrity": "sha512-/Mqc1oESndUNszJP0kx0UaQU9kEv9nNtJ7Kn8AdA0mNnH8eR1cj0kG+NbNuC1Wq/b21eA8prhKRA3bbkjONegQ==", 738 | "dev": true 739 | }, 740 | "chalk": { 741 | "version": "2.4.2", 742 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 743 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 744 | "dev": true, 745 | "requires": { 746 | "ansi-styles": "^3.2.1", 747 | "escape-string-regexp": "^1.0.5", 748 | "supports-color": "^5.3.0" 749 | } 750 | }, 751 | "chokidar": { 752 | "version": "3.5.3", 753 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 754 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 755 | "dev": true, 756 | "optional": true, 757 | "requires": { 758 | "anymatch": "~3.1.2", 759 | "braces": "~3.0.2", 760 | "fsevents": "~2.3.2", 761 | "glob-parent": "~5.1.2", 762 | "is-binary-path": "~2.1.0", 763 | "is-glob": "~4.0.1", 764 | "normalize-path": "~3.0.0", 765 | "readdirp": "~3.6.0" 766 | } 767 | }, 768 | "color-convert": { 769 | "version": "1.9.3", 770 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 771 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 772 | "dev": true, 773 | "requires": { 774 | "color-name": "1.1.3" 775 | } 776 | }, 777 | "color-name": { 778 | "version": "1.1.3", 779 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 780 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 781 | "dev": true 782 | }, 783 | "commander": { 784 | "version": "4.1.1", 785 | "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", 786 | "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", 787 | "dev": true 788 | }, 789 | "concat-map": { 790 | "version": "0.0.1", 791 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 792 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 793 | "dev": true 794 | }, 795 | "convert-source-map": { 796 | "version": "1.8.0", 797 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", 798 | "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", 799 | "dev": true, 800 | "requires": { 801 | "safe-buffer": "~5.1.1" 802 | } 803 | }, 804 | "cross-spawn": { 805 | "version": "7.0.3", 806 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 807 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 808 | "dev": true, 809 | "requires": { 810 | "path-key": "^3.1.0", 811 | "shebang-command": "^2.0.0", 812 | "which": "^2.0.1" 813 | } 814 | }, 815 | "debug": { 816 | "version": "4.3.3", 817 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 818 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 819 | "dev": true, 820 | "requires": { 821 | "ms": "2.1.2" 822 | } 823 | }, 824 | "deep-is": { 825 | "version": "0.1.4", 826 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 827 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 828 | "dev": true 829 | }, 830 | "define-properties": { 831 | "version": "1.1.3", 832 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 833 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 834 | "dev": true, 835 | "requires": { 836 | "object-keys": "^1.0.12" 837 | } 838 | }, 839 | "dir-glob": { 840 | "version": "3.0.1", 841 | "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", 842 | "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", 843 | "dev": true, 844 | "requires": { 845 | "path-type": "^4.0.0" 846 | } 847 | }, 848 | "doctrine": { 849 | "version": "3.0.0", 850 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 851 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 852 | "dev": true, 853 | "requires": { 854 | "esutils": "^2.0.2" 855 | } 856 | }, 857 | "electron-to-chromium": { 858 | "version": "1.4.56", 859 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.56.tgz", 860 | "integrity": "sha512-0k/S0FQqRRpJbX7YUjwCcLZ8D42RqGKtaiq90adXBOYgTIWwLA/g3toO8k9yEpqU8iC4QyaWYYWSTBIna8WV4g==", 861 | "dev": true 862 | }, 863 | "escalade": { 864 | "version": "3.1.1", 865 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 866 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 867 | "dev": true 868 | }, 869 | "escape-string-regexp": { 870 | "version": "1.0.5", 871 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 872 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 873 | "dev": true 874 | }, 875 | "eslint": { 876 | "version": "8.8.0", 877 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.8.0.tgz", 878 | "integrity": "sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==", 879 | "dev": true, 880 | "requires": { 881 | "@eslint/eslintrc": "^1.0.5", 882 | "@humanwhocodes/config-array": "^0.9.2", 883 | "ajv": "^6.10.0", 884 | "chalk": "^4.0.0", 885 | "cross-spawn": "^7.0.2", 886 | "debug": "^4.3.2", 887 | "doctrine": "^3.0.0", 888 | "escape-string-regexp": "^4.0.0", 889 | "eslint-scope": "^7.1.0", 890 | "eslint-utils": "^3.0.0", 891 | "eslint-visitor-keys": "^3.2.0", 892 | "espree": "^9.3.0", 893 | "esquery": "^1.4.0", 894 | "esutils": "^2.0.2", 895 | "fast-deep-equal": "^3.1.3", 896 | "file-entry-cache": "^6.0.1", 897 | "functional-red-black-tree": "^1.0.1", 898 | "glob-parent": "^6.0.1", 899 | "globals": "^13.6.0", 900 | "ignore": "^5.2.0", 901 | "import-fresh": "^3.0.0", 902 | "imurmurhash": "^0.1.4", 903 | "is-glob": "^4.0.0", 904 | "js-yaml": "^4.1.0", 905 | "json-stable-stringify-without-jsonify": "^1.0.1", 906 | "levn": "^0.4.1", 907 | "lodash.merge": "^4.6.2", 908 | "minimatch": "^3.0.4", 909 | "natural-compare": "^1.4.0", 910 | "optionator": "^0.9.1", 911 | "regexpp": "^3.2.0", 912 | "strip-ansi": "^6.0.1", 913 | "strip-json-comments": "^3.1.0", 914 | "text-table": "^0.2.0", 915 | "v8-compile-cache": "^2.0.3" 916 | }, 917 | "dependencies": { 918 | "ansi-styles": { 919 | "version": "4.3.0", 920 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 921 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 922 | "dev": true, 923 | "requires": { 924 | "color-convert": "^2.0.1" 925 | } 926 | }, 927 | "chalk": { 928 | "version": "4.1.2", 929 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 930 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 931 | "dev": true, 932 | "requires": { 933 | "ansi-styles": "^4.1.0", 934 | "supports-color": "^7.1.0" 935 | } 936 | }, 937 | "color-convert": { 938 | "version": "2.0.1", 939 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 940 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 941 | "dev": true, 942 | "requires": { 943 | "color-name": "~1.1.4" 944 | } 945 | }, 946 | "color-name": { 947 | "version": "1.1.4", 948 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 949 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 950 | "dev": true 951 | }, 952 | "escape-string-regexp": { 953 | "version": "4.0.0", 954 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 955 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 956 | "dev": true 957 | }, 958 | "glob-parent": { 959 | "version": "6.0.2", 960 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 961 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 962 | "dev": true, 963 | "requires": { 964 | "is-glob": "^4.0.3" 965 | } 966 | }, 967 | "globals": { 968 | "version": "13.12.0", 969 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", 970 | "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", 971 | "dev": true, 972 | "requires": { 973 | "type-fest": "^0.20.2" 974 | } 975 | }, 976 | "has-flag": { 977 | "version": "4.0.0", 978 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 979 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 980 | "dev": true 981 | }, 982 | "supports-color": { 983 | "version": "7.2.0", 984 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 985 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 986 | "dev": true, 987 | "requires": { 988 | "has-flag": "^4.0.0" 989 | } 990 | } 991 | } 992 | }, 993 | "eslint-scope": { 994 | "version": "7.1.0", 995 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", 996 | "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", 997 | "dev": true, 998 | "requires": { 999 | "esrecurse": "^4.3.0", 1000 | "estraverse": "^5.2.0" 1001 | } 1002 | }, 1003 | "eslint-utils": { 1004 | "version": "3.0.0", 1005 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", 1006 | "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", 1007 | "dev": true, 1008 | "requires": { 1009 | "eslint-visitor-keys": "^2.0.0" 1010 | }, 1011 | "dependencies": { 1012 | "eslint-visitor-keys": { 1013 | "version": "2.1.0", 1014 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", 1015 | "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", 1016 | "dev": true 1017 | } 1018 | } 1019 | }, 1020 | "eslint-visitor-keys": { 1021 | "version": "3.2.0", 1022 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz", 1023 | "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==", 1024 | "dev": true 1025 | }, 1026 | "espree": { 1027 | "version": "9.3.0", 1028 | "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", 1029 | "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==", 1030 | "dev": true, 1031 | "requires": { 1032 | "acorn": "^8.7.0", 1033 | "acorn-jsx": "^5.3.1", 1034 | "eslint-visitor-keys": "^3.1.0" 1035 | } 1036 | }, 1037 | "esquery": { 1038 | "version": "1.4.0", 1039 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", 1040 | "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", 1041 | "dev": true, 1042 | "requires": { 1043 | "estraverse": "^5.1.0" 1044 | } 1045 | }, 1046 | "esrecurse": { 1047 | "version": "4.3.0", 1048 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 1049 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 1050 | "dev": true, 1051 | "requires": { 1052 | "estraverse": "^5.2.0" 1053 | } 1054 | }, 1055 | "estraverse": { 1056 | "version": "5.3.0", 1057 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 1058 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 1059 | "dev": true 1060 | }, 1061 | "esutils": { 1062 | "version": "2.0.3", 1063 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 1064 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 1065 | "dev": true 1066 | }, 1067 | "fast-deep-equal": { 1068 | "version": "3.1.3", 1069 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 1070 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 1071 | "dev": true 1072 | }, 1073 | "fast-glob": { 1074 | "version": "3.2.11", 1075 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", 1076 | "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", 1077 | "dev": true, 1078 | "requires": { 1079 | "@nodelib/fs.stat": "^2.0.2", 1080 | "@nodelib/fs.walk": "^1.2.3", 1081 | "glob-parent": "^5.1.2", 1082 | "merge2": "^1.3.0", 1083 | "micromatch": "^4.0.4" 1084 | } 1085 | }, 1086 | "fast-json-stable-stringify": { 1087 | "version": "2.1.0", 1088 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 1089 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 1090 | "dev": true 1091 | }, 1092 | "fast-levenshtein": { 1093 | "version": "2.0.6", 1094 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 1095 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 1096 | "dev": true 1097 | }, 1098 | "fastq": { 1099 | "version": "1.13.0", 1100 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", 1101 | "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", 1102 | "dev": true, 1103 | "requires": { 1104 | "reusify": "^1.0.4" 1105 | } 1106 | }, 1107 | "file-entry-cache": { 1108 | "version": "6.0.1", 1109 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 1110 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 1111 | "dev": true, 1112 | "requires": { 1113 | "flat-cache": "^3.0.4" 1114 | } 1115 | }, 1116 | "fill-range": { 1117 | "version": "7.0.1", 1118 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 1119 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 1120 | "dev": true, 1121 | "requires": { 1122 | "to-regex-range": "^5.0.1" 1123 | } 1124 | }, 1125 | "flat-cache": { 1126 | "version": "3.0.4", 1127 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", 1128 | "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", 1129 | "dev": true, 1130 | "requires": { 1131 | "flatted": "^3.1.0", 1132 | "rimraf": "^3.0.2" 1133 | } 1134 | }, 1135 | "flatted": { 1136 | "version": "3.2.5", 1137 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", 1138 | "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", 1139 | "dev": true 1140 | }, 1141 | "fs-readdir-recursive": { 1142 | "version": "1.1.0", 1143 | "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", 1144 | "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", 1145 | "dev": true 1146 | }, 1147 | "fs.realpath": { 1148 | "version": "1.0.0", 1149 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1150 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 1151 | "dev": true 1152 | }, 1153 | "fsevents": { 1154 | "version": "2.3.2", 1155 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 1156 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 1157 | "dev": true, 1158 | "optional": true 1159 | }, 1160 | "function-bind": { 1161 | "version": "1.1.1", 1162 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 1163 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 1164 | "dev": true 1165 | }, 1166 | "functional-red-black-tree": { 1167 | "version": "1.0.1", 1168 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 1169 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 1170 | "dev": true 1171 | }, 1172 | "gensync": { 1173 | "version": "1.0.0-beta.2", 1174 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 1175 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 1176 | "dev": true 1177 | }, 1178 | "get-intrinsic": { 1179 | "version": "1.1.1", 1180 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 1181 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 1182 | "dev": true, 1183 | "requires": { 1184 | "function-bind": "^1.1.1", 1185 | "has": "^1.0.3", 1186 | "has-symbols": "^1.0.1" 1187 | } 1188 | }, 1189 | "glob": { 1190 | "version": "7.2.0", 1191 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", 1192 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", 1193 | "dev": true, 1194 | "requires": { 1195 | "fs.realpath": "^1.0.0", 1196 | "inflight": "^1.0.4", 1197 | "inherits": "2", 1198 | "minimatch": "^3.0.4", 1199 | "once": "^1.3.0", 1200 | "path-is-absolute": "^1.0.0" 1201 | } 1202 | }, 1203 | "glob-parent": { 1204 | "version": "5.1.2", 1205 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1206 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1207 | "dev": true, 1208 | "requires": { 1209 | "is-glob": "^4.0.1" 1210 | } 1211 | }, 1212 | "globals": { 1213 | "version": "11.12.0", 1214 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 1215 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 1216 | "dev": true 1217 | }, 1218 | "globby": { 1219 | "version": "11.1.0", 1220 | "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", 1221 | "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", 1222 | "dev": true, 1223 | "requires": { 1224 | "array-union": "^2.1.0", 1225 | "dir-glob": "^3.0.1", 1226 | "fast-glob": "^3.2.9", 1227 | "ignore": "^5.2.0", 1228 | "merge2": "^1.4.1", 1229 | "slash": "^3.0.0" 1230 | }, 1231 | "dependencies": { 1232 | "slash": { 1233 | "version": "3.0.0", 1234 | "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", 1235 | "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", 1236 | "dev": true 1237 | } 1238 | } 1239 | }, 1240 | "has": { 1241 | "version": "1.0.3", 1242 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1243 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1244 | "dev": true, 1245 | "requires": { 1246 | "function-bind": "^1.1.1" 1247 | } 1248 | }, 1249 | "has-flag": { 1250 | "version": "3.0.0", 1251 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1252 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 1253 | "dev": true 1254 | }, 1255 | "has-symbols": { 1256 | "version": "1.0.2", 1257 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 1258 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", 1259 | "dev": true 1260 | }, 1261 | "ignore": { 1262 | "version": "5.2.0", 1263 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", 1264 | "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", 1265 | "dev": true 1266 | }, 1267 | "import-fresh": { 1268 | "version": "3.3.0", 1269 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 1270 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 1271 | "dev": true, 1272 | "requires": { 1273 | "parent-module": "^1.0.0", 1274 | "resolve-from": "^4.0.0" 1275 | } 1276 | }, 1277 | "imurmurhash": { 1278 | "version": "0.1.4", 1279 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1280 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 1281 | "dev": true 1282 | }, 1283 | "inflight": { 1284 | "version": "1.0.6", 1285 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1286 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 1287 | "dev": true, 1288 | "requires": { 1289 | "once": "^1.3.0", 1290 | "wrappy": "1" 1291 | } 1292 | }, 1293 | "inherits": { 1294 | "version": "2.0.4", 1295 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1296 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1297 | "dev": true 1298 | }, 1299 | "is-binary-path": { 1300 | "version": "2.1.0", 1301 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1302 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1303 | "dev": true, 1304 | "optional": true, 1305 | "requires": { 1306 | "binary-extensions": "^2.0.0" 1307 | } 1308 | }, 1309 | "is-extglob": { 1310 | "version": "2.1.1", 1311 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1312 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 1313 | "dev": true 1314 | }, 1315 | "is-glob": { 1316 | "version": "4.0.3", 1317 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1318 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1319 | "dev": true, 1320 | "requires": { 1321 | "is-extglob": "^2.1.1" 1322 | } 1323 | }, 1324 | "is-number": { 1325 | "version": "7.0.0", 1326 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1327 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1328 | "dev": true 1329 | }, 1330 | "isexe": { 1331 | "version": "2.0.0", 1332 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1333 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 1334 | "dev": true 1335 | }, 1336 | "js-tokens": { 1337 | "version": "4.0.0", 1338 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1339 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1340 | "dev": true 1341 | }, 1342 | "js-yaml": { 1343 | "version": "4.1.0", 1344 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1345 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1346 | "dev": true, 1347 | "requires": { 1348 | "argparse": "^2.0.1" 1349 | } 1350 | }, 1351 | "jsesc": { 1352 | "version": "2.5.2", 1353 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 1354 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", 1355 | "dev": true 1356 | }, 1357 | "json-schema-traverse": { 1358 | "version": "0.4.1", 1359 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1360 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1361 | "dev": true 1362 | }, 1363 | "json-stable-stringify-without-jsonify": { 1364 | "version": "1.0.1", 1365 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1366 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", 1367 | "dev": true 1368 | }, 1369 | "json5": { 1370 | "version": "2.2.0", 1371 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", 1372 | "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", 1373 | "dev": true, 1374 | "requires": { 1375 | "minimist": "^1.2.5" 1376 | } 1377 | }, 1378 | "levn": { 1379 | "version": "0.4.1", 1380 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 1381 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 1382 | "dev": true, 1383 | "requires": { 1384 | "prelude-ls": "^1.2.1", 1385 | "type-check": "~0.4.0" 1386 | } 1387 | }, 1388 | "lodash.merge": { 1389 | "version": "4.6.2", 1390 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 1391 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 1392 | "dev": true 1393 | }, 1394 | "lru-cache": { 1395 | "version": "6.0.0", 1396 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1397 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1398 | "dev": true, 1399 | "requires": { 1400 | "yallist": "^4.0.0" 1401 | } 1402 | }, 1403 | "make-dir": { 1404 | "version": "2.1.0", 1405 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", 1406 | "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", 1407 | "dev": true, 1408 | "requires": { 1409 | "pify": "^4.0.1", 1410 | "semver": "^5.6.0" 1411 | } 1412 | }, 1413 | "merge2": { 1414 | "version": "1.4.1", 1415 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1416 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1417 | "dev": true 1418 | }, 1419 | "micromatch": { 1420 | "version": "4.0.4", 1421 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", 1422 | "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", 1423 | "dev": true, 1424 | "requires": { 1425 | "braces": "^3.0.1", 1426 | "picomatch": "^2.2.3" 1427 | } 1428 | }, 1429 | "minimatch": { 1430 | "version": "3.0.4", 1431 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1432 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1433 | "dev": true, 1434 | "requires": { 1435 | "brace-expansion": "^1.1.7" 1436 | } 1437 | }, 1438 | "minimist": { 1439 | "version": "1.2.5", 1440 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 1441 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 1442 | "dev": true 1443 | }, 1444 | "ms": { 1445 | "version": "2.1.2", 1446 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1447 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1448 | "dev": true 1449 | }, 1450 | "natural-compare": { 1451 | "version": "1.4.0", 1452 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1453 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 1454 | "dev": true 1455 | }, 1456 | "node-releases": { 1457 | "version": "2.0.1", 1458 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", 1459 | "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", 1460 | "dev": true 1461 | }, 1462 | "normalize-path": { 1463 | "version": "3.0.0", 1464 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1465 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1466 | "dev": true, 1467 | "optional": true 1468 | }, 1469 | "object-keys": { 1470 | "version": "1.1.1", 1471 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 1472 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 1473 | "dev": true 1474 | }, 1475 | "object.assign": { 1476 | "version": "4.1.2", 1477 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", 1478 | "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", 1479 | "dev": true, 1480 | "requires": { 1481 | "call-bind": "^1.0.0", 1482 | "define-properties": "^1.1.3", 1483 | "has-symbols": "^1.0.1", 1484 | "object-keys": "^1.1.1" 1485 | } 1486 | }, 1487 | "once": { 1488 | "version": "1.4.0", 1489 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1490 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1491 | "dev": true, 1492 | "requires": { 1493 | "wrappy": "1" 1494 | } 1495 | }, 1496 | "optionator": { 1497 | "version": "0.9.1", 1498 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", 1499 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", 1500 | "dev": true, 1501 | "requires": { 1502 | "deep-is": "^0.1.3", 1503 | "fast-levenshtein": "^2.0.6", 1504 | "levn": "^0.4.1", 1505 | "prelude-ls": "^1.2.1", 1506 | "type-check": "^0.4.0", 1507 | "word-wrap": "^1.2.3" 1508 | } 1509 | }, 1510 | "parent-module": { 1511 | "version": "1.0.1", 1512 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1513 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1514 | "dev": true, 1515 | "requires": { 1516 | "callsites": "^3.0.0" 1517 | } 1518 | }, 1519 | "path-is-absolute": { 1520 | "version": "1.0.1", 1521 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1522 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1523 | "dev": true 1524 | }, 1525 | "path-key": { 1526 | "version": "3.1.1", 1527 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1528 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1529 | "dev": true 1530 | }, 1531 | "path-type": { 1532 | "version": "4.0.0", 1533 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 1534 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 1535 | "dev": true 1536 | }, 1537 | "picocolors": { 1538 | "version": "1.0.0", 1539 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1540 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 1541 | "dev": true 1542 | }, 1543 | "picomatch": { 1544 | "version": "2.3.1", 1545 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1546 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1547 | "dev": true 1548 | }, 1549 | "pify": { 1550 | "version": "4.0.1", 1551 | "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", 1552 | "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", 1553 | "dev": true 1554 | }, 1555 | "prelude-ls": { 1556 | "version": "1.2.1", 1557 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1558 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1559 | "dev": true 1560 | }, 1561 | "punycode": { 1562 | "version": "2.1.1", 1563 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1564 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1565 | "dev": true 1566 | }, 1567 | "queue-microtask": { 1568 | "version": "1.2.3", 1569 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1570 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1571 | "dev": true 1572 | }, 1573 | "readdirp": { 1574 | "version": "3.6.0", 1575 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1576 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1577 | "dev": true, 1578 | "optional": true, 1579 | "requires": { 1580 | "picomatch": "^2.2.1" 1581 | } 1582 | }, 1583 | "regexpp": { 1584 | "version": "3.2.0", 1585 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", 1586 | "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", 1587 | "dev": true 1588 | }, 1589 | "resolve-from": { 1590 | "version": "4.0.0", 1591 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1592 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1593 | "dev": true 1594 | }, 1595 | "reusify": { 1596 | "version": "1.0.4", 1597 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1598 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1599 | "dev": true 1600 | }, 1601 | "rimraf": { 1602 | "version": "3.0.2", 1603 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1604 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1605 | "dev": true, 1606 | "requires": { 1607 | "glob": "^7.1.3" 1608 | } 1609 | }, 1610 | "run-parallel": { 1611 | "version": "1.2.0", 1612 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1613 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1614 | "dev": true, 1615 | "requires": { 1616 | "queue-microtask": "^1.2.2" 1617 | } 1618 | }, 1619 | "safe-buffer": { 1620 | "version": "5.1.2", 1621 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1622 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1623 | "dev": true 1624 | }, 1625 | "semver": { 1626 | "version": "5.7.1", 1627 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1628 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 1629 | "dev": true 1630 | }, 1631 | "shebang-command": { 1632 | "version": "2.0.0", 1633 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1634 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1635 | "dev": true, 1636 | "requires": { 1637 | "shebang-regex": "^3.0.0" 1638 | } 1639 | }, 1640 | "shebang-regex": { 1641 | "version": "3.0.0", 1642 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1643 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1644 | "dev": true 1645 | }, 1646 | "slash": { 1647 | "version": "2.0.0", 1648 | "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", 1649 | "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", 1650 | "dev": true 1651 | }, 1652 | "source-map": { 1653 | "version": "0.5.7", 1654 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 1655 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 1656 | "dev": true 1657 | }, 1658 | "strip-ansi": { 1659 | "version": "6.0.1", 1660 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1661 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1662 | "dev": true, 1663 | "requires": { 1664 | "ansi-regex": "^5.0.1" 1665 | } 1666 | }, 1667 | "strip-json-comments": { 1668 | "version": "3.1.1", 1669 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1670 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1671 | "dev": true 1672 | }, 1673 | "supports-color": { 1674 | "version": "5.5.0", 1675 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1676 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1677 | "dev": true, 1678 | "requires": { 1679 | "has-flag": "^3.0.0" 1680 | } 1681 | }, 1682 | "text-table": { 1683 | "version": "0.2.0", 1684 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1685 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1686 | "dev": true 1687 | }, 1688 | "to-fast-properties": { 1689 | "version": "2.0.0", 1690 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 1691 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", 1692 | "dev": true 1693 | }, 1694 | "to-regex-range": { 1695 | "version": "5.0.1", 1696 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1697 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1698 | "dev": true, 1699 | "requires": { 1700 | "is-number": "^7.0.0" 1701 | } 1702 | }, 1703 | "tslib": { 1704 | "version": "1.14.1", 1705 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 1706 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", 1707 | "dev": true 1708 | }, 1709 | "tsutils": { 1710 | "version": "3.21.0", 1711 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", 1712 | "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", 1713 | "dev": true, 1714 | "requires": { 1715 | "tslib": "^1.8.1" 1716 | } 1717 | }, 1718 | "type-check": { 1719 | "version": "0.4.0", 1720 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1721 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1722 | "dev": true, 1723 | "requires": { 1724 | "prelude-ls": "^1.2.1" 1725 | } 1726 | }, 1727 | "type-fest": { 1728 | "version": "0.20.2", 1729 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 1730 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 1731 | "dev": true 1732 | }, 1733 | "typescript": { 1734 | "version": "4.5.5", 1735 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", 1736 | "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", 1737 | "dev": true 1738 | }, 1739 | "uri-js": { 1740 | "version": "4.4.1", 1741 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1742 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1743 | "dev": true, 1744 | "requires": { 1745 | "punycode": "^2.1.0" 1746 | } 1747 | }, 1748 | "v8-compile-cache": { 1749 | "version": "2.3.0", 1750 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", 1751 | "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", 1752 | "dev": true 1753 | }, 1754 | "which": { 1755 | "version": "2.0.2", 1756 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1757 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1758 | "dev": true, 1759 | "requires": { 1760 | "isexe": "^2.0.0" 1761 | } 1762 | }, 1763 | "word-wrap": { 1764 | "version": "1.2.3", 1765 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1766 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", 1767 | "dev": true 1768 | }, 1769 | "wrappy": { 1770 | "version": "1.0.2", 1771 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1772 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1773 | "dev": true 1774 | }, 1775 | "yallist": { 1776 | "version": "4.0.0", 1777 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1778 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1779 | "dev": true 1780 | } 1781 | } 1782 | } 1783 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scriptables", 3 | "version": "1.0.1", 4 | "description": "collection of my scriptable projects", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "babel ./src -d build --extensions '.ts'", 9 | "copy": "cp -Rv ./build/. ~/Library/Mobile\\ Documents/iCloud~dk~simonbs~Scriptable/Documents", 10 | "copyi": "cp -Riv ./build/. ~/Library/Mobile\\ Documents/iCloud~dk~simonbs~Scriptable/Documents", 11 | "dist": "npm run build; npm run copyi", 12 | "distall": "npm run build; npm run copy" 13 | }, 14 | "author": "Dmytro Burmistrov (Nodman). https://github.com/Nodman", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "@babel/cli": "^7.16.8", 18 | "@babel/core": "^7.16.12", 19 | "@babel/plugin-transform-modules-commonjs": "^7.16.8", 20 | "@babel/preset-typescript": "^7.16.7", 21 | "@scriptable-ios/eslint-config": "^1.6.4", 22 | "@types/scriptable-ios": "^1.6.5", 23 | "@typescript-eslint/eslint-plugin": "^5.11.0", 24 | "@typescript-eslint/parser": "^5.11.0", 25 | "eslint": "^8.8.0", 26 | "typescript": "^4.5.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/demo.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-magic-numbers */ 2 | 3 | import { createWidget } from './tiny-dashboard' 4 | import { numberFormatterShort } from './utils' 5 | 6 | 7 | const dateFormatter = new DateFormatter() 8 | 9 | dateFormatter.useShortTimeStyle() 10 | 11 | function exec() { 12 | const widget = createWidget({ 13 | chartData: [89, 100, 69, 190, 59, 22, 40], 14 | subtitle1: `${numberFormatterShort(1400)} DAILY / ${numberFormatterShort(12780)} MONTHLY`, 15 | subtitle2: `UPDATED: ${dateFormatter.string(new Date())}`, 16 | value: numberFormatterShort(2888), 17 | subValue: `/ ${numberFormatterShort(190)}`, 18 | headerSymbol: 'hryvniasign.circle', 19 | header: 'CURRENT MONTH:', 20 | }, { dark: 'midnight' }) 21 | 22 | widget.presentSmall() 23 | } 24 | 25 | 26 | exec() 27 | 28 | Script.complete() 29 | -------------------------------------------------------------------------------- /src/mono-monthly-small.ts: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: blue; icon-glyph: hand-holding-usd; 4 | 5 | /* 6 | * author: https://github.com/Nodman 7 | * track expanses from monobank account with small widget 8 | */ 9 | 10 | 'use strict' 11 | 12 | import { createWidget } from './tiny-dashboard' 13 | import { numberFormatterShort, parseWidgetParams } from './utils' 14 | import { Monobank } from './monobank' 15 | 16 | const SCRIPT_NAME = 'mono-monthly-small' 17 | 18 | const dateFormatter = new DateFormatter() 19 | 20 | dateFormatter.useShortTimeStyle() 21 | 22 | async function exec() { 23 | const { name = SCRIPT_NAME } = parseWidgetParams(args.widgetParameter) 24 | const monobank = new Monobank(name) 25 | 26 | await monobank.setup() 27 | 28 | const { currentPeriod, history } = await monobank.fetchLatestStatement() 29 | 30 | const todaysValue = Monobank.getTodaysExpanses({ currentPeriod }) 31 | 32 | const { monthlyHistory, daily, monthly } = Monobank.getStatsForPeriod({ history }) 33 | 34 | monthlyHistory.push(currentPeriod.total) 35 | 36 | const widget = createWidget({ 37 | chartData: monthlyHistory, 38 | subtitle1: `${numberFormatterShort(daily / 100)} DAILY / ${numberFormatterShort(monthly / 100)} MONTHLY`, 39 | subtitle2: `UPDATED: ${dateFormatter.string(new Date())}`, 40 | value: numberFormatterShort(currentPeriod.total / 100), 41 | subValue: `/ ${numberFormatterShort(todaysValue / 100)}`, 42 | headerSymbol: 'hryvniasign.circle', 43 | header: 'CURRENT MONTH:', 44 | }) 45 | 46 | Script.setWidget(widget) 47 | 48 | return widget 49 | } 50 | 51 | 52 | if (config.runsInApp) { 53 | const widget = await exec() 54 | 55 | await widget.presentSmall() 56 | } else { 57 | await exec() 58 | } 59 | 60 | Script.complete() 61 | -------------------------------------------------------------------------------- /src/monobank.ts: -------------------------------------------------------------------------------- 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: university; 4 | 5 | /* 6 | * author: https://github.com/Nodman 7 | * Monobank api module 8 | */ 9 | 10 | // NOTE: This script uses the Cache script (https://github.com/yaylinda/scriptable/blob/main/Cache.js) 11 | 12 | 'use strict' 13 | 14 | import { fetchJson, createAlert, getDaysInMonth, Logger } from './utils' 15 | 16 | const Cache = importModule('cache') 17 | 18 | import type { PersonalInfoT, StatementItemT, StatementCacheT, SettingsT } from './monobank.types' 19 | 20 | const DEFAULT_PERIOD = 12 21 | const ACCOUNT_MASK_LEN = 4 22 | const SCRIPT_NAME = 'monobank' 23 | const CACHE_KEY_SETTINGS = 'settings.json' 24 | const MONOBANK_TOKEN_KEY = 'monobank_token@scriptable' 25 | const MONOBANK_API_URL = 'https://api.monobank.ua' 26 | const API_PATH = { 27 | PERSONAL_INFO: '/personal/client-info', 28 | STATEMENT: '/personal/statement', 29 | CURRENCIES: '/bank/currency', 30 | } 31 | const CURRENCIES: Record = { 32 | 978: { id: 978, name: 'EUR', sign: '€' }, 33 | 980: { id: 980, name: 'UAH', sign: '₴' }, 34 | 840: { id: 840, name: 'USD', sign: '$' }, 35 | } 36 | 37 | const cache = new Cache(SCRIPT_NAME) 38 | const logger = new Logger('Mono') 39 | 40 | export class Monobank { 41 | private parentScriptName?: string 42 | private settings: Promise 43 | 44 | constructor(parentScriptName?: string) { 45 | this.parentScriptName = parentScriptName 46 | this.readSettings() 47 | } 48 | 49 | static getTodaysExpanses({ currentPeriod }: { currentPeriod: StatementCacheT['currentPeriod'] }) { 50 | if (!currentPeriod || !currentPeriod.statement) { 51 | return 0 52 | } 53 | 54 | const date = (new Date()).getDate() 55 | const todaysStatement = currentPeriod.statement[date] 56 | 57 | if (!todaysStatement || !todaysStatement.length) { 58 | return 0 59 | } 60 | 61 | return todaysStatement.reduce((acc, item) => { 62 | return acc - (item?.amount ?? 0) 63 | }, 0) 64 | } 65 | 66 | static getStatsForPeriod({ history }: { history: StatementCacheT['history'] }, period: number = DEFAULT_PERIOD) { 67 | const yeatsAvailable = Object.keys(history).map(Number).sort((a, b) => a - b) 68 | const monthlyHistory: number[] = [] 69 | const dailyHistory: number[] = [] 70 | 71 | while (monthlyHistory.length < period && yeatsAvailable.length) { 72 | const [year] = yeatsAvailable.splice(-1) 73 | 74 | const yearPeriod = history[year] ?? {} 75 | 76 | Object 77 | .keys(yearPeriod) 78 | .map(Number) 79 | .sort((a, b) => b - a) 80 | .slice(-period + monthlyHistory.length) 81 | .forEach((month) => { 82 | const daysInMonth = getDaysInMonth(year, month) 83 | const monthValue = yearPeriod[month] 84 | 85 | monthlyHistory.unshift(monthValue) 86 | dailyHistory.unshift(monthValue / daysInMonth) 87 | }) 88 | } 89 | 90 | return { 91 | monthlyHistory, 92 | dailyHistory, 93 | monthly: monthlyHistory.reduce(( acc, item ) => acc + item, 0) / (monthlyHistory.length || 1), 94 | daily: dailyHistory.reduce(( acc, item ) => acc + item, 0) / (dailyHistory.length || 1), 95 | } 96 | } 97 | 98 | private async getStatementItemsFilter() { 99 | const settings = await this.settings 100 | const filter = this.parentScriptName && settings ? settings[this.parentScriptName]?.filter : undefined 101 | 102 | return (statementItem: StatementItemT) => { 103 | const isExpanse = statementItem.amount < 0 || statementItem.description.toLowerCase().startsWith('скасування') 104 | const isExcluded = filter ? filter.split(';').some(filterString => statementItem.description.match(filterString)) : false 105 | 106 | return isExpanse && !isExcluded 107 | } 108 | } 109 | 110 | async readSettings() { 111 | this.settings = cache.read(CACHE_KEY_SETTINGS) ?? {} 112 | 113 | return await this.settings 114 | } 115 | 116 | private async writeSettings(nextSettings: SettingsT) { 117 | const prevSettings = await this.readSettings() 118 | const settings = { ...prevSettings, ...nextSettings } 119 | 120 | cache.write(CACHE_KEY_SETTINGS, settings) 121 | 122 | this.settings = Promise.resolve(settings) 123 | } 124 | 125 | private get requestHeaders(): Record { 126 | const token = Keychain.get(MONOBANK_TOKEN_KEY) 127 | 128 | if (!token) { 129 | throw new Error('No token was found in keychain') 130 | } 131 | 132 | return { 133 | 'X-Token': token, 134 | } 135 | } 136 | 137 | private async getAccountId(parentScriptName?: string) { 138 | const parentScript = parentScriptName ?? this.parentScriptName 139 | const settings = await this.settings 140 | let accountId = parentScript && settings ? settings[parentScript]?.accountId : undefined 141 | 142 | if (!accountId) { 143 | accountId = await this.setupAccount() 144 | } 145 | 146 | if (!accountId) { 147 | throw new Error('Account ID is required') 148 | } 149 | 150 | return accountId 151 | } 152 | 153 | private fetchData(path: string): Promise { 154 | if (this.isTokenProvided()) { 155 | const url = `${MONOBANK_API_URL}${path}` 156 | 157 | return fetchJson(url, this.requestHeaders) 158 | } 159 | 160 | throw new Error('No token provided') 161 | } 162 | 163 | private isTokenProvided(): boolean { 164 | return Keychain.contains(MONOBANK_TOKEN_KEY) 165 | } 166 | 167 | private async getAccountCacheKey(parentScriptName?: string) { 168 | const accountId = await this.getAccountId(parentScriptName) 169 | 170 | return `mono-${accountId}.json` 171 | } 172 | 173 | async setupToken() { 174 | if (this.isTokenProvided()) { 175 | const tokenExistsAlert = createAlert('Monobank api token', 'Token already exists in your Keychain. Do you wish to change it?', 'Cancel') 176 | 177 | tokenExistsAlert.addAction('Change token') 178 | tokenExistsAlert.addDestructiveAction('Delete token') 179 | 180 | const actionIndex = await tokenExistsAlert.presentSheet() 181 | 182 | switch (actionIndex) { 183 | case -1: 184 | return 185 | case 1: { 186 | Keychain.remove(MONOBANK_TOKEN_KEY) 187 | await createAlert('Token removed', '', 'Ok').present() 188 | 189 | return 190 | } 191 | } 192 | } 193 | 194 | const setupAlert = createAlert( 195 | 'Monobank API token is required', 196 | `In order to fetch data from Monobank you need to provide your generated API token. 197 | If you don't have one - please visit https://api.monobank.ua and follow the instructions.`, 198 | 'Cancel') 199 | 200 | setupAlert.addAction('I have token') 201 | setupAlert.addAction('https://api.monobank.ua') 202 | 203 | const setupActionIndex = await setupAlert.presentSheet() 204 | 205 | const presentInputAlert = async () => { 206 | const inputAlert = createAlert('Insert token', 'Your token will be saved in Keychain', 'Cancel') 207 | 208 | inputAlert.addAction('Save') 209 | inputAlert.addSecureTextField('token:') 210 | 211 | const inputActionIndex = await inputAlert.present() 212 | const tokenValue = inputAlert.textFieldValue(0) 213 | 214 | if (inputActionIndex === -1) { 215 | return 216 | } 217 | 218 | if (tokenValue) { 219 | Keychain.set(MONOBANK_TOKEN_KEY, tokenValue) 220 | await createAlert('Token saved', '', 'Ok').present() 221 | } else { 222 | await createAlert('Invalid token value', '', 'Ok').present() 223 | } 224 | } 225 | 226 | switch (setupActionIndex) { 227 | case 0: { 228 | await presentInputAlert() 229 | break 230 | } 231 | case 1: 232 | await Safari.openInApp('https://api.monobank.ua') 233 | await presentInputAlert() 234 | break 235 | } 236 | } 237 | 238 | async setupFilters(parentScriptName?: string) { 239 | const scriptName = parentScriptName ?? this.parentScriptName 240 | 241 | if (!scriptName) { 242 | return 243 | } 244 | 245 | const settings = await this.settings ?? {} 246 | const setupFiltersAlert = createAlert( 247 | 'Set statement items filter', 248 | 'Enter semicolon separated values, e.g. "rent;netflix;spotify". Items with one of those words in description will be excluded from accounting', 249 | 'Cancel', 250 | ) 251 | 252 | setupFiltersAlert.addAction('Save') 253 | 254 | const filter = settings ? settings[scriptName]?.filter : undefined 255 | 256 | setupFiltersAlert.addTextField('filter:', filter ?? '') 257 | 258 | const actionIndex = await setupFiltersAlert.present() 259 | 260 | if (actionIndex === -1) { 261 | return 262 | } 263 | 264 | const nextFilter = setupFiltersAlert.textFieldValue(0) 265 | 266 | settings[scriptName] = settings[scriptName] ?? {} 267 | settings[scriptName].filter = nextFilter 268 | 269 | await this.writeSettings(settings) 270 | 271 | 272 | await createAlert('Filters saved', '', 'Ok').present() 273 | } 274 | 275 | async setupAccount(parentScriptName?: string) { 276 | const scriptName = parentScriptName ?? this.parentScriptName 277 | 278 | const setupAccountsAlert = createAlert( 279 | 'Select account', 280 | 'ID of selected account will be copied to clipboard and used for this script.', 281 | 'Cancel', 282 | ) 283 | 284 | const accounts = await this.fetchAccounts() 285 | 286 | accounts.forEach((item) => { 287 | const { name, type } = item 288 | 289 | setupAccountsAlert.addAction(`[${type}] ****${name}`) 290 | }) 291 | 292 | const actionIndex = await setupAccountsAlert.presentSheet() 293 | 294 | if (actionIndex === -1) { 295 | return 296 | } 297 | 298 | const id = accounts[actionIndex].id 299 | 300 | Pasteboard.copy(id) 301 | 302 | if (scriptName) { 303 | await this.writeSettings({ [scriptName]: { accountId: id } }) 304 | } 305 | 306 | return id 307 | } 308 | 309 | async fetchAccounts() { 310 | const response: PersonalInfoT = await this.fetchData(API_PATH.PERSONAL_INFO) 311 | 312 | const accounts = response.accounts.map((item) => { 313 | const name = (item.maskedPan[0] ?? item.iban).slice(-ACCOUNT_MASK_LEN) 314 | 315 | return { 316 | id: item.id, 317 | type: item.type, 318 | name, 319 | } 320 | }) 321 | 322 | return accounts 323 | } 324 | 325 | async fetchAccountStatement(fromArg: Date, toArg?: Date): Promise { 326 | const from = Math.round(Number(fromArg) / 1e3) 327 | const to = Math.round(Number(toArg ?? new Date()) / 1e3) 328 | const accountId = await this.getAccountId() 329 | 330 | const url = `${API_PATH.STATEMENT}/${accountId}/${from}/${to}` 331 | 332 | return this.fetchData(url) 333 | } 334 | 335 | fetchTodaysStatement() { 336 | const to = new Date() 337 | const from = new Date() 338 | 339 | from.setHours(0, 0, 0, 0) 340 | 341 | return this.fetchAccountStatement(from, to) 342 | } 343 | 344 | async fetchLatestStatement(): Promise { 345 | const accountCacheKey = await this.getAccountCacheKey() 346 | const { currentPeriod, history = {} }: StatementCacheT = await cache.read(accountCacheKey) ?? {} 347 | 348 | if (!currentPeriod?.lastUpdatedAt) { 349 | return this.fetchInitialPeriod() 350 | } 351 | 352 | const { lastUpdatedAt, lastOperationId } = currentPeriod 353 | const to = new Date() 354 | 355 | const response = await this.fetchAccountStatement(new Date(lastUpdatedAt), to) 356 | 357 | if (!response.length || (response[0].id === lastOperationId)) { 358 | logger.log('nothing to process, skipping...') 359 | 360 | return { currentPeriod, history } 361 | } 362 | 363 | const filter = await this.getStatementItemsFilter() 364 | 365 | for (let index = response.length - 1; index >= 0; index--) { 366 | const item = response[index] 367 | 368 | if (filter(item)) { 369 | const { id, time, amount, description, mcc, cashbackAmount } = item 370 | const opDate = new Date(item.time * 1e3) 371 | const month = opDate.getMonth() + 1 372 | const year = opDate.getFullYear() 373 | const date = opDate.getDate() 374 | 375 | if (month > currentPeriod.month) { 376 | history[currentPeriod.year] = history[currentPeriod.year] ?? {} 377 | history[currentPeriod.year][currentPeriod.month] = currentPeriod.total 378 | currentPeriod.year = year 379 | currentPeriod.month = month 380 | currentPeriod.total = 0 381 | currentPeriod.statement = [null] 382 | } else if (month < currentPeriod.month) { 383 | continue 384 | } 385 | 386 | currentPeriod.statement[date] = currentPeriod.statement[date] ?? [] 387 | 388 | // @ts-ignore 389 | currentPeriod.statement[date].push({ id, time, amount, description, mcc, cashbackAmount }) 390 | currentPeriod.total = currentPeriod.total += -1 * amount 391 | } 392 | } 393 | 394 | currentPeriod.lastUpdatedAt = Number(to) 395 | currentPeriod.lastOperationId = response[0].id 396 | 397 | cache.write(accountCacheKey, { currentPeriod, history }) 398 | 399 | return { currentPeriod, history } 400 | } 401 | 402 | async fetchInitialPeriod() { 403 | logger.log('fetching initial period...') 404 | 405 | const to = new Date() 406 | const from = new Date() 407 | const currentMonth = from.getMonth() + 1 408 | const currentYear = from.getFullYear() 409 | 410 | from.setDate(1) 411 | from.setHours(0, 0, 0, 0) 412 | 413 | const response = await this.fetchAccountStatement(from, to) 414 | const filter = await this.getStatementItemsFilter() 415 | 416 | const currentPeriod = response.reduce((acc, item, index) => { 417 | if (filter(item)) { 418 | const { id, time, amount, description, mcc, cashbackAmount } = item 419 | const date = new Date(item.time * 1e3).getDate() 420 | const currentValue = acc.statement[date] ?? [] 421 | 422 | acc.statement[date] = [{ id, time, amount, description, mcc, cashbackAmount }, ...currentValue] 423 | acc.total += -1 * amount 424 | 425 | if (!index) { 426 | acc.lastOperationId = id 427 | } 428 | } 429 | 430 | return acc 431 | }, { month: currentMonth, year: currentYear, total: 0, statement: [], lastOperationId: '', lastUpdatedAt: Number(to) } as StatementCacheT['currentPeriod']) 432 | 433 | const statement: StatementCacheT = { 434 | history: {}, 435 | currentPeriod, 436 | } 437 | 438 | const accountCacheKey = await this.getAccountCacheKey() 439 | 440 | await cache.write(accountCacheKey, statement) 441 | 442 | return statement 443 | } 444 | 445 | async editOP({ date, day, amount: editAmount }: {date: number, day: number, amount: number | null}, parentScriptName?: string) { 446 | const accountCacheKey = await this.getAccountCacheKey(parentScriptName) 447 | const statement: StatementCacheT = await cache.read(accountCacheKey) 448 | const item = statement.currentPeriod.statement?.[date]?.[day] 449 | 450 | if (item == null) { 451 | throw new Error(`OP not found: ${date}-${day}`) 452 | } 453 | 454 | if (editAmount === null) { 455 | item.amount = item.originalAmount ?? item.amount 456 | item.originalAmount = undefined 457 | } else { 458 | item.originalAmount = item.amount 459 | item.amount = item.amount + editAmount 460 | } 461 | 462 | const nextTotal = statement.currentPeriod.statement.reduce((total, day) => { 463 | return day ? total + day.reduce((acc, item) => item ? -1 * item.amount + acc : acc, 0) : total 464 | }, 0) 465 | 466 | statement.currentPeriod.total = nextTotal 467 | 468 | await cache.write(accountCacheKey, statement) 469 | 470 | await createAlert('Successfuly updated', '', 'Ok').present() 471 | } 472 | 473 | async editStatementItem(parentScriptName?: string) { 474 | const accountCacheKey = await this.getAccountCacheKey(parentScriptName) 475 | const { currentPeriod: { statement } }: StatementCacheT = await cache.read(accountCacheKey) 476 | 477 | const editAlert = createAlert('Select operation to edit', '', 'Cancel') 478 | const actions: {summary: string, dateIndex: number, opIndex: number, originalAmount?: number, amount: number}[] = [] 479 | 480 | for (let dateIndex = statement.length - 1; dateIndex > 0; dateIndex--) { 481 | const date = statement[dateIndex] 482 | 483 | if (date) { 484 | for (let opIndex = date.length - 1; opIndex >= 0; opIndex--) { 485 | const opItem = date[opIndex] 486 | 487 | if (!opItem) { 488 | continue 489 | } 490 | 491 | const { amount, description, originalAmount } = opItem 492 | const summary = `${amount / 100}${CURRENCIES[980].sign}${originalAmount ? '[E]' : ''}: "${description.replace('\n', '_')}"` 493 | 494 | editAlert.addAction(summary) 495 | actions.push({ summary, dateIndex, opIndex, originalAmount, amount }) 496 | } 497 | } 498 | } 499 | 500 | const opActionIndex = await editAlert.presentSheet() 501 | 502 | if (opActionIndex === -1) { 503 | return 504 | } 505 | 506 | let editActionIndex = -1 507 | let amount: number | null = 0 508 | 509 | const { originalAmount, amount: currentAmount } = actions[opActionIndex] 510 | const valueAlert = createAlert('Edit operation', `Current amount is: ${currentAmount / 1e2}.\nEnter either positive or negative value to add:`, 'Cancel') 511 | 512 | valueAlert.addTextField('Amount:') 513 | valueAlert.addAction('Submit') 514 | 515 | if (originalAmount) { 516 | valueAlert.addDestructiveAction('Restore this operation') 517 | } else { 518 | valueAlert.addDestructiveAction('Delete this operation') 519 | } 520 | 521 | const valueActionIndex = await valueAlert.present() 522 | 523 | editActionIndex = valueActionIndex 524 | 525 | switch (valueActionIndex) { 526 | case -1: break 527 | case 0: amount = Number(valueAlert.textFieldValue(0)) * 1e2; break 528 | case 1: amount = originalAmount ? null : -1 * currentAmount; break 529 | } 530 | 531 | if (editActionIndex === -1) { 532 | return 533 | } 534 | 535 | const { dateIndex, opIndex } = actions[opActionIndex] 536 | const actionSummary = amount === null ? `restored to ${(originalAmount ?? 0) / 1e2}` : `edited for ${amount / 1e2}.\nNew value: ${currentAmount / 1e2 + amount / 1e2}` 537 | const confirmationAlert = createAlert('Are you sure?', `${actions[opActionIndex].summary} will be ${actionSummary}.`, 'Cancel') 538 | 539 | confirmationAlert.addDestructiveAction('Submit') 540 | 541 | const confirmationIndex = await confirmationAlert.presentSheet() 542 | 543 | if (confirmationIndex === -1) { 544 | return 545 | } 546 | 547 | await this.editOP({ date: dateIndex, day: opIndex, amount }, parentScriptName) 548 | } 549 | 550 | async setup() { 551 | if (!this.parentScriptName) { 552 | return 553 | } 554 | 555 | if (!this.isTokenProvided()) { 556 | await this.setupToken() 557 | } 558 | 559 | const settings = await this.readSettings() ?? {} 560 | const scriptSettings = settings[this.parentScriptName] 561 | 562 | if (!scriptSettings || !scriptSettings.accountId) { 563 | await this.setupAccount() 564 | await this.setupFilters() 565 | } 566 | } 567 | 568 | async pickScript() { 569 | if (this.parentScriptName) { 570 | return 571 | } 572 | 573 | const settngs = await this.readSettings() 574 | const keys = Object.keys(settngs ?? {}) 575 | const selectScriptAlert = createAlert('Select script to modify', '', 'Cancel') 576 | 577 | if (!keys.length) { 578 | return 579 | } 580 | 581 | keys.forEach(item => { 582 | selectScriptAlert.addAction(item) 583 | }) 584 | 585 | const keyIndex = await selectScriptAlert.presentSheet() 586 | 587 | return keys[keyIndex] 588 | } 589 | 590 | async showSettings() { 591 | const settingsAlert = createAlert('Monobank settngs', '', 'Cancel') 592 | 593 | settingsAlert.addAction('Manage auth token') 594 | settingsAlert.addAction('Change account number') 595 | settingsAlert.addAction('Set filters') 596 | settingsAlert.addAction('Edit statement') 597 | 598 | const actionIndex = await settingsAlert.present() 599 | 600 | switch (actionIndex) { 601 | case -1: break 602 | case 0: await this.setupToken(); break 603 | case 1: { 604 | const scriptName = await this.pickScript() 605 | 606 | if (scriptName) { 607 | await this.setupAccount(scriptName) 608 | } 609 | 610 | break 611 | } 612 | case 2: { 613 | const scriptName = await this.pickScript() 614 | 615 | if (scriptName) { 616 | await this.setupFilters(scriptName) 617 | } 618 | 619 | break 620 | } 621 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers 622 | case 3: { 623 | const scriptName = await this.pickScript() 624 | 625 | if (scriptName) { 626 | await this.editStatementItem(scriptName) 627 | } 628 | 629 | break 630 | } 631 | } 632 | } 633 | } 634 | 635 | if (config.runsInApp && Script.name() === SCRIPT_NAME) { 636 | const monobank = new Monobank() 637 | 638 | monobank.showSettings() 639 | } 640 | -------------------------------------------------------------------------------- /src/monobank.types.ts: -------------------------------------------------------------------------------- 1 | export type AccountT = { 2 | balance: number, 3 | cashbackType: string, 4 | creditLimit: number, 5 | currencyCode: number, 6 | iban: string, 7 | id: string, 8 | maskedPan: string[], 9 | sendId: string, 10 | type: string, 11 | } 12 | 13 | export type PersonalInfoT = { 14 | accounts: AccountT[], 15 | clientId: string, 16 | name: string, 17 | permissions: string, 18 | webHookUrl: string, 19 | } 20 | 21 | export type AccountsMapT = Record 22 | 23 | export type StatementItemT = { 24 | id: string, 25 | time: number, 26 | description: string, 27 | mcc: number, 28 | hold: boolean, 29 | amount: number, 30 | operationAmount: number, 31 | currencyCode: number, 32 | commissionRate: number, 33 | cashbackAmount: number, 34 | balance: number, 35 | comment: string, 36 | receiptId: string, 37 | counterEdrpou: string, 38 | counterIban: string, 39 | } 40 | 41 | export type StatementItemShortT = Pick & {originalAmount?: number} 42 | 43 | export type StatementCacheT = { 44 | history: Record>, 45 | currentPeriod: { 46 | total: number, 47 | month: number, 48 | year: number, 49 | statement: ((StatementItemShortT | null)[] | null)[], 50 | lastUpdatedAt: number, 51 | lastOperationId: string, 52 | }, 53 | } 54 | 55 | export type SettingsT = Record 56 | -------------------------------------------------------------------------------- /src/tiny-charts.ts: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: teal; icon-glyph: chart-line; 4 | 5 | /* 6 | * author: https://github.com/Nodman 7 | * small chart, for now supports only area chart 8 | */ 9 | 10 | 'use strict' 11 | 12 | const SCRIPT_NAME = 'tiny-charts' 13 | 14 | export class TinyCharts { 15 | context: DrawContext 16 | width: number 17 | height: number 18 | strokeColor?: Color 19 | fillColor?: Color 20 | 21 | constructor(width: number, height: number) { 22 | this.width = width 23 | this.height = height 24 | } 25 | 26 | private createContext() { 27 | this.context = new DrawContext() 28 | this.context.size = new Size(this.width, this.height) 29 | this.context.opaque = false 30 | this.context.respectScreenScale = true 31 | this.context.setLineWidth(1) 32 | } 33 | 34 | private normalizeData(data: number[]) { 35 | const maxValue = Math.max(...data) 36 | const normalized = data.map(value => value / (maxValue / 1e2)) 37 | 38 | return normalized 39 | } 40 | 41 | private getCoordinates(data: number[], index: number) { 42 | const y = this.height - (this.height * data[index]) / 100 43 | const x = index * (this.width / (data.length - 1)) 44 | 45 | return { x, y } 46 | } 47 | 48 | setFillColor(color: Color) { 49 | this.fillColor = color 50 | } 51 | 52 | setStrokeColor(color: Color) { 53 | this.strokeColor = color 54 | } 55 | 56 | drawAreaChart(rawData: number[]) { 57 | this.createContext() 58 | 59 | const data = this.normalizeData(rawData) 60 | const startingPoint = this.getCoordinates(data, 0) 61 | const path = new Path() 62 | 63 | path.move(new Point(startingPoint.x, startingPoint.y)) 64 | 65 | for (let index = 0; index < data.length - 1; index++) { 66 | const point = this.getCoordinates(data, index) 67 | const nextPoint = this.getCoordinates(data, index + 1) 68 | 69 | const pointX = (point.x + nextPoint.x) / 2 70 | const pointY = (point.y + nextPoint.y) / 2 71 | const controlx1 = (pointX + point.x) / 2 72 | const controlx2 = (pointX + nextPoint.x) / 2 73 | 74 | path.addQuadCurve(new Point(pointX, pointY), new Point(controlx1, point.y)) 75 | path.addQuadCurve(new Point(nextPoint.x, nextPoint.y), new Point(controlx2, nextPoint.y)) 76 | } 77 | 78 | path.addLine(new Point(this.width, this.height)) 79 | path.addLine(new Point(0, this.height)) 80 | path.closeSubpath() 81 | 82 | if (this.strokeColor) { 83 | this.context.setStrokeColor(this.strokeColor) 84 | this.context.addPath(path) 85 | this.context.strokePath() 86 | } 87 | 88 | if (this.fillColor) { 89 | this.context.setFillColor(this.fillColor) 90 | this.context.addPath(path) 91 | this.context.fillPath() 92 | } 93 | } 94 | 95 | getContext() { 96 | return this.context 97 | } 98 | 99 | getImage() { 100 | return this.context.getImage() 101 | } 102 | } 103 | 104 | Script.complete() 105 | -------------------------------------------------------------------------------- /src/tiny-dashboard.ts: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: blue; icon-glyph: columns; 4 | 5 | /* 6 | * author: https://github.com/Nodman 7 | * small widget layout with graph 8 | */ 9 | 10 | 'use strict' 11 | 12 | import { getDynamicGradient, getDynamicColor, parseWidgetParams, getDeviceAppearance } from './utils' 13 | import type { PaletteT } from './utils.types' 14 | import { TinyCharts } from './tiny-charts' 15 | 16 | const SCRIPT_NAME = 'tiny-dashboard' 17 | const GAP = 10 18 | const SYMBOL_SIZE = 16 19 | const GRAPH_OPACITY = 0.3 20 | const GRAPH_WIDTH = 510 21 | const GRAPH_HEIGHT = 150 22 | 23 | /* 24 | * most of the colors taken from https://uigradients.com/ 25 | */ 26 | export const PALETTES: Record = { 27 | // darker ? palettes 28 | seablue: { 29 | bgStart: '#2b5876', 30 | bgEnd: '#4e4376', 31 | primary: '#ece9e6', 32 | secondary: '#7d7795', 33 | }, 34 | cloud: { 35 | bgStart: '#141E30', 36 | bgEnd: '#243B55', 37 | primary: '#d0d0da', 38 | secondary: '#141E30', 39 | }, 40 | midnight: { 41 | bgStart: '#232526', 42 | bgEnd: '#414345', 43 | primary: '#ece9e6', 44 | secondary: '#232526', 45 | }, 46 | // lighter palettes, kinda. I am bad with colors 47 | royal: { 48 | bgStart: '#ece9e6', 49 | bgEnd: '#ffffff', 50 | primary: '#141E30', 51 | secondary: '#c2bbb5', 52 | }, 53 | dull: { 54 | bgStart: '#c9d6ff', 55 | bgEnd: '#e2e2e2', 56 | primary: '#000c40', 57 | secondary: '#99b1fe', 58 | }, 59 | anamnisar: { 60 | bgStart: '#9796f0', 61 | bgEnd: '#fbc7d4', 62 | primary: '#1D2B64', 63 | secondary: '#9796f0', 64 | }, 65 | ash: { 66 | bgStart: '#606c88', 67 | bgEnd: '#3f4c6b', 68 | primary: '#c9d6ff', 69 | secondary: '#c9d6ff', 70 | }, 71 | // other color presets 72 | pacific: { 73 | bgStart: '#0f3443', 74 | bgEnd: '#34e89e', 75 | primary: '#BDFFF3', 76 | secondary: '#0f3443', 77 | }, 78 | sin: { 79 | bgStart: '#93291E', 80 | bgEnd: '#ED213A', 81 | primary: '#340707', 82 | secondary: '#333333', 83 | }, 84 | sandblue: { 85 | bgStart: '#DECBA4', 86 | bgEnd: '#3E5151', 87 | primary: '#243737', 88 | secondary: '#3E5151', 89 | }, 90 | 91 | // nord colors taken from https://www.nordtheme.com 92 | nord: { 93 | bgStart: '#2E3440', 94 | bgEnd: '#2E3440', 95 | primary: '#81a1c1', 96 | accent: '#d8dee9', 97 | secondary: '#d8dee9', 98 | }, 99 | nordlight: { 100 | bgStart: '#d8dee9', 101 | bgEnd: '#d8dee9', 102 | primary: '#4c566a', 103 | accent: '#5e81ac', 104 | secondary: '#81a1c1', 105 | }, 106 | } 107 | 108 | const TYPOGRAPHY = { 109 | title: 40, 110 | subtitle: 15, 111 | body: 10, 112 | caption: 8, 113 | } 114 | 115 | function getPalette(palette: { light?: string, dark?: string } = {}) { 116 | const { light, dark } = parseWidgetParams(args.widgetParameter) 117 | 118 | return { 119 | light: PALETTES[palette.light ?? light] ?? PALETTES.dull, 120 | dark: PALETTES[palette.dark ?? dark] ?? PALETTES.sandblue, 121 | } 122 | } 123 | 124 | type ArgsT = { 125 | header: string, 126 | headerSymbol?: string, 127 | value: string, 128 | subValue?: string, 129 | subtitle1?: string, 130 | subtitle2?: string, 131 | chartData?: number[], 132 | } 133 | 134 | export function createWidget(args: ArgsT, theme?: { light?: string, dark?: string }) { 135 | const { 136 | value, 137 | chartData, 138 | subtitle1, 139 | subtitle2, 140 | headerSymbol: headerSymbolProp, 141 | header, 142 | subValue, 143 | } = args 144 | 145 | const appearence = getDeviceAppearance() 146 | const palette = getPalette(theme) 147 | const listWidget = new ListWidget() 148 | const textColor = getDynamicColor(palette, 'primary') 149 | const titleColor = getDynamicColor(palette, 'accent') 150 | const gradient = getDynamicGradient(palette) 151 | const opacity = palette[appearence].bgStart === palette[appearence].bgEnd ? 1 : GRAPH_OPACITY 152 | const fillColor = getDynamicColor(palette, 'secondary', opacity) 153 | 154 | listWidget.setPadding(GAP, 0, 0, 0) 155 | 156 | gradient.locations = [0.0, 1] 157 | listWidget.backgroundGradient = gradient 158 | 159 | // HEADER 160 | const headerStack = listWidget.addStack() 161 | const headerText = headerStack.addText(header) 162 | 163 | headerStack.setPadding(0, GAP, 0, GAP) 164 | headerText.textColor = textColor 165 | headerText.font = Font.regularSystemFont(TYPOGRAPHY.body) 166 | headerText.minimumScaleFactor = 0.50 167 | 168 | if (headerSymbolProp) { 169 | const headerSymbol = SFSymbol.named(headerSymbolProp) 170 | 171 | headerSymbol.applyFont(Font.systemFont(SYMBOL_SIZE)) 172 | headerSymbol.applySemiboldWeight 173 | 174 | headerStack.addSpacer() 175 | 176 | const currencySymbolImg = headerStack.addImage(headerSymbol.image) 177 | 178 | currencySymbolImg.resizable = false 179 | currencySymbolImg.tintColor = textColor 180 | currencySymbolImg.imageSize = new Size(SYMBOL_SIZE, SYMBOL_SIZE) 181 | } 182 | 183 | listWidget.addSpacer() 184 | 185 | // VALUE 186 | const valueStack = listWidget.addStack() 187 | 188 | valueStack.setPadding(0, GAP, 1, GAP) 189 | valueStack.bottomAlignContent() 190 | valueStack.spacing = 2 191 | 192 | const valueText = valueStack.addText(value) 193 | 194 | valueText.textColor = titleColor 195 | valueText.font = Font.boldSystemFont(TYPOGRAPHY.title) 196 | valueText.minimumScaleFactor = 0.50 197 | 198 | if (subValue) { 199 | const subValueStack = valueStack.addStack() 200 | const subValueText = subValueStack.addText(subValue) 201 | 202 | // hack to proper align text 203 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers 204 | subValueStack.setPadding(0, 0, 6, 0) 205 | 206 | subValueText.textColor = titleColor 207 | subValueText.font = Font.boldSystemFont(TYPOGRAPHY.body) 208 | subValueText.minimumScaleFactor = 0.50 209 | } 210 | 211 | // FOOTER 212 | const footerStack = listWidget.addStack() 213 | 214 | const footerStackBottomPadding = (2 - [subtitle1, subtitle2].reduce((acc, item) => acc + (item ? 1 : 0), 0)) * GAP 215 | 216 | footerStack.setPadding(0, GAP, footerStackBottomPadding, 0) 217 | footerStack.layoutVertically() 218 | footerStack.spacing = 2 219 | 220 | if (subtitle1) { 221 | const subtitle1Text = footerStack.addText(subtitle1) 222 | 223 | subtitle1Text.textColor = textColor 224 | subtitle1Text.font = Font.lightSystemFont(TYPOGRAPHY.caption) 225 | } 226 | 227 | if (subtitle2) { 228 | const subtitle2Text = footerStack.addText(subtitle2) 229 | 230 | subtitle2Text.textColor = textColor 231 | subtitle2Text.font = Font.lightSystemFont(TYPOGRAPHY.caption) 232 | } 233 | 234 | 235 | listWidget.addSpacer(GAP / 2) 236 | 237 | // GRAPH 238 | const graphStack = listWidget.addStack() 239 | 240 | graphStack.setPadding(0, 0, 0, 0) 241 | graphStack.backgroundColor = Color.clear() 242 | 243 | if (chartData) { 244 | const chart = new TinyCharts(GRAPH_WIDTH, GRAPH_HEIGHT) 245 | 246 | chart.setFillColor(fillColor) 247 | chart.drawAreaChart(chartData) 248 | 249 | graphStack.addImage(chart.getImage()) 250 | } else { 251 | listWidget.addSpacer(GAP) 252 | listWidget.addSpacer() 253 | } 254 | 255 | return listWidget 256 | } 257 | 258 | Script.complete() 259 | -------------------------------------------------------------------------------- /src/update-code.ts: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: red; icon-glyph: check-circle; 4 | 5 | 'use strict' 6 | 7 | const SCRIPTS = [ 8 | 'utils', 9 | 'tiny-dashboard', 10 | 'tiny-charts', 11 | 'mono-monthly-small', 12 | 'update-code', 13 | 'monobank', 14 | ] 15 | 16 | const URL = 'https://raw.githubusercontent.com/Nodman/scriptables/main/build' 17 | 18 | // taken from Max Zeryck blur script 19 | const updateCode = async (scriptName: string) => { 20 | const alert = new Alert() 21 | const moduleName = `${scriptName}.js` 22 | const url = `${URL}/${moduleName}` 23 | const path = [...module.filename.split('/').slice(0, -1), moduleName].join('/') 24 | 25 | alert.title = `Update "${moduleName}" code?` 26 | alert.message = 'This will overwrite any of your local changes!' 27 | alert.addCancelAction('Nope') 28 | 29 | alert.addDestructiveAction('Yesh') 30 | 31 | const actionIndex = await alert.present() 32 | 33 | if (actionIndex === -1) { 34 | return 35 | } 36 | 37 | // Determine if the user is using iCloud. 38 | let files = FileManager.local() 39 | const iCloudInUse = files.isFileStoredIniCloud(path) 40 | 41 | // If so, use an iCloud file manager. 42 | files = iCloudInUse ? FileManager.iCloud() : files 43 | 44 | let message = '' 45 | 46 | // Try to download the file. 47 | try { 48 | const req = new Request(url) 49 | const codeString = await req.loadString() 50 | 51 | files.writeString(path, codeString) 52 | message = 'The code has been updated. If the script is open, close it for the change to take effect.' 53 | } catch { 54 | message = 'The update failed. Please try again later.' 55 | } 56 | 57 | const confirmAlert = new Alert() 58 | 59 | confirmAlert.title = 'Update code' 60 | confirmAlert.message = message 61 | confirmAlert.addCancelAction('Ok') 62 | 63 | await confirmAlert.present() 64 | } 65 | 66 | const selectScriptAlert = new Alert() 67 | 68 | selectScriptAlert.title = 'Select script to update' 69 | selectScriptAlert.addCancelAction('Cancel') 70 | 71 | SCRIPTS.forEach(item => { 72 | selectScriptAlert.addAction(item) 73 | }) 74 | 75 | const actionIndex = await selectScriptAlert.presentSheet() 76 | 77 | if (actionIndex !== -1) { 78 | await updateCode(SCRIPTS[actionIndex]) 79 | } 80 | 81 | export {} 82 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | // Variables used by Scriptable. 2 | // These must be at the very top of the file. Do not edit. 3 | // icon-color: deep-purple; icon-glyph: wrench; 4 | 5 | /* 6 | * author: https://github.com/Nodman 7 | * few common utils I use across scriptable widgets 8 | */ 9 | 10 | 'use strict' 11 | 12 | import type { PaletteT, DynamicPaletteT } from './utils.types' 13 | 14 | const SCRIPT_NAME = 'utils' 15 | const DEBUG = true 16 | const ERROR_NOTIFICATION_ID = '_error' 17 | 18 | export class Logger { 19 | private prefix?: string 20 | private separator: string 21 | 22 | constructor(prefix?: string) { 23 | this.prefix = prefix ?? '' 24 | this.separator = prefix ? ': ' : '' 25 | } 26 | 27 | private print(method: 'warn' | 'log' | 'error', message: string) { 28 | if (!DEBUG) { 29 | return 30 | } 31 | 32 | // eslint-disable-next-line no-console 33 | console[method](`${this.prefix}${this.separator}${message}`) 34 | } 35 | 36 | log(message: string): void { 37 | this.print('log', message) 38 | } 39 | 40 | warn(message: string): void { 41 | this.print('warn', message) 42 | } 43 | 44 | error(message: string): void { 45 | this.print('error', message) 46 | } 47 | } 48 | 49 | const requestLogger = new Logger('Request') 50 | 51 | export async function fetchJson(url: string, headers?: Record): Promise { 52 | try { 53 | const req = new Request(url) 54 | 55 | req.headers = headers ?? {} 56 | 57 | requestLogger.log(url) 58 | 59 | return await req.loadJSON() 60 | } catch (error) { 61 | requestLogger.error(JSON.stringify(error)) 62 | 63 | throw new Error(error) 64 | } 65 | } 66 | 67 | /** 68 | * Send notification 69 | * 70 | * @param {string} title Notification title 71 | * @param {string} message Notification body 72 | * @param {string} id Notification id 73 | * @param {*} userInfo Notification userInfo object 74 | * @param {Date} date Schedule date, if not provided - will be scheduled asap 75 | * @param {*} actions Array of actions, [[title, action]] 76 | * @param {string} scriptName Explicit scriptName 77 | * @param {string} openURL url to open when click on notification 78 | */ 79 | type SendNotificationT = (params: { 80 | title: string, 81 | message: string, 82 | id?: string, 83 | userInfo?: Record, 84 | date?: Date, 85 | actions?: [title: string, action: string, destructive?: boolean][], 86 | scriptName?: string, 87 | openURL?: string, 88 | }) => void 89 | 90 | export const sendNotification: SendNotificationT = ({ 91 | title, 92 | message, 93 | id = 'notification', 94 | userInfo, 95 | date, 96 | actions = [], 97 | scriptName, 98 | openURL, 99 | }) => { 100 | const notification = new Notification() 101 | 102 | notification.title = title 103 | notification.identifier = `${Script.name()}${id}` 104 | notification.threadIdentifier = `${Script.name()}${id}_thread` 105 | 106 | if (userInfo) { 107 | notification.userInfo = { error: true } 108 | } 109 | 110 | if (date) { 111 | notification.setTriggerDate(date) 112 | } 113 | 114 | if (openURL) { 115 | notification.openURL = openURL 116 | } 117 | 118 | actions.forEach(action => { 119 | notification.addAction(...action) 120 | }) 121 | 122 | notification.scriptName = scriptName || Script.name() 123 | notification.body = message 124 | notification.schedule() 125 | } 126 | 127 | export const sendErrorNotification = (err: Error, params: Parameters[0]) => { 128 | sendNotification({ 129 | ...params, 130 | title: `Something went wrong with ${Script.name()}`, 131 | id: ERROR_NOTIFICATION_ID, 132 | message: err.message, 133 | }) 134 | } 135 | 136 | export const createAlert = (title: string, message: string, cancelAction?: string): Alert => { 137 | const alert = new Alert() 138 | 139 | alert.message = message 140 | alert.title = title 141 | 142 | if (cancelAction) { 143 | alert.addCancelAction(cancelAction) 144 | } 145 | 146 | return alert 147 | } 148 | 149 | // starts from 1 150 | export const getDaysInMonth = (year: number, month: number) => { 151 | return new Date(year, month, 0).getDate() 152 | } 153 | 154 | export const getDynamicColor = ({ light, dark }: DynamicPaletteT, key: keyof PaletteT, opacity?: number) => { 155 | const lightColor = light[key] ?? light.primary 156 | const darkColor = dark[key] ?? dark.primary 157 | 158 | return Color.dynamic(new Color(lightColor, opacity), new Color(darkColor, opacity)) 159 | } 160 | 161 | export const getDynamicGradient = ({ dark, light }: DynamicPaletteT) => { 162 | const startColor = Color.dynamic(new Color(light.bgStart), new Color(dark.bgStart)) 163 | const endColor = Color.dynamic(new Color(light.bgEnd), new Color(dark.bgEnd)) 164 | 165 | const gradient = new LinearGradient() 166 | 167 | gradient.colors = [startColor, endColor] 168 | 169 | return gradient 170 | } 171 | 172 | export const parseWidgetParams = (params: unknown): Record => { 173 | if (typeof params !== 'string') { 174 | return {} 175 | } 176 | 177 | return params.split(';').reduce((acc, item) => { 178 | const [key, value = ''] = item.split('=') 179 | 180 | return { ...acc, [key.trim()]: value.trim() ?? true } 181 | }, {}) 182 | } 183 | 184 | export const numberFormatterShort = new Intl.NumberFormat('ua', { 185 | notation: 'compact', 186 | compactDisplay: 'short', 187 | maximumFractionDigits: 1, 188 | minimumFractionDigits: 0, 189 | }).format 190 | 191 | export const currencyFormatter = new Intl.NumberFormat('uk-UA', { 192 | minimumFractionDigits: 2, 193 | }).format 194 | 195 | export const getDeviceAppearance = (): 'dark' | 'light' => { 196 | return Device.isUsingDarkAppearance() ? 'dark' : 'light' 197 | } 198 | 199 | Script.complete() 200 | -------------------------------------------------------------------------------- /src/utils.types.ts: -------------------------------------------------------------------------------- 1 | export type PaletteT = { 2 | bgStart: string, 3 | bgEnd: string, 4 | primary: string, 5 | secondary: string, 6 | accent?: string, 7 | } 8 | 9 | export type DynamicPaletteT = { 10 | dark: PaletteT, 11 | light: PaletteT, 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["ES2020"], 5 | "target": "ES2020", 6 | "noImplicitAny": true, 7 | "noImplicitThis": true, 8 | "strictFunctionTypes": true, 9 | "strictNullChecks": true, 10 | "typeRoots": ["node_modules/@types"], 11 | "types": ["scriptable-ios"], 12 | "forceConsistentCasingInFileNames": true, 13 | "outDir": "./build", 14 | "esModuleInterop": false 15 | }, 16 | "include": ["src/*"], 17 | "exclude": ["src/**/*.types.ts"] 18 | } 19 | --------------------------------------------------------------------------------