diff --git a/.gitignore b/.gitignore index 456c050..f78e68a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +configs/*.ini + # PyCharm .idea .vscode/settings.json @@ -9,3 +11,4 @@ lookup_route.py icao_url_gen.py install.sh coul_icao_gen.py +test.py diff --git a/Dockerfile b/Dockerfile index bc1f050..a477612 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,37 @@ FROM python:3 WORKDIR /plane-notify +USER root COPY . . -# Set the Chrome repo. -RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ - && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list - -# Install Chrome. -RUN apt-get update && apt-get -y install google-chrome-stable - -# Add pipenv -RUN pip install pipenv==2021.5.29 - -# Install dependencies -RUN pipenv install +RUN set -ex && \ + apt-get update -qq && \ + apt-get -y -qq install --no-install-recommends \ + ca-certificates \ + gnupg && \ + curl -sSL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ + echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list && \ + apt-get -y -qq update \ + && apt-get -y -qq install --no-install-recommends \ + bash \ + curl \ + google-chrome-stable \ + python3 \ + python3-dev \ + python3-pip \ + python3-setuptools \ + python3-wheel \ + && rm -rf \ + /var/lib/apt/lists/* \ + /var/cache/apt/archives + + + +RUN pip3 install --upgrade pip && \ + pip3 install -U --no-cache-dir -r ./requirements.txt # Added needed folder for plane-notify process -RUN mkdir /home/plane-notify +RUN mkdir -p /home/plane-notify -CMD pipenv run python /plane-notify/__main__.py \ No newline at end of file +CMD python3 /plane-notify/__main__.py \ No newline at end of file diff --git a/ExImages/DiscordEX2.png b/ExImages/DiscordEX2.png new file mode 100644 index 0000000..97b5813 Binary files /dev/null and b/ExImages/DiscordEX2.png differ diff --git a/ExImages/DiscordEX3.png b/ExImages/DiscordEX3.png new file mode 100644 index 0000000..438c3fe Binary files /dev/null and b/ExImages/DiscordEX3.png differ diff --git a/ExImages/PushbulletEX.png b/ExImages/PushbulletEX.png deleted file mode 100644 index ecd353a..0000000 Binary files a/ExImages/PushbulletEX.png and /dev/null differ diff --git a/Pipfile b/Pipfile index fd92156..84cf089 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,6 @@ tabulate = "*" pytz = "*" pillow = "*" tweepy = "*" -"pushbullet.py" = "*" discord-webhook = "*" selenium = "*" opensky-api = {editable = true, git = "https://github.com/openskynetwork/opensky-api.git", subdirectory = "python"} @@ -27,5 +26,7 @@ lxml = "*" beautifulsoup4 = "*" python-telegram-bot = "*" configparser = "*" +"mastodon.py" = "*" + [requires] python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock index 1d5bd99..577a1fb 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "35921a1158bc5b11d88e9f2a4702ee3ae09228a7d5438d1562ba62f24b02fd05" + "sha256": "1d80adfb6e58767e8fa25016043df0f2363d57f736d19e43e7dffc55ad511a73" }, "pipfile-spec": 6, "requires": { @@ -40,19 +40,26 @@ }, "attrs": { "hashes": [ - "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", - "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", + "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.4.0" + "markers": "python_version >= '3.6'", + "version": "==22.2.0" }, "beautifulsoup4": { "hashes": [ - "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf", - "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891" + "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", + "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" ], "index": "pypi", - "version": "==4.10.0" + "version": "==4.11.1" + }, + "blurhash": { + "hashes": [ + "sha256:7611c1bc41383d2349b6129208587b5d61e8792ce953893cb49c38beeb400d1d", + "sha256:da56b163e5a816e4ad07172f5639287698e09d7f3dc38d18d9726d9c1dbc4cee" + ], + "version": "==1.1.4" }, "cachetools": { "hashes": [ @@ -64,123 +71,129 @@ }, "certifi": { "hashes": [ - "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", - "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", + "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" ], - "version": "==2021.10.8" + "markers": "python_version >= '3.6'", + "version": "==2022.12.7" }, "cffi": { "hashes": [ - "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", - "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", - "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", - "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", - "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", - "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", - "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", - "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", - "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", - "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", - "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", - "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", - "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", - "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", - "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", - "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", - "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", - "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", - "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", - "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", - "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", - "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", - "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", - "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", - "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", - "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", - "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", - "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", - "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", - "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", - "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", - "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", - "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", - "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", - "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", - "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", - "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", - "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", - "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", - "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", - "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", - "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", - "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", - "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", - "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", - "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", - "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", - "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", - "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", - "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" + "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", + "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", + "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", + "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", + "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", + "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", + "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", + "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", + "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", + "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", + "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", + "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", + "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", + "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", + "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", + "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", + "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", + "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", + "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", + "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", + "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", + "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", + "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", + "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", + "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", + "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", + "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", + "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", + "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", + "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", + "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", + "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", + "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", + "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", + "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", + "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", + "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", + "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", + "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", + "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", + "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", + "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", + "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", + "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", + "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", + "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", + "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", + "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", + "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", + "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", + "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", + "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", + "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", + "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", + "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", + "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", + "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", + "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", + "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", + "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", + "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", + "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", + "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", + "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" ], "markers": "os_name == 'nt' and implementation_name != 'pypy'", - "version": "==1.15.0" + "version": "==1.15.1" }, "charset-normalizer": { "hashes": [ - "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", - "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_version >= '3'", - "version": "==2.0.12" + "markers": "python_version >= '3.6'", + "version": "==2.1.1" }, "colorama": { "hashes": [ - "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", - "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" ], "index": "pypi", - "version": "==0.4.4" + "version": "==0.4.6" }, "configparser": { "hashes": [ - "sha256:1b35798fdf1713f1c3139016cfcbc461f09edbf099d1fb658d4b7479fcaa3daa", - "sha256:e8b39238fb6f0153a069aa253d349467c3c4737934f253ef6abac5fe0eca1e5d" + "sha256:8be267824b541c09b08db124917f48ab525a6c3e837011f3130781a224c57090", + "sha256:b065779fd93c6bf4cee42202fa4351b4bb842e96a3fb469440e484517a49b9fa" ], "index": "pypi", - "version": "==5.2.0" - }, - "cryptography": { - "hashes": [ - "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b", - "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51", - "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7", - "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d", - "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6", - "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29", - "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9", - "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf", - "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815", - "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf", - "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85", - "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77", - "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86", - "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb", - "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e", - "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0", - "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3", - "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84", - "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2", - "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6" - ], - "version": "==36.0.2" + "version": "==5.3.0" + }, + "decorator": { + "hashes": [ + "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", + "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" + ], + "markers": "python_version >= '3.5'", + "version": "==5.1.1" }, "discord-webhook": { "hashes": [ - "sha256:1557a3a86ec556d5fceeff3d0c8601affd83770280b7358a3c7f4fb7c8ee32e5", - "sha256:5c59e3c5b52be8d9273aebdc5647f564a3c24ce4f75635fd1d9e4d9d8dad30f4" + "sha256:b1ef5ae80ec9b28c978b7dbff07b9db2fd6597d728ac0524b27cd02336cedfca", + "sha256:b4bb897d32509c5bedc6e1c8485431e8ba81eff5bec28a969c9fe33552a08da4" ], "index": "pypi", - "version": "==0.15.0" + "version": "==1.0.0" + }, + "exceptiongroup": { + "hashes": [ + "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e", + "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23" + ], + "markers": "python_version < '3.11'", + "version": "==1.1.0" }, "future": { "hashes": [ @@ -198,225 +211,290 @@ }, "geographiclib": { "hashes": [ - "sha256:8f441c527b0b8a26cd96c965565ff0513d1e4d9952b704bf449409e5015c77b7", - "sha256:ac400d672b8954b0306bca890b088bb8ba2a757dc8133cca0b878f34b33b2740" + "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734", + "sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859" ], - "version": "==1.52" + "markers": "python_version >= '3.7'", + "version": "==2.0" }, "geopy": { "hashes": [ - "sha256:58b7edf526b8c32e33126570b5f4fcdfaa29d4416506064777ae8d84cd103fdd", - "sha256:8f1f949082b964385de61fcc3a667a6a9a6e242beb1ae8972449f164b2ba0e89" + "sha256:228cd53b6eef699b2289d1172e462a90d5057779a10388a7366291812601187f", + "sha256:4a29a16d41d8e56ba8e07310802a1cbdf098eeb6069cc3d6d3068fc770629ffc" ], "index": "pypi", - "version": "==2.2.0" + "version": "==2.3.0" }, "h11": { "hashes": [ - "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06", - "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442" + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" ], - "markers": "python_version >= '3.6'", - "version": "==0.13.0" + "markers": "python_version >= '3.7'", + "version": "==0.14.0" }, "idna": { "hashes": [ - "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", - "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], - "markers": "python_version >= '3'", - "version": "==3.3" + "markers": "python_version >= '3.5'", + "version": "==3.4" }, "lxml": { "hashes": [ - "sha256:078306d19a33920004addeb5f4630781aaeabb6a8d01398045fcde085091a169", - "sha256:0c1978ff1fd81ed9dcbba4f91cf09faf1f8082c9d72eb122e92294716c605428", - "sha256:1010042bfcac2b2dc6098260a2ed022968dbdfaf285fc65a3acf8e4eb1ffd1bc", - "sha256:1d650812b52d98679ed6c6b3b55cbb8fe5a5460a0aef29aeb08dc0b44577df85", - "sha256:20b8a746a026017acf07da39fdb10aa80ad9877046c9182442bf80c84a1c4696", - "sha256:2403a6d6fb61c285969b71f4a3527873fe93fd0abe0832d858a17fe68c8fa507", - "sha256:24f5c5ae618395ed871b3d8ebfcbb36e3f1091fd847bf54c4de623f9107942f3", - "sha256:28d1af847786f68bec57961f31221125c29d6f52d9187c01cd34dc14e2b29430", - "sha256:31499847fc5f73ee17dbe1b8e24c6dafc4e8d5b48803d17d22988976b0171f03", - "sha256:31ba2cbc64516dcdd6c24418daa7abff989ddf3ba6d3ea6f6ce6f2ed6e754ec9", - "sha256:330bff92c26d4aee79c5bc4d9967858bdbe73fdbdbacb5daf623a03a914fe05b", - "sha256:5045ee1ccd45a89c4daec1160217d363fcd23811e26734688007c26f28c9e9e7", - "sha256:52cbf2ff155b19dc4d4100f7442f6a697938bf4493f8d3b0c51d45568d5666b5", - "sha256:530f278849031b0eb12f46cca0e5db01cfe5177ab13bd6878c6e739319bae654", - "sha256:545bd39c9481f2e3f2727c78c169425efbfb3fbba6e7db4f46a80ebb249819ca", - "sha256:5804e04feb4e61babf3911c2a974a5b86f66ee227cc5006230b00ac6d285b3a9", - "sha256:5a58d0b12f5053e270510bf12f753a76aaf3d74c453c00942ed7d2c804ca845c", - "sha256:5f148b0c6133fb928503cfcdfdba395010f997aa44bcf6474fcdd0c5398d9b63", - "sha256:5f7d7d9afc7b293147e2d506a4596641d60181a35279ef3aa5778d0d9d9123fe", - "sha256:60d2f60bd5a2a979df28ab309352cdcf8181bda0cca4529769a945f09aba06f9", - "sha256:6259b511b0f2527e6d55ad87acc1c07b3cbffc3d5e050d7e7bcfa151b8202df9", - "sha256:6268e27873a3d191849204d00d03f65c0e343b3bcb518a6eaae05677c95621d1", - "sha256:627e79894770783c129cc5e89b947e52aa26e8e0557c7e205368a809da4b7939", - "sha256:62f93eac69ec0f4be98d1b96f4d6b964855b8255c345c17ff12c20b93f247b68", - "sha256:6d6483b1229470e1d8835e52e0ff3c6973b9b97b24cd1c116dca90b57a2cc613", - "sha256:6f7b82934c08e28a2d537d870293236b1000d94d0b4583825ab9649aef7ddf63", - "sha256:6fe4ef4402df0250b75ba876c3795510d782def5c1e63890bde02d622570d39e", - "sha256:719544565c2937c21a6f76d520e6e52b726d132815adb3447ccffbe9f44203c4", - "sha256:730766072fd5dcb219dd2b95c4c49752a54f00157f322bc6d71f7d2a31fecd79", - "sha256:74eb65ec61e3c7c019d7169387d1b6ffcfea1b9ec5894d116a9a903636e4a0b1", - "sha256:7993232bd4044392c47779a3c7e8889fea6883be46281d45a81451acfd704d7e", - "sha256:80bbaddf2baab7e6de4bc47405e34948e694a9efe0861c61cdc23aa774fcb141", - "sha256:86545e351e879d0b72b620db6a3b96346921fa87b3d366d6c074e5a9a0b8dadb", - "sha256:891dc8f522d7059ff0024cd3ae79fd224752676447f9c678f2a5c14b84d9a939", - "sha256:8a31f24e2a0b6317f33aafbb2f0895c0bce772980ae60c2c640d82caac49628a", - "sha256:8b99ec73073b37f9ebe8caf399001848fced9c08064effdbfc4da2b5a8d07b93", - "sha256:986b7a96228c9b4942ec420eff37556c5777bfba6758edcb95421e4a614b57f9", - "sha256:a1547ff4b8a833511eeaceacbcd17b043214fcdb385148f9c1bc5556ca9623e2", - "sha256:a2bfc7e2a0601b475477c954bf167dee6d0f55cb167e3f3e7cefad906e7759f6", - "sha256:a3c5f1a719aa11866ffc530d54ad965063a8cbbecae6515acbd5f0fae8f48eaa", - "sha256:a9f1c3489736ff8e1c7652e9dc39f80cff820f23624f23d9eab6e122ac99b150", - "sha256:aa0cf4922da7a3c905d000b35065df6184c0dc1d866dd3b86fd961905bbad2ea", - "sha256:ad4332a532e2d5acb231a2e5d33f943750091ee435daffca3fec0a53224e7e33", - "sha256:b2582b238e1658c4061ebe1b4df53c435190d22457642377fd0cb30685cdfb76", - "sha256:b6fc2e2fb6f532cf48b5fed57567ef286addcef38c28874458a41b7837a57807", - "sha256:b92d40121dcbd74831b690a75533da703750f7041b4bf951befc657c37e5695a", - "sha256:bbab6faf6568484707acc052f4dfc3802bdb0cafe079383fbaa23f1cdae9ecd4", - "sha256:c0b88ed1ae66777a798dc54f627e32d3b81c8009967c63993c450ee4cbcbec15", - "sha256:ce13d6291a5f47c1c8dbd375baa78551053bc6b5e5c0e9bb8e39c0a8359fd52f", - "sha256:db3535733f59e5605a88a706824dfcb9bd06725e709ecb017e165fc1d6e7d429", - "sha256:dd10383f1d6b7edf247d0960a3db274c07e96cf3a3fc7c41c8448f93eac3fb1c", - "sha256:e01f9531ba5420838c801c21c1b0f45dbc9607cb22ea2cf132844453bec863a5", - "sha256:e11527dc23d5ef44d76fef11213215c34f36af1608074561fcc561d983aeb870", - "sha256:e1ab2fac607842ac36864e358c42feb0960ae62c34aa4caaf12ada0a1fb5d99b", - "sha256:e1fd7d2fe11f1cb63d3336d147c852f6d07de0d0020d704c6031b46a30b02ca8", - "sha256:e9f84ed9f4d50b74fbc77298ee5c870f67cb7e91dcdc1a6915cb1ff6a317476c", - "sha256:ec4b4e75fc68da9dc0ed73dcdb431c25c57775383fec325d23a770a64e7ebc87", - "sha256:f10ce66fcdeb3543df51d423ede7e238be98412232fca5daec3e54bcd16b8da0", - "sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23", - "sha256:fa56bb08b3dd8eac3a8c5b7d075c94e74f755fd9d8a04543ae8d37b1612dd170", - "sha256:fa9b7c450be85bfc6cd39f6df8c5b8cbd76b5d6fc1f69efec80203f9894b885f" + "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7", + "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726", + "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03", + "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140", + "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a", + "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05", + "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03", + "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419", + "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4", + "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e", + "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67", + "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50", + "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894", + "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf", + "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947", + "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1", + "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd", + "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3", + "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92", + "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3", + "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457", + "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74", + "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf", + "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1", + "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4", + "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975", + "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5", + "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe", + "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7", + "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1", + "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2", + "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409", + "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f", + "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f", + "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5", + "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24", + "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e", + "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4", + "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a", + "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c", + "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de", + "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f", + "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b", + "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5", + "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7", + "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a", + "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c", + "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9", + "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e", + "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab", + "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941", + "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5", + "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45", + "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7", + "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892", + "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746", + "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c", + "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53", + "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe", + "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184", + "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38", + "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df", + "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9", + "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b", + "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2", + "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0", + "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda", + "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b", + "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5", + "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380", + "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33", + "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8", + "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1", + "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889", + "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9", + "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f", + "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c" + ], + "index": "pypi", + "version": "==4.9.2" + }, + "mastodon.py": { + "hashes": [ + "sha256:31624c881318682577b76c082a9e8e4114a42e80ad3652c6bc00e5c658cea1a7", + "sha256:f5af3bb16df6409bed0bb8b97543d7979237a6a2a2a4bc484dec261c36918668" ], "index": "pypi", - "version": "==4.8.0" + "version": "==1.8.0" }, "numpy": { "hashes": [ - "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676", - "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4", - "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce", - "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123", - "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1", - "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e", - "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5", - "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d", - "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a", - "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab", - "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75", - "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168", - "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4", - "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f", - "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18", - "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62", - "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe", - "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430", - "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802", - "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa" - ], - "markers": "python_version < '3.10' and platform_machine != 'aarch64' and platform_machine != 'arm64'", - "version": "==1.22.3" + "sha256:0104d8adaa3a4cc60c2777cab5196593bf8a7f416eda133be1f3803dd0838886", + "sha256:0885d9a7666cafe5f9876c57bfee34226e2b2847bfb94c9505e18d81011e5401", + "sha256:12bba5561d8118981f2f1ff069ecae200c05d7b6c78a5cdac0911f74bc71cbd1", + "sha256:2f8e0df2ecc1928ef7256f18e309c9d6229b08b5be859163f5caa59c93d53646", + "sha256:4445f472b246cad6514cc09fbb5ecb7aab09ca2acc3c16f29f8dca6c468af501", + "sha256:4d01f7832fa319a36fd75ba10ea4027c9338ede875792f7bf617f4b45056fc3a", + "sha256:4f5e78b8b710cd7cd1a8145994cfffc6ddd5911669a437777d8cedfce6c83a98", + "sha256:667b5b1f6a352419e340f6475ef9930348ae5cb7fca15f2cc3afcb530823715e", + "sha256:6e73a1f4f5b74a42abb55bc2b3d869f1b38cbc8776da5f8b66bf110284f7a437", + "sha256:73cf2c5b5a07450f20a0c8e04d9955491970177dce8df8d6903bf253e53268e0", + "sha256:7ad6a024a32ee61d18f5b402cd02e9c0e22c0fb9dc23751991b3a16d209d972e", + "sha256:8b1ddfac6a82d4f3c8e99436c90b9c2c68c0bb14658d1684cdd00f05fab241f5", + "sha256:90075ef2c6ac6397d0035bcd8b298b26e481a7035f7a3f382c047eb9c3414db0", + "sha256:9387c7d6d50e8f8c31e7bfc034241e9c6f4b3eb5db8d118d6487047b922f82af", + "sha256:9af91f794d2d3007d91d749ebc955302889261db514eb24caef30e03e8ec1e41", + "sha256:ab11f6a7602cf8ea4c093e091938207de3068c5693a0520168ecf4395750f7ea", + "sha256:ac4fe68f1a5a18136acebd4eff91aab8bed00d1ef2fdb34b5d9192297ffbbdfc", + "sha256:ada6c1e9608ceadaf7020e1deea508b73ace85560a16f51bef26aecb93626a72", + "sha256:c4ab7c9711fe6b235e86487ca74c1b092a6dd59a3cb45b63241ea0a148501853", + "sha256:cec79ff3984b2d1d103183fc4a3361f5b55bbb66cb395cbf5a920a4bb1fd588d", + "sha256:cf8960f72997e56781eb1c2ea256a70124f92a543b384f89e5fb3503a308b1d3", + "sha256:d7f223554aba7280e6057727333ed357b71b7da7422d02ff5e91b857888c25d1", + "sha256:dbb0490f0a880700a6cc4d000384baf19c1f4df59fff158d9482d4dbbca2b239", + "sha256:e63d2157f9fc98cc178870db83b0e0c85acdadd598b134b00ebec9e0db57a01f", + "sha256:ec3e5e8172a0a6a4f3c2e7423d4a8434c41349141b04744b11a90e017a95bad5", + "sha256:f3c4a9a9f92734a4728ddbd331e0124eabbc968a0359a506e8e74a9b0d2d419b", + "sha256:f9168790149f917ad8e3cf5047b353fefef753bd50b07c547da0bdf30bc15d91", + "sha256:fe44e925c68fb5e8db1334bf30ac1a1b6b963b932a19cf41d2e899cf02f36aab" + ], + "markers": "python_version < '3.10'", + "version": "==1.24.0" }, "oauthlib": { "hashes": [ - "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2", - "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe" + "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", + "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918" ], "markers": "python_version >= '3.6'", - "version": "==3.2.0" + "version": "==3.2.2" }, "opensky-api": { "editable": true, "git": "https://github.com/openskynetwork/opensky-api.git", - "ref": "68c8304471fe32f5eaca8a41144fdad35d8a8ce4", + "ref": "3d01d1a774da0dbd4a65a3bb615fd2063d0320c9", "subdirectory": "python" }, "outcome": { "hashes": [ - "sha256:c7dd9375cfd3c12db9801d080a3b63d4b0a261aa996c4c13152380587288d958", - "sha256:e862f01d4e626e63e8f92c38d1f8d5546d3f9cce989263c521b2e7990d186967" + "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672", + "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5" ], - "markers": "python_version >= '3.6'", - "version": "==1.1.0" + "markers": "python_version >= '3.7'", + "version": "==1.2.0" }, - "pandas": { + "packaging": { "hashes": [ - "sha256:0259cd11e7e6125aaea3af823b80444f3adad6149ff4c97fef760093598b3e34", - "sha256:04dd15d9db538470900c851498e532ef28d4e56bfe72c9523acb32042de43dfb", - "sha256:0b1a13f647e4209ed7dbb5da3497891d0045da9785327530ab696417ef478f84", - "sha256:19f7c632436b1b4f84615c3b127bbd7bc603db95e3d4332ed259dc815c9aaa26", - "sha256:1b384516dbb4e6aae30e3464c2e77c563da5980440fbdfbd0968e3942f8f9d70", - "sha256:1d85d5f6be66dfd6d1d8d13b9535e342a2214260f1852654b19fa4d7b8d1218b", - "sha256:2e5a7a1e0ecaac652326af627a3eca84886da9e667d68286866d4e33f6547caf", - "sha256:3129a35d9dad1d80c234dd78f8f03141b914395d23f97cf92a366dcd19f8f8bf", - "sha256:358b0bc98a5ff067132d23bf7a2242ee95db9ea5b7bbc401cf79205f11502fd3", - "sha256:3dfb32ed50122fe8c5e7f2b8d97387edd742cc78f9ec36f007ee126cd3720907", - "sha256:4e1176f45981c8ccc8161bc036916c004ca51037a7ed73f2d2a9857e6dbe654f", - "sha256:508c99debccd15790d526ce6b1624b97a5e1e4ca5b871319fb0ebfd46b8f4dad", - "sha256:6105af6533f8b63a43ea9f08a2ede04e8f43e49daef0209ab0d30352bcf08bee", - "sha256:6d6ad1da00c7cc7d8dd1559a6ba59ba3973be6b15722d49738b2be0977eb8a0c", - "sha256:7ea47ba1d6f359680130bd29af497333be6110de8f4c35b9211eec5a5a9630fa", - "sha256:8db93ec98ac7cb5f8ac1420c10f5e3c43533153f253fe7fb6d891cf5aa2b80d2", - "sha256:96e9ece5759f9b47ae43794b6359bbc54805d76e573b161ae770c1ea59393106", - "sha256:bbb15ad79050e8b8d39ec40dd96a30cd09b886a2ae8848d0df1abba4d5502a67", - "sha256:c614001129b2a5add5e3677c3a213a9e6fd376204cb8d17c04e84ff7dfc02a73", - "sha256:e6a7bbbb7950063bfc942f8794bc3e31697c020a14f1cd8905fc1d28ec674a01", - "sha256:f02e85e6d832be37d7f16cf6ac8bb26b519ace3e5f3235564a91c7f658ab2a43" + "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3", + "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3" ], - "index": "pypi", - "version": "==1.4.1" + "markers": "python_version >= '3.7'", + "version": "==22.0" }, - "pillow": { + "pandas": { "hashes": [ - "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97", - "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049", - "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c", - "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae", - "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28", - "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030", - "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56", - "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976", - "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e", - "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e", - "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f", - "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b", - "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a", - "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e", - "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa", - "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7", - "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00", - "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838", - "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360", - "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b", - "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a", - "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd", - "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4", - "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70", - "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204", - "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc", - "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b", - "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669", - "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7", - "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e", - "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c", - "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092", - "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c", - "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5", - "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac" + "sha256:0183cb04a057cc38fde5244909fca9826d5d57c4a5b7390c0cc3fa7acd9fa883", + "sha256:1fc87eac0541a7d24648a001d553406f4256e744d92df1df8ebe41829a915028", + "sha256:220b98d15cee0b2cd839a6358bd1f273d0356bf964c1a1aeb32d47db0215488b", + "sha256:2552bffc808641c6eb471e55aa6899fa002ac94e4eebfa9ec058649122db5824", + "sha256:315e19a3e5c2ab47a67467fc0362cb36c7c60a93b6457f675d7d9615edad2ebe", + "sha256:344021ed3e639e017b452aa8f5f6bf38a8806f5852e217a7594417fb9bbfa00e", + "sha256:375262829c8c700c3e7cbb336810b94367b9c4889818bbd910d0ecb4e45dc261", + "sha256:457d8c3d42314ff47cc2d6c54f8fc0d23954b47977b2caed09cd9635cb75388b", + "sha256:4aed257c7484d01c9a194d9a94758b37d3d751849c05a0050c087a358c41ad1f", + "sha256:530948945e7b6c95e6fa7aa4be2be25764af53fba93fe76d912e35d1c9ee46f5", + "sha256:5ae7e989f12628f41e804847a8cc2943d362440132919a69429d4dea1f164da0", + "sha256:71f510b0efe1629bf2f7c0eadb1ff0b9cf611e87b73cd017e6b7d6adb40e2b3a", + "sha256:73f219fdc1777cf3c45fde7f0708732ec6950dfc598afc50588d0d285fddaefc", + "sha256:8092a368d3eb7116e270525329a3e5c15ae796ccdf7ccb17839a73b4f5084a39", + "sha256:82ae615826da838a8e5d4d630eb70c993ab8636f0eff13cb28aafc4291b632b5", + "sha256:9608000a5a45f663be6af5c70c3cbe634fa19243e720eb380c0d378666bc7702", + "sha256:a40dd1e9f22e01e66ed534d6a965eb99546b41d4d52dbdb66565608fde48203f", + "sha256:b4f5a82afa4f1ff482ab8ded2ae8a453a2cdfde2001567b3ca24a4c5c5ca0db3", + "sha256:c009a92e81ce836212ce7aa98b219db7961a8b95999b97af566b8dc8c33e9519", + "sha256:c218796d59d5abd8780170c937b812c9637e84c32f8271bbf9845970f8c1351f", + "sha256:cc3cd122bea268998b79adebbb8343b735a5511ec14efb70a39e7acbc11ccbdc", + "sha256:d0d8fd58df5d17ddb8c72a5075d87cd80d71b542571b5f78178fb067fa4e9c72", + "sha256:e18bc3764cbb5e118be139b3b611bc3fbc5d3be42a7e827d1096f46087b395eb", + "sha256:e2b83abd292194f350bb04e188f9379d36b8dfac24dd445d5c87575f3beaf789", + "sha256:e7469271497960b6a781eaa930cba8af400dd59b62ec9ca2f4d31a19f2f91090", + "sha256:e9dbacd22555c2d47f262ef96bb4e30880e5956169741400af8b306bbb24a273", + "sha256:f6257b314fc14958f8122779e5a1557517b0f8e500cfb2bd53fa1f75a8ad0af2" ], "index": "pypi", - "version": "==9.0.1" + "version": "==1.5.2" }, - "pushbullet.py": { + "pillow": { "hashes": [ - "sha256:38e3ce79843efaf839c8dc43485c0c7eedbe5825a8751751f13d041dd00c5a37", - "sha256:917883e1af4a0c979ce46076b391e0243eb8fe0a81c086544bcfa10f53e5ae64" + "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040", + "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8", + "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65", + "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2", + "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627", + "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07", + "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef", + "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535", + "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c", + "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc", + "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3", + "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1", + "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c", + "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa", + "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32", + "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502", + "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4", + "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f", + "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812", + "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636", + "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20", + "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c", + "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91", + "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe", + "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b", + "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad", + "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9", + "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72", + "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4", + "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de", + "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29", + "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee", + "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c", + "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7", + "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11", + "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c", + "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c", + "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448", + "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b", + "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20", + "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228", + "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd", + "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699", + "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b", + "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2", + "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4", + "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c", + "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f", + "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2", + "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c", + "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3", + "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193", + "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48", + "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02", + "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8", + "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e", + "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f", + "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b", + "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74", + "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb", + "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0" ], "index": "pypi", - "version": "==0.12.0" + "version": "==9.3.0" }, "py-staticmaps": { "hashes": [ @@ -427,18 +505,20 @@ }, "pycairo": { "hashes": [ - "sha256:251907f18a552df938aa3386657ff4b5a4937dde70e11aa042bc297957f4b74b", - "sha256:26b72b813c6f9d495f71057eab89c13e70a21c92360e9265abc049e0a931fa39", - "sha256:31e1c4850db03201d33929cbe1905ce1b33202683ebda7bb0d4dba489115066b", - "sha256:4357f20a6b1de8f1e8072a74ff68ab4c9a0ae698cd9f5c0f2b2cdd9b28b635f6", - "sha256:44a2ecf34968de07b3b9dfdcdbccbd25aa3cab267200f234f84e81481a73bbf6", - "sha256:6d37375aab9f2bb6136f076c19815d72108383baae89fbc0d6cb8e5092217d02", - "sha256:70936b19f967fa3cb3cd200c2608911227fa5d09dae21c166f64bc15e714ee41", - "sha256:dace6b356c476de27f8e1522428ac21a799c225703f746e2957d441f885dcb6c", - "sha256:f63c153a9ea3d21aff85e2caeee4b0c5d566b2368b4ed64826020d12953d76a4" + "sha256:1a6d8e0f353062ad92954784e33dbbaf66c880c9c30e947996c542ed9748aaaf", + "sha256:2dec5378133778961993fb59d66df16070e03f4d491b67eb695ca9ad7a696008", + "sha256:3a71f758e461180d241e62ef52e85499c843bd2660fd6d87cec99c9833792bfa", + "sha256:564601e5f528531c6caec1c0177c3d0709081e1a2a5cccc13561f715080ae535", + "sha256:82e335774a17870bc038e0c2fb106c1e5e7ad0c764662023886dfcfce5bb5a52", + "sha256:87efd62a7b7afad9a0a420f05b6008742a6cfc59077697be65afe8dc73ae15ad", + "sha256:9b61ac818723adc04367301317eb2e814a83522f07bbd1f409af0dada463c44c", + "sha256:a4b1f525bbdf637c40f4d91378de36c01ec2b7f8ecc585b700a079b9ff83298e", + "sha256:d6bacff15d688ed135b4567965a4b664d9fb8de7417a7865bb138ad612043c9f", + "sha256:e7cde633986435d87a86b6118b7b6109c384266fd719ef959883e2729f6eafae", + "sha256:ec305fc7f2f0299df78aadec0eaf6eb9accb90eda242b5d3492544d3f2b28027" ], "index": "pypi", - "version": "==1.21.0" + "version": "==1.23.0" }, "pycparser": { "hashes": [ @@ -447,38 +527,46 @@ ], "version": "==2.21" }, - "pyopenssl": { - "hashes": [ - "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf", - "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0" - ], - "version": "==22.0.0" - }, "pyproj": { "hashes": [ - "sha256:067a5c6099949edd66e9a10b139af4e2f65ebadb9f59583923a1d3feefac749a", - "sha256:0e1fd560b509b722db6566fa9685252f25640e93464d09e13d5190ed7ab491ba", - "sha256:235b52d8700ffb6be1f3638b1e25d83a9c13edcdb793236d8a98fd39227c5c27", - "sha256:277072176a17471c0b1d25d6cae75401d81e9b50ea625ba546f5b79acd757dfc", - "sha256:2c41c9b7b5e1a1b0acc2b7b2f5de65b226f7b96c870888e4f679ff96322b1ed0", - "sha256:3d28b84913cd849832a8f154c0e0c2ee4618057f7389ee68bfdb2145e7ed78cc", - "sha256:4125e6704751d0e82d8d912d9851da097e8d38599d4c45f9944faaeb21771938", - "sha256:44b5590c0b8dd002154916e170ef88f57abf91005b34bcb23faef97abb4d42c2", - "sha256:4d2fc49c73d9f34e932bf37926d56916ba1b6f2f693cd4d8cc1d0d9eacc0e537", - "sha256:5a105bfe37c78416d2641cd5d3368c99057d041f15f8d51ea3898953b21395c9", - "sha256:99f171da5f885efeec8d7fb2e2557175ffa8834eeb488842b1f52ac78a9a98e5", - "sha256:ab4baf781721640659db83a6b4da636fc403008f4978c668275754284c946778", - "sha256:b15e199c1da8fd132e11dfa68d8cf65d4812dedabc776b308df778ecd0d07658", - "sha256:b48dd9e5736957707fce1d9253fb0772bcf80480198c7790e21fed73fee61240", - "sha256:ce1adec823738e2d7c6af019fc38f58b4204bacfc782e4430373578c672f3833", - "sha256:ce8bfbc212729e9a643f5f5d77f7a93394e032eda1e2d8799ae902d08add747e", - "sha256:dbf479bd481774ad217e9db5674868eee8f01dfe3868f61753328895ae7da61a", - "sha256:e70a1ea6f198cace1a492397bdd0a46e640201120973293d6c48031e370d6a87", - "sha256:eca8ecf2b6b3225d93c723e6a2f51143d9195ac407f69e979363cdde344b93bb", - "sha256:fcceb6736085bf19291b707bc67c8cebe05330bd02268e9b8eba6d28a1905fce" + "sha256:0189fdd7aa789542a7a623010dfff066c5849b24397f81f860ec3ee085cbf55c", + "sha256:0406f64ff59eb3342efb102c9f31536430aa5cde5ef0bfabd5aaccb73dd8cd5a", + "sha256:0c7b32382ae22a9bf5b690d24c7b4c0fb89ba313c3a91ef1a8c54b50baf10954", + "sha256:129234afa179c8293b010ea4f73655ff7b20b5afdf7fac170f223bcf0ed6defd", + "sha256:19f5de1a7c3b81b676d846350d4bdf2ae6af13b9a450d1881706f088ecad0e2c", + "sha256:231c038c6b65395c41ae3362320f03ce8054cb54dc63556e605695e5d461a27e", + "sha256:25a5425cd2a0b16f5f944d49165196eebaa60b898a08c404a644c29e6a7a04b3", + "sha256:261eb29b1d55b1eb7f336127344d9b31284d950a9446d1e0d1c2411f7dd8e3ac", + "sha256:2bd633f3b8ca6eb09135dfaf06f09e2869deb139985aab26d728e8a60c9938b9", + "sha256:2d1e7f42da205e0534831ae9aa9cee0353ab8c1aab2c369474adbb060294d98a", + "sha256:2f87f16b902c8b2af007295c63a435f043db9e40bd45e6f96962c7b8cd08fdb5", + "sha256:321b82210dc5271558573d0874b9967c5a25872a28d0168049ddabe8bfecffce", + "sha256:3d70ca5933cddbe6f51396006fb9fc78bc2b1f9d28775922453c4b04625a7efb", + "sha256:57ec7d2b7f2773d877927abc72e2229ef8530c09181be0e28217742bae1bc4f5", + "sha256:5a53acbde511a7a9e1873c7f93c68f35b8c3653467b77195fe18e847555dcb7a", + "sha256:5f3f75b030cf811f040c90a8758a20115e8746063e4cad0d0e941a4954d1219b", + "sha256:6bdac3bc1899fcc4021be06d303b342923fb8311fe06f8d862c348a1a0e78b41", + "sha256:7aef19d5a0a3b2d6b17f7dc9a87af722e71139cd1eea7eb82ed062a8a4b0e272", + "sha256:8078c90cea07d53e3406c7c84cbf76a2ac0ffc580c365f13801575486b9d558c", + "sha256:97065fe82e80f7e2740e7897a0e36e8defc0a3614927f0276b4f1d1ea1ef66fa", + "sha256:a30d78e619dae5cd1bb69addae2f1e5f8ee1b4a8ab4f3d954e9eaf41948db506", + "sha256:a32e1d12340ad93232b7ea4dc1a4f4b21fa9fa9efa4b293adad45be7af6b51ec", + "sha256:a5eada965e8ac24e783f2493d1d9bcd11c5c93959bd43558224dd31d9faebd1c", + "sha256:a98fe3e53be428e67ae6a9ee9affff92346622e0e3ea0cbc15dce939b318d395", + "sha256:c240fe6bcb5c325b50fc967d5458d708412633f4f05fefc7fb14c14254ebf421", + "sha256:c60d112d8f1621a606b7f2adb0b1582f80498e663413d2ba9f5df1c93d99f432", + "sha256:cd9f9c409f465834988ce0aa8c1ed496081c6957f2e5ef40ed28de04397d3c0b", + "sha256:ce50126dad7cd4749ab86fc4c8b54ec0898149ce6710ab5c93c76a54a4afa249", + "sha256:da96319b137cfd66f0bae0e300cdc77dd17af4785b9360a9bdddb1d7176a0bbb", + "sha256:e463c687007861a9949909211986850cfc2e72930deda0d06449ef2e315db534", + "sha256:e8c0d1ac9ef5a4d2e6501a4b30136c55f1e1db049d1626cc313855c4f97d196d", + "sha256:e9d82df555cf19001bac40e1de0e40fb762dec785685b77edd6993286c01b7f7", + "sha256:ef76abfee1a0676ef973470abe11e22998750f2bd944afaf76d44ad70b538c06", + "sha256:ef8c30c62fe4e386e523e14e1e83bd460f745bd2c8dfd0d0c327f9460c4d3c0c", + "sha256:f38dea459e22e86326b1c7d47718a3e10c7a27910cf5eb86ea2679b8084d0c4e" ], "index": "pypi", - "version": "==3.3.0" + "version": "==3.4.1" }, "pysocks": { "hashes": [ @@ -496,37 +584,45 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.2" }, + "python-dotenv": { + "hashes": [ + "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5", + "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045" + ], + "markers": "python_version >= '3.7'", + "version": "==0.21.0" + }, "python-magic": { "hashes": [ - "sha256:1a2c81e8f395c744536369790bd75094665e9644110a6623bcc3bbea30f03973", - "sha256:21f5f542aa0330f5c8a64442528542f6215c8e18d2466b399b0d9d39356d83fc" + "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", + "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.4.25" + "version": "==0.4.27" }, "python-slugify": { "hashes": [ - "sha256:00003397f4e31414e922ce567b3a4da28cf1436a53d332c9aeeb51c7d8c469fd", - "sha256:8c0016b2d74503eb64761821612d58fcfc729493634b1eb0575d8f5b4aa1fbcf" + "sha256:003aee64f9fd955d111549f96c4b58a3f40b9319383c70fad6277a4974bbf570", + "sha256:7a0f21a39fa6c1c4bf2e5984c9b9ae944483fd10b54804cb0e23a3ccd4954f0b" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==6.1.1" + "markers": "python_version >= '3.7'", + "version": "==7.0.0" }, "python-telegram-bot": { "hashes": [ - "sha256:534f5bb0ff4ca34c9252e97e0b3bcdab81d97be0eb4821682a361cb426c00e55", - "sha256:baeff704baa2ac3dc17a944c02da888228ad258e89be2e5bcbd13a8a5102d573" + "sha256:06780c258d3f2a3c6c79a7aeb45714f4cd1dd6275941b7dc4628bba64fddd465", + "sha256:b4047606b8081b62bbd6aa361f7ca1efe87fa8f1881ec9d932d35844bf57a154" ], "index": "pypi", - "version": "==13.11" + "version": "==13.15" }, "pytz": { "hashes": [ - "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", - "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" + "sha256:7ccfae7b4b2c067464a6733c6261673fdb8fd1be905460396b97a073e9fa683a", + "sha256:93007def75ae22f7cd991c84e02d434876818661f8df9ad5df9e950ff4e52cfd" ], "index": "pypi", - "version": "==2022.1" + "version": "==2022.7" }, "pytz-deprecation-shim": { "hashes": [ @@ -538,11 +634,11 @@ }, "requests": { "hashes": [ - "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", - "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" + "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", + "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==2.27.1" + "markers": "python_version >= '3.7' and python_version < '4.0'", + "version": "==2.28.1" }, "requests-oauthlib": { "hashes": [ @@ -561,42 +657,55 @@ }, "selenium": { "hashes": [ - "sha256:14d28a628c831c105d38305c881c9c7847199bfd728ec84240c5e86fa1c9bd5a" + "sha256:06a1c7d9f313130b21c3218ddd8852070d0e7419afdd31f96160cd576555a5ce", + "sha256:3aefa14a28a42e520550c1cd0f29cf1d566328186ea63aa9a3e01fb265b5894d" ], "index": "pypi", - "version": "==4.1.3" + "version": "==4.7.2" }, "shapely": { "hashes": [ - "sha256:0ca96a3314b7a38a3bb385531469de1fcf2b2c2979ec2aa4f37b4c70632cf1ad", - "sha256:2020fda37c708d44a613c020cea09e81e476f96866f348afc2601e66c0e71db1", - "sha256:2381ce0aff67d569eb509bcc051264aa5fbdc1fdd54f4c09963d0e09f16a8f1b", - "sha256:363df36370f28fdc7789857929f6ff27e659f64087b4c89f7a47ed43bd3bfe4d", - "sha256:3a3602ba2e7715ddd5d4114173dec83d3181bfb2497e8589676c284aa739fd67", - "sha256:3e792635e92c9aacd1452a589a4fa2970114b6a9b1165e09655481f6e58970f5", - "sha256:437fff3b6274be26ffa3e450de711ee01e436324b5a405952add2146227e3eb5", - "sha256:44cb895b1710f7559c28d69dfa08cafe4f58cd4b7a87091a55bdf6711ad9ad66", - "sha256:493902923fdd135316161a4ece5294ba3ce81accaa54540d2af3b93f7231143a", - "sha256:54aeb2a57978ce731fd52289d0e1deee7c232d41aed53091f38776378f644184", - "sha256:5a420e7112b55a1587412a5b03ebf59e302ddd354da68516d3721718f6b8a7c5", - "sha256:679789d774cfe09ca05118cab78c0a6a42985b3ed23bc93606272a4509b4df28", - "sha256:69d5352fb977655c85d2f40a05ae24fc5053cccee77d0a8b1f773e54804e723e", - "sha256:83f3c8191d30ae0e3dd557434c48ca591d75342d5a3f42fc5148ec42796be624", - "sha256:89bc5f3abc1ccbc7682c2e1664153c4f8f125fa9c24bff4abca48685739d5636", - "sha256:8cf7331f61780506976fe2175e069d898e1b04ace73be21aad55c3ee92e58e3a", - "sha256:9248aad099ecf228fbdd877b0c668823dd83c48798cf04d49a1be75167e3a7ce", - "sha256:93ff06ff05fbe2be843b93c7b1ad8292e56e665ba01b4708f75ae8a757972e9f", - "sha256:aea1e87450adffba3d04ccbaa790df719bb7aa23b05ac797ad16be236a5d0db8", - "sha256:b4d35e72022b2dbf152d476b0362596011c674ff68be9fc8f2e68e71d86502ca", - "sha256:b82fc74d5efb11a71283c4ed69b4e036997cc70db4b73c646207ddf0476ade44", - "sha256:bab5ff7c576588acccd665ecce2a0fe7b47d4ce0398f2d5c1e5b2e27d09398d2", - "sha256:bc6063875182515d3888180cc4cbdbaa6443e4a4386c4bb25499e9875b75dcac", - "sha256:c4c366e18edf91196a399f8f0f046f93516002e6d8af0b57c23e7c7d91944b16", - "sha256:dc0f46212f84c57d13189fc33cf61e13eee292704d7652e931e4b51c54b0c73c", - "sha256:f109064bdb0753a6bac6238538cfeeb4a09739e2d556036b343b2eabeb9520b2" + "sha256:11f1b1231a6c04213fb1226c6968d1b1b3b369ec42d1e9655066af87631860ea", + "sha256:13a9f978cd287e0fa95f39904a2bb36deddab490e4fab8bf43eba01b7d9eb58f", + "sha256:17d0f89581aa15f7887052a6adf2753f9fe1c3fdbb6116653972e0d43e720e65", + "sha256:21ba32a6c45b7f8ab7d2d8d5cf339704e2d1dfdf3e2fb465b950a0c9bc894a4f", + "sha256:2287d0cb592c1814e9f48065888af7ee3f13e090e6f7fa3e208b06a83fb2f6af", + "sha256:292c22ff7806e3a25bc4324295e9204169c61a09165d4c9ee0a9784c1709c85e", + "sha256:40c397d67ba609a163d38b649eee2b06c5f9bdc86d244a8e4cd09c6e2791cf3c", + "sha256:44198fc188fe4b7dd39ef0fd325395d1d6ab0c29a7bbaa15663a16c362bf6f62", + "sha256:5477be8c11bf3109f7b804bb2d57536538b8d0a6118207f1020d71338f1a827c", + "sha256:550f110940d79931b6a12a17de07f6b158c9586c4b121f885af11458ae5626d7", + "sha256:56c0e70749f8c2956493e9333375d2e2264ce25c838fc49c3a2ececbf2d3ba92", + "sha256:5fe8649aafe6adcb4d90f7f735f06ca8ca02a16da273d901f1dd02afc0d3618e", + "sha256:6c71738702cf5c3fc60b3bbe869c321b053ea754f57addded540a71c78c2612e", + "sha256:7266080d39946395ba4b31fa35b9b7695e0a4e38ccabf0c67e2936caf9f9b054", + "sha256:73771b3f65c2949cce0b310b9b62b8ce069407ceb497a9dd4436f9a4d059f12c", + "sha256:73d605fcefd06ee997ba307ef363448d355f3c3e81b3f56ed332eaf6d506e1b5", + "sha256:7b2c41514ba985ea3772eee9b386d620784cccb7a459a270a072f3ef01fdd807", + "sha256:820bee508e4a0e564db22f8b55bb5e6e7f326d8d7c103639c42f5d3f378f4067", + "sha256:8a7ba97c97d85c1f07c57f9524c45128ef2bf8279061945d78052c78862b357f", + "sha256:8b9f780c3b79b4a6501e0e8833b1877841b7b0e0a243e77b529fda8f1030afc2", + "sha256:91bbca0378eb82f0808f0e59150ac0952086f4caaab87ad8515a5e55e896c21e", + "sha256:99420c89af78f371b96f0e2bad9afdebc6d0707d4275d157101483e4c4049fd6", + "sha256:a391cae931976fb6d8d15a4f4a92006358e93486454a812dde1d64184041a476", + "sha256:a9b6651812f2caa23e4d06bc06a2ed34450f82cb1c110c170a25b01bbb090895", + "sha256:b1def13ec2a74ebda2210d2fc1c53cecce5a079ec90f341101399427874507f1", + "sha256:b3d97f3ce6df47ca68c2d64b8c3cfa5c8ccc0fbc81ef8e15ff6004a6426e71b1", + "sha256:c47a61b1cd0c5b064c6d912bce7dba78c01f319f65ecccd6e61eecd21861a37a", + "sha256:c4b99a3456e06dc55482569669ece969cdab311f2ad2a1d5622fc770f68cf3cd", + "sha256:d28e19791c9be2ba1cb2fddefa86f73364bdf8334e88dbcd78a8e4494c0af66b", + "sha256:d486cab823f0a978964ae97ca10564ea2b2ced93e84a2ef0b7b62cbacec9d3d2", + "sha256:de3722c68e49fbde8cb6859695bbb8fb9a4d48bbdf34fcf38b7994d2bd9772e2", + "sha256:e4ed31658fd0799eaa3569982aab1a5bc8fcf25ec196606bf137ee4fa984be88", + "sha256:e991ad155783cd0830b895ec8f310fde9e79a7b283776b889a751fb1e7c819fc", + "sha256:eab24b60ae96b7375adceb1f120be818c59bd69db0f3540dc89527d8a371d253", + "sha256:eaea9ddee706654026a84aceb9a3156105917bab3de58fcf150343f847478202", + "sha256:ef98fec4a3aca6d33e3b9fdd680fe513cc7d1c6aedc65ada8a3965601d9d4bcf", + "sha256:f69c418f2040c8593e33b1aba8f2acf890804b073b817535b5d291139d152af5", + "sha256:f96b24da0242791cd6042f6caf074e7a4537a66ca2d1b57d423feb98ba901295" ], "index": "pypi", - "version": "==1.8.1.post1" + "version": "==2.0.0" }, "six": { "hashes": [ @@ -608,11 +717,11 @@ }, "sniffio": { "hashes": [ - "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663", - "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de" + "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", + "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" ], - "markers": "python_version >= '3.5'", - "version": "==1.2.0" + "markers": "python_version >= '3.7'", + "version": "==1.3.0" }, "sortedcontainers": { "hashes": [ @@ -623,27 +732,27 @@ }, "soupsieve": { "hashes": [ - "sha256:1a3cca2617c6b38c0343ed661b1fa5de5637f257d4fe22bd9f1338010a1efefb", - "sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9" + "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", + "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" ], "markers": "python_version >= '3.6'", - "version": "==2.3.1" + "version": "==2.3.2.post1" }, "svgwrite": { "hashes": [ - "sha256:ca63d76396d1f6f099a2b2d8cf1419e1c1de8deece9a2b7f4da0632067d71d43", - "sha256:d304a929f197d31647c287c10eee0f64378058e1c83a9df83433a5980864e59f" + "sha256:a8fbdfd4443302a6619a7f76bc937fc683daf2628d9b737c891ec08b8ce524c3", + "sha256:bb6b2b5450f1edbfa597d924f9ac2dd099e625562e492021d7dd614f65f8a22d" ], "markers": "python_version >= '3.6'", - "version": "==1.4.2" + "version": "==1.4.3" }, "tabulate": { "hashes": [ - "sha256:d7c013fe7abbc5e491394e10fa845f8f32fe54f8dc60c6622c6cf482d25d47e4", - "sha256:eb1d13f25760052e8931f2ef80aaf6045a6cceb47514db8beab24cded16f13a7" + "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", + "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f" ], "index": "pypi", - "version": "==0.8.9" + "version": "==0.9.0" }, "text-unidecode": { "hashes": [ @@ -699,13 +808,21 @@ "markers": "python_version >= '3.5'", "version": "==6.1" }, + "tqdm": { + "hashes": [ + "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", + "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==4.64.1" + }, "trio": { "hashes": [ - "sha256:670a52d3115d0e879e1ac838a4eb999af32f858163e3a704fe4839de2a676070", - "sha256:fb2d48e4eab0dfb786a472cd514aaadc71e3445b203bc300bad93daa75d77c1a" + "sha256:ce68f1c5400a47b137c5a4de72c7c901bd4e7a24fbdebfe9b41de8c6c04eaacf", + "sha256:f1dd0780a89bfc880c7c7994519cb53f62aacb2c25ff487001c0052bd721cdf0" ], "markers": "python_version >= '3.7'", - "version": "==0.20.0" + "version": "==0.22.0" }, "trio-websocket": { "hashes": [ @@ -717,63 +834,54 @@ }, "tweepy": { "hashes": [ - "sha256:8ba5774ac1663b09e5fce1b030daf076f2c9b3ddbf2e7e7ea0bae762e3b1fe3e", - "sha256:f281bb53ab3ba999ff5e3d743d92d3ed543ee5551c7250948f9e56190ec7a43e" + "sha256:5e4c5b5d22f9e5dd9678a708fae4e40e6eeb1a860a89891a5de3040d5f3da8fe", + "sha256:86d4f6738cbd5a57f86dff7ae0caf52f66004d5777626bf5da970aa8c8aa35df" ], "index": "pypi", - "version": "==4.8.0" + "version": "==4.12.1" }, "tzdata": { "hashes": [ - "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9", - "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3" + "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d", + "sha256:fe5f866eddd8b96e9fcba978f8e503c909b19ea7efda11e52e39494bad3a7bfa" ], - "markers": "python_version >= '3.6'", - "version": "==2022.1" + "markers": "platform_system == 'Windows'", + "version": "==2022.7" }, "tzlocal": { "hashes": [ - "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09", - "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f" + "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745", + "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7" ], "markers": "python_version >= '3.6'", - "version": "==4.1" + "version": "==4.2" }, "urllib3": { "extras": [ - "secure", "socks" ], "hashes": [ - "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", - "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", + "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.9" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.26.13" }, "webdriver-manager": { "hashes": [ - "sha256:2eb7c2fe38ec5b06e2090164923e4dfb7c3ac4e7140333a3de9c7956f5047858", - "sha256:b5b91b5df83181e002263fe27296967a5b19cb1ebe8e4a63ee83538394037df4" + "sha256:2d807ec270d2d990022b86efabd4096053e0a1e7a34c6174f9937320dfdc6826", + "sha256:e671f3b738af52351341d6df6674c2f0b959db0fe978e9a7135cfa36d1ad9560" ], "index": "pypi", - "version": "==3.5.4" - }, - "websocket-client": { - "hashes": [ - "sha256:50b21db0058f7a953d67cc0445be4b948d7fc196ecbeb8083d68d94628e4abf6", - "sha256:722b171be00f2b90e1d4fb2f2b53146a536ca38db1da8ff49c972a4e1365d0ef" - ], - "markers": "python_version >= '3.7'", - "version": "==1.3.2" + "version": "==3.8.5" }, "wsproto": { "hashes": [ - "sha256:2218cb57952d90b9fca325c0dcfb08c3bda93e8fd8070b0a17f048e2e47a521b", - "sha256:a2e56bfd5c7cd83c1369d83b5feccd6d37798b74872866e62616e0ecf111bda8" + "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", + "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736" ], "markers": "python_version >= '3.7'", - "version": "==1.1.0" + "version": "==1.2.0" } }, "develop": {} diff --git a/PseudoCode.md b/PseudoCode.md index f19c627..5c9dcd9 100644 --- a/PseudoCode.md +++ b/PseudoCode.md @@ -4,5 +4,5 @@ - A landing event is previously below 10k feet and (previously getting data, no longer getting data and previously not on the ground) or (now on the ground and previously not on the ground). - Given the coordinates of the aircraft the nearest airport is found in an airport database from the distance is calculated using the Haversine formula. The state, region and country are also found in this database with the airport. - At the time of takeoff a takeoff time is set, which is referenced in the landing event to calculate approximate total flight time. -- A Static map image is created based off location name. (Google Static Maps API) or a screenshot of is created using Selenium/ChromeDriver. The selected plane is locked on in the screenshot. +- A Static map image is created based off location name. (Google Static Maps API) or a screenshot of is created using Selenium/ChromeDriver. The selected plane is locked on in the screenshot. - If the landing event or takeoff event is true, It will output to any of the following built-in output methods. (Twitter, Pushbullet, and Discord can all be setup and enabled in each plane's config file). Outputs the location name, map, image, and flight time on landing. (Tweepy and "Pushbullet.py" and Discord_webhooks) diff --git a/README.md b/README.md index d28500b..4979799 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/c4e1d839eec3468cadfe351d64dc1ac4)](https://app.codacy.com/manual/Jxck-S/plane-notify?utm_source=github.com&utm_medium=referral&utm_content=Jxck-S/plane-notify&utm_campaign=Badge_Grade_Settings) [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg)](https://opensource.org/licenses/) -Notify if configured planes have taken off or landed using Python with OpenSky(free) or ADSBExchange Data(paid but much better), outputs location of takeoff location of landing and takeoff by reverse lookup of coordinates. +Notify if configured planes have taken off or landed using Python with OpenSky(free) or ADSBExchange Data(paid, declining data, and run by clowns), outputs location of takeoff location of landing and takeoff by reverse lookup of coordinates. ### Discord Output Example -![Discord Output Example](./ExImages/DiscordEX.png?raw=true) +![Discord Output Example](./ExImages/DiscordEX2.png?raw=true) #### More examples are in the ExImages folder @@ -15,15 +15,16 @@ Notify if configured planes have taken off or landed using Python with /etc/apt/sources.list.d/google.list +apt update +apt install google-chrome-stable ``` These output methods once installed can be configured in the planes config you create, using the example plane1.ini @@ -67,6 +71,7 @@ cd plane-notify ### Configure main config file with keys and URLs (mainconf.ini) in the configs directory +- Copy `mainconf.ini.example` to `mainconf.ini` andCopy `plane1.ini.example` to `plane1.ini`. `plane1.ini` can change names as long as it ends with the ini extension - Edit them with nano or vi on the running machine or on your pc and transfer the config to where you will be running the bot - Pick between OpenSky and ADS-B Exchange - The OpenSky API is free for everyone but the data is not as good as ADS-B Exchange. The ADS-B Exchange API is not free and this program will not work for the Rapid API from ADS-B Exchange. It only works with the API that they give when you have a partnership with ADS-B Exchange. It is not cheap to get the ADS-B Exchange full API, Don't contact them unless you are ready to pay. @@ -91,7 +96,7 @@ screen -R ### Start Program ```bash -pipenv run python __main__ +pipenv run python __main__.py ``` ## Using with Docker diff --git a/__main__.py b/__main__.py index ed14422..53248d6 100644 --- a/__main__.py +++ b/__main__.py @@ -1,16 +1,21 @@ import configparser -from logging import DEBUG import time from colorama import Fore, Back, Style import platform import traceback +import os if platform.system() == "Windows": from colorama import init init(convert=True) +elif platform.system() == "Linux": + if os.path.exists("/tmp/plane-notify"): + import shutil + shutil.rmtree("/tmp/plane-notify") + os.makedirs("/tmp/plane-notify") + os.makedirs("/tmp/plane-notify/chrome") from planeClass import Plane from datetime import datetime import pytz -import os import signal abspath = os.path.abspath(__file__) dname = os.path.dirname(abspath) @@ -20,7 +25,11 @@ sys.path.extend([os.getcwd()]) #Dependency Handling if not os.path.isdir("./dependencies/"): os.mkdir("./dependencies/") -required_files = [("Roboto-Regular.ttf", 'https://github.com/googlefonts/roboto/blob/main/src/hinted/Roboto-Regular.ttf?raw=true'), ('airports.csv', 'https://ourairports.com/data/airports.csv'), ('regions.csv', 'https://ourairports.com/data/regions.csv'), ('ADSBX_Logo.png', "https://www.adsbexchange.com/wp-content/uploads/cropped-Stealth.png"), ('Mictronics_db.zip', "https://www.mictronics.de/aircraft-database/indexedDB.php")] +required_files = [ + ("Roboto-Regular.ttf", 'https://github.com/googlefonts/roboto/blob/main/src/hinted/Roboto-Regular.ttf?raw=true'), + ('airports.csv', 'https://ourairports.com/data/airports.csv'), + ('regions.csv', 'https://ourairports.com/data/regions.csv'), + ('Mictronics_db.zip', "https://www.mictronics.de/aircraft-database/indexedDB.php")] for file in required_files: file_name = file[0] url = file[1] @@ -49,11 +58,13 @@ main_config.read('./configs/mainconf.ini') source = main_config.get('DATA', 'SOURCE') if main_config.getboolean('DISCORD', 'ENABLE'): from defDiscord import sendDis + role_id = main_config.get('DISCORD', 'ROLE_ID') if main_config.has_option('DISCORD', 'ROLE_ID') and main_config.get('DISCORD', 'ROLE_ID').strip() != "" else None sendDis("Started", main_config, role_id = main_config.get('DISCORD', 'ROLE_ID')) def service_exit(signum, frame): if main_config.getboolean('DISCORD', 'ENABLE'): from defDiscord import sendDis - sendDis("Service Stop", main_config, role_id = main_config.get('DISCORD', 'ROLE_ID')) + role_id = main_config.get('DISCORD', 'ROLE_ID') if main_config.has_option('DISCORD', 'ROLE_ID') and main_config.get('DISCORD', 'ROLE_ID').strip() != "" else None + sendDis("Service Stop", main_config, role_id = role_id) raise SystemExit("Service Stop") signal.signal(signal.SIGTERM, service_exit) if os.path.isfile("lookup_route.py"): @@ -69,7 +80,7 @@ try: print("Found the following configs") for dirpath, dirname, filename in os.walk("./configs"): for filename in [f for f in filename if f.endswith(".ini") and f != "mainconf.ini"]: - if not "disabled" in dirpath: + if "disabled" not in dirpath: print(os.path.join(dirpath, filename)) plane_config = configparser.ConfigParser() plane_config.read((os.path.join(dirpath, filename))) @@ -140,18 +151,66 @@ try: for planeData in data['ac']: data_indexed[planeData[icao_key].upper()] = planeData for key, obj in planes.items(): - if key in data_indexed.keys(): + try: if api_version == 1: obj.run_adsbx_v1(data_indexed[key.upper()]) elif api_version == 2: obj.run_adsbx_v2(data_indexed[key.upper()]) - else: + except KeyError: obj.run_empty() else: for obj in planes.values(): obj.run_empty() else: failed_count += 1 + elif source == "RpdADSBX": + #ACAS data + from defADSBX import pull_date_ras + import ast + today = datetime.utcnow() + date = today.strftime("%Y/%m/%d") + ras = pull_date_ras(date) + sorted_ras = {} + if ras is not None: + #Testing RAs + #if last_ra_count is not None: + # with open('./testing/acastest.json') as f: + # data = f.readlines() + # ras += data + ra_count = len(ras) + if last_ra_count is not None and ra_count != last_ra_count: + print(abs(ra_count - last_ra_count), "new Resolution Advisories") + for ra_num, ra in enumerate(ras[last_ra_count:]): + ra = ast.literal_eval(ra) + if ra['hex'].upper() in planes.keys(): + if ra['hex'].upper() not in sorted_ras.keys(): + sorted_ras[ra['hex'].upper()] = [ra] + else: + sorted_ras[ra['hex'].upper()].append(ra) + else: + print("No new Resolution Advisories") + last_ra_count = ra_count + for key, obj in planes.items(): + if sorted_ras != {} and key in sorted_ras.keys(): + print(key, "has", len(sorted_ras[key]), "RAs") + obj.check_new_ras(sorted_ras[key]) + obj.expire_ra_types() + from defRpdADSBX import pull_rpdadsbx + data_indexed = {} + for icao in planes: + plane = planes[icao] + plane_info = pull_rpdadsbx(icao) + if plane_info: + if plane_info['ac']: + data_indexed[icao.upper()] = plane_info['ac'][0] + plane.run_adsbx_v2(data_indexed[icao.upper()]) + else: + plane.run_empty() + else: + print(f"No data for icao {icao}. Skipping...") + plane.run_empty() + if not data_indexed: + failed_count += 1 elif source == "OPENS": from defOpenSky import pull_opensky planeData, failed = pull_opensky(planes) @@ -186,7 +245,10 @@ try: footer = "-------- " + str(running_Count) + " -------- " + str(datetime_tz.strftime("%I:%M:%S %p")) + " ------------------------Elapsed Time- " + str(round(elapsed_calc_time, 3)) + " -------------------------------------" print (Back.GREEN + Fore.BLACK + footer[0:100] + Style.RESET_ALL) - sleep_sec = 30 + if main_config.has_section('SLEEP'): + sleep_sec = int(main_config.get('SLEEP', 'SLEEPSEC')) + else: + sleep_sec = 30 for i in range(sleep_sec,0,-1): if i < 10: i = " " + str(i) @@ -208,10 +270,10 @@ except Exception as e: except OSError: pass import logging - logging.basicConfig(filename='crash_latest.log', filemode='w', format='%(asctime)s - %(message)s',level=logging.DEBUG) + logging.basicConfig(filename='crash_latest.log', filemode='w', format='%(asctime)s - %(message)s') logging.Formatter.converter = time.gmtime logging.error(e) logging.error(str(traceback.format_exc())) from defDiscord import sendDis - sendDis(str("Error Exiting: " + str(e) + " Failed on " + "https://globe.adsbexchange.com/?icao=" + key), main_config, main_config.get('DISCORD', 'ROLE_ID'), "crash_latest.log") + sendDis(str("Error Exiting: " + str(e) + f"Failed on ({obj.config_path}) https://globe.theairtraffic.com/?icao={key} "), main_config, main_config.get('DISCORD', 'ROLE_ID'), "crash_latest.log") raise e diff --git a/aircraft_type_fuel_consumption_rates.json b/aircraft_type_fuel_consumption_rates.json index 742a3e9..ce2471b 100644 --- a/aircraft_type_fuel_consumption_rates.json +++ b/aircraft_type_fuel_consumption_rates.json @@ -1,214 +1,412 @@ { "EA50": { - "name": "Eclipse 550", - "galph": 76, - "category": "VLJ" + "name": "Eclipse 550", + "galph": 76, + "category": "VLJ" }, "LJ31": { - "name": "Learjet 31", - "galph": 202, - "category": "Light" + "name": "Learjet 31", + "galph": 202, + "category": "Light" }, "LJ40": { - "name": "Learjet 40", - "galph": 207, - "category": "Light" + "name": "Learjet 40", + "galph": 207, + "category": "Light" }, "PC24": { - "name": "Pilatus PC-24", - "galph": 154, - "category": "Light" + "name": "Pilatus PC-24", + "galph": 154, + "category": "Light" }, "LJ45": { - "name": "Learjet 45", - "galph": 205, - "category": "Super Light" + "name": "Learjet 45", + "galph": 205, + "category": "Super Light" }, "LJ70": { - "name": "Learjet 70", - "galph": 198, - "category": "Super Light" + "name": "Learjet 70", + "galph": 198, + "category": "Super Light" }, "LJ75": { - "name": "Learjet 75", - "galph": 199, - "category": "Super Light" + "name": "Learjet 75", + "galph": 199, + "category": "Super Light" }, "G150": { - "name": "Gulfstream G150", - "galph": 228, - "category": "Midsize" + "name": "Gulfstream G150", + "galph": 228, + "category": "Midsize" }, "LJ60": { - "name": "Learjet 60", - "galph": 239, - "category": "Midsize" + "name": "Learjet 60", + "galph": 239, + "category": "Midsize" }, "GALX": { - "name": "Gulfstream G200", - "galph": 278, - "category": "Super Midsize" + "name": "Gulfstream G200", + "galph": 278, + "category": "Super Midsize" }, "G280": { - "name": "Gulfstream G280", - "galph": 297, - "category": "Super Midsize" + "name": "Gulfstream G280", + "galph": 297, + "category": "Super Midsize" }, "GLF5": { - "name": "Gulfstream G500", - "galph": 447, - "category": "Large" + "name": "Gulfstream G500", + "galph": 447, + "category": "Large" }, "GLF6": { - "name": "Gulfstream G650", - "galph": 503, - "category": "Ultra Long Range" + "name": "Gulfstream G650", + "galph": 503, + "category": "Ultra Long Range" }, "PC12": { - "name": "Pilatus PC-12", - "galph": 66, - "category": "Turboprop Aircraft" + "name": "Pilatus PC-12", + "galph": 66, + "category": "Turboprop Aircraft" }, "GLEX": { - "name": "Global", - "galph": 500, - "category": "Ultra Long Range" - } - , + "name": "Global", + "galph": 500, + "category": "Ultra Long Range" + }, "CL30": { - "name": "Challenger 300", - "galph": 295, - "category": "Super Midsize" - }, "B742": { - "name": "Boeing 747-200", - "galph": 3830, - "category": "Large" - }, "T38": { - "name": "T-38 Talon", - "galph": 375, - "category": "Fighter" - }, "WB57": { - "name": "Martin B-57 Canberra", - "galph": 531, - "category": "Twinjet Tactical Bomber and Reconnaissance" - }, "B74S": { - "name": "747 SP", - "galph": 2289, - "category": "Large" - }, "B752": { - "name": "757 200", - "galph": 877, - "category": "Large" - }, - "B738": { - "name": "737 800", - "galph": 832, - "category": "Medium" + "name": "Challenger 300", + "galph": 295, + "category": "Super Midsize" + }, + "B742": { + "name": "Boeing 747-200", + "galph": 3830, + "category": "Large" + }, + "T38": { + "name": "T-38 Talon", + "galph": 375, + "category": "Fighter" + }, + "WB57": { + "name": "Martin B-57 Canberra", + "galph": 531, + "category": "Twinjet Tactical Bomber and Reconnaissance" + }, + "B74S": { + "name": "747 SP", + "galph": 2289, + "category": "Large" + }, + "B752": { + "name": "757 200", + "galph": 877, + "category": "Large" + }, + "B738": { + "name": "737 800", + "galph": 832, + "category": "Medium" }, "B737": { - "name": "737 700", - "galph": 796, - "category": "Medium" + "name": "737 700", + "galph": 796, + "category": "Medium" }, "A320": { - "name": "A320", - "galph": 800, - "category": "Medium" + "name": "A320", + "galph": 800, + "category": "Medium" }, "P3": { - "name": "Lockheed Orion P3", - "galph": 671, - "category": "Turboprop" + "name": "Lockheed Orion P3", + "galph": 671, + "category": "Turboprop" }, "C750": { - "name": "Cessna 750 Citation X", - "galph": 347, - "category": "Small Private Jet" + "name": "Cessna 750 Citation X", + "galph": 347, + "category": "Small Private Jet" }, "FA7X": { - "name": "Dassult Falcon 7X", - "galph": 380, - "category": "Small Private Jet" + "name": "Dassult Falcon 7X", + "galph": 380, + "category": "Small Private Jet" }, "F900": { - "name": "Dassult Falcon 900", - "galph": 347, - "category": "Small Private Jet" + "name": "Dassult Falcon 900", + "galph": 347, + "category": "Small Private Jet" }, "H25B": { - "name": "Hawker 750/850", - "galph": 270, - "category": "Small Private Jet" + "name": "Hawker 750/850", + "galph": 270, + "category": "Small Private Jet" }, "C680": { - "name": "Cessna 680 Citation", - "galph": 247, - "category": "Small Private Jet" + "name": "Cessna 680 Citation", + "galph": 247, + "category": "Small Private Jet" }, "GLF3": { - "name": "Gulfstream 3", - "galph": 568, - "category": "Heavy Private Jet" - }, + "name": "Gulfstream 3", + "galph": 568, + "category": "Heavy Private Jet" + }, "GLF4": { - "name": "Gulfstream 4", - "galph": 479, - "category": "Heavy Private Jet" + "name": "Gulfstream 4", + "galph": 479, + "category": "Heavy Private Jet" }, "CL60": { - "name": "Bombardier CL-600 Challenge", - "galph": 262, - "category": "Mid-size Private Jet" + "name": "Bombardier CL-600 Challenge", + "galph": 262, + "category": "Mid-size Private Jet" }, "A139": { - "name": "Agusta-Bell AW139", - "galph": 150, - "category": "Medium Utility Helicopter" + "name": "Agusta-Bell AW139", + "galph": 150, + "category": "Medium Utility Helicopter" }, "GL5T": { - "name": "Global 5000", - "galph": 455, - "category": "Heavy Private Jet" + "name": "Global 5000", + "galph": 455, + "category": "Heavy Private Jet" }, "GA6C": { - "name": "Gulfstream G600", - "galph": 458, - "category": "Heavy Private Jet" + "name": "Gulfstream G600", + "galph": 458, + "category": "Heavy Private Jet" }, "A337": { - "name": "Airbus Beluga XL", - "galph": 1800, - "category": "Large Transport Aircraft" + "name": "Airbus Beluga XL", + "galph": 1800, + "category": "Large Transport Aircraft" }, "A3ST": { - "name": "Airbus Beluga", - "galph": 1260, - "category": "Large Transport Aircraft" + "name": "Airbus Beluga", + "galph": 1260, + "category": "Large Transport Aircraft" }, "F2TH": { - "name": "Dassault Falcon 2000", - "galph": 245, - "category": "Medium Private Jet" + "name": "Dassault Falcon 2000", + "galph": 245, + "category": "Medium Private Jet" }, "GA5C": { - "name": "Gulfstream G500", - "galph": 402, - "category": "Large Private Jet" + "name": "Gulfstream G500", + "galph": 402, + "category": "Large Private Jet" }, "C130": { - "name": "Lockheed C130", - "galph": 740, - "category": "Medium Cargo" + "name": "Lockheed C130", + "galph": 740, + "category": "Medium Cargo" }, "B762": { - "name": "Boeing 767 200", - "galph": 1722, - "category": "Wide-body" + "name": "Boeing 767 200", + "galph": 1722, + "category": "Wide-body" }, "B772": { - "name": "Boeing 777 200", - "galph": 2300, - "category": "Wide-body" + "name": "Boeing 777 200", + "galph": 2300, + "category": "Wide-body" + }, + "SLCH": { + "name": "Stratolaunch", + "galph": 2396, + "category": "Special" + }, + "P51": { + "name": "P51 Mustang", + "galph": 65, + "category": "Fighter" + }, + "HDJT": { + "name": "Honda Jet", + "galph": 90, + "category": "Light Jet" + }, + "B744": { + "name": "Boeing 747-400", + "galph": 3700, + "category": "Heavy Airliner" + }, + "E190": { + "name": "Embrar E190", + "galph": 469, + "category": "Heavy Jet" + }, + "FA50": { + "name": "Falcon 50", + "galph": 229, + "category": "Heavy Jet" + }, + "GL7T": { + "name": "Global 7000", + "galph": 460, + "category": "Heavy Jet" + }, + "GL6T": { + "name": "", + "galph": 455.0, + "category": "" + }, + "C68A": { + "name": "", + "galph": 212.0, + "category": "" + }, + "C56X": { + "name": "", + "galph": 217.0, + "category": "" + }, + "B763": { + "name": "", + "galph": 1320.0, + "category": "" + }, + "A310": { + "name": "", + "galph": 1189.0, + "category": "" + }, + "A330": { + "name": "", + "galph": 1505.0, + "category": "" + }, + "A380": { + "name": "", + "galph": 4062.0, + "category": "" + }, + "E170": { + "name": "", + "galph": 469.0, + "category": "" + }, + "DC87": { + "name": "", + "galph": 1250.0, + "category": "" + }, + "SGUP": { + "name": "", + "galph": 1156.0, + "category": "" + }, + "WHK2": { + "name": "", + "galph": 500.0, + "category": "" + }, + "B350": { + "name": "", + "galph": 122.0, + "category": "" + }, + "BE30": { + "name": "", + "galph": 121.0, + "category": "" + }, + "FA8X": { + "name": "", + "galph": 380.0, + "category": "" + }, + "E550": { + "name": "", + "galph": 280.0, + "category": "" + }, + "E55P": { + "name": "", + "galph": 166.0, + "category": "" + }, + "A332": { + "name": "", + "galph": 1480, + "category": "" + }, + "GA7C": { + "name": "", + "galph": 382.0, + "category": "" + }, + "FA6X": { + "name": "", + "galph": 419.0, + "category": "" + }, + "B3XM": { + "name": "", + "galph": 716.0, + "category": "" + }, + "B779": { + "name": "", + "galph": 2250.0, + "category": "" + }, + "BE22": { + "name": "", + "galph": 60.0, + "category": "" + }, + "C560": { + "name": "", + "galph": 182.0, + "category": "" + }, + "E145": { + "name": "", + "galph": 284.0, + "category": "" + }, + "C25C": { + "name": "", + "galph": 110.0, + "category": "" + }, + "C25B": { + "name": "", + "galph": 110.0, + "category": "" + }, + "C441": { + "name": "", + "galph": 57.0, + "category": "" + }, + "E50P": { + "name": "", + "galph": 109.0, + "category": "" + }, + "CRJ2": { + "name": "", + "galph": 325.0, + "category": "" + }, + "CRJ7": { + "name": "", + "galph": 444.0, + "category": "" + }, + "BE40": { + "name": "", + "galph": 220.0, + "category": "" + }, + "C700": { + "name": "", + "galph": 288.0, + "category": "" } - } \ No newline at end of file diff --git a/calculate_headings.py b/calculate_headings.py index 2ac8ac0..617227b 100644 --- a/calculate_headings.py +++ b/calculate_headings.py @@ -18,6 +18,9 @@ def calculate_cardinal(d): return card def calculate_deg_change(new_heading, original_heading): """Calculates change between two headings, returns negative degree if change is left, positive if right""" + if new_heading is None: + print("Track heading missing. No change") + return 0 normal = abs(original_heading-new_heading) across_inital = 360 - abs(original_heading-new_heading) if across_inital < normal: diff --git a/configs/mainconf.ini b/configs/mainconf.ini.example similarity index 68% rename from configs/mainconf.ini rename to configs/mainconf.ini.example index 1ea437c..ab65c60 100644 --- a/configs/mainconf.ini +++ b/configs/mainconf.ini.example @@ -2,8 +2,8 @@ #Source to pull data from #SHOULD BE ADSBX which is ADS-B Exchange or OPENS which is OpenSky #By default configured with OpenSky which anyone can use without a login -#ADS-B Exchange has better data but is not avalible unless you pay (see: https://www.adsbexchange.com/data/ ) -SOURCE = OPENS +#ADS-B Exchange has better data but is not available unless you feed their network or pay. +SOURCE = RpdADSBX #Default amount of time after data loss to trigger a landing when under 10k ft DATA_LOSS_MINS = 5 #Failover from one source to the other, only enable if you have both sources setup. @@ -20,7 +20,7 @@ API_VERSION = 1 #ADSBX API Proxy, https://gitlab.com/jjwiseman/adsbx-api-proxy, v2 input, v1 or v2 output from proxy ENABLE_PROXY = FALSE #Full URL http://host:port -PROXY_HOST = +PROXY_HOST = #OpenSky https://opensky-network.org/apidoc/index.html #When using without your own login user and pass should be None @@ -28,15 +28,25 @@ PROXY_HOST = USERNAME = None PASSWORD = None +#ADS-B Exchange on RapidAPI https://rapidapi.com/adsbx/api/adsbexchange-com1/ +[RpdADSBX] +API_KEY = none +API_VERSION = 2 + +#Define the delay interval in seconds between each data request. This is useful if you have limited requests in the API. +[SLEEP] +SLEEPSEC = 60 + [GOOGLE] #API KEY for Google Static Maps only if you using this on any of the planes. API_KEY = googleapikey -#Used for failover messages and program exits notifcation +#Used for failover messages and program exits notification [DISCORD] ENABLE = FALSE USERNAME = usernamehere URL = webhookurl +ROLE_ID = [TFRS] URL = http://127.0.0.1:5000/detailed_all @@ -47,3 +57,8 @@ ENABLE = False ENABLE = False CONSUMER_KEY = ck CONSUMER_SECRET = cs + +[MAP] +#Map to create from Google Static Maps or screenshot global tar1090 from globe.theairtraffic.com +#Enter GOOGLESTATICMAP or ADSBX +OPTION = ADSBX diff --git a/configs/plane1.ini b/configs/plane1.ini.example similarity index 82% rename from configs/plane1.ini rename to configs/plane1.ini.example index 1b0cbc8..eca4e8f 100644 --- a/configs/plane1.ini +++ b/configs/plane1.ini.example @@ -9,11 +9,11 @@ ICAO = icaohere ; OVERRIDE_TYPELONG = ; OVERRIDE_OWNER = ; DATA_LOSS_MINS = 20 +; CONCEAL_AC_ID = True +; CONCEAL_PIA = False [MAP] -#Map to create from Google Static Maps or screenshot global tar1090 from globe.adsbexchange.com -#Enter GOOGLESTATICMAP or ADSBX -OPTION = ADSBX +#Optional, map selection moved to mainconf, this is for map overlays per plane #Tar1090 overlays option, should be seperated by comma no space, remove option all together to disable any OVERLAYS = nexrad @@ -29,12 +29,6 @@ TITLE = ACCESS_TOKEN = athere ACCESS_TOKEN_SECRET = atshere -[PUSHBULLET] -ENABLE = FALSE -TITLE = Title Of Pushbullet message -API_KEY = apikey -CHANNEL_TAG = channeltag - [DISCORD] ENABLE = FALSE #WEBHOOK URL https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks @@ -56,3 +50,9 @@ ENABLE = FALSE TITLE = Title Of Telegram message ROOM_ID = -100xxxxxxxxxx BOT_TOKEN = xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +[MASTODON] +ENABLE = TRUE +ACCESS_TOKEN = mastodonaccesstoken +APP_URL = mastodonappurl + diff --git a/defAirport.py b/defAirport.py index ed64da8..dd3d8ec 100644 --- a/defAirport.py +++ b/defAirport.py @@ -30,6 +30,7 @@ def getClosestAirport(latitude, longitude, allowed_types): return closest_airport_dict def get_airport_by_icao(icao): with open('./dependencies/airports.csv', 'r', encoding='utf-8') as airport_csv: + matching_airport = None airport_csv_reader = csv.DictReader(filter(lambda row: row[0]!='#', airport_csv)) for airport in airport_csv_reader: if airport['gps_code'] == icao: @@ -37,5 +38,8 @@ def get_airport_by_icao(icao): #Convert indent key to icao key as its labeled icao in other places not ident matching_airport['icao'] = matching_airport.pop('gps_code') break - matching_airport = add_airport_region(matching_airport) - return matching_airport \ No newline at end of file + if matching_airport: + matching_airport = add_airport_region(matching_airport) + return matching_airport + else: + return None \ No newline at end of file diff --git a/defMastodon.py b/defMastodon.py new file mode 100644 index 0000000..ecf5ae2 --- /dev/null +++ b/defMastodon.py @@ -0,0 +1,39 @@ +def sendMastodon(photo, message, config): + from mastodon import Mastodon + sent = False + retry_c = 0 + while sent == False: + try: + bot = Mastodon( + access_token=config.get('MASTODON','ACCESS_TOKEN'), + api_base_url=config.get('MASTODON','APP_URL') + ) + mediaid = bot.media_post(photo, mime_type="image/jpeg") + sent = bot.status_post(message,None,mediaid,False, "Public") + except Exception as err: + print('err.args:') + print(err.args) + print(f"Unexpected {err=}, {type(err)=}") + print("\nString err:\n"+str(err)) + if retry_c > 4: + print('Mastodon attempts exceeded. Message not sent.') + break + elif str(err) == 'Unauthorized': + print('Invalid Mastodon bot token, message not sent.') + break + elif str(err) == 'Timed out': + retry_c += 1 + print('Mastodon timeout count: '+str(retry_c)) + pass + elif str(err)[:35] == '[Errno 2] No such file or directory': + print('Mastodon module couldn\'t find an image to send.') + break + elif str(err) == 'Media_caption_too_long': + print('Mastodon image caption lenght exceeds 1024 characters. Message not send.') + break + else: + print('[X] Unknown error. Message not sent.') + break + else: + print("Mastodon message successfully sent.") + return sent diff --git a/defOpenSky.py b/defOpenSky.py index 7b84e8b..551e164 100644 --- a/defOpenSky.py +++ b/defOpenSky.py @@ -4,7 +4,9 @@ def pull_opensky(planes): main_config.read('./configs/mainconf.ini') from opensky_api import OpenSkyApi planeData = None - opens_api = OpenSkyApi(username= None if main_config.get('OPENSKY', 'USERNAME').upper() == "NONE" else main_config.get('OPENSKY', 'USERNAME'), password= None if main_config.get('OPENSKY', 'PASSWORD').upper() == "NONE" else main_config.get('OPENSKY', 'PASSWORD').upper()) + opens_api = OpenSkyApi( + username= None if main_config.get('OPENSKY', 'USERNAME').upper() == "NONE" else main_config.get('OPENSKY', 'USERNAME'), + password= None if main_config.get('OPENSKY', 'PASSWORD').upper() == "NONE" else main_config.get('OPENSKY', 'PASSWORD')) failed = False icao_array = [] for key in planes.keys(): diff --git a/defRpdADSBX.py b/defRpdADSBX.py new file mode 100644 index 0000000..6d2631c --- /dev/null +++ b/defRpdADSBX.py @@ -0,0 +1,34 @@ +import requests +import configparser +from datetime import datetime + +main_config = configparser.ConfigParser() +main_config.read('./configs/mainconf.ini') +api_version = main_config.get('RpdADSBX', 'API_VERSION') + +def pull_rpdadsbx(planes): + api_version = int(main_config.get('RpdADSBX', 'API_VERSION')) + if api_version != 2: + raise ValueError("Bad RapidAPI ADSBX API Version") + url = "https://adsbexchange-com1.p.rapidapi.com/v2/icao/" + planes + "/" + headers = { + "X-RapidAPI-Host": "adsbexchange-com1.p.rapidapi.com", + "X-RapidAPI-Key": main_config.get('RpdADSBX', 'API_KEY') + } + try: + response = requests.get(url, headers = headers, timeout=30) + response.raise_for_status() + data = response.json() + if "msg" in data.keys() and data['msg'] != "No error": + raise ValueError("Error from ADSBX: msg = ", data['msg']) + if "ctime" in data.keys(): + data_ctime = float(data['ctime']) / 1000.0 + print("Data ctime:",datetime.utcfromtimestamp(data_ctime)) + if "now" in data.keys(): + data_now = float(data['now']) / 1000.0 + print("Data now time:",datetime.utcfromtimestamp(data_now)) + print("Current UTC:", datetime.utcnow()) + return data + except Exception as e: + print('Error calling RapidAPI', e) + return None diff --git a/defSS.py b/defSS.py index 67f1783..140bc66 100644 --- a/defSS.py +++ b/defSS.py @@ -6,31 +6,41 @@ import time from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException - -def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_track_labels=False, overrides={}): +def blur_elements_by_id(browser, element_ids): + for element in element_ids: + try: + element = browser.find_element(By.ID, element) + browser.execute_script("arguments[0].style.filter = 'blur(7px)';", element) + except NoSuchElementException: + print("Issue finding:", element, "on page") +def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_track_labels=False, overrides={}, conceal_ac_id=False, conceal_pia=False): + import os + import platform chrome_options = webdriver.ChromeOptions() chrome_options.headless = True chrome_options.add_argument('window-size=800,800') chrome_options.add_argument('ignore-certificate-errors') - #Plane images issue loading when in headless setting agent fixes. + chrome_options.add_experimental_option('excludeSwitches', ['enable-logging']) + if platform.system() == "Linux": + chrome_options.add_argument('crash-dumps-dir=/tmp/plane-notify/chrome') + + #Plane images issue loading when in headless setting agent fixes. chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36") - import os - import platform if platform.system() == "Linux" and os.geteuid()==0: chrome_options.add_argument('--no-sandbox') # required when running as root user. otherwise you would get no sandbox errors. browser = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options) - url = f"https://globe.adsbexchange.com/?{url_params}" - print(url) + url = f"https://globe.theairtraffic.com/?{url_params}" + print(f"Getting Screenshot of {url}") browser.set_page_load_timeout(80) browser.get(url) WebDriverWait(browser, 40).until(lambda d: d.execute_script("return jQuery.active == 0")) remove_id_elements = ["show_trace", "credits", 'infoblock_close', 'selected_photo_link', "history_collapse"] for element in remove_id_elements: try: - element = browser.find_element_by_id(element) + element = browser.find_element(By.ID, element) browser.execute_script("""var element = arguments[0]; element.parentNode.removeChild(element); """, element) except: - print("issue removing", element, "from map") + print("Issue finding:", element, "on page") #Remove watermark on data try: browser.execute_script("document.getElementById('selected_infoblock').className = 'none';") @@ -43,41 +53,45 @@ def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_trac print("Couldn't disable sidebar on map") #Remove Google Ads try: - element = browser.find_element_by_xpath("//*[contains(@id, 'FIOnDemandWrapper_')]") + element = browser.find_element(By.XPATH, "//*[contains(@id, 'FIOnDemandWrapper_')]") browser.execute_script("""var element = arguments[0]; element.parentNode.removeChild(element); """, element) except: print("Couldn't remove Google Ads") - #Remove share - # try: - # element = browser.find_element_by_xpath("//*[contains(text(), 'Copy Link')]") - # browser.execute_script("""var element = arguments[0]; element.parentNode.removeChild(element); """, element) - # except Exception as e: - # print("Couldn't remove share button from map", e) + #Remove Copy Link + try: + element = browser.find_element(By.XPATH, "//*[@id='selected_icao']/span[2]/a") + browser.execute_script("""var element = arguments[0]; element.parentNode.removeChild(element); """, element) + except Exception as e: + print("Couldn't remove copy link button from map", e) #browser.execute_script("toggleFollow()") + if conceal_pia or conceal_ac_id: + blur_elements_by_id(browser, ["selected_callsign", "selected_icao", "selected_squawk1"]) + if conceal_ac_id: + blur_elements_by_id(browser, ["selected_registration", "selected_country", "selected_dbFlags", "selected_ownop", "selected_typelong", "selected_icaotype", "airplanePhoto", "silhouette", "copyrightInfo"]) if enable_labels: - browser.find_element_by_tag_name('body').send_keys('l') + browser.find_element(By.TAG_NAME, 'body').send_keys('l') if enable_track_labels: - browser.find_element_by_tag_name('body').send_keys('k') + browser.find_element(By.TAG_NAME, 'body').send_keys('k') from selenium.webdriver.support import expected_conditions as EC time.sleep(15) if 'reg' in overrides.keys(): - element = browser.find_element_by_id("selected_registration") + element = browser.find_element(By.ID, "selected_registration") browser.execute_script(f"arguments[0].innerText = '* {overrides['reg']}'", element) reg = overrides['reg'] else: - try: - reg = browser.find_element_by_id("selected_registration").get_attribute("innerHTML") + try: + reg = browser.find_element(By.ID, "selected_registration").get_attribute("innerHTML") print("Reg from tar1090 is", reg) except Exception as e: print("Couldn't find reg in tar1090", e) - reg = None + reg = None if reg is not None: try: try: - photo_box = browser.find_element_by_id("silhouette") + photo_box = browser.find_element(By.ID, "silhouette") except NoSuchElementException: - photo_box = browser.find_element_by_id("airplanePhoto") + photo_box = browser.find_element(By.ID, "airplanePhoto") finally: import requests, json photo_list = json.loads(requests.get("https://raw.githubusercontent.com/Jxck-S/aircraft-photos/main/photo-list.json", timeout=20).text) @@ -87,7 +101,7 @@ def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_trac browser.execute_script("arguments[0].style.width = '200px';", photo_box) browser.execute_script("arguments[0].style.float = 'left';", photo_box) browser.execute_script(f"arguments[0].src = 'https://raw.githubusercontent.com/Jxck-S/aircraft-photos/main/images/{reg}.jpg';", photo_box) - image_copy_right = browser.find_element_by_id("copyrightInfo") + image_copy_right = browser.find_element(By.ID, "copyrightInfo") browser.execute_cdp_cmd('Emulation.setScriptExecutionDisabled', {'value': True}) copy_right_children = image_copy_right.find_elements(By.XPATH, "*") if len(copy_right_children) > 0: @@ -97,16 +111,17 @@ def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_trac except Exception as e: print("Error on changing photo", e) if 'type' in overrides.keys(): - element = browser.find_element_by_id("selected_icaotype") + element = browser.find_element(By.ID, "selected_icaotype") browser.execute_script(f"arguments[0].innerText = '* {overrides['type']}'", element) if 'typelong' in overrides.keys(): - element = browser.find_element_by_id("selected_typelong") + element = browser.find_element(By.ID, "selected_typelong") browser.execute_script(f"arguments[0].innerText = '* {overrides['typelong']}'", element) if 'ownop' in overrides.keys(): - element = browser.find_element_by_id("selected_ownop") + element = browser.find_element(By.ID, "selected_ownop") browser.execute_script(f"arguments[0].innerText = '* {overrides['ownop']}'", element) time.sleep(5) browser.save_screenshot(file_path) + browser.quit() def generate_adsbx_screenshot_time_params(timestamp): from datetime import datetime from datetime import timedelta diff --git a/defTelegram.py b/defTelegram.py index d56378c..7290b4d 100644 --- a/defTelegram.py +++ b/defTelegram.py @@ -1,4 +1,17 @@ def sendTeleg(photo, message, config): + try: + from telegram import __version_info__ + except ImportError: + __version_info__ = (0, 0, 0, 0, 0) + if __version_info__ < (20, 0, 0, "alpha", 5): + sent = sendTelegOld(photo, message, config) + return sent + else: + import asyncio + sent = asyncio.run(t_send_photo(photo,message,config)) + return sent + +def sendTelegOld(photo, message, config): import telegram sent = False retry_c = 0 @@ -6,6 +19,45 @@ def sendTeleg(photo, message, config): try: bot = telegram.Bot(token=config.get('TELEGRAM', 'BOT_TOKEN'), request=telegram.utils.request.Request(connect_timeout=20, read_timeout=20)) sent = bot.send_photo(chat_id=config.get('TELEGRAM', 'ROOM_ID'), photo=photo, caption=message, parse_mode=telegram.ParseMode.MARKDOWN, timeout=20) + except Exception as err: + print('err.args:') + print(err.args) + print(f"Unexpected {err=}, {type(err)=}") + print("\nString err:\n"+str(err)) + if retry_c > 4: + print('Telegram attempts exceeded. Message not sent.') + break + elif str(err) == 'Unauthorized': + print('Invalid Telegram bot token, message not sent.') + break + elif str(err) == 'Timed out': + retry_c += 1 + print('Telegram timeout count: '+str(retry_c)) + pass + elif str(err) == 'Chat not found': + print('Invalid Telegram Chat ID, message not sent.') + break + elif str(err)[:35] == '[Errno 2] No such file or directory': + print('Telegram module couldn\'t find an image to sent.') + break + elif str(err) == 'Media_caption_too_long': + print('Telegram image caption length exceeds 1024 characters. Message not sent.') + break + else: + print('[X] Unknown Telegram error. Message not sent.') + break + else: + print("Telegram message successfully sent.") + return sent + +async def t_send_photo(photo,message,config): + import telegram + sent = False + retry_c = 0 + while sent == False: + try: + bot = telegram.Bot(token=config.get('TELEGRAM', 'BOT_TOKEN')) + sent = await bot.send_photo(chat_id=config.get('TELEGRAM', 'ROOM_ID'), photo=photo, caption=message) except Exception as err: print('err.args:') print(err.args) @@ -28,11 +80,11 @@ def sendTeleg(photo, message, config): print('Telegram module couldn\'t find an image to send.') break elif str(err) == 'Media_caption_too_long': - print('Telegram image caption lenght exceeds 1024 characters. Message not send.') + print('Telegram image caption length exceeds 1024 characters. Message not sent.') break else: print('[X] Unknown Telegram error. Message not sent.') break else: print("Telegram message successfully sent.") - return sent \ No newline at end of file + return sent diff --git a/docker-compose.yml b/docker-compose.yml index 223040b..da549c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,7 @@ version: "3.9" services: plane-notify: platform: linux/amd64 + shm_size: 2gb build: context: . volumes: diff --git a/fuel_calc.py b/fuel_calc.py index 4dec4e0..f887231 100644 --- a/fuel_calc.py +++ b/fuel_calc.py @@ -14,25 +14,24 @@ def get_avg_fuel_price(): except Exception as e: print(e) return None - + def fuel_calculation(aircraft_icao_type, minutes): """Calculates fuel usage, price, c02 output of a flight depending on aircraft type and flight length""" with open("aircraft_type_fuel_consumption_rates.json", "r") as f: fuellist = json.loads(f.read()) - #avg_fuel_price_per_gallon = 5.08 fuel_flight_info = {} if aircraft_icao_type in fuellist.keys(): avg_fuel_price_per_gallon = get_avg_fuel_price() galph = fuellist[aircraft_icao_type]["galph"] - fuel_used_gal = round(galph * (minutes/60), 2) + fuel_used_gal = galph * (minutes/60) fuel_flight_info["fuel_price"] = round(fuel_used_gal * avg_fuel_price_per_gallon) fuel_used_kg = fuel_used_gal * 3.04 - c02_tons = round((fuel_used_kg * 3.15 ) / 907.185) + c02_tons = (fuel_used_kg * 3.15 ) / 907.185 fuel_flight_info['fuel_used_kg'] = round(fuel_used_kg) fuel_flight_info["fuel_used_gal"] = round(fuel_used_gal) fuel_flight_info['fuel_used_lters'] = round(fuel_used_gal*3.78541) fuel_flight_info["fuel_used_lbs"] = round(fuel_used_kg * 2.20462) - fuel_flight_info["c02_tons"] = c02_tons + fuel_flight_info["c02_tons"] = round(c02_tons) if c02_tons > 1 else round(c02_tons, 4) print ("Fuel info", fuel_flight_info) return fuel_flight_info else: @@ -43,10 +42,8 @@ def fuel_message(fuel_info): cost = "{:,}".format(fuel_info['fuel_price']) gallons = "{:,}".format(fuel_info['fuel_used_gal']) lters = "{:,}".format(fuel_info['fuel_used_lters']) - lbs = "{:, }".format(fuel_info['fuel_used_lbs']) + lbs = "{:,}".format(fuel_info['fuel_used_lbs']) kgs = "{:,}".format(fuel_info['fuel_used_kg']) - fuel_message = f"~ {gallons} gallons ({lters} liters). \n~ {lbs} lbs ({kgs} kg) of jet fuel used. \n~ ${cost} cost of fuel. \n~ {fuel_info['c02_tons']} tons of CO2 emissions." + fuel_message = f"\n~ {gallons} gallons ({lters} liters). \n~ {lbs} lbs ({kgs} kg) of jet fuel used. \n~ ${cost} cost of fuel. \n~ {fuel_info['c02_tons']} tons of CO2 emissions." print(fuel_message) return fuel_message -#fuel_info = fuel_calculation("GLF6", 548.1) -#fuel_message(fuel_info) diff --git a/modify_image.py b/modify_image.py index 1690193..2725b41 100644 --- a/modify_image.py +++ b/modify_image.py @@ -26,11 +26,6 @@ def append_airport(filename, airport, text_credit=None): draw.rectangle(((325, 760), (624, 800)), fill= white, outline=black) #Header Box draw.rectangle(((401, 738), (549, 760)), fill= navish) - #ADSBX Logo - # - # adsbx = Image.open("./dependencies/ADSBX_Logo.png") - # adsbx = adsbx.resize((25, 25), Image.ANTIALIAS) - # image.paste(adsbx, (632, 757), adsbx) #Create Text #ADSBX Credit if text_credit is not None: diff --git a/planeClass.py b/planeClass.py index dd8f62f..4837ec5 100644 --- a/planeClass.py +++ b/planeClass.py @@ -8,6 +8,7 @@ class Plane: self.icao = icao.upper() self.callsign = None self.config = config + self.config_path = config_path self.overrides = {} if self.config.has_option('DATA', 'OVERRIDE_REG'): self.reg = self.config.get('DATA', 'OVERRIDE_REG') @@ -23,6 +24,14 @@ class Plane: self.overrides['typelong'] = self.config.get('DATA', 'OVERRIDE_TYPELONG') if self.config.has_option('DATA', 'OVERRIDE_OWNER'): self.overrides['ownop'] = self.config.get('DATA', 'OVERRIDE_OWNER') + if self.config.has_option('DATA', 'CONCEAL_AC_ID'): + self.conceal_ac_id = self.config.getboolean('DATA', 'CONCEAL_AC_ID') + else: + self.conceal_ac_id = False + if self.config.has_option('DATA', 'CONCEAL_PIA'): + self.conceal_pia = self.config.getboolean('DATA', 'CONCEAL_PIA') + else: + self.conceal_pia = False self.conf_file_path = config_path self.alt_ft = None self.below_desired_ft = None @@ -55,6 +64,7 @@ class Plane: self.track = None self.last_track = None self.circle_history = None + self.nearest_from_airport = None if self.config.has_option('DATA', 'DATA_LOSS_MINS'): self.data_loss_mins = self.config.getint('DATA', 'DATA_LOSS_MINS') else: @@ -69,18 +79,20 @@ class Plane: self.latest_tweet_id = self.tweet_api.user_timeline(count = 1)[0] except IndexError: self.latest_tweet_id = None - #Setup PushBullet - if self.config.getboolean('PUSHBULLET', 'ENABLE'): - from pushbullet import Pushbullet - self.pb = Pushbullet(self.config['PUSHBULLET']['API_KEY']) - self.pb_channel = self.pb.get_channel(self.config.get('PUSHBULLET', 'CHANNEL_TAG')) def run_opens(self, ac_dict): #Parse OpenSky Vector from colorama import Fore, Back, Style self.print_header("BEGIN") #print (Fore.YELLOW + "OpenSky Sourced Data: ", ac_dict) try: - self.__dict__.update({'icao' : ac_dict.icao24.upper(), 'callsign' : ac_dict.callsign, 'latitude' : ac_dict.latitude, 'longitude' : ac_dict.longitude, 'on_ground' : bool(ac_dict.on_ground), 'squawk' : ac_dict.squawk, 'track' : float(ac_dict.heading)}) + self.__dict__.update({ + 'icao' : ac_dict.icao24.upper(), + 'callsign' : ac_dict.callsign, + 'latitude' : ac_dict.latitude, + 'longitude' : ac_dict.longitude, + 'on_ground' : bool(ac_dict.on_ground), + 'squawk' : ac_dict.squawk, + 'track' : float(ac_dict.true_track)}) if ac_dict.baro_altitude != None: self.alt_ft = round(float(ac_dict.baro_altitude) * 3.281) elif self.on_ground: @@ -88,7 +100,8 @@ class Plane: from mictronics_parse import get_aircraft_reg_by_icao, get_type_code_by_icao self.reg = get_aircraft_reg_by_icao(self.icao) self.type = get_type_code_by_icao(self.icao) - self.last_pos_datetime = datetime.fromtimestamp(ac_dict.time_position) + if ac_dict.time_position is not None: + self.last_pos_datetime = datetime.fromtimestamp(ac_dict.time_position) except ValueError as e: print("Got data but some data is invalid!") print(e) @@ -221,8 +234,11 @@ class Plane: def route_format(extra_route_info, type): from defAirport import get_airport_by_icao to_airport = get_airport_by_icao(self.known_to_airport) - code = to_airport['iata_code'] if to_airport['iata_code'] != "" else to_airport['icao'] - airport_text = f"{code}, {to_airport['name']}" + if to_airport: + code = to_airport['iata_code'] if to_airport['iata_code'] != "" else to_airport['icao'] + airport_text = f"{code}, {to_airport['name']}" + else: + airport_text = f"{self.known_to_airport}" if 'time_to' in extra_route_info.keys() and type != "divert": arrival_rel = "in ~" + extra_route_info['time_to'] else: @@ -234,7 +250,10 @@ class Plane: header = "Now going to" elif type == "divert": header = "Now diverting to" - area = f"{to_airport['municipality']}, {to_airport['region']}, {to_airport['iso_country']}" + if to_airport: + area = f"{to_airport['municipality']}, {to_airport['region']}, {to_airport['iso_country']}" + else: + area = "" route_to = f"{header} {area} ({airport_text})" + (f" arriving {arrival_rel}" if arrival_rel is not None else "") else: if type == "inital": @@ -388,7 +407,7 @@ class Plane: else: self.dis_title = self.config.get('DISCORD', 'TITLE') #Set Twitter Title - if self.config.getboolean('TWITTER', 'ENABLE'): + if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'): if self.config.get('TWITTER', 'TITLE') in ["DYNAMIC", "callsign"]: self.twitter_title = dynamic_title else: @@ -423,12 +442,8 @@ class Plane: elif self.landed: landed_time_msg = None landed_time = None - if self.icao != "A835AF": - message = (f"{type_header} {location_string}.") + ("" if route_to is None else f" {route_to}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "") - dirty_message = None - else: - message = (f"{type_header} {location_string}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "") - dirty_message = (f"{type_header} {location_string}.") + ("" if route_to is None else f" {route_to}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "") + + message = (f"{type_header} {location_string}.") + ("" if route_to is None else f" {route_to}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "") print (message) #Google Map or tar1090 screenshot if Plane.main_config.get('MAP', 'OPTION') == "GOOGLESTATICMAP": @@ -436,8 +451,8 @@ class Plane: getMap((municipality + ", " + state + ", " + country_code), self.map_file_name) elif Plane.main_config.get('MAP', 'OPTION') == "ADSBX": from defSS import get_adsbx_screenshot - url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}" - get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides) + url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}&limitupdates=0" + get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides, conceal_ac_id=self.conceal_ac_id, conceal_pia=self.conceal_pia) from modify_image import append_airport text_credit = self.config.get('MAP', 'TEXT_CREDIT') if self.config.has_option('MAP', 'TEXT_CREDIT') else None append_airport(self.map_file_name, nearest_airport_dict, text_credit) @@ -448,19 +463,17 @@ class Plane: from defTelegram import sendTeleg photo = open(self.map_file_name, "rb") sendTeleg(photo, message, self.config) + #Mastodon + if self.config.has_section('MASTODON') and self.config.getboolean('MASTODON', 'ENABLE'): + from defMastodon import sendMastodon + sendMastodon(self.map_file_name, message, self.config) + #Discord if self.config.getboolean('DISCORD', 'ENABLE'): - dis_message = f"{self.dis_title} {message}".strip() if dirty_message is None else f"{self.dis_title} {dirty_message}".strip() - role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None - sendDis(dis_message, self.config, role_id, self.map_file_name) - #PushBullet - if self.config.getboolean('PUSHBULLET', 'ENABLE'): - with open(self.map_file_name, "rb") as pic: - map_data = self.pb.upload_file(pic, "Tookoff IMG" if self.tookoff else "Landed IMG") - self.pb_channel.push_note(self.config.get('PUSHBULLET', 'TITLE'), message) - self.pb_channel.push_file(**map_data) + role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None + sendDis(message, self.config, role_id, self.map_file_name) #Twitter - if self.config.getboolean('TWITTER', 'ENABLE'): + if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'): import tweepy try: twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name) @@ -468,21 +481,20 @@ class Plane: self.tweet_api.create_media_metadata(media_id= twitter_media_map_obj.media_id, alt_text= alt_text) self.latest_tweet_id = self.tweet_api.update_status(status = ((self.twitter_title + " " + message).strip()), media_ids=[twitter_media_map_obj.media_id]).id except tweepy.errors.TweepyException as e: - print(e) - raise Exception(self.icao) from e + raise #Meta if self.config.has_option('META', 'ENABLE') and self.config.getboolean('META', 'ENABLE'): from meta_toolkit import post_to_meta_both post_to_meta_both(self.config.get("META", "FB_PAGE_ID"), self.config.get("META", "IG_USER_ID"), self.map_file_name, message, self.config.get("META", "ACCESS_TOKEN")) os.remove(self.map_file_name) if self.landed: - if self.known_to_airport is not None and self.nearest_from_airport is not None and self.known_to_airport != self.nearest_from_airport: + if nearest_airport_dict is not None and self.nearest_from_airport is not None and nearest_airport_dict['icao'] != self.nearest_from_airport: from defAirport import get_airport_by_icao from geopy.distance import geodesic - known_to_airport = get_airport_by_icao(self.known_to_airport) + landed_airport = nearest_airport_dict nearest_from_airport = get_airport_by_icao(self.nearest_from_airport) from_coord = (nearest_from_airport['latitude_deg'], nearest_from_airport['longitude_deg']) - to_coord = (known_to_airport['latitude_deg'], known_to_airport['longitude_deg']) + to_coord = (landed_airport['latitude_deg'], landed_airport['longitude_deg']) distance_mi = float(geodesic(from_coord, to_coord).mi) distance_nm = distance_mi / 1.150779448 distance_message = f"{'{:,}'.format(round(distance_mi))} mile ({'{:,}'.format(round(distance_nm))} NM) flight from {nearest_from_airport['iata_code'] if nearest_from_airport['iata_code'] != '' else nearest_from_airport['ident']} to {nearest_airport_dict['iata_code'] if nearest_airport_dict['iata_code'] != '' else nearest_airport_dict['ident']}\n" @@ -497,14 +509,13 @@ class Plane: fuel_message = fuel_message(fuel_info) if self.config.getboolean('DISCORD', 'ENABLE'): dis_message = f"{self.dis_title} {distance_message} \nFlight Fuel Info ```{fuel_message}```".strip() - role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None + role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None sendDis(dis_message, self.config, role_id) - if self.config.getboolean('TWITTER', 'ENABLE'): + if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'): try: self.latest_tweet_id = self.tweet_api.update_status(status = ((self.twitter_title + " " + distance_message + " " + fuel_message).strip()), in_reply_to_status_id = self.latest_tweet_id).id except tweepy.errors.TweepyException as e: - print(e) - raise Exception(self.icao) from e + raise self.latest_tweet_id = None self.recheck_route_time = None self.known_to_airport = None @@ -524,11 +535,10 @@ class Plane: #Discord if self.config.getboolean('DISCORD', 'ENABLE'): dis_message = f"{self.dis_title} {route_to}".strip() - role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None + role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None sendDis(dis_message, self.config, role_id) #Twitter - if self.config.getboolean('TWITTER', 'ENABLE') and self.icao == 'A835AF': - #tweet = self.tweet_api.user_timeline(count = 1)[0] + if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'): self.latest_tweet_id = self.tweet_api.update_status(status = f"{self.twitter_title} {route_to}".strip(), in_reply_to_status_id = self.latest_tweet_id).id if self.circle_history is not None: @@ -558,8 +568,8 @@ class Plane: getMap((municipality + ", " + state + ", " + country_code), self.map_file_name) if Plane.main_config.get('MAP', 'OPTION') == "ADSBX": from defSS import get_adsbx_screenshot - url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}" - get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides) + url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}&limitupdates=0" + get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides, conceal_ac_id=self.conceal_ac_id, conceal_pia=self.conceal_pia) if self.config.getboolean('DISCORD', 'ENABLE'): dis_message = (self.dis_title + " " + squawk_message) sendDis(dis_message, self.config, None, self.map_file_name) @@ -581,8 +591,8 @@ class Plane: dis_message = (self.dis_title + " " + mode + " mode enabled.") if mode == "Approach": from defSS import get_adsbx_screenshot - url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}" - get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides) + url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}&limitupdates=0" + get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides, conceal_ac_id=self.conceal_ac_id, conceal_pia=self.conceal_pia) sendDis(dis_message, self.config, None, self.map_file_name) #elif mode in ["Althold", "VNAV", "LNAV"] and self.sel_nav_alt != None: # sendDis((dis_message + ", Sel Alt. " + str(self.sel_nav_alt) + ", Current Alt. " + str(self.alt_ft)), self.config) @@ -608,7 +618,8 @@ class Plane: from calculate_headings import calculate_deg_change track_change = calculate_deg_change(self.track, self.last_track) track_change = round(track_change, 3) - self.circle_history["traces"].append((time.time(), self.latitude, self.longitude, track_change)) + if self.latitude is not None and self.longitude is not None: + self.circle_history["traces"].append((time.time(), self.latitude, self.longitude, track_change)) total_change = 0 coords = [] @@ -648,7 +659,7 @@ class Plane: in_tfr = None if Plane.main_config.getboolean("TFRS", "ENABLE"): tfr_url = Plane.main_config.get("TFRS", "URL") - response = requests.get(tfr_url, timeout=30) + response = requests.get(tfr_url, timeout=60) tfrs = json.loads(response.text) for tfr in tfrs: if in_tfr is not None: @@ -764,9 +775,8 @@ class Plane: return tfr_map_filename from defSS import get_adsbx_screenshot - - url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}" - get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides) + url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}&limitupdates=0" + get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides, conceal_ac_id=self.conceal_ac_id, conceal_pia=self.conceal_pia) if nearest_airport_dict['distance_mi'] < 3: if "touchngo" in self.circle_history.keys(): message = f"Doing touch and goes at {nearest_airport_dict['icao']}" @@ -776,8 +786,8 @@ class Plane: message = f"Circling {round(nearest_airport_dict['distance_mi'], 2)}mi {cardinal} of {nearest_airport_dict['icao']}, {nearest_airport_dict['name']} at {self.alt_ft}ft. " tfr_map_filename = None if in_tfr is not None: - context = "Inside" if 'context' not in in_tfr.keys() else "Above" if in_tfr['context'] == 'above' else "Below" - message += f" {context} TFR {in_tfr['info']['NOTAM']}, a TFR for {in_tfr['info']['Type'].title()}" + wording_context = "Inside" if 'context' not in in_tfr.keys() else "Above" if in_tfr['context'] == 'above' else "Below" + message += f" {wording_context} TFR {in_tfr['info']['NOTAM']}, a TFR for {in_tfr['info']['Type'].title()}" tfr_map_filename = tfr_image(context, (self.latitude, self.longitude)) elif in_tfr is None and closest_tfr is not None and "distance" in closest_tfr.keys() and closest_tfr["distance"] <= 20: message += f" {closest_tfr['distance']} miles from TFR {closest_tfr['info']['NOTAM']}, a TFR for {closest_tfr['info']['Type']}" @@ -793,12 +803,12 @@ class Plane: from defTelegram import sendTeleg sendTeleg(photo, message, self.config) if self.config.getboolean('DISCORD', 'ENABLE'): - role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None - if tfr_map_filename is not None: + role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None + if tfr_map_filename is not None: sendDis(message, self.config, role_id, self.map_file_name, tfr_map_filename) elif tfr_map_filename is None: sendDis(message, self.config, role_id, self.map_file_name) - if self.config.getboolean('TWITTER', 'ENABLE'): + if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'): twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name) media_ids = [twitter_media_map_obj.media_id] if tfr_map_filename is not None: @@ -812,6 +822,10 @@ class Plane: if self.config.has_option('META', 'ENABLE') and self.config.getboolean('META', 'ENABLE'): from meta_toolkit import post_to_meta_both post_to_meta_both(self.config.get("META", "FB_PAGE_ID"), self.config.get("META", "IG_USER_ID"), self.map_file_name, message, self.config.get("META", "ACCESS_TOKEN")) + #Mastodon + if self.config.has_section('MASTODON') and self.config.getboolean('MASTODON', 'ENABLE'): + from defMastodon import sendMastodon + sendMastodon(self.map_file_name, message, self.config) self.circle_history['triggered'] = True elif abs(total_change) <= 360 and self.circle_history["triggered"]: print("No Longer Circling, trigger cleared") @@ -850,7 +864,7 @@ class Plane: if bool(int(ra['acas_ra']['MTE'])): ra_message += ", Multi threat" from defSS import get_adsbx_screenshot, generate_adsbx_screenshot_time_params - url_params = f"&lat={ra['lat']}&lon={ra['lon']}&zoom=11&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays={self.get_adsbx_map_overlays()}" + url_params = f"&lat={ra['lat']}&lon={ra['lon']}&zoom=11&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays={self.get_adsbx_map_overlays()}&limitupdates=0" if "threat_id_hex" in ra['acas_ra'].keys(): from mictronics_parse import get_aircraft_reg_by_icao threat_reg = get_aircraft_reg_by_icao(ra['acas_ra']['threat_id_hex']) @@ -860,12 +874,12 @@ class Plane: else: url_params += f"&icao={self.icao.lower()}&noIsolation" print(url_params) - get_adsbx_screenshot(self.map_file_name, url_params, True, True, overrides=self.overrides) + get_adsbx_screenshot(self.map_file_name, url_params, True, True, overrides=self.overrides, conceal_ac_id=self.conceal_ac_id, conceal_pia=self.conceal_pia) if self.config.getboolean('DISCORD', 'ENABLE'): from defDiscord import sendDis dis_message = f"{self.dis_title} {ra_message}" - role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None + role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None sendDis(dis_message, self.config, role_id, self.map_file_name) #if twitter def expire_ra_types(self): diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0c21f8b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,22 @@ +# requirements.txt +colorama +geopy +tabulate +pytz +pillow +tweepy +discord-webhook +selenium +git+https://github.com/openskynetwork/opensky-api.git@master#subdirectory=python/ +webdriver-manager +shapely +pandas +python-telegram-bot +mastodon.py +beautifulsoup4 +pycairo +py-staticmaps +pyproj +lxml +configparser +geog