From e36e176f8e15f81e4111ecfbd3c1ae0e3908c33b Mon Sep 17 00:00:00 2001 From: John Wiseman Date: Fri, 13 Dec 2019 23:50:19 -0800 Subject: [PATCH] Initial commit. --- TODO.md | 4 + package-lock.json | 1614 +++++++++++++++++ package.json | 18 + shadow-cljs.edn | 20 + src/main/lemondronor/circlebot.cljs | 426 +++++ src/main/lemondronor/circlebot/adsbx.cljs | 161 ++ .../lemondronor/circlebot/generation.cljc | 110 ++ src/main/lemondronor/circlebot/geo.cljc | 112 ++ src/main/lemondronor/circlebot/logging.cljc | 11 + src/main/lemondronor/circlebot/logging.cljs | 37 + src/main/lemondronor/circlebot/pelias.cljs | 47 + src/main/lemondronor/circlebot/twitter.cljs | 47 + src/main/lemondronor/circlebot/util.cljs | 50 + .../circlebot/generation_test.cljs | 66 + src/test/lemondronor/circlebot_test.cljs | 102 ++ vrs-settings.json | 1 + 16 files changed, 2826 insertions(+) create mode 100644 TODO.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 shadow-cljs.edn create mode 100644 src/main/lemondronor/circlebot.cljs create mode 100644 src/main/lemondronor/circlebot/adsbx.cljs create mode 100644 src/main/lemondronor/circlebot/generation.cljc create mode 100644 src/main/lemondronor/circlebot/geo.cljc create mode 100644 src/main/lemondronor/circlebot/logging.cljc create mode 100644 src/main/lemondronor/circlebot/logging.cljs create mode 100644 src/main/lemondronor/circlebot/pelias.cljs create mode 100644 src/main/lemondronor/circlebot/twitter.cljs create mode 100644 src/main/lemondronor/circlebot/util.cljs create mode 100644 src/test/lemondronor/circlebot/generation_test.cljs create mode 100644 src/test/lemondronor/circlebot_test.cljs create mode 100644 vrs-settings.json diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..3cfd8c6 --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +- [ ] If we don't have neighbourhood or locality (just county), try to use a locality of a nearby venue. +- [ ] Add list with custom airport coords and radii. +- [ ] Lookup registration info. Possible sources: FAA, adsbexchange.com. +- [ ] Use Wikipedia graph to rank landmarks? diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4b3c7a7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1614 @@ +{ + "name": "circlebot", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", + "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.0.1.tgz", + "integrity": "sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "requires": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "requires": { + "env-variable": "0.0.x" + } + }, + "env-variable": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", + "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "requires": { + "pend": "~1.2.0" + } + }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "geolib": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/geolib/-/geolib-3.2.0.tgz", + "integrity": "sha512-GEsrhSlqrQG9a3nras/hDOnrKOzi4ngUOjpjKycXZWTaOrGHsz0vQQuKNwHSEA+X8Q57POnEn5iQ/GTz55r5Ag==" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", + "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "requires": { + "colornames": "^1.1.1" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "logform": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + }, + "mime-db": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", + "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" + }, + "mime-types": { + "version": "2.1.25", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", + "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", + "requires": { + "mime-db": "1.42.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=" + }, + "psl": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", + "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "puppeteer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-2.0.0.tgz", + "integrity": "sha512-t3MmTWzQxPRP71teU6l0jX47PHXlc4Z52sQv4LJQSZLq1ttkKS2yGM3gaI57uQwZkNaoGd0+HPPMELZkcyhlqA==", + "requires": { + "debug": "^4.1.0", + "extract-zip": "^1.6.6", + "https-proxy-agent": "^3.0.0", + "mime": "^2.0.3", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^2.6.1", + "ws": "^6.1.0" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "requires": { + "lodash": "^4.17.15" + } + }, + "request-promise-native": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", + "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "requires": { + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shadow-cljs": { + "version": "2.8.83", + "resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-2.8.83.tgz", + "integrity": "sha512-oqqSLARvYXopA9QLf5znrguvJOSRm65LYL9XHuRWaUbMGXlygqgCjSFgW1yREXmqUQ+i2TLoA1zulYO6nTTy6g==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1", + "node-libs-browser": "^2.0.0", + "readline-sync": "^1.4.7", + "shadow-cljs-jar": "1.3.1", + "source-map-support": "^0.4.15", + "which": "^1.3.1", + "ws": "^3.0.0" + }, + "dependencies": { + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } + } + }, + "shadow-cljs-jar": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/shadow-cljs-jar/-/shadow-cljs-jar-1.3.1.tgz", + "integrity": "sha512-IJSm4Gfu/wWDsOQ0wNrSxuaGdjzsd78us+3bop3cpWsoO2Igdu6VIBItYrZHRRBKl5LIZKXfnSh/2eWG3C1EFw==", + "dev": true + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "timers-browserify": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "twit": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/twit/-/twit-2.2.11.tgz", + "integrity": "sha512-BkdwvZGRVoUTcEBp0zuocuqfih4LB+kEFUWkWJOVBg6pAE9Ebv9vmsYTTrfXleZGf45Bj5H3A1/O9YhF2uSYNg==", + "requires": { + "bluebird": "^3.1.5", + "mime": "^1.3.4", + "request": "^2.68.0" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + } + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "winston": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", + "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "requires": { + "async": "^2.6.1", + "diagnostics": "^1.1.1", + "is-stream": "^1.1.0", + "logform": "^2.1.1", + "one-time": "0.0.4", + "readable-stream": "^3.1.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.3.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "winston-transport": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", + "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", + "requires": { + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "requires": { + "fd-slicer": "~1.0.1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..868b6b9 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "circlebot", + "version": "0.0.1", + "private": true, + "devDependencies": { + "shadow-cljs": "^2.8.78" + }, + "dependencies": { + "commander": "^4.0.1", + "geolib": "^3.2.0", + "js-yaml": "^3.13.1", + "puppeteer": "^2.0.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.8", + "twit": "^2.2.11", + "winston": "^3.2.1" + } +} diff --git a/shadow-cljs.edn b/shadow-cljs.edn new file mode 100644 index 0000000..b1e4a2c --- /dev/null +++ b/shadow-cljs.edn @@ -0,0 +1,20 @@ +{:source-paths + ["src/dev" + "src/main" + "src/test"] + :dependencies + [[org.clojure/math.combinatorics "0.1.5"] + [com.cemerick/url "0.1.1"] + [fipp "0.6.22"] + [instaparse "1.4.10"] + [kitchen-async "0.1.0-SNAPSHOT"]] + :builds + {:script + {:target :node-script + :main lemondronor.circlebot/main + :output-to "out/script.js"} + :test + {:target :node-test + :output-to "out/node-tests.js" + ;;:ns-regexp "-spec$" + :autorun true}}} diff --git a/src/main/lemondronor/circlebot.cljs b/src/main/lemondronor/circlebot.cljs new file mode 100644 index 0000000..3ae04a7 --- /dev/null +++ b/src/main/lemondronor/circlebot.cljs @@ -0,0 +1,426 @@ +(ns lemondronor.circlebot + (:require + ["commander" :as commander] + ["fs" :as fs] + [cljs.pprint :as pprint] + [cljs.reader :as reader] + [clojure.set :as set] + [clojure.string :as string] + [fipp.edn :as fippedn] + [kitchen-async.promise :as p] + [lemondronor.circlebot.adsbx :as adsbx] + [lemondronor.circlebot.generation :as generation] + [lemondronor.circlebot.geo :as geo] + [lemondronor.circlebot.logging :as logging] + [lemondronor.circlebot.pelias :as pelias] + [lemondronor.circlebot.twitter :as twitter] + [lemondronor.circlebot.util :as util])) + +(logging/deflog "circlebot" logger) + + +(defn parse-adsbexchange-ac-element [e] + (let [nilstr #(if (= % "") nil %) + numstr #(if (= % "") nil (js/parseFloat %))] + {:postime (numstr (e "postime")) + :lat (numstr (e "lat")) + :lon (numstr (e "lon")) + :icao (e "icao") + :registration (e "reg") + :alt (numstr (e "alt")) + :mlat? (= (e "mlat") "1") + :speed (numstr (e "spd")) + :squawk (nilstr (e "sqk")) + :military? (= (e "mil") "1") + :callsign (nilstr (e "call")) + :type (nilstr (e "type"))})) + + +(defn parse-adsbexchange-live-data [json-str] + {:aircraft + (map parse-adsbexchange-ac-element + (get + (js->clj (.parse js/JSON json-str)) + "ac"))}) + + +(defn get-adsbexchange-live-data [{:keys [url lat lon radius-nm api-key]}] + (let [url (->> [url + "lat" lat + "lon" lon + "dist" radius-nm] + (map str) + (string/join "/"))] + (p/let [http-result (util/http-get url {:headers {:api-auth api-key}})] + (let [result (parse-adsbexchange-live-data http-result)] + (log-verbose "Got %s aircraft from API" (count (:aircraft result))) + result)))) + + +;; We keep position reports going back this far. + +(def max-history-age-ms (* 25 60 1000)) + + +;; Given a vector of position history, removes old entries. + +(defn prune-history [history now] + (let [h (filterv #(< (- now (:time %)) max-history-age-ms) history)] + h)) + + +(defn update-history-db-record [db ac] + (let [icao (:icao ac) + new-history-entry {:lat (:lat ac) + :lon (:lon ac) + :time (:postime ac)}] + (if (contains? db icao) + (let [old-record (db icao) + history (:history (db icao)) + updated-record (-> old-record + (merge ac) + (assoc :history (conj history new-history-entry)))] + (assoc db icao updated-record)) + (assoc db icao (assoc ac :history [new-history-entry]))))) + + +(defn update-history-db-add-new-data [db new-data now] + (let [initial-count (count db) + initial-icaos (set (keys db)) + updated-db (reduce update-history-db-record db new-data) + new-count (count updated-db) + new-icaos (set/difference (set (keys updated-db)) initial-icaos)] + (log-verbose "Added %s new aircraft records (%s). %s total." + (- new-count initial-count) + (string/join "," new-icaos) + new-count) + updated-db)) + + +(defn prune-histories [db now] + (reduce-kv (fn [m k v] + (assoc m k (update v :history prune-history now))) + {} + db)) + + +;; Removes entries for any aircraft that we haven't seen in a while. + +(defn prune-records [db now] + (let [initial-count (count db) + initial-icaos (set (keys db)) + pruned-db (reduce-kv (fn [m k v] + (if (or (> (count (:history v)) 0) + (if-let [ended-circling-time (:ended-circling-time v)] + (< (- now ended-circling-time) (* 20 60 1000)))) + (assoc m k v) + m)) + {} + db) + new-count (count pruned-db) + pruned-icaos (set/difference initial-icaos (set (keys pruned-db)))] + (log-verbose "Pruned %s stale aircraft records (%s). %s remain" + (- initial-count new-count) + (string/join "," pruned-icaos) + new-count) + pruned-db)) + + +(defn debug-print [& args] + (apply println (drop 1 args)) + (println (first args)) + (first args)) + + +(defn update-history-db [db new-data now] + (-> db + (update-history-db-add-new-data new-data now) + (prune-histories now) + (prune-records now))) + + +(defn write-history-db [db path] + (fs/writeFileSync path (with-out-str (fippedn/pprint db))) + db) + + +;; Reads the history database from a path. Returns a promise that +;; resolves to the database value. + +(defn read-history-db [path] + (p/let [edn-str (util/read-file path {:encoding "utf-8"}) + db (reader/read-string edn-str)] + (log-verbose "Loaded %s aircraft from database %s" (count db) path) + db)) + + +(defn current-time [] + (/ (.getTime (js/Date.)) 1)) + + +;; This is how many degrees of turning we need to see over +;; max-history-age-ms ms to consider it a potential circling aircraft. + +(def curviness-threshold-degrees 1440) + + +(defn ac-desc [ac] + (str (:icao ac) " " (:lat ac) " " (:lon ac) + " #" (:registration ac) " " (:alt ac) " " (:curviness ac) " " + (:normalized-curviness ac))) + + +(defn screenshot [icao lat lon] + (p/let [image-path + (adsbx/screenshot-aircraft icao lat lon + {:timeout 30000 + ;;:headless? false + ;; :viewport {:width 1600 :height 800} + ;; :clip {:width 1600 :height 800 :x 0 :y 0} + :vrs-settings (fs/readFileSync "vrs-settings.json" "utf-8")})] + (log-warn "%s: Got screenshot" icao) + image-path)) + + +(defn circling? [ac] + (and (> (geo/flight-curviness (:history ac)) curviness-threshold-degrees) + (> (:alt ac) 300))) + + +;; Returns a vector of two elements, +;; [updated-database potentially-circling-aircraft] + +(defn detect-circles [db now] + (log-verbose "Detecting circles") + (loop [old-db (seq db) + new-db {} + potential-circles '()] + (if (seq old-db) + (let [[icao ac] (first old-db) + curviness (geo/flight-curviness (:history ac)) + ac (assoc ac + :curviness curviness + :normalized-curviness (geo/flight-normalized-curviness (:history ac))) + currently-circling? (circling? ac) + previously-circling? (:started-circling-time ac)] + (cond + (and currently-circling? + (not previously-circling?) + (or (nil? (:ended-circling-time ac)) + (> (- now (:ended-circling-time ac)) (* 20 60 1000)))) + (let [new-ac (assoc ac :started-circling-time now)] + (recur (rest old-db) + (assoc new-db icao new-ac) + (conj potential-circles new-ac))) + (and previously-circling? + (not currently-circling?)) + (let [started-circling-time (:started-circling-time ac) + new-ac (assoc ac + :started-circling-time nil + :ended-circling-time now)] + (log-info "%s: Circle terminated after %s secs: %s" + icao + (/ (- now started-circling-time) 1000) + (ac-desc ac)) + (recur (rest old-db) + (assoc new-db icao new-ac) + potential-circles)) + :else + (recur (rest old-db) + (assoc new-db icao ac) + potential-circles))) + [new-db potential-circles]))) + + +(defn parse-number [s] + (let [v (js/parseFloat s)] + (if (js/isNaN v) + (throw (str "Not a number: " s)) + v))) + + +(defn debug-prn [x msg] + (println msg (with-out-str (fippedn/pprint x))) + x) + + +(defn closest-airport [lat lon] + (p/let [results (pelias/nearby lat lon + {:categories "transport:air:aerodrome" + :boundary.circle.radius 7})] + (-> results + (get :features) + (->> (sort-by #(get-in % [:properties :distance]))) + first))) + + +(defn log-table [table keys] + (let [s (with-out-str (pprint/print-table keys table)) + lines (string/split-lines s)] + (doseq [l lines] + (log-info "%s" l))) + ) + +(def description-templates + (map generation/parse-template + [(str "[{registration}|{militaryregistration}, a military aircraft,|" + "Aircraft with unknown registration, ICAO {icao}|" + "Military aircraft with unknown registration, ICAO {militaryicao}] " + "?:[(callsign {callsign}) ]" + "is circling over [{neighbourhood}, {locality}|{neighbourhood}, {county}|{locality}] " + "?:[at {alt} feet, ]" + "?:[speed {speed} MPH, ]" + "?:[squawking {squawk}, ]" + "?:[{nearbydistance} miles from {nearbylandmark} ]" + "?:[#{registration}|#{militaryregistration}]")])) + +(defn expand-template [data] + (let [results (take 3 (generation/expand + description-templates + data + {:weights {:militaryregistration 4 + :registration 3 + :militaryicao 2 + :icao 1 + :neighbourhood 3 + :locality 3}}))] + (log-table results [:score :text]) + (first results))) + + +(defn generate-description [ac reverse wiki-nearby nearby] + (let [rev-props (:properties reverse) + nearby (:properties (first nearby)) + wiki-nearby (:properties (first wiki-nearby)) + info (cond-> (-> ac (dissoc :history) (merge rev-props)) + (:military? ac) + (-> (assoc :militaryregistration (:registration ac) + :militaryicao (:icao ac))) + wiki-nearby + (assoc :nearbylandmark (:name wiki-nearby) + :nearbydistance (:distance wiki-nearby)) + (and nearby (not wiki-nearby)) + (assoc :nearbylandmark (:name nearby) + :nearbydistance (:distance nearby)) + (:speed ac) + (assoc :speed (.toFixed (* (:speed ac) 1.15078) 0)) + (= (:registration ac) (:callsign ac)) + (dissoc :callsign) + ;; TODO: If layer is "county", find the nearest city. + ) + expansion (expand-template info)] + (log-info "Description data: %s" info) + (log-info "Description [score: %s] %s" (:score expansion) (:text expansion)) + (:text expansion))) + + +(defn feature-has-wikipedia-page? [f] + (get-in f [:addendum :osm :wikipedia])) + + +;; If the centroid of the aircraft's positions is less than this close +;; to an airport, then it's probably just doinf flight training. +;;(def minimum-airport-distance-miles 2.5) +(def minimum-airport-distance-miles 0) + +(defn process-potential-circle [ac config now] + (p/let [icao (:icao ac) + centroid (geo/centroid (filter #(< (- now (:time %)) (* 3 60 1000)) (:history ac))) + lat (:lat centroid) + lon (:lon centroid) + airport (closest-airport lat lon) + airport-properties (:properties airport)] + (log-info "%s: Recent centroid is %s %s" icao lat lon) + (if airport + (log-info "%s: Closest airport is %s, distance: %s" + (:icao ac) (:label airport-properties) (:distance airport-properties)) + (log-info "%s: No airports nearby" (:icao ac))) + (if (and airport-properties (<= (:distance airport-properties) minimum-airport-distance-miles)) + (log-info "%s: Filtering out because it's %s miles (minimum is %s) from %s" + (:icao ac) + (:distance airport-properties) + minimum-airport-distance-miles + (:label airport-properties) + ()) + (do + (p/let [coarse (pelias/reverse lat lon {:layers "coarse"})] + (let [coarse (first (:features coarse))] + (log-info "%s: Reverse geocode: %s" icao (:properties coarse)) + ;; Note that if we're over the ocean we get null :( + (if (or (nil? coarse) + ;; TODO: Filter using the layer hierarchy; we want + ;; anything smaller than "region" (state). + (= (get-in coarse [:properties :name]) "California")) + (log-info "%s: Filtering out because it is outside Los Angeles County" (:icao ac)) + (p/then (p/all [(screenshot (:icao ac) lat lon) + (p/let [nearby (pelias/nearby lat lon {:boundary.circle.radius 100 + :layers "venue" + :size 50}) + nearby (:features nearby) + wiki-nearby (filter feature-has-wikipedia-page? nearby)] + (log-info "%s: Nearby geo search: %s potential landmarks, %s with wikipedia pages" + icao (count nearby) (count wiki-nearby)) + (doseq [f wiki-nearby] + (log-info "%s: %s %s" + icao + (get-in f [:properties :label] f) + (get-in f [:properties :addendum] f))) + (let [description (generate-description ac coarse wiki-nearby nearby)] + (log-warn "Description: %s" description) + description))]) + (fn [[image-path description]] + (if (and image-path description) + (if (:twitter config) + (twitter/tweet (twitter/twit (:twitter config)) + description + [image-path]) + (log-warn "Skipping tweeting: No twitter config provided")) + (log-warn "Skipping tweet %s %s" image-path description))))))))))) + + +(defn process-potential-circles [acs config now] + (p/loop [acs acs] + (when (seq acs) + (p/do + (process-potential-circle (first acs) config now) + (p/recur (rest acs)))))) + + +(def history-db-path "advisory-circular.db") +(def secrets-path "secrets.yaml") + + +(defn main [& args] + (-> commander + (.requiredOption "--lat " "Latitude of the circle of region of interest" parse-number) + (.requiredOption "--lon " "Longitude of the circle of the region of interest" parse-number) + (.requiredOption "--url " "API url") + (.option "--queue " "Queue URL (graphile)") + (.option "--radius " "Radius of the circle of interest, in nautical miles" 20 parse-number) + (.parse (.-argv js/process))) + (let [start-time (current-time)] + (p/then (p/all [(read-history-db history-db-path) + (util/read-config secrets-path)]) + (fn [[db config]] + (p/let [data (get-adsbexchange-live-data + {:url (.-url commander) + :api-key (get-in config [:adsbx :api-key]) + :lat (.-lat commander) + :lon (.-lon commander) + :radius-nm (.-radius commander)}) + now (current-time) + [new-db potential-circles] (-> db + (update-history-db (:aircraft data) now) + (detect-circles now))] + (p/do + (when potential-circles + (doseq [ac potential-circles] + (log-warn "%s: New circle detected: %s" (:icao ac) (ac-desc ac))) + (process-potential-circles potential-circles config now)) + (write-history-db new-db history-db-path) + (let [end-time (current-time)] + (log-info + "Completed processing in %s seconds: tracking %s aircraft; %s potential circles" + (/ (- end-time start-time) 1000) + (count new-db) + (count potential-circles))))))))) diff --git a/src/main/lemondronor/circlebot/adsbx.cljs b/src/main/lemondronor/circlebot/adsbx.cljs new file mode 100644 index 0000000..3bad5bd --- /dev/null +++ b/src/main/lemondronor/circlebot/adsbx.cljs @@ -0,0 +1,161 @@ +;; Scripts a real live chromium browser to take screenshots from ADS-B +;; Exchange. + +(ns lemondronor.circlebot.adsbx + (:require + ["fs" :as fs] + ["puppeteer" :as puppeteer] + [clojure.string :as string] + [goog.string :as gstring] + [kitchen-async.promise :as p] + [lemondronor.circlebot.logging :as logging] + goog.string.format)) + +(logging/deflog "adsbx" logger) + +(def user-agent "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/53 (KHTML, like Gecko) Chrome/15.0.87") + + +;; (defn wait-for-property [page prop-string] +;; (let [pieces (string/split prop-string ".")] +;; (p/loop [pieces-checking (take 1 pieces) +;; pieces-remaining (drop 1 pieces) +;; ] +;; (println "Waiting for" (string/join "." pieces-checking)) +;; (p/resolve (.waitForFunction page (string/join "." pieces-checking))) +;; (when (seq pieces-remaining) +;; (recur (concat pieces-checking (take 1 pieces-remaining)) +;; (drop 1 pieces-remaining)))))) + + +(defn timeout + ([ms] + (timeout ms nil)) + ([ms v] + (p/promise [resolve] + (js/setTimeout #(resolve v) ms)))) + + +(defn current-time-secs [] + (/ (.getTime (js/Date.)) 1000)) + + +(defn import-settings [page settings] + (let [import-settings-btn-xpath "//button[contains(text(), 'Import Settings')]"] + (p/try + (log-debug "Importing settings") + (p/all [(.waitForNavigation page) + (.waitForXPath page import-settings-btn-xpath) + (.goto page + "https://global.adsbexchange.com/VirtualRadar/settings.html" + (clj->js {:referer "https://adsbexchange.com/"}))]) + (timeout 50) + (p/let [import-settings-btn (.$x page import-settings-btn-xpath)] + (.click (nth import-settings-btn 0)) + (.waitForSelector page "textarea") + (p/let [textarea (.$x page "//textarea")] + (.evaluate page + (fn [el value] + (set! (.-value el) value)) + (nth textarea 0) + settings)) + (p/let [import-btn (.$x page "//button[text()='Import']")] + (.click (nth import-btn 0)) + (timeout 100))) + (log-debug "Imported settings")))) + + +(defn screenshot-aircraft + ([icao lat lon] + (screenshot-aircraft icao lat lon {})) + ([icao lat lon options] + (log-info "Taking screenshot of aircraft %s at %s %s" icao lat lon) + (let [icao-selector (gstring/format "//td[contains(., '%s')]" + (string/upper-case icao)) + launch-options {:headless (get options :headless? true) + :defaultViewport (get options :viewport {:width 1600 + :height 800})} + took-screenshot?_ (atom false)] + (p/race + [(p/promise [resolve reject] + (js/setTimeout + #(when-not @took-screenshot?_ + (log-error "Screenshot timing out") + (reject (js/Error. "Timeout"))) + (get options :timeout 30000))) + (p/let [browser (puppeteer/launch (clj->js launch-options)) + page (.newPage browser)] + (if-let [user-agent (:user-agent options)] + (.setUserAgent page user-agent)) + (if-let [vrs-settings (:vrs-settings options)] + (import-settings page vrs-settings)) + (log-debug "Navigating") + (p/all [(.waitForNavigation page) + (.goto page + "https://global.adsbexchange.com/VirtualRadar/desktop.html" + (clj->js {:referer "https://adsbexchange.com/"}))]) + ;; Wait until the page has loaded the pan and zoom functions + ;; we need. + (log-debug "Waiting for script objects") + (.waitForFunction page "window.VRS") + (.waitForFunction page "VRS.bootstrap") + (.waitForFunction page "VRS.bootstrap.pageSettings") + (.waitForFunction page "VRS.bootstrap.pageSettings.mapPlugin") + (log-debug "Xooming") + ;; Zoom. + (.evaluate + page + (gstring/format "VRS.bootstrap.pageSettings.mapPlugin.setZoom(%s);" (get options :zoom 13))) + (timeout 500) + ;; Pan to the coordinates of interest. + (log-debug "Panning") + (.evaluate + page + (gstring/format + "VRS.bootstrap.pageSettings.mapPlugin.panTo({lat:%f,lng:%f});" + lat + lon)) + ;; Once we pan to our area of interest it can take some time + ;; for VRS to load in the map tiles and the aircraft present + ;; in that area. So we wait before clicking. + (log-debug "Waiting for aircraft") + (.waitForXPath page icao-selector) + ;; Now try to click on the aircraft of interest in the list + ;; display. + (log-debug "Selecting aircraft") + (p/let [aircraft-entry (.$x page icao-selector)] + (.click (nth aircraft-entry 0))) + ;; Click "Show on map" to center it. + (p/let [show-on-map (.$x page "//a[contains(., 'Show on map')]")] + (.click (nth show-on-map 0))) + ;; Wait for tiles to load. I wonder if we could try something + ;; like this: + ;; https://stackoverflow.com/questions/22288933/how-to-tell-when-all-visible-tiles-have-fully-loaded + (timeout 3000) + (let [path (get options :output-path (str "screenshot-" + (.toFixed (current-time-secs) 0) + "-" + icao + ".png"))] + (log-info "Writing %s" path) + (p/do + (.screenshot page (clj->js {:path path + :clip (get options :clip {:width 860 + :height 680 + :x 46 + :y 46})})) + (log-info "screenshot done") + (reset! took-screenshot?_ true) + (log-info "closing browser") + (.close browser) + (log-info "closing browser done") + path)))])))) + + +;; (defn main [& args] +;; (let [settings (fs/readFileSync "vrs-settings.json" "utf-8") +;; zoom 13 +;; icao (nth args 0) +;; lat (js/parseFloat (nth args 1)) +;; lon (js/parseFloat (nth args 2))] +;; (screenshot-aircraft icao lat lon {:zoom zoom :vrs-settings settings}))) diff --git a/src/main/lemondronor/circlebot/generation.cljc b/src/main/lemondronor/circlebot/generation.cljc new file mode 100644 index 0000000..300e813 --- /dev/null +++ b/src/main/lemondronor/circlebot/generation.cljc @@ -0,0 +1,110 @@ +(ns lemondronor.circlebot.generation + (:require + [clojure.math.combinatorics :as combo] + [clojure.string :as string] + [instaparse.core :as insta])) + + +(def ^:private %parse-template + (insta/parser + " = term | implicit-sequence + implicit-sequence = term (term)+ + = sequence | optional | choice | varref | text + = sequence | optional | choice | varref | no-ws-text + sequence = <'['> term+ <']'> + optional = <'⁇'> no-ws-term + choice = <'['> pattern <'|'> pattern (<'|'> pattern)* <']'> + varref = <'{'> #'[a-zA-Z\\_][a-zA-Z0-9\\-\\_\\.]+' <'}'> + text = #'[^\\{\\[\\]\\|⁇]+' + no-ws-text = #'[^\\{\\[\\]\\|⁇\\s]+' +")) + + +;; Because I don't know enough to write the correct negative-lookahead +;; regex so that "?:" doesn't get parsed into a `text` or +;; `no-ws-text`, I lex it into a unicode character that I'm sure no +;; one will ever use (right?). + +(defn ^:private lex-template [template] + (string/replace template "?:" "⁇")) + + +(defn parse-template [template] + (let [result (-> template lex-template %parse-template) + ;; Convert :implicit-sequence into :sequence and :no-ws-text + ;; into :text. + xformed-result (insta/transform + {:implicit-sequence (fn [& children] + (into [:sequence] children)) + :no-ws-text (fn [text] + [:text text])} + result)] + ;; Put in the implicit :sequence if necessary. + (if (= (count xformed-result) 1) + (first xformed-result) + (into [:sequence] xformed-result)))) + + +(defmulti expand% (fn [template data] (first template))) + +(defmethod expand% :varref [template data] + (let [var (keyword (second template)) + val (data var)] + (if (or (nil? val) (= val "")) + '() + (list {:varrefs [var] :text (str (data var))})))) + +(defmethod expand% :text [template data] + (list {:varrefs [] :text (second template)})) + +(defmethod expand% :optional [template data] + (concat (list {:varrefs [] :text ""}) + (expand% (second template) data))) + +(defmethod expand% :choice [template data] + (apply concat (map #(expand% % data) (rest template)))) + +(defmethod expand% :sequence [template data] + (let [merge-expansions1 (fn + ([a] a) + ([a b] {:varrefs (concat (:varrefs a) (:varrefs b)) + :text (str (:text a) (:text b))})) + merge-expansions (fn [args] + (reduce merge-expansions1 args)) + things (map #(expand% % data) (rest template)) + chains (apply combo/cartesian-product things)] + (map merge-expansions chains))) + + +;; A simple expansion scorer that gives high scores to expansions that +;; use more variables, with optional per-variable weights. + +(defn score-by-varref-count [expansion weights] + (assoc expansion + :score (reduce + (map #(weights % 1) (:varrefs expansion))))) + + +(defn expand + ([templates data] + (expand templates data {})) + ([templates data options] + (->> (apply concat (map #(expand% % data) templates)) + (map (get options :scorer #(score-by-varref-count % (get options :weights {})))) + (sort-by :score) + reverse))) + + +(defn generate-all + ([templates data] + (generate-all templates data {})) + ([templates data options] + (->> (expand templates data options) + (map :text)))) + + +(defn generate + ([templates data] + (generate templates data {})) + ([templates data options] + (-> (generate-all templates data options) + first))) diff --git a/src/main/lemondronor/circlebot/geo.cljc b/src/main/lemondronor/circlebot/geo.cljc new file mode 100644 index 0000000..1431ce1 --- /dev/null +++ b/src/main/lemondronor/circlebot/geo.cljc @@ -0,0 +1,112 @@ +;; Most of this was written before we started using geolib. Maybe +;; replace with geolib functions? + +(ns lemondronor.circlebot.geo + (:require + [lemondronor.circlebot.logging :as logging] + ["geolib" :as geolib])) + +(logging/deflog "geo" logger) + + +(def to-radians + #?(:clj Math/toRadians + :cljs (fn [d] (/ (* d Math/PI) 180)))) + + +(def to-degrees + #?(:clj Math/toDegrees + :cljs (fn [d] (/ (* d 180) Math/PI)))) + + +(defn centroid [coords] + (let [center (js->clj (geolib/getCenter (clj->js coords)) :keywordize-keys true) + xcenter {:lat (:latitude center) + :lon (:longitude center)}] + xcenter)) + + +(defn distance + "Returns the distance between two positions, in km." + [pos1 pos2] + (let [earth-radius 6372.8 ;; km + sin2 (fn sin2 ^double [^double theta] (* (Math/sin theta) (Math/sin theta))) + alpha (fn alpha ^double [^double lat1 ^double lat2 ^double delta-lat ^double delta-lon] + (+ (sin2 (/ delta-lat 2.0)) + (* (sin2 (/ delta-lon 2)) (Math/cos lat1) (Math/cos lat2)))) + {lat1 :lat lon1 :lon} pos1 + {lat2 :lat lon2 :lon} pos2 + delta-lat (to-radians (- ^double lat2 ^double lat1)) + delta-lon (to-radians (- ^double lon2 ^double lon1)) + lat1 (to-radians lat1) + lat2 (to-radians lat2)] + (* earth-radius 2 + (Math/asin (Math/sqrt (alpha lat1 lat2 delta-lat delta-lon)))))) + + +(defn position= [p1 p2] + (= p1 p2)) + + +(defn bearing + "Returns the bearing from one position to another, in degrees." + [pos1 pos2] + (if (position= pos1 pos2) + nil + (let [{lat1 :lat lon1 :lon} pos1 + {lat2 :lat lon2 :lon} pos2 + lat1 ^double (to-radians lat1) + lat2 ^double (to-radians lat2) + lon-diff ^double (to-radians (- ^double lon2 ^double lon1)) + y ^double (* (Math/sin lon-diff) (Math/cos lat2)) + x ^double (- (* (Math/cos lat1) (Math/sin lat2)) + (* (Math/sin lat1) (Math/cos lat2) (Math/cos lon-diff)))] + (mod (+ (to-degrees (Math/atan2 y x)) 360.0) 360.0)))) + + +(defn bearing-diff + "Computes difference between two bearings. Result is [-180, 180]." + ^double [a b] + (let [d (- 180.0 (mod (+ (- a b) 180.0) 360.0))] + d)) + + +(defn spurious-bearing [bearing] + (nil? bearing)) + + +(defn spurious-bearing-diff [^double bearing] + (> (Math/abs bearing) 160)) + + +(defn curviness + "Computes the total curvature of a sequence of bearings." + ^double [bearings] + (Math/abs + ^double (reduce (fn [^double sum [^double a ^double b]] + (let [d (bearing-diff a b)] + (if (spurious-bearing-diff d) + sum + (+ sum d)))) + 0.0 + (partition 2 1 bearings)))) + + +;; flight is a vector of {:lat :lon > (partition 2 1 flight) + (map #(apply bearing %)) + (filter #(not (spurious-bearing %))) + curviness)) + + +(defn flight-distance [flight] + (->> (partition 2 1 flight) + (map #(apply distance %)) + (apply +))) + + +(defn flight-normalized-curviness [flight] + (/ (flight-curviness flight) + (flight-distance flight))) diff --git a/src/main/lemondronor/circlebot/logging.cljc b/src/main/lemondronor/circlebot/logging.cljc new file mode 100644 index 0000000..e614c00 --- /dev/null +++ b/src/main/lemondronor/circlebot/logging.cljc @@ -0,0 +1,11 @@ +(ns lemondronor.circlebot.logging) + +(defmacro deflog [service name] + `(do + (def ~name (logging/get-logger ~service)) + (let [log# (.bind (.-log ~name) ~name)] + (defn ~'log-debug [& args#] (apply log# "debug" args#)) + (defn ~'log-verbose [& args#] (apply log# "verbose" args#)) + (defn ~'log-warn [& args#] (apply log# "warn" args#)) + (defn ~'log-info [& args#] (apply log# "info" args#)) + (defn ~'log-error [& args#] (apply log# "error" args#))))) diff --git a/src/main/lemondronor/circlebot/logging.cljs b/src/main/lemondronor/circlebot/logging.cljs new file mode 100644 index 0000000..1a66ad4 --- /dev/null +++ b/src/main/lemondronor/circlebot/logging.cljs @@ -0,0 +1,37 @@ +(ns lemondronor.circlebot.logging + (:require + [goog.string :as gstring] + goog.string.format + ["winston" :as winston]) + (:require-macros + [lemondronor.circlebot.logging])) + +(let [createLogger (.-createLogger winston) + format (.-format winston) + transports (.-transports winston) + printf-fmt #(gstring/format "%s%-7s %-9s/%-18s| %s" + (.-timestamp %) + (.-ms %) + (.-service %) + (.-level %) + (.-message %))] + (def logger (createLogger + #js {:level "verbose" + :format (.combine + format + (.colorize format #js {:all true}) + (.timestamp format #js {:format "YYYYMMDD HHmmss"}) + (.errors format #js {:stack true}) + (.splat format) + (.timestamp format) + (.label format) + (.ms format) + (.json format)) + :defaultMeta #js {}})) + (.add logger (new (.-Console transports) + #js {:format (.combine format + (.printf format printf-fmt))}))) + + +(defn get-logger [service] + (.child logger #js {:service service})) diff --git a/src/main/lemondronor/circlebot/pelias.cljs b/src/main/lemondronor/circlebot/pelias.cljs new file mode 100644 index 0000000..3427503 --- /dev/null +++ b/src/main/lemondronor/circlebot/pelias.cljs @@ -0,0 +1,47 @@ +;; Simple interface to the pelias geocoder/reverse geocoder. + +(ns lemondronor.circlebot.pelias + (:refer-clojure :exclude [reverse]) + (:require + [cemerick.url :as c-url] + [kitchen-async.promise :as p] + [lemondronor.circlebot.logging :as logging] + [lemondronor.circlebot.util :as util])) + +(logging/deflog "pelias" logger) + + +(def base-pelias-url "http://raytheon.local:4000/v1") + + +;; Does an HTTP GET to a pelias API url. Returns a promise that +;; resolves to the API results. + +(defn pelias-get [base-url endpoint options] + (p/let [json-str (util/http-get (c-url/url base-url endpoint) options)] + (let [x (js->clj (.parse js/JSON json-str) :keywordize-keys true)] + x))) + + +;; Performs a pelias "nearby" query. Returns a promsie that resolves +;; to the query results. + +(defn nearby + ([lat lon options] + (log-verbose "Performing nearby query %s %s %s" lat lon options) + (pelias-get base-pelias-url "nearby" + {:query (assoc options + :point.lat lat + :point.lon lon)}))) + + +;; Performs a pelias "reverse" query. Retuns a promise that resolves +;; to the query result. + +(defn reverse + ([lat lon options] + (log-verbose "Performing reverse query %s %s %s" lat lon options) + (pelias-get base-pelias-url "reverse" + {:query (assoc options + :point.lat lat + :point.lon lon)}))) diff --git a/src/main/lemondronor/circlebot/twitter.cljs b/src/main/lemondronor/circlebot/twitter.cljs new file mode 100644 index 0000000..6dab93a --- /dev/null +++ b/src/main/lemondronor/circlebot/twitter.cljs @@ -0,0 +1,47 @@ +(ns lemondronor.circlebot.twitter + (:require + ["fs" :as fs] + [kitchen-async.promise :as p] + [lemondronor.circlebot.logging :as logging] + [lemondronor.circlebot.util :as util] + ["twit" :as Twit])) + +(def fsprom (.-promises fs)) + +(logging/deflog "twitter" logger) + + +;; Creates a new twit object. + +(defn twit [config] + (Twit. (clj->js {:consumer_key (:consumer-key config) + :consumer_secret (:consumer-secret config) + :access_token (:access-token config) + :access_token_secret (:access-token-secret config)}))) + + +;; Uploads an image to twitter. Returns a promise that resolves to the +;; new media ID of the image. + +(defn upload-image [twit path] + (log-info "Uploading media to twitter: %s" path) + (p/let [b64content (util/read-file path {:encoding "base64"}) + result (.post twit "media/upload" (clj->js {:media_data b64content})) + media-id (get-in (js->clj result :keywordize-keys true) [:data :media_id_string])] + (log-info "%s got media ID %s" path media-id) + media-id)) + + +;; Posts a tweet with optional multiple media. Returns a promise that +;; resolves to the response result. + +(defn tweet [twit status image-paths] + (p/then (p/all (map #(upload-image twit %) image-paths)) + (fn [media-ids] + (log-info "Tweeting status:'%s' with media: %s" status media-ids) + (p/let [result (.post twit "statuses/update" + (clj->js {:status status + :media_ids [media-ids]})) + result (js->clj result :keywordize-keys true)] + (log-info "Tweet posted") + result)))) diff --git a/src/main/lemondronor/circlebot/util.cljs b/src/main/lemondronor/circlebot/util.cljs new file mode 100644 index 0000000..32cfa3b --- /dev/null +++ b/src/main/lemondronor/circlebot/util.cljs @@ -0,0 +1,50 @@ +(ns lemondronor.circlebot.util + (:require [cemerick.url :as c-url] + ["fs" :as fs] + [kitchen-async.promise :as p] + [lemondronor.circlebot.logging :as logging] + ["request-promise-native" :as request] + ["js-yaml" :as yaml])) + +(logging/deflog "util" logger) + +(def fs-promises (.-promises fs)) + + +;; Reads a file, returns a promise resolving to the file contents. + +(defn read-file [path options] + (.readFile fs-promises path (clj->js options))) + + +;; Writes a file, returns a promise that resolves to no arguments on success. + +(defn write-file [path data options] + (.writeFile fs-promises path data (clj->js options))) + + +;; Reads a YAML config file. Returns a promise that resolves to the parsed YAML. + +(defn read-config [path] + (log-verbose "Reading config file %s" path) + (p/let [data (read-file path {:encoding "utf-8"})] + (-> (yaml/safeLoad data) + (js->clj :keywordize-keys true)))) + + +;; Fetches a URL. Returns a promise that resolves to the body of the +;; response. + +(defn http-get + ([url] + (http-get url {})) + ([url options] + (let [query (or (:query options) {}) + options (dissoc options :query) + url (-> (c-url/url (str url)) + (assoc :query query))] + (p/do + (log-info "Fetching %s" url) + (p/let [result (.get request + (clj->js (merge {:url (str url) :gzip true} options)))] + result))))) diff --git a/src/test/lemondronor/circlebot/generation_test.cljs b/src/test/lemondronor/circlebot/generation_test.cljs new file mode 100644 index 0000000..d9be395 --- /dev/null +++ b/src/test/lemondronor/circlebot/generation_test.cljs @@ -0,0 +1,66 @@ +(ns lemondronor.circlebot.generation-test + (:require [cljs.test :refer (deftest is)] + [lemondronor.circlebot.generation :as generation])) + +(deftest parse-template + (is (= [:optional [:varref "woo"]] + (generation/parse-template "?:{woo}"))) + (is (= [:sequence [:text "Hello "] [:varref "foo"] [:text "!"]] + (generation/parse-template "Hello {foo}!"))) + (is (= [:sequence + [:text "Hi "] [:varref "name"] [:text ", "] + [:optional [:varref "woo"]] + [:text " "] + [:choice + [:text "What up?"] + [:text "Seeya"]]] + (generation/parse-template "Hi {name}, ?:{woo} [What up?|Seeya]"))) + (is (= [:sequence + [:text "Hi "] [:varref "name"] [:text ", "] + [:optional + [:sequence [:varref "woo"] [:text " "]]] + [:choice [:text "What up?"] [:text "Seeya"]]] + (generation/parse-template "Hi {name}, ?:[{woo} ][What up?|Seeya]")))) + +(deftest generate + (is (= ["Hello!"] + (generation/generate-all + [(generation/parse-template "Hello!")] + {}))) + (is (= "Hello!" + (generation/generate + [(generation/parse-template "Hello!")] + {})))) + +(deftest generate-test + (is (= [{:varrefs [], :text ""} {:varrefs [], :text "woo"}] + (generation/expand% + (generation/parse-template "?:woo")))) + (is (= [{:varrefs [], :text ""} {:varrefs [], :text "woo bar"}] + (generation/expand% + (generation/parse-template "?:[woo bar]")))) + (is (= [{:varrefs [], :text ""} {:varrefs [:woo], :text "WOO"}] + (generation/expand% + (generation/parse-template "?:{woo}") + {:woo "WOO"}))) + (is (= ["Hi Tim, What up?" + "Hi Tim, Seeya" + "Hi Tim, WOO! What up?" + "Hi Tim, WOO! Seeya"] + (map :text + (generation/expand% + (generation/parse-template "Hi {name}, ?:[{woo} ][What up?|Seeya]") + {:name "Tim" :woo "WOO!"}))))) + +(deftest generate-scoring-with-weights + (is (= ["Neighborhood" + "Locality Region Country"] + (map :text + (generation/expand% + (generation/parse-template "[{neighborhood}|{locality} {region} {country}]") + {:neighborhood "Neighborhood" + :locality "Locality" + :region "Region" + :country "Country"} + {:neighborhood 5 + :locality 2}))))) diff --git a/src/test/lemondronor/circlebot_test.cljs b/src/test/lemondronor/circlebot_test.cljs new file mode 100644 index 0000000..81fb406 --- /dev/null +++ b/src/test/lemondronor/circlebot_test.cljs @@ -0,0 +1,102 @@ +(ns lemondronor.circlebot-test + (:require [cljs.test :refer (deftest is testing)] + [lemondronor.circlebot :as circlebot])) + +(def epsilon 0.0000001) + +(defn a= [a b] + (< (Math/abs (- a b)) epsilon)) + +;; (deftest bearing->angle +;; (is (angles= (cooleradar/bearing->angle 0) (/ Math/PI 2))) +;; (is (angles= (cooleradar/bearing->angle (/ Math/PI 2)) 0)) +;; (is (angles= (cooleradar/bearing->angle Math/PI) (* 2 (/ 3 4) Math/PI)))) + +(deftest parse-adsbexchange-ac-element + (let [ac {"gnd" "0", "trt" "2", "pos" "1", "call" "", "mil" "0", "ttrk" "", + "dst" "14.6", "reg" "HL7634", "altt" "0", "cou" "South Korea", + "postime" "1575488288571", "galt" "139", "mlat" "0", "spd" "10.5", + "sqk" "", "talt" "", "wtc" "3", "alt" "100", "lon" "-118.416438", + "opicao" "AAR", "interested" "0", "trak" "264.4", "type" "A388", + "trkh" "0", "icao" "71BE34", "lat" "33.937908", "vsit" "1", + "tisb" "0", "vsi" "0", "sat" "0"}] + (is (= (circlebot/parse-adsbexchange-ac-element ac) + {:icao "71BE34" + :registration "HL7634" + :callsign nil + :lon -118.416438 + :lat 33.937908 + :alt 100 + :speed 10.5 + :squawk nil + :military? false + :mlat? false + :postime 1575488288571})))) + + +(deftest prune-history + (let [hist [{:time 0 :id 0} {:time 1000000 :id 1} {:time 2000000 :id 2}]] + (is (= (circlebot/prune-history hist 2500000) + [{:time 2000000 :id 2}])))) + + +(deftest update-history-db-record + (testing "Updating an existing record" + (let [db {"0" {:icao "0" + :lat 0 + :lon 0 + :postime 3000 + :history [{:time 1000 :id 0} + {:time 2000 :id 1} + {:time 3000 :id 2}]} + "1" :anything} + record {:icao "0" + :lat 1 + :lon 1 + :postime 3500}] + (is (= (circlebot/update-history-db-record db record) + {"0" {:icao "0" + :lat 1 + :lon 1 + :postime 3500 + :history [{:time 1000 :id 0} + {:time 2000 :id 1} + {:time 3000 :id 2} + {:time 3500 :lat 1 :lon 1}]} + "1" :anything})))) + (testing "Adding a new record" + (let [db {"0" {:icao "0" + :lat 0 + :lon 0 + :postime 3000 + :history [{:time 1000 :id 0} + {:time 2000 :id 1} + {:time 3000 :id 2}]} + "1" :anything} + record {:icao "2" + :lat 1 + :lon 1 + :postime 3500}] + (is (= (circlebot/update-history-db-record db record) + {"0" {:icao "0" + :lat 0 + :lon 0 + :postime 3000 + :history [{:time 1000 :id 0} + {:time 2000 :id 1} + {:time 3000 :id 2}]} + "1" :anything + "2" {:icao "2" + :lat 1 + :lon 1 + :postime 3500 + :history [{:time 3500 :lat 1 :lon 1}]}}))))) + + +(deftest generation + (let [data {:locality "Palmdale", :continent "North America", :military? true, :alt 3850, :speed "209", :normalized-curviness 14.768651250300287, :accuracy "centroid", :country_a "USA", :continent_gid "whosonfirst:continent:102191575", :name "Palmdale", :squawk "5330", :icao "AE1482", :county_a "LO", :county "Los Angeles County", :source "whosonfirst", :gid "whosonfirst:locality:85923493", :curviness 1269.8089810739468, :locality_gid "whosonfirst:locality:85923493", :region "California", :militaryicao "AE1482", :region_a "CA", :nearbydistance 8.167, :callsign "RAIDR49", :layer "locality", :mlat? false, :country_gid "whosonfirst:country:85633793", :label "Palmdale, CA, USA", :id "85923493", :lon -118.00375, :region_gid "whosonfirst:region:85688637", :lat 34.661074, :militaryregistration "166765", :county_gid "whosonfirst:county:102086957", :started-circling-time 1576266715691, :distance 6.855, :source_id "85923493", :registration "166765", :confidence 0.5, :country "United States", :postime 1576266689756, :nearbylandmark "Living Faith Foursquare Church"}] + (is (re-find #"military" (-> (circlebot/expand-template data) :text)))) + (let [data {:locality "Palmdale", :continent "North America", :military? true, :alt 3200, :speed "161", :normalized-curviness 15.783422690487765, :accuracy "centroid", :country_a "USA", :continent_gid "whosonfirst:continent:102191575", :name "Palmdale", :squawk "5330", :icao "AE1482", :county_a "LO", :county "Los Angeles County", :source "whosonfirst", :gid "whosonfirst:locality:85923493", :curviness 1098.803548060181, :locality_gid "whosonfirst:locality:85923493", :region "California", :militaryicao "AE1482", :region_a "CA", :nearbydistance 7.828, :callsign "RAIDR49", :layer "locality", :mlat? false, :country_gid "whosonfirst:country:85633793", :label "Palmdale, CA, USA", :id "85923493", :lon -118.049183, :region_gid "whosonfirst:region:85688637", :lat 34.649808, :militaryregistration "166765", :county_gid "whosonfirst:county:102086957", :started-circling-time 1576267564959, :distance 6.336, :source_id "85923493", :registration "166765", :confidence 0.5, :country "United States", :postime 1576267555709, :nearbylandmark "Living Faith Foursquare Church"}] + (is (re-find #"military" (-> (circlebot/expand-template data) :text)))) + + ) diff --git a/vrs-settings.json b/vrs-settings.json new file mode 100644 index 0000000..599429b --- /dev/null +++ b/vrs-settings.json @@ -0,0 +1 @@ +{"ver":1,"values":{"VRadarServer-#desktop#-vrsCurrentLocation-default":{"userSuppliedLocation":{"lat":51.45315114582281,"lng":-0.193634033203125},"useBrowserLocation":true,"showCurrentLocation":true},"VRadarServer-#desktop#-vrsMapState-default":{"zoom":12,"mapTypeId":"m","center":{"lat":30.837627249110625,"lng":-117.43440628051759}},"VRadarServer-#desktop#-vrsAircraftListFetcher-default":{"interval":5000,"hideAircraftNotOnMap":true},"VRadarServer-#desktop#-vrsSplitterPosition-default-vrsLayout-A00":{"lengths":[{"name":"S2","pane":1,"vertical":false,"length":659},{"name":"S1","pane":2,"vertical":true,"length":650}]},"VRadarServer-#desktop#-vrsAircraftAutoSelect-default":{"enabled":false,"selectClosest":true,"offRadarAction":"---","filters":[]},"VRadarServer-#desktop#-vrsAircraftPlotterOptions-default":{"showAltitudeStalk":false,"suppressAltitudeStalkWhenZoomedOut":true,"showPinText":true,"pinTexts":["reg","csn","alt","---","---","---"],"pinTextLines":3,"hideEmptyPinTextLines":true,"trailDisplay":"b","trailType":"b","showRangeCircles":true,"rangeCircleInterval":10,"rangeCircleDistanceUnit":"nm","rangeCircleCount":6,"rangeCircleOddColour":"#333333","rangeCircleOddWeight":1,"rangeCircleEvenColour":"#111111","rangeCircleEvenWeight":2,"onlyUsePre22Icons":false,"aircraftMarkerClustererMaxZoom":5},"VRadarServer-#desktop#-vrsAircraftDetailPlugin-default":{"showUnits":true,"items":["alt","aty","vsi","spd","hdg","bng","dis","lat","lng","sqk","eng","spc","wtc","trt","avs","sig","mlt","tsb","mct","tim","fct","opi","ser","yrb","int","tag","pic"],"useShortLabels":false},"VRadarServer-#desktop#-vrsAircraftSorter-default":{"sortFields":[{"field":"mod","ascending":true},{"field":"---","ascending":true},{"field":"---","ascending":true}],"showEmergencySquawks":1,"showInteresting":1},"VRadarServer-#desktop#-vrsAircraftListPlugin-default":{"columns":["opf","sil","typ","csn","reg","alt","spd","ico","mlt","mil","cou"],"showUnits":true}}}