Merge branch 'multi' into Dockerfile-modify

This commit is contained in:
Jack Sweeney 2022-12-22 21:24:38 -05:00 committed by GitHub
commit 2bd870f29e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1857 additions and 333 deletions

6
.gitignore vendored
View File

@ -1,3 +1,5 @@
configs/*.ini
# PyCharm
.idea
.vscode/settings.json
@ -6,3 +8,7 @@ __pycache__
dependencies
testing
lookup_route.py
icao_url_gen.py
install.sh
coul_icao_gen.py
test.py

View File

@ -18,7 +18,14 @@ selenium = "*"
opensky-api = {editable = true, git = "https://github.com/openskynetwork/opensky-api.git", subdirectory = "python"}
webdriver-manager = "*"
shapely = "*"
pycairo = "*"
geog = "*"
py-staticmaps = "*"
pyproj = "*"
pandas = "*"
lxml = "*"
beautifulsoup4 = "*"
python-telegram-bot = "*"
configparser = "*"
[requires]
python_version = "3.9"

833
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "5131229ab384051accd51e665cc63da8e2d08a651ad0c9df09041fca9f306977"
"sha256": "35921a1158bc5b11d88e9f2a4702ee3ae09228a7d5438d1562ba62f24b02fd05"
},
"pipfile-spec": 6,
"requires": {
@ -16,51 +16,214 @@
]
},
"default": {
"appdirs": {
"hashes": [
"sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
"sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
],
"version": "==1.4.4"
},
"apscheduler": {
"hashes": [
"sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244",
"sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"
],
"version": "==3.6.3"
},
"async-generator": {
"hashes": [
"sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b",
"sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"
],
"markers": "python_version >= '3.5'",
"version": "==1.10"
},
"attrs": {
"hashes": [
"sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
"sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
],
"markers": "python_version >= '3.6'",
"version": "==22.2.0"
},
"beautifulsoup4": {
"hashes": [
"sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
"sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
],
"index": "pypi",
"version": "==4.11.1"
},
"cachetools": {
"hashes": [
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
],
"markers": "python_version ~= '3.5'",
"version": "==4.2.2"
},
"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: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.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:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da",
"sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"
],
"index": "pypi",
"version": "==0.4.4"
"version": "==0.4.5"
},
"configparser": {
"hashes": [
"sha256:1b35798fdf1713f1c3139016cfcbc461f09edbf099d1fb658d4b7479fcaa3daa",
"sha256:e8b39238fb6f0153a069aa253d349467c3c4737934f253ef6abac5fe0eca1e5d"
],
"markers": "python_version >= '3.6'",
"index": "pypi",
"version": "==5.2.0"
},
"crayons": {
"cryptography": {
"hashes": [
"sha256:bd33b7547800f2cfbd26b38431f9e64b487a7de74a947b0fafc89b45a601813f",
"sha256:e73ad105c78935d71fe454dd4b85c5c437ba199294e7ffd3341842bc683654b1"
"sha256:0e70da4bdff7601b0ef48e6348339e490ebfb0cbe638e083c9c41fb49f00c8bd",
"sha256:10652dd7282de17990b88679cb82f832752c4e8237f0c714be518044269415db",
"sha256:175c1a818b87c9ac80bb7377f5520b7f31b3ef2a0004e2420319beadedb67290",
"sha256:1d7e632804a248103b60b16fb145e8df0bc60eed790ece0d12efe8cd3f3e7744",
"sha256:1f13ddda26a04c06eb57119caf27a524ccae20533729f4b1e4a69b54e07035eb",
"sha256:2ec2a8714dd005949d4019195d72abed84198d877112abb5a27740e217e0ea8d",
"sha256:2fa36a7b2cc0998a3a4d5af26ccb6273f3df133d61da2ba13b3286261e7efb70",
"sha256:2fb481682873035600b5502f0015b664abc26466153fab5c6bc92c1ea69d478b",
"sha256:3178d46f363d4549b9a76264f41c6948752183b3f587666aff0555ac50fd7876",
"sha256:4367da5705922cf7070462e964f66e4ac24162e22ab0a2e9d31f1b270dd78083",
"sha256:4eb85075437f0b1fd8cd66c688469a0c4119e0ba855e3fef86691971b887caf6",
"sha256:50a1494ed0c3f5b4d07650a68cd6ca62efe8b596ce743a5c94403e6f11bf06c1",
"sha256:53049f3379ef05182864d13bb9686657659407148f901f3f1eee57a733fb4b00",
"sha256:6391e59ebe7c62d9902c24a4d8bcbc79a68e7c4ab65863536127c8a9cd94043b",
"sha256:67461b5ebca2e4c2ab991733f8ab637a7265bb582f07c7c88914b5afb88cb95b",
"sha256:78e47e28ddc4ace41dd38c42e6feecfdadf9c3be2af389abbfeef1ff06822285",
"sha256:80ca53981ceeb3241998443c4964a387771588c4e4a5d92735a493af868294f9",
"sha256:8a4b2bdb68a447fadebfd7d24855758fe2d6fecc7fed0b78d190b1af39a8e3b0",
"sha256:8e45653fb97eb2f20b8c96f9cd2b3a0654d742b47d638cf2897afbd97f80fa6d",
"sha256:998cd19189d8a747b226d24c0207fdaa1e6658a1d3f2494541cb9dfbf7dcb6d2",
"sha256:a10498349d4c8eab7357a8f9aa3463791292845b79597ad1b98a543686fb1ec8",
"sha256:b4cad0cea995af760f82820ab4ca54e5471fc782f70a007f31531957f43e9dee",
"sha256:bfe6472507986613dc6cc00b3d492b2f7564b02b3b3682d25ca7f40fa3fd321b",
"sha256:c9e0d79ee4c56d841bd4ac6e7697c8ff3c8d6da67379057f29e66acffcd1e9a7",
"sha256:ca57eb3ddaccd1112c18fc80abe41db443cc2e9dcb1917078e02dfa010a4f353",
"sha256:ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c"
],
"version": "==0.4.0"
"version": "==38.0.4"
},
"discord-webhook": {
"hashes": [
"sha256:17e475d8a52fe0bfa26b071925f55087600e9bb96e821b611dc463f4b4998c89",
"sha256:f3d660df572caaa9c2621edd7e8634a70d6d8295ce9256c365838312457069a1"
"sha256:5a044a26ceea6e39651c2fa17194b463dc0b704b8b20991e8db3f3d86dd5e553",
"sha256:e9434e516a918e78915b5ce32b864d5b3f24556b92e1606bcad6a4f83f5615bd"
],
"index": "pypi",
"version": "==0.14.0"
"version": "==0.16.3"
},
"exceptiongroup": {
"hashes": [
"sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828",
"sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"
],
"markers": "python_version < '3.11'",
"version": "==1.0.4"
},
"future": {
"hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.18.2"
},
"geog": {
"hashes": [
"sha256:9b6b020b72bf135d49299115e5a4a751f2432def4d4cd87d10b48a5ae51ec643"
],
"index": "pypi",
"version": "==0.0.2"
},
"geographiclib": {
"hashes": [
@ -77,68 +240,247 @@
"index": "pypi",
"version": "==2.2.0"
},
"h11": {
"hashes": [
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
],
"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:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318",
"sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c",
"sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b",
"sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000",
"sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73",
"sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d",
"sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb",
"sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8",
"sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2",
"sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345",
"sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94",
"sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e",
"sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b",
"sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc",
"sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a",
"sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9",
"sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc",
"sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387",
"sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb",
"sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7",
"sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4",
"sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97",
"sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67",
"sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627",
"sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7",
"sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd",
"sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3",
"sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7",
"sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130",
"sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b",
"sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036",
"sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785",
"sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca",
"sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91",
"sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc",
"sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536",
"sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391",
"sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3",
"sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d",
"sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21",
"sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3",
"sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d",
"sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29",
"sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715",
"sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed",
"sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25",
"sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c",
"sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785",
"sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837",
"sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4",
"sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b",
"sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2",
"sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067",
"sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448",
"sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d",
"sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2",
"sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc",
"sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c",
"sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5",
"sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84",
"sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8",
"sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf",
"sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7",
"sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e",
"sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb",
"sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b",
"sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3",
"sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad",
"sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8",
"sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"
],
"index": "pypi",
"version": "==4.9.1"
},
"numpy": {
"hashes": [
"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' and platform_machine != 'aarch64' and platform_machine != 'arm64'",
"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": "d576cf260affd99156e352528ea46817273512d7",
"ref": "7bda8d14ada6241f1f246fd42d7d032aabe7ea91",
"subdirectory": "python"
},
"outcome": {
"hashes": [
"sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672",
"sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"
],
"markers": "python_version >= '3.7'",
"version": "==1.2.0"
},
"pandas": {
"hashes": [
"sha256:07238a58d7cbc8a004855ade7b75bbd22c0db4b0ffccc721556bab8a095515f6",
"sha256:0daf876dba6c622154b2e6741f29e87161f844e64f84801554f879d27ba63c0d",
"sha256:16ad23db55efcc93fa878f7837267973b61ea85d244fc5ff0ccbcfa5638706c5",
"sha256:1d9382f72a4f0e93909feece6fef5500e838ce1c355a581b3d8f259839f2ea76",
"sha256:24ea75f47bbd5574675dae21d51779a4948715416413b30614c1e8b480909f81",
"sha256:2893e923472a5e090c2d5e8db83e8f907364ec048572084c7d10ef93546be6d1",
"sha256:2ff7788468e75917574f080cd4681b27e1a7bf36461fe968b49a87b5a54d007c",
"sha256:41fc406e374590a3d492325b889a2686b31e7a7780bec83db2512988550dadbf",
"sha256:48350592665ea3cbcd07efc8c12ff12d89be09cd47231c7925e3b8afada9d50d",
"sha256:605d572126eb4ab2eadf5c59d5d69f0608df2bf7bcad5c5880a47a20a0699e3e",
"sha256:6dfbf16b1ea4f4d0ee11084d9c026340514d1d30270eaa82a9f1297b6c8ecbf0",
"sha256:6f803320c9da732cc79210d7e8cc5c8019aad512589c910c66529eb1b1818230",
"sha256:721a3dd2f06ef942f83a819c0f3f6a648b2830b191a72bbe9451bcd49c3bd42e",
"sha256:755679c49460bd0d2f837ab99f0a26948e68fa0718b7e42afbabd074d945bf84",
"sha256:78b00429161ccb0da252229bcda8010b445c4bf924e721265bec5a6e96a92e92",
"sha256:958a0588149190c22cdebbc0797e01972950c927a11a900fe6c2296f207b1d6f",
"sha256:a3924692160e3d847e18702bb048dc38e0e13411d2b503fecb1adf0fcf950ba4",
"sha256:d51674ed8e2551ef7773820ef5dab9322be0828629f2cbf8d1fc31a0c4fed640",
"sha256:d5ebc990bd34f4ac3c73a2724c2dcc9ee7bf1ce6cf08e87bb25c6ad33507e318",
"sha256:d6c0106415ff1a10c326c49bc5dd9ea8b9897a6ca0c8688eb9c30ddec49535ef",
"sha256:e48fbb64165cda451c06a0f9e4c7a16b534fcabd32546d531b3c240ce2844112"
],
"index": "pypi",
"version": "==1.4.3"
},
"pillow": {
"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: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": "==9.0.1"
"version": "==9.3.0"
},
"pushbullet.py": {
"hashes": [
@ -148,29 +490,144 @@
"index": "pypi",
"version": "==0.12.0"
},
"py-staticmaps": {
"hashes": [
"sha256:5aba5ad59f30a63f860e76ed99407a6efb24eaad5c8997aa8617363989f17389"
],
"index": "pypi",
"version": "==0.4.0"
},
"pycairo": {
"hashes": [
"sha256:251907f18a552df938aa3386657ff4b5a4937dde70e11aa042bc297957f4b74b",
"sha256:26b72b813c6f9d495f71057eab89c13e70a21c92360e9265abc049e0a931fa39",
"sha256:31e1c4850db03201d33929cbe1905ce1b33202683ebda7bb0d4dba489115066b",
"sha256:4357f20a6b1de8f1e8072a74ff68ab4c9a0ae698cd9f5c0f2b2cdd9b28b635f6",
"sha256:44a2ecf34968de07b3b9dfdcdbccbd25aa3cab267200f234f84e81481a73bbf6",
"sha256:6d37375aab9f2bb6136f076c19815d72108383baae89fbc0d6cb8e5092217d02",
"sha256:70936b19f967fa3cb3cd200c2608911227fa5d09dae21c166f64bc15e714ee41",
"sha256:dace6b356c476de27f8e1522428ac21a799c225703f746e2957d441f885dcb6c",
"sha256:f63c153a9ea3d21aff85e2caeee4b0c5d566b2368b4ed64826020d12953d76a4"
],
"index": "pypi",
"version": "==1.21.0"
},
"pycparser": {
"hashes": [
"sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
"sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
],
"version": "==2.21"
},
"pyopenssl": {
"hashes": [
"sha256:7a83b7b272dd595222d672f5ce29aa030f1fb837630ef229f62e72e395ce8968",
"sha256:b28437c9773bb6c6958628cf9c3bebe585de661dba6f63df17111966363dd15e"
],
"version": "==22.1.0"
},
"pyproj": {
"hashes": [
"sha256:07c9d8d7ec009bbac09e233cfc725601586fe06880e5538a3a44eaf560ba3a62",
"sha256:120d45ed73144c65e9677dc73ba8a531c495d179dd9f9f0471ac5acc02d7ac4b",
"sha256:140fa649fedd04f680a39f8ad339799a55cb1c49f6a84e1b32b97e49646647aa",
"sha256:1adc9ccd1bf04998493b6a2e87e60656c75ab790653b36cfe351e9ef214828ed",
"sha256:1ef1bfbe2dcc558c7a98e2f1836abdcd630390f3160724a6f4f5c818b2be0ad5",
"sha256:2fef9c1e339f25c57f6ae0558b5ab1bbdf7994529a30d8d7504fc6302ea51c03",
"sha256:3cc4771403db54494e1e55bca8e6d33cde322f8cf0ed39f1557ff109c66d2cd1",
"sha256:42eea10afc750fccd1c5c4ba56de29ab791ab4d83c1f7db72705566282ac5396",
"sha256:45487942c19c5a8b09c91964ea3201f4e094518e34743cae373889a36e3d9260",
"sha256:473961faef7a9fd723c5d432f65220ea6ab3854e606bf84b4d409a75a4261c78",
"sha256:52efb681647dfac185cc655a709bc0caaf910031a0390f816f5fc8ce150cbedc",
"sha256:531ea36519fa7b581466d4b6ab32f66ae4dadd9499d726352f71ee5e19c3d1c5",
"sha256:56b0f9ee2c5b2520b18db30a393a7b86130cf527ddbb8c96e7f3c837474a9d79",
"sha256:5ab0d6e38fda7c13726afacaf62e9f9dd858089d67910471758afd9cb24e0ecd",
"sha256:5ca5f32b56210429b367ca4f9a57ffe67975c487af82e179a24370879a3daf68",
"sha256:5dac03d4338a4c8bd0f69144c527474f517b4cbd7d2d8c532cd8937799723248",
"sha256:5f92d8f6514516124abb714dce912b20867831162cfff9fae2678ef07b6fcf0f",
"sha256:67025e37598a6bbed2c9c6c9e4c911f6dd39315d3e1148ead935a5c4d64309d5",
"sha256:797ad5655d484feac14b0fbb4a4efeaac0cf780a223046e2465494c767fd1c3b",
"sha256:aba199704c824fb84ab64927e7bc9ef71e603e483130ec0f7e09e97259b8f61f",
"sha256:aed1a3c0cd4182425f91b48d5db39f459bc2fe0d88017ead6425a1bc85faee33",
"sha256:b3d8e14d91cc95fb3dbc03a9d0588ac58326803eefa5bbb0978d109de3304fbe",
"sha256:b59c08aea13ee428cf8a919212d55c036cc94784805ed77c8f31a4d1f541058c",
"sha256:c99f7b5757a28040a2dd4a28c9805fdf13eef79a796f4a566ab5cb362d10630d",
"sha256:f1032e5dfb50eae06382bcc7b9011b994f7104d932fe91bd83a722275e30e8ce"
],
"index": "pypi",
"version": "==3.3.1"
},
"pysocks": {
"hashes": [
"sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299",
"sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5",
"sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"
],
"version": "==1.7.1"
},
"python-dateutil": {
"hashes": [
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
"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:003aee64f9fd955d111549f96c4b58a3f40b9319383c70fad6277a4974bbf570",
"sha256:7a0f21a39fa6c1c4bf2e5984c9b9ae944483fd10b54804cb0e23a3ccd4954f0b"
],
"markers": "python_version >= '3.7'",
"version": "==7.0.0"
},
"python-telegram-bot": {
"hashes": [
"sha256:13f83ec4433f24a67e7a2df0206dfde83c1627c92880a2fcf95a83585b3cc589",
"sha256:4296d81a38b7e5ef1f9795651128e58fb354678b8dc4db93ca166c96828c57b2"
],
"index": "pypi",
"version": "==13.13"
},
"pytz": {
"hashes": [
"sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
"sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
"sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
"sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
],
"index": "pypi",
"version": "==2021.1"
"version": "==2022.1"
},
"pytz-deprecation-shim": {
"hashes": [
"sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6",
"sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==0.1.0.post0"
},
"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'",
"version": "==2.28.1"
},
"requests-oauthlib": {
"hashes": [
@ -180,82 +637,222 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.3.1"
},
"s2sphere": {
"hashes": [
"sha256:c2478c1ff7c601a59a7151a57b605435897514578fa6bdb8730721c182adbbaf",
"sha256:d2340c9cf458ddc9a89afd1d8048a4195ce6fa6b0095ab900d4be5271e537401"
],
"version": "==0.2.5"
},
"selenium": {
"hashes": [
"sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c",
"sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"
"sha256:f67402b8f973aaa98d9c55b8f9aa63532009cd1859b2222a8b9800354942d8bc"
],
"index": "pypi",
"version": "==3.141.0"
"version": "==4.3.0"
},
"setuptools": {
"hashes": [
"sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54",
"sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"
],
"markers": "python_version >= '3.7'",
"version": "==65.6.3"
},
"shapely": {
"hashes": [
"sha256:052eb5b9ba756808a7825e8a8020fb146ec489dd5c919e7d139014775411e688",
"sha256:1641724c1055459a7e2b8bbe47ba25bdc89554582e62aec23cb3f3ca25f9b129",
"sha256:17df66e87d0fe0193910aeaa938c99f0b04f67b430edb8adae01e7be557b141b",
"sha256:182716ffb500d114b5d1b75d7fd9d14b7d3414cef3c38c0490534cc9ce20981a",
"sha256:2df5260d0f2983309776cb41bfa85c464ec07018d88c0ecfca23d40bfadae2f1",
"sha256:35be1c5d869966569d3dfd4ec31832d7c780e9df760e1fe52131105685941891",
"sha256:46da0ea527da9cf9503e66c18bab6981c5556859e518fe71578b47126e54ca93",
"sha256:4c10f317e379cc404f8fc510cd9982d5d3e7ba13a9cfd39aa251d894c6366798",
"sha256:4f3c59f6dbf86a9fc293546de492f5e07344e045f9333f3a753f2dda903c45d1",
"sha256:60e5b2282619249dbe8dc5266d781cc7d7fb1b27fa49f8241f2167672ad26719",
"sha256:617bf046a6861d7c6b44d2d9cb9e2311548638e684c2cd071d8945f24a926263",
"sha256:6593026cd3f5daaea12bcc51ae5c979318070fefee210e7990cb8ac2364e79a1",
"sha256:6871acba8fbe744efa4f9f34e726d070bfbf9bffb356a8f6d64557846324232b",
"sha256:791477edb422692e7dc351c5ed6530eb0e949a31b45569946619a0d9cd5f53cb",
"sha256:8e7659dd994792a0aad8fb80439f59055a21163e236faf2f9823beb63a380e19",
"sha256:8f15b6ce67dcc05b61f19c689b60f3fe58550ba994290ff8332f711f5aaa9840",
"sha256:90a3e2ae0d6d7d50ff2370ba168fbd416a53e7d8448410758c5d6a5920646c1d",
"sha256:a3774516c8a83abfd1ddffb8b6ec1b0935d7fe6ea0ff5c31a18bfdae567b4eba",
"sha256:a5c3a50d823c192f32615a2a6920e8c046b09e07a58eba220407335a9cd2e8ea",
"sha256:b40cc7bb089ae4aa9ddba1db900b4cd1bce3925d2a4b5837b639e49de054784f",
"sha256:da38ed3d65b8091447dc3717e5218cc336d20303b77b0634b261bc5c1aa2bae8",
"sha256:de618e67b64a51a0768d26a9963ecd7d338a2cf6e9e7582d2385f88ad005b3d1",
"sha256:e3afccf0437edc108eef1e2bb9cc4c7073e7705924eb4cd0bf7715cd1ef0ce1b"
"sha256:0c0fd457ce477b1dced507a72f1e2084c9191bfcb8a1e09886990ebd02acf024",
"sha256:137f1369630408024a62ff79a437a5657e6c5b76b9cd352dde704b425acdb298",
"sha256:15a856fbb588ad5d042784e00918c662902776452008c771ecba2ff615cd197a",
"sha256:1d95842cc6bbbeab673061b63e70b07be9a375c15a60f4098f8fbd29f43af1b4",
"sha256:256bdf8080bb7bb504d47b2c76919ecebab9708cc1b26266b3ec32b42448f642",
"sha256:2e02da2e988e74d61f15c720f9f613fab51942aae2dfeacdcb78eadece00e1f3",
"sha256:3423299254deec075e79fb7dc7909d702104e4167149de7f45510c3a6342eeea",
"sha256:3a40bf497b57a6625b83996aed10ce2233bca0e5471b8af771b186d681433ac5",
"sha256:44d2832c1b706bf43101fda92831a083467cc4b4923a7ed17319ab599c1025d8",
"sha256:5254240eefc44139ab0d128faf671635d8bdd9c23955ee063d4d6b8f20073ae0",
"sha256:56413f7d32c70b63f239eb0865b24c0c61029e38757de456cc4ab3c416559a0b",
"sha256:572af9d5006fd5e3213e37ee548912b0341fb26724d6dc8a4e3950c10197ebb6",
"sha256:62056e64b12b6d483d79f8e34bf058d2fe734d51c9227c1713705399434eff3b",
"sha256:68c8e18dc9dc8a198c3addc8c9596f64137101f566f04b96ecfca0b214cb8b12",
"sha256:6bdc7728f1e5df430d8c588661f79f1eed4a2728c8b689e12707cfec217f68f8",
"sha256:6fcb28836ae93809de1dde73c03c9c24bab0ba2b2bf419ddb2aeb72c96d110e9",
"sha256:75042e8039c79dd01f102bb288beace9dc2f49fc44a2dea875f9b697aa8cd30d",
"sha256:78966332a89813b237de357a03f612fd451a871fe6e26c12b6b71645fe8eee39",
"sha256:7c8eda45085ccdd7f9805ea4a93fdd5eb0b6039a61d5f0cefb960487e6dc17a1",
"sha256:7c9e3400b716c51ba43eea1678c28272580114e009b6c78cdd00c44df3e325fa",
"sha256:840be3f27a1152851c54b968f2e12d718c9f13b7acd51c482e58a70f60f29e31",
"sha256:8e3ed52a081da58eb4a885c157c594876633dbd4eb283f13ba5bf39c82322d76",
"sha256:8fe641f1f61b3d43dd61b5a85d2ef023e6e19bf8f204a5160a1cb1ec645cbc09",
"sha256:a58e1f362f2091743e5e13212f5d5d16251a4bb63dd0ed587c652d3be9620d3a",
"sha256:a60861b5ca2c488ebcdc706eca94d325c26d1567921c74acc83df5e6913590c7",
"sha256:beee3949ddf381735049cfa6532fb234d5d20a5be910c4f2fb7c7295fd7960e3",
"sha256:c0a0d7752b145343838bd36ed09382d85f5befe426832d7384c5b051c147acbd",
"sha256:c60f3758212ec480675b820b13035dda8af8f7cc560d2cc67999b2717fb8faef",
"sha256:ce0b5c5f7acbccf98b3460eecaa40e9b18272b2a734f74fcddf1d7696e047e95",
"sha256:cec89a5617c0137f4678282e983c3d63bf838fb00cdf318cc555b4d8409f7130",
"sha256:d3f3fac625690f01f35af665649e993f15f924e740b5c0ac0376900655815521",
"sha256:d74de394684d66e25e780b0359fda85be7766af85940fa2dfad728b1a815c71f",
"sha256:e07b0bd2a0e61a8afd4d1c1bd23f3550b711f01274ffb53de99358fd781eefd8",
"sha256:f12695662c3ad1e6031b3de98f191963d0f09de6d1a4988acd907405644032ba"
],
"index": "pypi",
"version": "==1.7.1"
"version": "==1.8.2"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sniffio": {
"hashes": [
"sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
"sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.0"
},
"sortedcontainers": {
"hashes": [
"sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88",
"sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"
],
"version": "==2.4.0"
},
"soupsieve": {
"hashes": [
"sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759",
"sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"
],
"markers": "python_version >= '3.6'",
"version": "==2.3.2.post1"
},
"svgwrite": {
"hashes": [
"sha256:a8fbdfd4443302a6619a7f76bc937fc683daf2628d9b737c891ec08b8ce524c3",
"sha256:bb6b2b5450f1edbfa597d924f9ac2dd099e625562e492021d7dd614f65f8a22d"
],
"markers": "python_version >= '3.6'",
"version": "==1.4.3"
},
"tabulate": {
"hashes": [
"sha256:d7c013fe7abbc5e491394e10fa845f8f32fe54f8dc60c6622c6cf482d25d47e4",
"sha256:eb1d13f25760052e8931f2ef80aaf6045a6cceb47514db8beab24cded16f13a7"
"sha256:0ba055423dbaa164b9e456abe7920c5e8ed33fcc16f6d1b2f2d152c8e1e8b4fc",
"sha256:436f1c768b424654fce8597290d2764def1eea6a77cfa5c33be00b1bc0f4f63d",
"sha256:6c57f3f3dd7ac2782770155f3adb2db0b1a269637e42f27599925e64b114f519"
],
"index": "pypi",
"version": "==0.8.9"
"version": "==0.8.10"
},
"text-unidecode": {
"hashes": [
"sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8",
"sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"
],
"version": "==1.3"
},
"tornado": {
"hashes": [
"sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca",
"sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72",
"sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23",
"sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8",
"sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b",
"sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9",
"sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13",
"sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75",
"sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac",
"sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e",
"sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"
],
"markers": "python_version >= '3.7'",
"version": "==6.2"
},
"trio": {
"hashes": [
"sha256:ce68f1c5400a47b137c5a4de72c7c901bd4e7a24fbdebfe9b41de8c6c04eaacf",
"sha256:f1dd0780a89bfc880c7c7994519cb53f62aacb2c25ff487001c0052bd721cdf0"
],
"markers": "python_version >= '3.7'",
"version": "==0.22.0"
},
"trio-websocket": {
"hashes": [
"sha256:5b558f6e83cc20a37c3b61202476c5295d1addf57bd65543364e0337e37ed2bc",
"sha256:a3d34de8fac26023eee701ed1e7bf4da9a8326b61a62934ec9e53b64970fd8fe"
],
"markers": "python_version >= '3.5'",
"version": "==0.9.2"
},
"tweepy": {
"hashes": [
"sha256:b28d72073b794141ad1cfdc34cb52b83b14d6b34cd4c5ea9d47bb85159b656e8",
"sha256:bd3af045bed9cd6a838c32a1344d48191096e5cb930012452f8397b12c89d785"
"sha256:7f92574920c2f233663fff154745fc2bb0d10aedc23617379a912d8e4fefa399",
"sha256:f0abbd234a588e572f880f99a094ac321217ff3eade6c0eca118ed6db8e2cf0a"
],
"index": "pypi",
"version": "==4.0.0"
"version": "==4.10.0"
},
"tzdata": {
"hashes": [
"sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d",
"sha256:fe5f866eddd8b96e9fcba978f8e503c909b19ea7efda11e52e39494bad3a7bfa"
],
"markers": "python_version >= '3.6'",
"version": "==2022.7"
},
"tzlocal": {
"hashes": [
"sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745",
"sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"
],
"markers": "python_version >= '3.6'",
"version": "==4.2"
},
"urllib3": {
"hashes": [
"sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed",
"sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"
"extras": [
"secure",
"socks"
],
"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.8"
"hashes": [
"sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc",
"sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"
],
"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"
},
"urllib3-secure-extra": {
"hashes": [
"sha256:ee9409cbfeb4b8609047be4c32fb4317870c602767e53fd8a41005ebe6a41dff",
"sha256:f7adcb108b4d12a4b26b99eb60e265d087f435052a76aefa396b6ee85e9a6ef9"
],
"version": "==0.1.0"
},
"webdriver-manager": {
"hashes": [
"sha256:50a6e174106542f5335cacc387cec7ada26812babc1aeca61c208a1bab2ac2c5",
"sha256:c6d81590aae6fc0fb10cf7dd20c8c1b9bb043501f9cf62c316a854a0de841e32"
"sha256:821e0737415044d7ad2fc4c1b25fbe0c0de602c62c94c5a25e4a096cf10f792f",
"sha256:9e115be96f04f4a912431766e7675679527f62867a3c73f4738901ea89e2a1e9"
],
"index": "pypi",
"version": "==3.4.2"
"version": "==3.8.1"
},
"websocket-client": {
"hashes": [
"sha256:074e2ed575e7c822fc0940d31c3ac9bb2b1142c303eafcf3e304e6ce035522e8",
"sha256:6278a75065395418283f887de7c3beafb3aa68dada5cacbe4b214e8d26da499b"
"sha256:d6b06432f184438d99ac1f456eaf22fe1ade524c3dd16e661142dc54e9cba574",
"sha256:d6e8f90ca8e2dd4e8027c4561adeb9456b54044312dba655e7cae652ceb9ae59"
],
"markers": "python_version >= '3.6'",
"version": "==1.3.1"
"markers": "python_version >= '3.7'",
"version": "==1.4.2"
},
"wsproto": {
"hashes": [
"sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065",
"sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"
],
"markers": "python_version >= '3.7'",
"version": "==1.2.0"
}
},
"develop": {}

View File

@ -15,11 +15,11 @@ Notify if configured planes have taken off or landed using Python with <a href="
### Background
I made this program so I could track Elon Musk's Jet and share with others of his whereabouts on Twitter. [![Twitter Follow](https://img.shields.io/twitter/follow/ElonJet.svg?style=social)](https://twitter.com/ElonJet) I have now Expanded and run multiple accounts for multiple planes, a list of the accounts here [plane-notify Twitter List](https://twitter.com/i/lists/1307414615316467715)
I made this program so I could track Elon Musk's Jet and share his whereabouts with others on Twitter. [![Twitter Follow](https://img.shields.io/twitter/follow/ElonJet.svg?style=social)](https://twitter.com/ElonJet) I have now expanded and run multiple accounts for multiple planes, a list of the accounts can be found here [plane-notify Twitter List](https://twitter.com/i/lists/1307414615316467715)
### Contributing
I'm open to any help or suggestions, I realize there are many better ways to improve this program and better ways to get this program to work properly, Im only a noob. I'll accept pull requests. If you'd like to discuss join <https://JacksTech.net/Discord>
I'm open to any help or suggestions, I realize there are many better ways to improve this program and better ways to get this program to work properly, I'm only a noob. I'll accept pull requests. If you'd like to discuss join <https://discord.gg/groundcontrol>
### [Algorithm](PseudoCode.md)
@ -67,6 +67,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 +92,7 @@ screen -R <name screen whatever you want>
### Start Program
```bash
pipenv run python __main__
pipenv run python __main__.py
```
## Using with Docker

View File

@ -3,13 +3,19 @@ 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)
@ -48,11 +54,13 @@ main_config.read('./configs/mainconf.ini')
source = main_config.get('DATA', 'SOURCE')
if main_config.getboolean('DISCORD', 'ENABLE'):
from defDiscord import sendDis
sendDis("Started", main_config)
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') 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"):
@ -68,7 +76,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)))
@ -150,6 +158,54 @@ try:
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)
@ -184,7 +240,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)
@ -211,5 +270,5 @@ except Exception as e:
logging.error(e)
logging.error(str(traceback.format_exc()))
from defDiscord import sendDis
sendDis(str("Error Exiting: " + str(e) + "Failed on " + key), main_config, "crash_latest.log")
raise e
sendDis(str("Error Exiting: " + str(e) + f"Failed on ({obj.config_path}) https://globe.adsbexchange.com/?icao={key} "), main_config, main_config.get('DISCORD', 'ROLE_ID'), "crash_latest.log")
raise e

View File

@ -0,0 +1,412 @@
{
"EA50": {
"name": "Eclipse 550",
"galph": 76,
"category": "VLJ"
},
"LJ31": {
"name": "Learjet 31",
"galph": 202,
"category": "Light"
},
"LJ40": {
"name": "Learjet 40",
"galph": 207,
"category": "Light"
},
"PC24": {
"name": "Pilatus PC-24",
"galph": 154,
"category": "Light"
},
"LJ45": {
"name": "Learjet 45",
"galph": 205,
"category": "Super Light"
},
"LJ70": {
"name": "Learjet 70",
"galph": 198,
"category": "Super Light"
},
"LJ75": {
"name": "Learjet 75",
"galph": 199,
"category": "Super Light"
},
"G150": {
"name": "Gulfstream G150",
"galph": 228,
"category": "Midsize"
},
"LJ60": {
"name": "Learjet 60",
"galph": 239,
"category": "Midsize"
},
"GALX": {
"name": "Gulfstream G200",
"galph": 278,
"category": "Super Midsize"
},
"G280": {
"name": "Gulfstream G280",
"galph": 297,
"category": "Super Midsize"
},
"GLF5": {
"name": "Gulfstream G500",
"galph": 447,
"category": "Large"
},
"GLF6": {
"name": "Gulfstream G650",
"galph": 503,
"category": "Ultra Long Range"
},
"PC12": {
"name": "Pilatus PC-12",
"galph": 66,
"category": "Turboprop Aircraft"
},
"GLEX": {
"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"
},
"B737": {
"name": "737 700",
"galph": 796,
"category": "Medium"
},
"A320": {
"name": "A320",
"galph": 800,
"category": "Medium"
},
"P3": {
"name": "Lockheed Orion P3",
"galph": 671,
"category": "Turboprop"
},
"C750": {
"name": "Cessna 750 Citation X",
"galph": 347,
"category": "Small Private Jet"
},
"FA7X": {
"name": "Dassult Falcon 7X",
"galph": 380,
"category": "Small Private Jet"
},
"F900": {
"name": "Dassult Falcon 900",
"galph": 347,
"category": "Small Private Jet"
},
"H25B": {
"name": "Hawker 750/850",
"galph": 270,
"category": "Small Private Jet"
},
"C680": {
"name": "Cessna 680 Citation",
"galph": 247,
"category": "Small Private Jet"
},
"GLF3": {
"name": "Gulfstream 3",
"galph": 568,
"category": "Heavy Private Jet"
},
"GLF4": {
"name": "Gulfstream 4",
"galph": 479,
"category": "Heavy Private Jet"
},
"CL60": {
"name": "Bombardier CL-600 Challenge",
"galph": 262,
"category": "Mid-size Private Jet"
},
"A139": {
"name": "Agusta-Bell AW139",
"galph": 150,
"category": "Medium Utility Helicopter"
},
"GL5T": {
"name": "Global 5000",
"galph": 455,
"category": "Heavy Private Jet"
},
"GA6C": {
"name": "Gulfstream G600",
"galph": 458,
"category": "Heavy Private Jet"
},
"A337": {
"name": "Airbus Beluga XL",
"galph": 1800,
"category": "Large Transport Aircraft"
},
"A3ST": {
"name": "Airbus Beluga",
"galph": 1260,
"category": "Large Transport Aircraft"
},
"F2TH": {
"name": "Dassault Falcon 2000",
"galph": 245,
"category": "Medium Private Jet"
},
"GA5C": {
"name": "Gulfstream G500",
"galph": 402,
"category": "Large Private Jet"
},
"C130": {
"name": "Lockheed C130",
"galph": 740,
"category": "Medium Cargo"
},
"B762": {
"name": "Boeing 767 200",
"galph": 1722,
"category": "Wide-body"
},
"B772": {
"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": ""
}
}

View File

@ -15,7 +15,6 @@ def calculate_cardinal(d):
dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
ix = int(round(d / (360. / len(dirs))))
card = dirs[ix % len(dirs)]
print(card)
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"""
@ -29,6 +28,7 @@ def calculate_deg_change(new_heading, original_heading):
track_change = normal
if direction == "left":
track_change *= -1
track_change = round(track_change, 2)
print(f"Track change of {track_change}° which is {direction}")
return track_change

View File

@ -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,12 +28,37 @@ 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
ENABLE = False
[TWITTER]
#GLOBAL TWITTER CONSUMER KEY AND CONSUMERS SECRET ONLY NEED TO BE HERE NOW
ENABLE = False
CONSUMER_KEY = ck
CONSUMER_SECRET = cs
[MAP]
#Map to create from Google Static Maps or screenshot global tar1090 from globe.adsbexchange.com
#Enter GOOGLESTATICMAP or ADSBX
OPTION = ADSBX

View File

@ -2,8 +2,15 @@
#Plane to track, based off ICAO or ICAO24 which is the unique transponder address of a plane.
ICAO = icaohere
#Optional Per Plane Override
#DATA_LOSS_MINS = 20
#Optional Per Plane Overrides
; OVERRIDE_REG=
; OVERRIDE_ICAO_TYPE=
; 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
@ -21,17 +28,9 @@ TYPES = [small_airport, medium_airport, large_airport]
[TWITTER]
ENABLE = FALSE
TITLE =
CONSUMER_KEY = ckhere
CONSUMER_SECRET = cshere
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
@ -41,10 +40,15 @@ ROLE_ID =
Title =
USERNAME = plane-notify
[META]
ENABLE = False
FB_PAGE_ID =
IG_USER_ID =
ACCESS_TOKEN =
[TELEGRAM]
ENABLE = FALSE
TITLE = Title Of Telegram message
ROOM_ID = -100xxxxxxxxxx
BOT_TOKEN = xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
=======
BOT_TOKEN = xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

View File

@ -1,45 +1,45 @@
import csv
import math
def add_airport_region(airport_dict):
# Get full region/state name from iso region name
with open('./dependencies/regions.csv', 'r', encoding='utf-8') as regions_csv:
regions_csv = csv.DictReader(filter(lambda row: row[0] != '#', regions_csv))
for region in regions_csv:
if region['code'] == airport_dict['iso_region']:
airport_dict['region'] = region['name']
return airport_dict
#Get full region/state name from iso region name
with open('./dependencies/regions.csv', 'r', encoding='utf-8') as regions_csv:
regions_csv = csv.DictReader(filter(lambda row: row[0]!='#', regions_csv))
for region in regions_csv:
if region['code'] == airport_dict['iso_region']:
airport_dict['region'] = region['name']
return airport_dict
def getClosestAirport(latitude, longitude, allowed_types):
from geopy.distance import geodesic
plane = (latitude, longitude)
closest_airport_dict = dict()
with open('./dependencies/airports.csv', 'r', encoding='utf-8') as airport_csv:
airport_csv_reader = csv.DictReader(filter(lambda row: row[0] != '#', airport_csv))
for airport in airport_csv_reader:
if airport['type'] in allowed_types:
airport_coord = float(airport['latitude_deg']), float(airport['longitude_deg'])
airport_dist = float((geodesic(plane, airport_coord).mi))
if not closest_airport_dict or airport_dist < closest_airport_dist:
closest_airport_dict = airport
closest_airport_dist = airport_dist
closest_airport_dict['distance_mi'] = closest_airport_dist
# Convert indent key to icao key as its labeled icao in other places not ident
closest_airport_dict['icao'] = closest_airport_dict.pop('gps_code')
closest_airport_dict = add_airport_region(closest_airport_dict)
return closest_airport_dict
from geopy.distance import geodesic
plane = (latitude, longitude)
with open('./dependencies/airports.csv', 'r', encoding='utf-8') as airport_csv:
airport_csv_reader = csv.DictReader(filter(lambda row: row[0]!='#', airport_csv))
for airport in airport_csv_reader:
if airport['type'] in allowed_types:
airport_coord = float(airport['latitude_deg']), float(airport['longitude_deg'])
airport_dist = float((geodesic(plane, airport_coord).mi))
if "closest_airport_dict" not in locals():
closest_airport_dict = airport
closest_airport_dist = airport_dist
elif airport_dist < closest_airport_dist:
closest_airport_dict = airport
closest_airport_dist = airport_dist
closest_airport_dict['distance_mi'] = closest_airport_dist
#Convert indent key to icao key as its labeled icao in other places not ident
closest_airport_dict['icao'] = closest_airport_dict.pop('gps_code')
closest_airport_dict = add_airport_region(closest_airport_dict)
return closest_airport_dict
def get_airport_by_icao(icao):
with open('./dependencies/airports.csv', 'r', encoding='utf-8') as airport_csv:
airport_csv_reader = csv.DictReader(filter(lambda row: row[0] != '#', airport_csv))
for airport in airport_csv_reader:
if airport['gps_code'] == icao:
matching_airport = airport
# 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
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:
matching_airport = airport
#Convert indent key to icao key as its labeled icao in other places not ident
matching_airport['icao'] = matching_airport.pop('gps_code')
break
if matching_airport:
matching_airport = add_airport_region(matching_airport)
return matching_airport
else:
return None

View File

@ -1,12 +1,14 @@
def sendDis(message, config, file_name = None, role_id = None):
def sendDis(message, config, role_id = None, *file_names):
import requests
from discord_webhook import DiscordWebhook
if role_id != None:
message += f" <@&{role_id}>"
webhook = DiscordWebhook(url=config.get('DISCORD', 'URL'), content=message[0:1999], username=config.get('DISCORD', 'USERNAME'))
if file_name != None:
with open(file_name, "rb") as f:
webhook.add_file(file=f.read(), filename=file_name)
if file_names != []:
for file_name in file_names:
with open(file_name, "rb") as f:
webhook.add_file(file=f.read(), filename=file_name)
try:
webhook.execute()
except requests.exceptions.RequestException:

View File

@ -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():

34
defRpdADSBX.py Normal file
View File

@ -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

117
defSS.py
View File

@ -1,29 +1,46 @@
import json
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_track_labels=False):
from selenium.common.exceptions import NoSuchElementException
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')
chrome_options.add_argument("--enable-logging --v=1")
import os
import platform
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")
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(ChromeDriverManager().install(), options=chrome_options)
browser = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
url = f"https://globe.adsbexchange.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';")
@ -36,42 +53,72 @@ 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
#Remove Copy Link
try:
element = browser.find_element_by_xpath("//*[contains(text(), 'Share')]")
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:
print("Couldn't remove share button from map")
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')
WebDriverWait(browser, 40).until(lambda d: d.execute_script("return jQuery.active == 0"))
try:
photo_box = browser.find_element_by_id("silhouette")
except:
pass
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")
browser.execute_script(f"arguments[0].innerText = '* {overrides['reg']}'", element)
reg = overrides['reg']
else:
import requests, json
photo_list = json.loads(requests.get("https://raw.githubusercontent.com/Jxck-S/aircraft-photos/main/photo-list.json").text)
if "icao" in url_params:
import re
icao = re.search('icao=(.+?)&', url_params).group(1).lower()
print(icao)
if icao in photo_list.keys():
browser.execute_script("arguments[0].id = 'airplanePhoto';", photo_box)
browser.execute_script(f"arguments[0].src = 'https://raw.githubusercontent.com/Jxck-S/aircraft-photos/main/images/{photo_list[icao]['reg']}.jpg';", photo_box)
copyright = browser.find_element_by_id("copyrightInfo")
browser.execute_script("arguments[0].id = 'copyrightInfoFreeze';", copyright)
browser.execute_script("$('#copyrightInfoFreeze').css('font-size', '12px');")
browser.execute_script(f"arguments[0].appendChild(document.createTextNode('Image © {photo_list[icao]['photographer']}'))", copyright)
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
if reg is not None:
try:
try:
photo_box = browser.find_element(By.ID, "silhouette")
except NoSuchElementException:
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)
if reg in photo_list.keys():
browser.execute_script("arguments[0].id = 'airplanePhoto';", photo_box)
browser.execute_script("arguments[0].removeAttribute('width')", photo_box)
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")
browser.execute_cdp_cmd('Emulation.setScriptExecutionDisabled', {'value': True})
copy_right_children = image_copy_right.find_elements(By.XPATH, "*")
if len(copy_right_children) > 0:
browser.execute_script(f"arguments[0].innerText = 'Image © {photo_list[reg]['photographer']}'", copy_right_children[0])
else:
browser.execute_script(f"arguments[0].appendChild(document.createTextNode('Image © {photo_list[reg]['photographer']}'))", image_copy_right)
except Exception as e:
print("Error on changing photo", e)
if 'type' in overrides.keys():
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")
browser.execute_script(f"arguments[0].innerText = '* {overrides['typelong']}'", element)
if 'ownop' in overrides.keys():
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()
@ -82,4 +129,4 @@ def generate_adsbx_screenshot_time_params(timestamp):
print(timestamp_dt)
start_time = timestamp_dt - timedelta(minutes=1)
time_params = "&showTrace=" + timestamp_dt.strftime("%Y-%m-%d") + "&startTime=" + start_time.strftime("%H:%M:%S") + "&endTime=" + timestamp_dt.strftime("%H:%M:%S")
return time_params
return time_params

View File

@ -28,11 +28,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 send.')
break
else:
print('[X] Unknown Telegram error. Message not sent.')
break
else:
print("Telegram message successfully sent.")
return sent
return sent

View File

@ -1,9 +0,0 @@
# Authenticate to Twitter
def tweepysetup(config):
import tweepy
#DOCU
#https://realpython.com/twitter-bot-python-tweepy/
auth = tweepy.OAuthHandler(config.get('TWITTER', 'CONSUMER_KEY'), config.get('TWITTER', 'CONSUMER_SECRET'))
auth.set_access_token(config.get('TWITTER', 'ACCESS_TOKEN'), config.get('TWITTER', 'ACCESS_TOKEN_SECRET'))
tweet_api = tweepy.API(auth, wait_on_rate_limit=True)
return tweet_api

View File

@ -2,6 +2,7 @@ version: "3.9"
services:
plane-notify:
platform: linux/amd64
shm_size: 2gb
build:
context: .
volumes:

49
fuel_calc.py Normal file
View File

@ -0,0 +1,49 @@
import json
import requests
def get_avg_fuel_price():
import pandas as pd
from bs4 import BeautifulSoup
try:
response = requests.get("https://www.airnav.com/fuel/report.html")
soup = BeautifulSoup(response.text, 'lxml') # Parse the HTML as a string
table = soup.find_all('table')[3] # Grab the first table
nation_wide = table.find_all('tr')[4]
nation_wide_avg_jeta = nation_wide.find_all('td')[6]
print("Current nationwide Jet-A fuel price avg per G is $", nation_wide_avg_jeta.text[1:])
return(float(nation_wide_avg_jeta.text[1:]))
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())
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 = 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 = (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"] = round(c02_tons) if c02_tons > 1 else round(c02_tons, 4)
print ("Fuel info", fuel_flight_info)
return fuel_flight_info
else:
print("Can't calculate fuel info unknown aircraft ICAO type")
return None
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'])
kgs = "{:,}".format(fuel_info['fuel_used_kg'])
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

51
meta_toolkit.py Normal file
View File

@ -0,0 +1,51 @@
import requests
import json
def post_fb(page_id, file_path, message, access_token):
"""Posts to Facebook with Image"""
import os
file_name = os.path.basename(file_path)
files= {'image':(file_name, open(file_path, 'rb'), "multipart/form-data")}
url = f"https://graph.facebook.com/{page_id}/photos?message={message}&access_token={access_token}"
resp = requests.post(url, files=files)
resp.raise_for_status()
print("Facebook Post Response: ", resp.json())
return resp.json()
def get_fb_post_image_link(post_id, access_token):
"""Returns Highest Resolution image link of a Facebook Post by FBID"""
url = f"https://graph.facebook.com/{post_id}?fields=images&access_token={access_token}"
resp = requests.get(url)
resp.raise_for_status()
image_url = resp.json()['images'][0]['source']
print("Highest Resoulution Image URL for FBID", post_id, "is", image_url)
return image_url
def post_to_instagram(ig_user_id, access_token, image_url, caption):
"""Posts to Instagram"""
post_url = f'https://graph.facebook.com/v13.0/{ig_user_id}/media'
payload = {
'caption': caption,
'access_token': access_token,
'image_url': image_url
}
resp = requests.post(post_url, data=payload)
resp.raise_for_status()
print("IG Media Response:", resp.json())
result = json.loads(resp.text)
if 'id' in result:
creation_id = result['id']
second_url = f'https://graph.facebook.com/v13.0/{ig_user_id}/media_publish'
second_payload = {
'creation_id': creation_id,
'access_token':access_token
}
resp = requests.post(second_url, data=second_payload)
resp.raise_for_status()
print('Posted to Instagram', caption, "IG response:", resp.json())
else:
print('Could not post to Instagram: ', resp.json())
def post_to_meta_both(fb_page_id, ig_user_id, file_path, message, access_token):
"""Posts to Facebook and Instagram"""
post_info = post_fb(fb_page_id, file_path, message, access_token)
fb_image_link = get_fb_post_image_link(post_info['id'], access_token)
post_to_instagram(ig_user_id, access_token, fb_image_link, message)

View File

@ -1,4 +1,4 @@
def append_airport(filename, airport):
def append_airport(filename, airport, text_credit=None):
from PIL import Image, ImageDraw, ImageFont
distance_mi = airport['distance_mi']
icao = airport['icao']
@ -27,23 +27,30 @@ def append_airport(filename, airport):
#Header Box
draw.rectangle(((401, 738), (549, 760)), fill= navish)
#ADSBX Logo
draw.rectangle(((658, 762), (800, 782)), fill= white)
adsbx = Image.open("./dependencies/ADSBX_Logo.png")
adsbx = adsbx.resize((25, 25), Image.ANTIALIAS)
image.paste(adsbx, (632, 757), adsbx)
#
# adsbx = Image.open("./dependencies/ADSBX_Logo.png")
# adsbx = adsbx.resize((25, 25), Image.ANTIALIAS)
# image.paste(adsbx, (632, 757), adsbx)
#Create Text
#ADSBX Credit
(x, y) = (660, 760)
text = "adsbexchange.com"
draw.text((x, y), text, fill=black, font=head_font)
if text_credit is not None:
draw.rectangle(((658, 762), (800, 782)), fill= white)
(x, y) = (660, 760)
text = text_credit
draw.text((x, y), text, fill=black, font=head_font)
#Nearest Airport Header
(x, y) = (422, 740)
text = "Nearest Airport"
draw.text((x, y), text, fill=white, font=head_font)
#ICAO | IATA
(x, y) = (330, 765)
text = iata + " / " + icao
draw.text((x, y), text, fill=black, font=font)
if airport['iata_code'] != '' and airport['icao'] != '':
airport_codes = airport['iata_code'] + " / " + airport['icao']
elif airport['icao'] != '':
airport_codes = airport['icao']
else:
airport_codes = airport['ident']
draw.text((x, y), airport_codes, fill=black, font=font)
#Distance
(x, y) = (460, 765)
text = str(round(distance_mi, 2)) + "mi / " + str(round(distance_km, 2)) + "km away"

View File

@ -7,8 +7,31 @@ class Plane:
"""Initializes a plane object from its config file and given icao."""
self.icao = icao.upper()
self.callsign = None
self.reg = 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')
self.overrides['reg'] = self.reg
else:
self.reg = None
if self.config.has_option('DATA', 'OVERRIDE_ICAO_TYPE'):
self.type = self.config.get('DATA', 'OVERRIDE_ICAO_TYPE')
self.overrides['type'] = self.type
else:
self.type = None
if self.config.has_option('DATA', 'OVERRIDE_ICAO_TYPE'):
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
@ -41,26 +64,35 @@ 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:
self.data_loss_mins = Plane.main_config.getint('DATA', 'DATA_LOSS_MINS')
#Setup Tweepy
if self.config.getboolean('TWITTER', 'ENABLE'):
from defTweet import tweepysetup
self.tweet_api = tweepysetup(self.config)
#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'))
if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'):
import tweepy
twitter_app_auth = tweepy.OAuthHandler(Plane.main_config.get('TWITTER', 'CONSUMER_KEY'), Plane.main_config.get('TWITTER', 'CONSUMER_SECRET'))
twitter_app_auth.set_access_token(config.get('TWITTER', 'ACCESS_TOKEN'), config.get('TWITTER', 'ACCESS_TOKEN_SECRET'))
self.tweet_api = tweepy.API(twitter_app_auth, wait_on_rate_limit=True)
try:
self.latest_tweet_id = self.tweet_api.user_timeline(count = 1)[0]
except IndexError:
self.latest_tweet_id = None
def run_opens(self, ac_dict):
#Parse OpenSky Vector
from colorama import Fore, Back, Style
self.printheader("head")
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:
@ -68,18 +100,19 @@ 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)
except Exception as e:
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)
self.printheader("foot")
self.print_header("END")
else:
self.feeding = True
self.run_check()
def run_adsbx_v1(self, ac_dict):
#Parse ADBSX V1 Vector
from colorama import Fore, Back, Style
self.printheader("head")
self.print_header("BEGIN")
#print (Fore.YELLOW +"ADSBX Sourced Data: ", ac_dict, Style.RESET_ALL)
try:
#postime is divided by 1000 to get seconds from milliseconds, from timestamp expects secs.
@ -92,7 +125,7 @@ class Plane:
print("Got data but some data is invalid!")
print(e)
print (Fore.YELLOW +"ADSBX Sourced Data: ", ac_dict, Style.RESET_ALL)
self.printheader("foot")
self.print_header("END")
else:
self.feeding = True
self.run_check()
@ -100,7 +133,7 @@ class Plane:
def run_adsbx_v2(self, ac_dict):
#Parse ADBSX V2 Vector
from colorama import Fore, Back, Style
self.printheader("head")
self.print_header("BEGIN")
print(ac_dict)
try:
self.__dict__.update({'icao' : ac_dict['hex'].upper(), 'latitude' : float(ac_dict['lat']), 'longitude' : float(ac_dict['lon']), 'speed': ac_dict['gs']})
@ -116,6 +149,8 @@ class Plane:
self.on_ground = True
if ac_dict.get('flight') is not None:
self.callsign = ac_dict.get('flight').strip()
else:
self.callsign = None
if ac_dict.get('dbFlags') is not None:
self.db_flags = ac_dict['dbFlags']
if 'nav_modes' in ac_dict:
@ -142,7 +177,7 @@ class Plane:
print("Got data but some data is invalid!")
print(e)
print (Fore.YELLOW +"ADSBX Sourced Data: ", ac_dict, Style.RESET_ALL)
self.printheader("foot")
self.print_header("END")
else:
#Error Handling for bad data, sometimes it would seem to be ADSB Decode error
if (not self.on_ground) and self.speed <= 10:
@ -169,13 +204,19 @@ class Plane:
]
output = list(filter(None, output))
return tabulate(output, [], 'fancy_grid')
def printheader(self, type):
def print_header(self, note):
from colorama import Fore, Back, Style
if type == "head":
header = str("--------- " + self.conf_file_path + " ---------------------------- ICAO: " + self.icao + " ---------------------------------------")
elif type == "foot":
header = "----------------------------------------------------------------------------------------------------"
print(Back.MAGENTA + header[0:100] + Style.RESET_ALL)
if note == "BEGIN":
header = f"---BEGIN---------{self.conf_file_path}"
elif note == "END":
header = f"---END"
remaning_len = 85 - len(header)
for x in range(0, remaning_len):
header += "-"
header += f"ICAO: {self.icao}---"
if note =="END":
header += Style.RESET_ALL + "\n"
print(Back.MAGENTA + header + Style.RESET_ALL)
def get_time_since(self, datetime_obj):
if datetime_obj != None:
time_since = datetime.now() - datetime_obj
@ -186,15 +227,18 @@ class Plane:
if self.config.has_option('MAP', 'OVERLAYS'):
overlays = self.config.get('MAP', 'OVERLAYS')
else:
overlays = ""
overlays = "null"
return overlays
def route_info(self):
from lookup_route import lookup_route, clean_data
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:
@ -206,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":
@ -243,7 +290,7 @@ class Plane:
return route_to
def run_empty(self):
self.printheader("head")
self.print_header("BEGIN")
self.feeding = False
self.run_check()
def run_check(self):
@ -352,17 +399,19 @@ class Plane:
area = f"{municipality}, {state}"
location_string = (f"{area}, {country_code}")
print (Fore.GREEN + "Country Code:", country_code, "State:", state, "Municipality:", municipality + Style.RESET_ALL)
title_switch = {
"reg": self.reg,
"callsign": self.callsign,
"icao": self.icao,
}
dynamic_title = self.callsign or self.reg or self.icao
#Set Discord Title
if self.config.getboolean('DISCORD', 'ENABLE'):
self.dis_title = (title_switch.get(self.config.get('DISCORD', 'TITLE')) or "NA").strip() if self.config.get('DISCORD', 'TITLE') in title_switch.keys() else self.config.get('DISCORD', 'TITLE')
if self.config.get('DISCORD', 'TITLE') in ["DYNAMIC", "callsign"]:
self.dis_title = dynamic_title
else:
self.dis_title = self.config.get('DISCORD', 'TITLE')
#Set Twitter Title
if self.config.getboolean('TWITTER', 'ENABLE'):
self.twitter_title = (title_switch.get(self.config.get('TWITTER', 'TITLE')) or "NA") if self.config.get('TWITTER', 'TITLE') in title_switch.keys() else self.config.get('TWITTER', 'TITLE')
if self.config.get('TWITTER', 'TITLE') in ["DYNAMIC", "callsign"]:
self.twitter_title = dynamic_title
else:
self.twitter_title = self.config.get('TWITTER', 'TITLE')
#Takeoff and Land Notification
if self.tookoff or self.landed:
route_to = None
@ -392,19 +441,21 @@ class Plane:
self.takeoff_time = None
elif self.landed:
landed_time_msg = None
landed_time = None
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 self.config.get('MAP', 'OPTION') == "GOOGLESTATICMAP":
if Plane.main_config.get('MAP', 'OPTION') == "GOOGLESTATICMAP":
from defMap import getMap
getMap((municipality + ", " + state + ", " + country_code), self.map_file_name)
elif self.config.get('MAP', 'OPTION') == "ADSBX":
elif Plane.main_config.get('MAP', 'OPTION') == "ADSBX":
from defSS import get_adsbx_screenshot
url_params = f"icao={self.icao}&zoom=9&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays=" + self.get_adsbx_map_overlays()
get_adsbx_screenshot(self.map_file_name, url_params)
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
append_airport(self.map_file_name, nearest_airport_dict)
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)
else:
raise ValueError("Map option not set correctly in this planes conf")
#Telegram
@ -414,23 +465,52 @@ class Plane:
sendTeleg(photo, message, self.config)
#Discord
if self.config.getboolean('DISCORD', 'ENABLE'):
dis_message = f"{self.dis_title} {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, self.map_file_name, role_id = role_id)
#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'):
twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name)
alt_text = f"Reg: {self.reg} On Ground: {str(self.on_ground)} Alt: {str(self.alt_ft)} Last Contact: {str(time_since_contact)} Trigger: {trigger_type}"
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
import tweepy
try:
twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name)
alt_text = f"Reg: {self.reg} On Ground: {str(self.on_ground)} Alt: {str(self.alt_ft)} Last Contact: {str(time_since_contact)} Trigger: {trigger_type}"
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:
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 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
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 = (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"
else:
distance_message = ""
if landed_time is not None and self.type is not None:
print("Running fuel info calc")
flight_time_min = landed_time.total_seconds() / 60
from fuel_calc import fuel_calculation, fuel_message
fuel_info = fuel_calculation(self.type, flight_time_min)
if fuel_info is not None:
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') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None
sendDis(dis_message, self.config, role_id)
if self.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:
raise
self.latest_tweet_id = None
self.recheck_route_time = None
self.known_to_airport = None
@ -445,15 +525,15 @@ class Plane:
if self.config.has_section('TELEGRAM') and self.config.getboolean('TELEGRAM', 'ENABLE'):
message = f"{self.dis_title} {route_to}".strip()
photo = open(self.map_file_name, "rb")
from defTelegram import sendTeleg
sendTeleg(photo, message, self.config)
#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
sendDis(dis_message, self.config, role_id = role_id)
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'):
#tweet = self.tweet_api.user_timeline(count = 1)[0]
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:
@ -479,15 +559,15 @@ class Plane:
squawk_message = (f"{self.dis_title} Squawking {self.last_emergency[1]} {emergency_squawks[self.squawk]}").strip()
print(squawk_message)
#Google Map or tar1090 screenshot
if self.config.get('MAP', 'OPTION') == "GOOGLESTATICMAP":
if Plane.main_config.get('MAP', 'OPTION') == "GOOGLESTATICMAP":
getMap((municipality + ", " + state + ", " + country_code), self.map_file_name)
if self.config.get('MAP', 'OPTION') == "ADSBX":
if Plane.main_config.get('MAP', 'OPTION') == "ADSBX":
from defSS import get_adsbx_screenshot
url_params = f"icao={self.icao}&zoom=9&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays=" + self.get_adsbx_map_overlays()
get_adsbx_screenshot(self.map_file_name, url_params)
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, self.map_file_name)
sendDis(dis_message, self.config, None, self.map_file_name)
os.remove(self.map_file_name)
#Realizes first time seeing emergency, stores time and type
elif self.squawk in emergency_squawks.keys() and not self.emergency_already_triggered and not self.on_ground:
@ -506,9 +586,9 @@ class Plane:
dis_message = (self.dis_title + " " + mode + " mode enabled.")
if mode == "Approach":
from defSS import get_adsbx_screenshot
url_params = f"icao={self.icao}&zoom=9&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays={self.get_adsbx_map_overlays()}"
get_adsbx_screenshot(self.map_file_name, url_params)
sendDis(dis_message, self.config, self.map_file_name)
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)
else:
@ -519,7 +599,7 @@ class Plane:
print("Nav altitude is now", self.sel_nav_alt)
if self.config.getboolean('DISCORD', 'ENABLE'):
dis_message = (self.dis_title + " Sel. alt. " + str("{:,} ft".format(self.sel_nav_alt)))
sendDis(dis_message,self.config)
sendDis(dis_message, self.config)
#Circling
if self.last_track is not None:
import time
@ -553,40 +633,189 @@ class Plane:
#rp = (points.representative_point()) #A represenative point, not centroid,
print(cent)
#print(rp)
distance_to_centroid = geodesic(aircraft_coords, cent.coords).mi
distance_to_centroid = round(geodesic(aircraft_coords, cent.coords).mi, 2)
print(f"Distance to centroid of circling coordinates {distance_to_centroid} miles")
if distance_to_centroid <= 15:
print("Within 15 miles of centroid, CIRCLING")
#Finds Nearest Airport
from defAirport import getClosestAirport
nearest_airport_dict = getClosestAirport(self.latitude, self.longitude, ["small_airport", "medium_airport", "large_airport"])
from calculate_headings import calculate_from_bearing, calculate_cardinal
from_bearing = calculate_from_bearing((float(nearest_airport_dict['latitude_deg']), float(nearest_airport_dict['longitude_deg'])), (self.latitude, self.longitude))
cardinal = calculate_cardinal(from_bearing)
#Finds Nearest TFR or in TFR
from shapely.geometry import MultiPoint
from geopy.distance import geodesic
from shapely.geometry.polygon import Polygon
from shapely.geometry import Point
import requests, json
closest_tfr = None
in_tfr = None
if Plane.main_config.getboolean("TFRS", "ENABLE"):
tfr_url = Plane.main_config.get("TFRS", "URL")
response = requests.get(tfr_url, timeout=60)
tfrs = json.loads(response.text)
for tfr in tfrs:
if in_tfr is not None:
break
elif tfr['details'] is not None and 'shapes' in tfr['details'].keys():
for index, shape in enumerate(tfr['details']['shapes']):
if 'txtName' not in shape.keys():
shape['txtName'] = 'shape_'+str(index)
polygon = None
if shape['type'] == "poly":
points = shape['points']
elif shape['type'] == "circle":
from functools import partial
import pyproj
from shapely.ops import transform
from shapely.geometry import Point
proj_wgs84 = pyproj.Proj('+proj=longlat +datum=WGS84')
def geodesic_point_buffer(lat, lon, km):
# Azimuthal equidistant projection
aeqd_proj = '+proj=aeqd +lat_0={lat} +lon_0={lon} +x_0=0 +y_0=0'
project = partial(
pyproj.transform,
pyproj.Proj(aeqd_proj.format(lat=lat, lon=lon)),
proj_wgs84)
buf = Point(0, 0).buffer(km * 1000) # distance in metres
return transform(project, buf).exterior.coords[:]
radius_km = float(shape['radius']) * 1.852
b = geodesic_point_buffer(shape['lat'], shape['lon'], radius_km)
points = []
for coordinate in b:
points.append([coordinate[1], coordinate[0]])
elif shape['type'] in ["polyarc", "polyexclude"]:
points = shape['all_points']
aircraft_location = Point(self.latitude, self.longitude)
if polygon is None:
polygon = Polygon(points)
if polygon.contains(aircraft_location):
in_tfr = {'info': tfr, 'closest_shape_name' : shape['txtName']}
break
else:
point_dists = []
for point in points:
from geopy.distance import geodesic
point = tuple(point)
point_dists.append(float((geodesic((self.latitude, self.longitude), point).mi)))
distance = min(point_dists)
if closest_tfr is None:
closest_tfr = {'info': tfr, 'closest_shape_name' : shape['txtName'], 'distance' : round(distance)}
elif distance < closest_tfr['distance']:
closest_tfr = {'info': tfr, 'closest_shape_name' : shape['txtName'], 'distance' : round(distance)}
if in_tfr is not None:
for shape in in_tfr['info']['details']['shapes']:
if shape['txtName'] == in_tfr['closest_shape_name']:
valDistVerUpper, valDistVerLower = int(shape['valDistVerUpper']), int(shape['valDistVerLower'])
print("In TFR based off location checking alt next", in_tfr)
break
if not (self.alt_ft >= valDistVerLower and self.alt_ft <= valDistVerUpper):
if self.alt_ft > valDistVerUpper:
in_tfr['context'] = "above"
elif self.alt_ft < valDistVerLower:
in_tfr['context'] = "below"
print("But not in alt of TFR", in_tfr['context'])
if in_tfr is None:
print("Closest TFR", closest_tfr)
#Generate Map
import staticmaps
context = staticmaps.Context()
context.set_tile_provider(staticmaps.tile_provider_OSM)
if in_tfr is not None:
shapes = in_tfr['info']['details']['shapes']
else:
shapes = closest_tfr['info']['details']['shapes']
def draw_poly(context, pairs):
pairs.append(pairs[0])
context.add_object(
staticmaps.Area(
[staticmaps.create_latlng(lat, lng) for lat, lng in pairs],
fill_color=staticmaps.parse_color("#FF000033"),
width=2,
color=staticmaps.parse_color("#8B0000"),
)
)
return context
for shape in shapes:
if shape['type'] == "poly":
pairs = shape['points']
context = draw_poly(context, pairs)
elif shape['type'] == "polyarc" or shape['type'] == "polyexclude":
pairs = shape['all_points']
context = draw_poly(context, pairs)
elif shape['type'] =="circle":
center = [shape['lat'], shape['lon']]
center1 = staticmaps.create_latlng(center[0], center[1])
context.add_object(staticmaps.Circle(center1, (float(shape['radius']) * 1.852), fill_color=staticmaps.parse_color("#FF000033"), color=staticmaps.parse_color("#8B0000"), width=2))
context.add_object(staticmaps.Marker(center1, color=staticmaps.RED))
def tfr_image(context, aircraft_coords):
from PIL import Image
heading = self.track
heading *= -1
im = Image.open('./dependencies/ac.png')
im_rotate = im.rotate(heading, resample=Image.BICUBIC)
import tempfile
rotated_file = f"{tempfile.gettempdir()}/rotated_ac.png"
im_rotate.save(rotated_file)
pos = staticmaps.create_latlng(aircraft_coords[0], aircraft_coords[1])
marker = staticmaps.ImageMarker(pos, rotated_file, origin_x=35, origin_y=35)
context.add_object(marker)
image = context.render_cairo(1000, 1000)
os.remove(rotated_file)
tfr_map_filename = f"{tempfile.gettempdir()}/{self.icao}_TFR_.png"
image.write_to_png(tfr_map_filename)
return tfr_map_filename
from defSS import get_adsbx_screenshot
url_params = f"icao={self.icao}&zoom=10&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays={self.get_adsbx_map_overlays()}"
get_adsbx_screenshot(self.map_file_name, url_params)
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']}"
else:
message = f"Circling over {nearest_airport_dict['icao']} at {self.alt_ft}ft"
message = f"Circling over {nearest_airport_dict['icao']} at {self.alt_ft}ft."
else:
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"
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:
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']}"
tfr_map_filename = tfr_image(context, (self.latitude, self.longitude))
elif in_tfr is None and closest_tfr is not None and "distance" not in closest_tfr.keys():
message += f" near TFR {closest_tfr['info']['NOTAM']}, a TFR for {closest_tfr['info']['Type']}"
raise Exception(message)
print(message)
#Telegram
if self.config.has_section('TELEGRAM') and self.config.getboolean('TELEGRAM', 'ENABLE'):
photo = open(self.map_file_name, "rb")
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
sendDis(message, self.config, self.map_file_name, role_id)
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'):
twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name)
alt_text = f"Distance to centroid: {distance_to_centroid}, Total change: {total_change}"
self.tweet_api.create_media_metadata(media_id= twitter_media_map_obj.media_id, alt_text= alt_text)
tweet = self.tweet_api.user_timeline(count = 1)[0]
self.latest_tweet_id = self.tweet_api.update_status(status = f"{self.twitter_title} {message}".strip(), in_reply_to_status_id = self.latest_tweet_id, media_ids=[twitter_media_map_obj.media_id]).id
media_ids = [twitter_media_map_obj.media_id]
if tfr_map_filename is not None:
twitter_media_tfr_map_obj = self.tweet_api.media_upload(tfr_map_filename)
media_ids.append(twitter_media_tfr_map_obj.media_id)
elif tfr_map_filename is None:
print("No TFR Map")
tweet = f"{self.twitter_title} {message}".strip()
self.tweet_api.update_status(status = tweet, media_ids=media_ids)
#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"))
self.circle_history['triggered'] = True
elif abs(total_change) <= 360 and self.circle_history["triggered"]:
print("No Longer Circling, trigger cleared")
@ -614,7 +843,7 @@ class Plane:
hours, remainder = divmod(elapsed_time.total_seconds(), 3600)
minutes, seconds = divmod(remainder, 60)
print((f"Time Since Take off {int(hours)} Hours : {int(minutes)} Mins : {int(seconds)} Secs"))
self.printheader("foot")
self.print_header("END")
def check_new_ras(self, ras):
for ra in ras:
if self.recent_ra_types == {} or ra['acas_ra']['advisory'] not in self.recent_ra_types.keys():
@ -625,7 +854,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'])
@ -635,13 +864,13 @@ 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)
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
sendDis(dis_message, self.config, self.map_file_name, role_id = role_id)
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):
if self.recent_ra_types != {}:
@ -651,4 +880,4 @@ class Plane:
print(time_since_ra)
if time_since_ra.seconds >= 600:
print(ra_type)
self.recent_ra_types.pop(ra_type)
self.recent_ra_types.pop(ra_type)