Merge branch 'multi' into patch-4
This commit is contained in:
commit
b08c0a195c
|
@ -1,3 +1,5 @@
|
|||
configs/*.ini
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
.vscode/settings.json
|
||||
|
@ -9,3 +11,4 @@ lookup_route.py
|
|||
icao_url_gen.py
|
||||
install.sh
|
||||
coul_icao_gen.py
|
||||
test.py
|
||||
|
|
36
Dockerfile
36
Dockerfile
|
@ -1,23 +1,37 @@
|
|||
FROM python:3
|
||||
|
||||
WORKDIR /plane-notify
|
||||
USER root
|
||||
|
||||
COPY . .
|
||||
|
||||
# Set the Chrome repo.
|
||||
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||
&& echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list
|
||||
RUN set -ex && \
|
||||
apt-get update -qq && \
|
||||
apt-get -y -qq install --no-install-recommends \
|
||||
ca-certificates \
|
||||
gnupg && \
|
||||
curl -sSL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
|
||||
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list && \
|
||||
apt-get -y -qq update \
|
||||
&& apt-get -y -qq install --no-install-recommends \
|
||||
bash \
|
||||
curl \
|
||||
google-chrome-stable \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-wheel \
|
||||
&& rm -rf \
|
||||
/var/lib/apt/lists/* \
|
||||
/var/cache/apt/archives
|
||||
|
||||
# Install Chrome.
|
||||
RUN apt-get update && apt-get -y install google-chrome-stable
|
||||
|
||||
# Add pipenv
|
||||
RUN pip install pipenv==2021.5.29
|
||||
|
||||
# Install dependencies
|
||||
RUN pipenv install
|
||||
RUN pip3 install --upgrade pip && \
|
||||
pip3 install -U --no-cache-dir -r ./requirements.txt
|
||||
|
||||
# Added needed folder for plane-notify process
|
||||
RUN mkdir /home/plane-notify
|
||||
RUN mkdir -p /home/plane-notify
|
||||
|
||||
CMD pipenv run python /plane-notify/__main__.py
|
||||
CMD python3 /plane-notify/__main__.py
|
Binary file not shown.
After Width: | Height: | Size: 209 KiB |
Binary file not shown.
After Width: | Height: | Size: 224 KiB |
Binary file not shown.
Before Width: | Height: | Size: 35 KiB |
3
Pipfile
3
Pipfile
|
@ -12,7 +12,6 @@ tabulate = "*"
|
|||
pytz = "*"
|
||||
pillow = "*"
|
||||
tweepy = "*"
|
||||
"pushbullet.py" = "*"
|
||||
discord-webhook = "*"
|
||||
selenium = "*"
|
||||
opensky-api = {editable = true, git = "https://github.com/openskynetwork/opensky-api.git", subdirectory = "python"}
|
||||
|
@ -27,5 +26,7 @@ lxml = "*"
|
|||
beautifulsoup4 = "*"
|
||||
python-telegram-bot = "*"
|
||||
configparser = "*"
|
||||
"mastodon.py" = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,5 +4,5 @@
|
|||
- A landing event is previously below 10k feet and (previously getting data, no longer getting data and previously not on the ground) or (now on the ground and previously not on the ground).
|
||||
- Given the coordinates of the aircraft the nearest airport is found in an airport database from <https://ourairports.com/data> the distance is calculated using the Haversine formula. The state, region and country are also found in this database with the airport.
|
||||
- At the time of takeoff a takeoff time is set, which is referenced in the landing event to calculate approximate total flight time.
|
||||
- A Static map image is created based off location name. (Google Static Maps API) or a screenshot of <https://global.adsbexchange.com/> is created using Selenium/ChromeDriver. The selected plane is locked on in the screenshot.
|
||||
- A Static map image is created based off location name. (Google Static Maps API) or a screenshot of <https://globe.theairtraffic.com/> is created using Selenium/ChromeDriver. The selected plane is locked on in the screenshot.
|
||||
- If the landing event or takeoff event is true, It will output to any of the following built-in output methods. (Twitter, Pushbullet, and Discord can all be setup and enabled in each plane's config file). Outputs the location name, map, image, and flight time on landing. (Tweepy and "Pushbullet.py" and Discord_webhooks)
|
||||
|
|
23
README.md
23
README.md
|
@ -3,11 +3,11 @@
|
|||
[](https://app.codacy.com/manual/Jxck-S/plane-notify?utm_source=github.com&utm_medium=referral&utm_content=Jxck-S/plane-notify&utm_campaign=Badge_Grade_Settings)
|
||||
[](https://opensource.org/licenses/)
|
||||
|
||||
Notify if configured planes have taken off or landed using Python with <a href="https://opensky-network.org/">OpenSky</a>(free) or <a href="https://www.adsbexchange.com/">ADSBExchange</a> Data(paid but much better), outputs location of takeoff location of landing and takeoff by reverse lookup of coordinates.
|
||||
Notify if configured planes have taken off or landed using Python with <a href="https://opensky-network.org/">OpenSky</a>(free) or <a href="https://www.adsbexchange.com/">ADSBExchange</a> Data(paid, declining data, and run by clowns), outputs location of takeoff location of landing and takeoff by reverse lookup of coordinates.
|
||||
|
||||
### Discord Output Example
|
||||
|
||||

|
||||

|
||||
|
||||
#### More examples are in the ExImages folder
|
||||
|
||||
|
@ -15,15 +15,16 @@ 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. [](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 orginally on Twitter (but now suspended, but now also on other platforms). I have now expanded and run multiple accounts for multiple planes, a list of the accounts can be found here <https://grndcntrl.net/links>
|
||||
|
||||
### 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)
|
||||
|
||||
## Setup / Install
|
||||
- Install using the following steps or use Docker, scroll down to the Docker section.
|
||||
|
||||
### Make sure Python/PIP is installed
|
||||
|
||||
|
@ -42,12 +43,15 @@ pipenv install
|
|||
|
||||
### Install Selenium / ChromeDriver or setup Google Static Maps
|
||||
|
||||
Selenium/ChromeDriver is used to take a screenshot of the plane on globe.adsbexchange.com. Or use Google Static Maps, which can cost money if overused(No tutorial use <https://developers.google.com/maps/documentation/maps-static/get-api-key> to get to a key).
|
||||
|
||||
#### Chromium
|
||||
Selenium/ChromeDriver is used to take a screenshot of the plane on globe.theairtraffic.com. Or use Google Static Maps, which can cost money if overused(No tutorial use <https://developers.google.com/maps/documentation/maps-static/get-api-key> to get to a key).
|
||||
|
||||
#### Chrome
|
||||
- This is assuming linux/debian
|
||||
```bash
|
||||
sudo apt-get install chromium
|
||||
curl -sSL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add
|
||||
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list
|
||||
apt update
|
||||
apt install google-chrome-stable
|
||||
```
|
||||
These output methods once installed can be configured in the planes config you create, using the example plane1.ini
|
||||
|
||||
|
@ -67,6 +71,7 @@ cd plane-notify
|
|||
|
||||
### Configure main config file with keys and URLs (mainconf.ini) in the configs directory
|
||||
|
||||
- Copy `mainconf.ini.example` to `mainconf.ini` andCopy `plane1.ini.example` to `plane1.ini`. `plane1.ini` can change names as long as it ends with the ini extension
|
||||
- Edit them with nano or vi on the running machine or on your pc and transfer the config to where you will be running the bot
|
||||
- Pick between OpenSky and ADS-B Exchange
|
||||
- The OpenSky API is free for everyone but the data is not as good as ADS-B Exchange. The ADS-B Exchange API is not free and this program will not work for the Rapid API from ADS-B Exchange. It only works with the API that they give when you have a partnership with ADS-B Exchange. It is not cheap to get the ADS-B Exchange full API, Don't contact them unless you are ready to pay.
|
||||
|
@ -91,7 +96,7 @@ screen -R <name screen whatever you want>
|
|||
### Start Program
|
||||
|
||||
```bash
|
||||
pipenv run python __main__
|
||||
pipenv run python __main__.py
|
||||
```
|
||||
|
||||
## Using with Docker
|
||||
|
|
82
__main__.py
82
__main__.py
|
@ -1,16 +1,21 @@
|
|||
import configparser
|
||||
from logging import DEBUG
|
||||
import time
|
||||
from colorama import Fore, Back, Style
|
||||
import platform
|
||||
import traceback
|
||||
import os
|
||||
if platform.system() == "Windows":
|
||||
from colorama import init
|
||||
init(convert=True)
|
||||
elif platform.system() == "Linux":
|
||||
if os.path.exists("/tmp/plane-notify"):
|
||||
import shutil
|
||||
shutil.rmtree("/tmp/plane-notify")
|
||||
os.makedirs("/tmp/plane-notify")
|
||||
os.makedirs("/tmp/plane-notify/chrome")
|
||||
from planeClass import Plane
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
import os
|
||||
import signal
|
||||
abspath = os.path.abspath(__file__)
|
||||
dname = os.path.dirname(abspath)
|
||||
|
@ -20,7 +25,11 @@ sys.path.extend([os.getcwd()])
|
|||
#Dependency Handling
|
||||
if not os.path.isdir("./dependencies/"):
|
||||
os.mkdir("./dependencies/")
|
||||
required_files = [("Roboto-Regular.ttf", 'https://github.com/googlefonts/roboto/blob/main/src/hinted/Roboto-Regular.ttf?raw=true'), ('airports.csv', 'https://ourairports.com/data/airports.csv'), ('regions.csv', 'https://ourairports.com/data/regions.csv'), ('ADSBX_Logo.png', "https://www.adsbexchange.com/wp-content/uploads/cropped-Stealth.png"), ('Mictronics_db.zip', "https://www.mictronics.de/aircraft-database/indexedDB.php")]
|
||||
required_files = [
|
||||
("Roboto-Regular.ttf", 'https://github.com/googlefonts/roboto/blob/main/src/hinted/Roboto-Regular.ttf?raw=true'),
|
||||
('airports.csv', 'https://ourairports.com/data/airports.csv'),
|
||||
('regions.csv', 'https://ourairports.com/data/regions.csv'),
|
||||
('Mictronics_db.zip', "https://www.mictronics.de/aircraft-database/indexedDB.php")]
|
||||
for file in required_files:
|
||||
file_name = file[0]
|
||||
url = file[1]
|
||||
|
@ -49,11 +58,13 @@ main_config.read('./configs/mainconf.ini')
|
|||
source = main_config.get('DATA', 'SOURCE')
|
||||
if main_config.getboolean('DISCORD', 'ENABLE'):
|
||||
from defDiscord import sendDis
|
||||
role_id = main_config.get('DISCORD', 'ROLE_ID') if main_config.has_option('DISCORD', 'ROLE_ID') and main_config.get('DISCORD', 'ROLE_ID').strip() != "" else None
|
||||
sendDis("Started", main_config, role_id = main_config.get('DISCORD', 'ROLE_ID'))
|
||||
def service_exit(signum, frame):
|
||||
if main_config.getboolean('DISCORD', 'ENABLE'):
|
||||
from defDiscord import sendDis
|
||||
sendDis("Service Stop", main_config, role_id = main_config.get('DISCORD', 'ROLE_ID'))
|
||||
role_id = main_config.get('DISCORD', 'ROLE_ID') if main_config.has_option('DISCORD', 'ROLE_ID') and main_config.get('DISCORD', 'ROLE_ID').strip() != "" else None
|
||||
sendDis("Service Stop", main_config, role_id = role_id)
|
||||
raise SystemExit("Service Stop")
|
||||
signal.signal(signal.SIGTERM, service_exit)
|
||||
if os.path.isfile("lookup_route.py"):
|
||||
|
@ -69,7 +80,7 @@ try:
|
|||
print("Found the following configs")
|
||||
for dirpath, dirname, filename in os.walk("./configs"):
|
||||
for filename in [f for f in filename if f.endswith(".ini") and f != "mainconf.ini"]:
|
||||
if not "disabled" in dirpath:
|
||||
if "disabled" not in dirpath:
|
||||
print(os.path.join(dirpath, filename))
|
||||
plane_config = configparser.ConfigParser()
|
||||
plane_config.read((os.path.join(dirpath, filename)))
|
||||
|
@ -140,18 +151,66 @@ try:
|
|||
for planeData in data['ac']:
|
||||
data_indexed[planeData[icao_key].upper()] = planeData
|
||||
for key, obj in planes.items():
|
||||
if key in data_indexed.keys():
|
||||
try:
|
||||
if api_version == 1:
|
||||
obj.run_adsbx_v1(data_indexed[key.upper()])
|
||||
elif api_version == 2:
|
||||
obj.run_adsbx_v2(data_indexed[key.upper()])
|
||||
else:
|
||||
except KeyError:
|
||||
obj.run_empty()
|
||||
else:
|
||||
for obj in planes.values():
|
||||
obj.run_empty()
|
||||
else:
|
||||
failed_count += 1
|
||||
elif source == "RpdADSBX":
|
||||
#ACAS data
|
||||
from defADSBX import pull_date_ras
|
||||
import ast
|
||||
today = datetime.utcnow()
|
||||
date = today.strftime("%Y/%m/%d")
|
||||
ras = pull_date_ras(date)
|
||||
sorted_ras = {}
|
||||
if ras is not None:
|
||||
#Testing RAs
|
||||
#if last_ra_count is not None:
|
||||
# with open('./testing/acastest.json') as f:
|
||||
# data = f.readlines()
|
||||
# ras += data
|
||||
ra_count = len(ras)
|
||||
if last_ra_count is not None and ra_count != last_ra_count:
|
||||
print(abs(ra_count - last_ra_count), "new Resolution Advisories")
|
||||
for ra_num, ra in enumerate(ras[last_ra_count:]):
|
||||
ra = ast.literal_eval(ra)
|
||||
if ra['hex'].upper() in planes.keys():
|
||||
if ra['hex'].upper() not in sorted_ras.keys():
|
||||
sorted_ras[ra['hex'].upper()] = [ra]
|
||||
else:
|
||||
sorted_ras[ra['hex'].upper()].append(ra)
|
||||
else:
|
||||
print("No new Resolution Advisories")
|
||||
last_ra_count = ra_count
|
||||
for key, obj in planes.items():
|
||||
if sorted_ras != {} and key in sorted_ras.keys():
|
||||
print(key, "has", len(sorted_ras[key]), "RAs")
|
||||
obj.check_new_ras(sorted_ras[key])
|
||||
obj.expire_ra_types()
|
||||
from defRpdADSBX import pull_rpdadsbx
|
||||
data_indexed = {}
|
||||
for icao in planes:
|
||||
plane = planes[icao]
|
||||
plane_info = pull_rpdadsbx(icao)
|
||||
if plane_info:
|
||||
if plane_info['ac']:
|
||||
data_indexed[icao.upper()] = plane_info['ac'][0]
|
||||
plane.run_adsbx_v2(data_indexed[icao.upper()])
|
||||
else:
|
||||
plane.run_empty()
|
||||
else:
|
||||
print(f"No data for icao {icao}. Skipping...")
|
||||
plane.run_empty()
|
||||
if not data_indexed:
|
||||
failed_count += 1
|
||||
elif source == "OPENS":
|
||||
from defOpenSky import pull_opensky
|
||||
planeData, failed = pull_opensky(planes)
|
||||
|
@ -186,7 +245,10 @@ try:
|
|||
footer = "-------- " + str(running_Count) + " -------- " + str(datetime_tz.strftime("%I:%M:%S %p")) + " ------------------------Elapsed Time- " + str(round(elapsed_calc_time, 3)) + " -------------------------------------"
|
||||
print (Back.GREEN + Fore.BLACK + footer[0:100] + Style.RESET_ALL)
|
||||
|
||||
sleep_sec = 30
|
||||
if main_config.has_section('SLEEP'):
|
||||
sleep_sec = int(main_config.get('SLEEP', 'SLEEPSEC'))
|
||||
else:
|
||||
sleep_sec = 30
|
||||
for i in range(sleep_sec,0,-1):
|
||||
if i < 10:
|
||||
i = " " + str(i)
|
||||
|
@ -208,10 +270,10 @@ except Exception as e:
|
|||
except OSError:
|
||||
pass
|
||||
import logging
|
||||
logging.basicConfig(filename='crash_latest.log', filemode='w', format='%(asctime)s - %(message)s',level=logging.DEBUG)
|
||||
logging.basicConfig(filename='crash_latest.log', filemode='w', format='%(asctime)s - %(message)s')
|
||||
logging.Formatter.converter = time.gmtime
|
||||
logging.error(e)
|
||||
logging.error(str(traceback.format_exc()))
|
||||
from defDiscord import sendDis
|
||||
sendDis(str("Error Exiting: " + str(e) + " Failed on " + "https://globe.adsbexchange.com/?icao=" + key), main_config, main_config.get('DISCORD', 'ROLE_ID'), "crash_latest.log")
|
||||
sendDis(str("Error Exiting: " + str(e) + f"Failed on ({obj.config_path}) https://globe.theairtraffic.com/?icao={key} "), main_config, main_config.get('DISCORD', 'ROLE_ID'), "crash_latest.log")
|
||||
raise e
|
||||
|
|
|
@ -1,214 +1,412 @@
|
|||
{
|
||||
"EA50": {
|
||||
"name": "Eclipse 550",
|
||||
"galph": 76,
|
||||
"category": "VLJ"
|
||||
"name": "Eclipse 550",
|
||||
"galph": 76,
|
||||
"category": "VLJ"
|
||||
},
|
||||
"LJ31": {
|
||||
"name": "Learjet 31",
|
||||
"galph": 202,
|
||||
"category": "Light"
|
||||
"name": "Learjet 31",
|
||||
"galph": 202,
|
||||
"category": "Light"
|
||||
},
|
||||
"LJ40": {
|
||||
"name": "Learjet 40",
|
||||
"galph": 207,
|
||||
"category": "Light"
|
||||
"name": "Learjet 40",
|
||||
"galph": 207,
|
||||
"category": "Light"
|
||||
},
|
||||
"PC24": {
|
||||
"name": "Pilatus PC-24",
|
||||
"galph": 154,
|
||||
"category": "Light"
|
||||
"name": "Pilatus PC-24",
|
||||
"galph": 154,
|
||||
"category": "Light"
|
||||
},
|
||||
"LJ45": {
|
||||
"name": "Learjet 45",
|
||||
"galph": 205,
|
||||
"category": "Super Light"
|
||||
"name": "Learjet 45",
|
||||
"galph": 205,
|
||||
"category": "Super Light"
|
||||
},
|
||||
"LJ70": {
|
||||
"name": "Learjet 70",
|
||||
"galph": 198,
|
||||
"category": "Super Light"
|
||||
"name": "Learjet 70",
|
||||
"galph": 198,
|
||||
"category": "Super Light"
|
||||
},
|
||||
"LJ75": {
|
||||
"name": "Learjet 75",
|
||||
"galph": 199,
|
||||
"category": "Super Light"
|
||||
"name": "Learjet 75",
|
||||
"galph": 199,
|
||||
"category": "Super Light"
|
||||
},
|
||||
"G150": {
|
||||
"name": "Gulfstream G150",
|
||||
"galph": 228,
|
||||
"category": "Midsize"
|
||||
"name": "Gulfstream G150",
|
||||
"galph": 228,
|
||||
"category": "Midsize"
|
||||
},
|
||||
"LJ60": {
|
||||
"name": "Learjet 60",
|
||||
"galph": 239,
|
||||
"category": "Midsize"
|
||||
"name": "Learjet 60",
|
||||
"galph": 239,
|
||||
"category": "Midsize"
|
||||
},
|
||||
"GALX": {
|
||||
"name": "Gulfstream G200",
|
||||
"galph": 278,
|
||||
"category": "Super Midsize"
|
||||
"name": "Gulfstream G200",
|
||||
"galph": 278,
|
||||
"category": "Super Midsize"
|
||||
},
|
||||
"G280": {
|
||||
"name": "Gulfstream G280",
|
||||
"galph": 297,
|
||||
"category": "Super Midsize"
|
||||
"name": "Gulfstream G280",
|
||||
"galph": 297,
|
||||
"category": "Super Midsize"
|
||||
},
|
||||
"GLF5": {
|
||||
"name": "Gulfstream G500",
|
||||
"galph": 447,
|
||||
"category": "Large"
|
||||
"name": "Gulfstream G500",
|
||||
"galph": 447,
|
||||
"category": "Large"
|
||||
},
|
||||
"GLF6": {
|
||||
"name": "Gulfstream G650",
|
||||
"galph": 503,
|
||||
"category": "Ultra Long Range"
|
||||
"name": "Gulfstream G650",
|
||||
"galph": 503,
|
||||
"category": "Ultra Long Range"
|
||||
},
|
||||
"PC12": {
|
||||
"name": "Pilatus PC-12",
|
||||
"galph": 66,
|
||||
"category": "Turboprop Aircraft"
|
||||
"name": "Pilatus PC-12",
|
||||
"galph": 66,
|
||||
"category": "Turboprop Aircraft"
|
||||
},
|
||||
"GLEX": {
|
||||
"name": "Global",
|
||||
"galph": 500,
|
||||
"category": "Ultra Long Range"
|
||||
}
|
||||
,
|
||||
"name": "Global",
|
||||
"galph": 500,
|
||||
"category": "Ultra Long Range"
|
||||
},
|
||||
"CL30": {
|
||||
"name": "Challenger 300",
|
||||
"galph": 295,
|
||||
"category": "Super Midsize"
|
||||
}, "B742": {
|
||||
"name": "Boeing 747-200",
|
||||
"galph": 3830,
|
||||
"category": "Large"
|
||||
}, "T38": {
|
||||
"name": "T-38 Talon",
|
||||
"galph": 375,
|
||||
"category": "Fighter"
|
||||
}, "WB57": {
|
||||
"name": "Martin B-57 Canberra",
|
||||
"galph": 531,
|
||||
"category": "Twinjet Tactical Bomber and Reconnaissance"
|
||||
}, "B74S": {
|
||||
"name": "747 SP",
|
||||
"galph": 2289,
|
||||
"category": "Large"
|
||||
}, "B752": {
|
||||
"name": "757 200",
|
||||
"galph": 877,
|
||||
"category": "Large"
|
||||
},
|
||||
"B738": {
|
||||
"name": "737 800",
|
||||
"galph": 832,
|
||||
"category": "Medium"
|
||||
"name": "Challenger 300",
|
||||
"galph": 295,
|
||||
"category": "Super Midsize"
|
||||
},
|
||||
"B742": {
|
||||
"name": "Boeing 747-200",
|
||||
"galph": 3830,
|
||||
"category": "Large"
|
||||
},
|
||||
"T38": {
|
||||
"name": "T-38 Talon",
|
||||
"galph": 375,
|
||||
"category": "Fighter"
|
||||
},
|
||||
"WB57": {
|
||||
"name": "Martin B-57 Canberra",
|
||||
"galph": 531,
|
||||
"category": "Twinjet Tactical Bomber and Reconnaissance"
|
||||
},
|
||||
"B74S": {
|
||||
"name": "747 SP",
|
||||
"galph": 2289,
|
||||
"category": "Large"
|
||||
},
|
||||
"B752": {
|
||||
"name": "757 200",
|
||||
"galph": 877,
|
||||
"category": "Large"
|
||||
},
|
||||
"B738": {
|
||||
"name": "737 800",
|
||||
"galph": 832,
|
||||
"category": "Medium"
|
||||
},
|
||||
"B737": {
|
||||
"name": "737 700",
|
||||
"galph": 796,
|
||||
"category": "Medium"
|
||||
"name": "737 700",
|
||||
"galph": 796,
|
||||
"category": "Medium"
|
||||
},
|
||||
"A320": {
|
||||
"name": "A320",
|
||||
"galph": 800,
|
||||
"category": "Medium"
|
||||
"name": "A320",
|
||||
"galph": 800,
|
||||
"category": "Medium"
|
||||
},
|
||||
"P3": {
|
||||
"name": "Lockheed Orion P3",
|
||||
"galph": 671,
|
||||
"category": "Turboprop"
|
||||
"name": "Lockheed Orion P3",
|
||||
"galph": 671,
|
||||
"category": "Turboprop"
|
||||
},
|
||||
"C750": {
|
||||
"name": "Cessna 750 Citation X",
|
||||
"galph": 347,
|
||||
"category": "Small Private Jet"
|
||||
"name": "Cessna 750 Citation X",
|
||||
"galph": 347,
|
||||
"category": "Small Private Jet"
|
||||
},
|
||||
"FA7X": {
|
||||
"name": "Dassult Falcon 7X",
|
||||
"galph": 380,
|
||||
"category": "Small Private Jet"
|
||||
"name": "Dassult Falcon 7X",
|
||||
"galph": 380,
|
||||
"category": "Small Private Jet"
|
||||
},
|
||||
"F900": {
|
||||
"name": "Dassult Falcon 900",
|
||||
"galph": 347,
|
||||
"category": "Small Private Jet"
|
||||
"name": "Dassult Falcon 900",
|
||||
"galph": 347,
|
||||
"category": "Small Private Jet"
|
||||
},
|
||||
"H25B": {
|
||||
"name": "Hawker 750/850",
|
||||
"galph": 270,
|
||||
"category": "Small Private Jet"
|
||||
"name": "Hawker 750/850",
|
||||
"galph": 270,
|
||||
"category": "Small Private Jet"
|
||||
},
|
||||
"C680": {
|
||||
"name": "Cessna 680 Citation",
|
||||
"galph": 247,
|
||||
"category": "Small Private Jet"
|
||||
"name": "Cessna 680 Citation",
|
||||
"galph": 247,
|
||||
"category": "Small Private Jet"
|
||||
},
|
||||
"GLF3": {
|
||||
"name": "Gulfstream 3",
|
||||
"galph": 568,
|
||||
"category": "Heavy Private Jet"
|
||||
},
|
||||
"name": "Gulfstream 3",
|
||||
"galph": 568,
|
||||
"category": "Heavy Private Jet"
|
||||
},
|
||||
"GLF4": {
|
||||
"name": "Gulfstream 4",
|
||||
"galph": 479,
|
||||
"category": "Heavy Private Jet"
|
||||
"name": "Gulfstream 4",
|
||||
"galph": 479,
|
||||
"category": "Heavy Private Jet"
|
||||
},
|
||||
"CL60": {
|
||||
"name": "Bombardier CL-600 Challenge",
|
||||
"galph": 262,
|
||||
"category": "Mid-size Private Jet"
|
||||
"name": "Bombardier CL-600 Challenge",
|
||||
"galph": 262,
|
||||
"category": "Mid-size Private Jet"
|
||||
},
|
||||
"A139": {
|
||||
"name": "Agusta-Bell AW139",
|
||||
"galph": 150,
|
||||
"category": "Medium Utility Helicopter"
|
||||
"name": "Agusta-Bell AW139",
|
||||
"galph": 150,
|
||||
"category": "Medium Utility Helicopter"
|
||||
},
|
||||
"GL5T": {
|
||||
"name": "Global 5000",
|
||||
"galph": 455,
|
||||
"category": "Heavy Private Jet"
|
||||
"name": "Global 5000",
|
||||
"galph": 455,
|
||||
"category": "Heavy Private Jet"
|
||||
},
|
||||
"GA6C": {
|
||||
"name": "Gulfstream G600",
|
||||
"galph": 458,
|
||||
"category": "Heavy Private Jet"
|
||||
"name": "Gulfstream G600",
|
||||
"galph": 458,
|
||||
"category": "Heavy Private Jet"
|
||||
},
|
||||
"A337": {
|
||||
"name": "Airbus Beluga XL",
|
||||
"galph": 1800,
|
||||
"category": "Large Transport Aircraft"
|
||||
"name": "Airbus Beluga XL",
|
||||
"galph": 1800,
|
||||
"category": "Large Transport Aircraft"
|
||||
},
|
||||
"A3ST": {
|
||||
"name": "Airbus Beluga",
|
||||
"galph": 1260,
|
||||
"category": "Large Transport Aircraft"
|
||||
"name": "Airbus Beluga",
|
||||
"galph": 1260,
|
||||
"category": "Large Transport Aircraft"
|
||||
},
|
||||
"F2TH": {
|
||||
"name": "Dassault Falcon 2000",
|
||||
"galph": 245,
|
||||
"category": "Medium Private Jet"
|
||||
"name": "Dassault Falcon 2000",
|
||||
"galph": 245,
|
||||
"category": "Medium Private Jet"
|
||||
},
|
||||
"GA5C": {
|
||||
"name": "Gulfstream G500",
|
||||
"galph": 402,
|
||||
"category": "Large Private Jet"
|
||||
"name": "Gulfstream G500",
|
||||
"galph": 402,
|
||||
"category": "Large Private Jet"
|
||||
},
|
||||
"C130": {
|
||||
"name": "Lockheed C130",
|
||||
"galph": 740,
|
||||
"category": "Medium Cargo"
|
||||
"name": "Lockheed C130",
|
||||
"galph": 740,
|
||||
"category": "Medium Cargo"
|
||||
},
|
||||
"B762": {
|
||||
"name": "Boeing 767 200",
|
||||
"galph": 1722,
|
||||
"category": "Wide-body"
|
||||
"name": "Boeing 767 200",
|
||||
"galph": 1722,
|
||||
"category": "Wide-body"
|
||||
},
|
||||
"B772": {
|
||||
"name": "Boeing 777 200",
|
||||
"galph": 2300,
|
||||
"category": "Wide-body"
|
||||
"name": "Boeing 777 200",
|
||||
"galph": 2300,
|
||||
"category": "Wide-body"
|
||||
},
|
||||
"SLCH": {
|
||||
"name": "Stratolaunch",
|
||||
"galph": 2396,
|
||||
"category": "Special"
|
||||
},
|
||||
"P51": {
|
||||
"name": "P51 Mustang",
|
||||
"galph": 65,
|
||||
"category": "Fighter"
|
||||
},
|
||||
"HDJT": {
|
||||
"name": "Honda Jet",
|
||||
"galph": 90,
|
||||
"category": "Light Jet"
|
||||
},
|
||||
"B744": {
|
||||
"name": "Boeing 747-400",
|
||||
"galph": 3700,
|
||||
"category": "Heavy Airliner"
|
||||
},
|
||||
"E190": {
|
||||
"name": "Embrar E190",
|
||||
"galph": 469,
|
||||
"category": "Heavy Jet"
|
||||
},
|
||||
"FA50": {
|
||||
"name": "Falcon 50",
|
||||
"galph": 229,
|
||||
"category": "Heavy Jet"
|
||||
},
|
||||
"GL7T": {
|
||||
"name": "Global 7000",
|
||||
"galph": 460,
|
||||
"category": "Heavy Jet"
|
||||
},
|
||||
"GL6T": {
|
||||
"name": "",
|
||||
"galph": 455.0,
|
||||
"category": ""
|
||||
},
|
||||
"C68A": {
|
||||
"name": "",
|
||||
"galph": 212.0,
|
||||
"category": ""
|
||||
},
|
||||
"C56X": {
|
||||
"name": "",
|
||||
"galph": 217.0,
|
||||
"category": ""
|
||||
},
|
||||
"B763": {
|
||||
"name": "",
|
||||
"galph": 1320.0,
|
||||
"category": ""
|
||||
},
|
||||
"A310": {
|
||||
"name": "",
|
||||
"galph": 1189.0,
|
||||
"category": ""
|
||||
},
|
||||
"A330": {
|
||||
"name": "",
|
||||
"galph": 1505.0,
|
||||
"category": ""
|
||||
},
|
||||
"A380": {
|
||||
"name": "",
|
||||
"galph": 4062.0,
|
||||
"category": ""
|
||||
},
|
||||
"E170": {
|
||||
"name": "",
|
||||
"galph": 469.0,
|
||||
"category": ""
|
||||
},
|
||||
"DC87": {
|
||||
"name": "",
|
||||
"galph": 1250.0,
|
||||
"category": ""
|
||||
},
|
||||
"SGUP": {
|
||||
"name": "",
|
||||
"galph": 1156.0,
|
||||
"category": ""
|
||||
},
|
||||
"WHK2": {
|
||||
"name": "",
|
||||
"galph": 500.0,
|
||||
"category": ""
|
||||
},
|
||||
"B350": {
|
||||
"name": "",
|
||||
"galph": 122.0,
|
||||
"category": ""
|
||||
},
|
||||
"BE30": {
|
||||
"name": "",
|
||||
"galph": 121.0,
|
||||
"category": ""
|
||||
},
|
||||
"FA8X": {
|
||||
"name": "",
|
||||
"galph": 380.0,
|
||||
"category": ""
|
||||
},
|
||||
"E550": {
|
||||
"name": "",
|
||||
"galph": 280.0,
|
||||
"category": ""
|
||||
},
|
||||
"E55P": {
|
||||
"name": "",
|
||||
"galph": 166.0,
|
||||
"category": ""
|
||||
},
|
||||
"A332": {
|
||||
"name": "",
|
||||
"galph": 1480,
|
||||
"category": ""
|
||||
},
|
||||
"GA7C": {
|
||||
"name": "",
|
||||
"galph": 382.0,
|
||||
"category": ""
|
||||
},
|
||||
"FA6X": {
|
||||
"name": "",
|
||||
"galph": 419.0,
|
||||
"category": ""
|
||||
},
|
||||
"B3XM": {
|
||||
"name": "",
|
||||
"galph": 716.0,
|
||||
"category": ""
|
||||
},
|
||||
"B779": {
|
||||
"name": "",
|
||||
"galph": 2250.0,
|
||||
"category": ""
|
||||
},
|
||||
"BE22": {
|
||||
"name": "",
|
||||
"galph": 60.0,
|
||||
"category": ""
|
||||
},
|
||||
"C560": {
|
||||
"name": "",
|
||||
"galph": 182.0,
|
||||
"category": ""
|
||||
},
|
||||
"E145": {
|
||||
"name": "",
|
||||
"galph": 284.0,
|
||||
"category": ""
|
||||
},
|
||||
"C25C": {
|
||||
"name": "",
|
||||
"galph": 110.0,
|
||||
"category": ""
|
||||
},
|
||||
"C25B": {
|
||||
"name": "",
|
||||
"galph": 110.0,
|
||||
"category": ""
|
||||
},
|
||||
"C441": {
|
||||
"name": "",
|
||||
"galph": 57.0,
|
||||
"category": ""
|
||||
},
|
||||
"E50P": {
|
||||
"name": "",
|
||||
"galph": 109.0,
|
||||
"category": ""
|
||||
},
|
||||
"CRJ2": {
|
||||
"name": "",
|
||||
"galph": 325.0,
|
||||
"category": ""
|
||||
},
|
||||
"CRJ7": {
|
||||
"name": "",
|
||||
"galph": 444.0,
|
||||
"category": ""
|
||||
},
|
||||
"BE40": {
|
||||
"name": "",
|
||||
"galph": 220.0,
|
||||
"category": ""
|
||||
},
|
||||
"C700": {
|
||||
"name": "",
|
||||
"galph": 288.0,
|
||||
"category": ""
|
||||
}
|
||||
|
||||
}
|
|
@ -18,6 +18,9 @@ def calculate_cardinal(d):
|
|||
return card
|
||||
def calculate_deg_change(new_heading, original_heading):
|
||||
"""Calculates change between two headings, returns negative degree if change is left, positive if right"""
|
||||
if new_heading is None:
|
||||
print("Track heading missing. No change")
|
||||
return 0
|
||||
normal = abs(original_heading-new_heading)
|
||||
across_inital = 360 - abs(original_heading-new_heading)
|
||||
if across_inital < normal:
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
#Source to pull data from
|
||||
#SHOULD BE ADSBX which is ADS-B Exchange or OPENS which is OpenSky
|
||||
#By default configured with OpenSky which anyone can use without a login
|
||||
#ADS-B Exchange has better data but is not avalible unless you pay (see: https://www.adsbexchange.com/data/ )
|
||||
SOURCE = OPENS
|
||||
#ADS-B Exchange has better data but is not available unless you feed their network or pay.
|
||||
SOURCE = RpdADSBX
|
||||
#Default amount of time after data loss to trigger a landing when under 10k ft
|
||||
DATA_LOSS_MINS = 5
|
||||
#Failover from one source to the other, only enable if you have both sources setup.
|
||||
|
@ -20,7 +20,7 @@ API_VERSION = 1
|
|||
#ADSBX API Proxy, https://gitlab.com/jjwiseman/adsbx-api-proxy, v2 input, v1 or v2 output from proxy
|
||||
ENABLE_PROXY = FALSE
|
||||
#Full URL http://host:port
|
||||
PROXY_HOST =
|
||||
PROXY_HOST =
|
||||
|
||||
#OpenSky https://opensky-network.org/apidoc/index.html
|
||||
#When using without your own login user and pass should be None
|
||||
|
@ -28,15 +28,25 @@ PROXY_HOST =
|
|||
USERNAME = None
|
||||
PASSWORD = None
|
||||
|
||||
#ADS-B Exchange on RapidAPI https://rapidapi.com/adsbx/api/adsbexchange-com1/
|
||||
[RpdADSBX]
|
||||
API_KEY = none
|
||||
API_VERSION = 2
|
||||
|
||||
#Define the delay interval in seconds between each data request. This is useful if you have limited requests in the API.
|
||||
[SLEEP]
|
||||
SLEEPSEC = 60
|
||||
|
||||
[GOOGLE]
|
||||
#API KEY for Google Static Maps only if you using this on any of the planes.
|
||||
API_KEY = googleapikey
|
||||
|
||||
#Used for failover messages and program exits notifcation
|
||||
#Used for failover messages and program exits notification
|
||||
[DISCORD]
|
||||
ENABLE = FALSE
|
||||
USERNAME = usernamehere
|
||||
URL = webhookurl
|
||||
ROLE_ID =
|
||||
|
||||
[TFRS]
|
||||
URL = http://127.0.0.1:5000/detailed_all
|
||||
|
@ -47,3 +57,8 @@ ENABLE = False
|
|||
ENABLE = False
|
||||
CONSUMER_KEY = ck
|
||||
CONSUMER_SECRET = cs
|
||||
|
||||
[MAP]
|
||||
#Map to create from Google Static Maps or screenshot global tar1090 from globe.theairtraffic.com
|
||||
#Enter GOOGLESTATICMAP or ADSBX
|
||||
OPTION = ADSBX
|
|
@ -9,11 +9,11 @@ ICAO = icaohere
|
|||
; OVERRIDE_TYPELONG =
|
||||
; OVERRIDE_OWNER =
|
||||
; DATA_LOSS_MINS = 20
|
||||
; CONCEAL_AC_ID = True
|
||||
; CONCEAL_PIA = False
|
||||
|
||||
[MAP]
|
||||
#Map to create from Google Static Maps or screenshot global tar1090 from globe.adsbexchange.com
|
||||
#Enter GOOGLESTATICMAP or ADSBX
|
||||
OPTION = ADSBX
|
||||
#Optional, map selection moved to mainconf, this is for map overlays per plane
|
||||
#Tar1090 overlays option, should be seperated by comma no space, remove option all together to disable any
|
||||
OVERLAYS = nexrad
|
||||
|
||||
|
@ -29,12 +29,6 @@ TITLE =
|
|||
ACCESS_TOKEN = athere
|
||||
ACCESS_TOKEN_SECRET = atshere
|
||||
|
||||
[PUSHBULLET]
|
||||
ENABLE = FALSE
|
||||
TITLE = Title Of Pushbullet message
|
||||
API_KEY = apikey
|
||||
CHANNEL_TAG = channeltag
|
||||
|
||||
[DISCORD]
|
||||
ENABLE = FALSE
|
||||
#WEBHOOK URL https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks
|
||||
|
@ -56,3 +50,9 @@ ENABLE = FALSE
|
|||
TITLE = Title Of Telegram message
|
||||
ROOM_ID = -100xxxxxxxxxx
|
||||
BOT_TOKEN = xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
[MASTODON]
|
||||
ENABLE = TRUE
|
||||
ACCESS_TOKEN = mastodonaccesstoken
|
||||
APP_URL = mastodonappurl
|
||||
|
|
@ -30,6 +30,7 @@ def getClosestAirport(latitude, longitude, allowed_types):
|
|||
return closest_airport_dict
|
||||
def get_airport_by_icao(icao):
|
||||
with open('./dependencies/airports.csv', 'r', encoding='utf-8') as airport_csv:
|
||||
matching_airport = None
|
||||
airport_csv_reader = csv.DictReader(filter(lambda row: row[0]!='#', airport_csv))
|
||||
for airport in airport_csv_reader:
|
||||
if airport['gps_code'] == icao:
|
||||
|
@ -37,5 +38,8 @@ def get_airport_by_icao(icao):
|
|||
#Convert indent key to icao key as its labeled icao in other places not ident
|
||||
matching_airport['icao'] = matching_airport.pop('gps_code')
|
||||
break
|
||||
matching_airport = add_airport_region(matching_airport)
|
||||
return matching_airport
|
||||
if matching_airport:
|
||||
matching_airport = add_airport_region(matching_airport)
|
||||
return matching_airport
|
||||
else:
|
||||
return None
|
|
@ -0,0 +1,39 @@
|
|||
def sendMastodon(photo, message, config):
|
||||
from mastodon import Mastodon
|
||||
sent = False
|
||||
retry_c = 0
|
||||
while sent == False:
|
||||
try:
|
||||
bot = Mastodon(
|
||||
access_token=config.get('MASTODON','ACCESS_TOKEN'),
|
||||
api_base_url=config.get('MASTODON','APP_URL')
|
||||
)
|
||||
mediaid = bot.media_post(photo, mime_type="image/jpeg")
|
||||
sent = bot.status_post(message,None,mediaid,False, "Public")
|
||||
except Exception as err:
|
||||
print('err.args:')
|
||||
print(err.args)
|
||||
print(f"Unexpected {err=}, {type(err)=}")
|
||||
print("\nString err:\n"+str(err))
|
||||
if retry_c > 4:
|
||||
print('Mastodon attempts exceeded. Message not sent.')
|
||||
break
|
||||
elif str(err) == 'Unauthorized':
|
||||
print('Invalid Mastodon bot token, message not sent.')
|
||||
break
|
||||
elif str(err) == 'Timed out':
|
||||
retry_c += 1
|
||||
print('Mastodon timeout count: '+str(retry_c))
|
||||
pass
|
||||
elif str(err)[:35] == '[Errno 2] No such file or directory':
|
||||
print('Mastodon module couldn\'t find an image to send.')
|
||||
break
|
||||
elif str(err) == 'Media_caption_too_long':
|
||||
print('Mastodon image caption lenght exceeds 1024 characters. Message not send.')
|
||||
break
|
||||
else:
|
||||
print('[X] Unknown error. Message not sent.')
|
||||
break
|
||||
else:
|
||||
print("Mastodon message successfully sent.")
|
||||
return sent
|
|
@ -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():
|
||||
|
|
|
@ -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
|
71
defSS.py
71
defSS.py
|
@ -6,31 +6,41 @@ import time
|
|||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
|
||||
def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_track_labels=False, overrides={}):
|
||||
def blur_elements_by_id(browser, element_ids):
|
||||
for element in element_ids:
|
||||
try:
|
||||
element = browser.find_element(By.ID, element)
|
||||
browser.execute_script("arguments[0].style.filter = 'blur(7px)';", element)
|
||||
except NoSuchElementException:
|
||||
print("Issue finding:", element, "on page")
|
||||
def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_track_labels=False, overrides={}, conceal_ac_id=False, conceal_pia=False):
|
||||
import os
|
||||
import platform
|
||||
chrome_options = webdriver.ChromeOptions()
|
||||
chrome_options.headless = True
|
||||
chrome_options.add_argument('window-size=800,800')
|
||||
chrome_options.add_argument('ignore-certificate-errors')
|
||||
#Plane images issue loading when in headless setting agent fixes.
|
||||
chrome_options.add_experimental_option('excludeSwitches', ['enable-logging'])
|
||||
if platform.system() == "Linux":
|
||||
chrome_options.add_argument('crash-dumps-dir=/tmp/plane-notify/chrome')
|
||||
|
||||
#Plane images issue loading when in headless setting agent fixes.
|
||||
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36")
|
||||
import os
|
||||
import platform
|
||||
if platform.system() == "Linux" and os.geteuid()==0:
|
||||
chrome_options.add_argument('--no-sandbox') # required when running as root user. otherwise you would get no sandbox errors.
|
||||
browser = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
|
||||
url = f"https://globe.adsbexchange.com/?{url_params}"
|
||||
print(url)
|
||||
url = f"https://globe.theairtraffic.com/?{url_params}"
|
||||
print(f"Getting Screenshot of {url}")
|
||||
browser.set_page_load_timeout(80)
|
||||
browser.get(url)
|
||||
WebDriverWait(browser, 40).until(lambda d: d.execute_script("return jQuery.active == 0"))
|
||||
remove_id_elements = ["show_trace", "credits", 'infoblock_close', 'selected_photo_link', "history_collapse"]
|
||||
for element in remove_id_elements:
|
||||
try:
|
||||
element = browser.find_element_by_id(element)
|
||||
element = browser.find_element(By.ID, element)
|
||||
browser.execute_script("""var element = arguments[0]; element.parentNode.removeChild(element); """, element)
|
||||
except:
|
||||
print("issue removing", element, "from map")
|
||||
print("Issue finding:", element, "on page")
|
||||
#Remove watermark on data
|
||||
try:
|
||||
browser.execute_script("document.getElementById('selected_infoblock').className = 'none';")
|
||||
|
@ -43,41 +53,45 @@ def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_trac
|
|||
print("Couldn't disable sidebar on map")
|
||||
#Remove Google Ads
|
||||
try:
|
||||
element = browser.find_element_by_xpath("//*[contains(@id, 'FIOnDemandWrapper_')]")
|
||||
element = browser.find_element(By.XPATH, "//*[contains(@id, 'FIOnDemandWrapper_')]")
|
||||
browser.execute_script("""var element = arguments[0]; element.parentNode.removeChild(element); """, element)
|
||||
except:
|
||||
print("Couldn't remove Google Ads")
|
||||
#Remove share
|
||||
# try:
|
||||
# element = browser.find_element_by_xpath("//*[contains(text(), 'Copy Link')]")
|
||||
# browser.execute_script("""var element = arguments[0]; element.parentNode.removeChild(element); """, element)
|
||||
# except Exception as e:
|
||||
# print("Couldn't remove share button from map", e)
|
||||
#Remove Copy Link
|
||||
try:
|
||||
element = browser.find_element(By.XPATH, "//*[@id='selected_icao']/span[2]/a")
|
||||
browser.execute_script("""var element = arguments[0]; element.parentNode.removeChild(element); """, element)
|
||||
except Exception as e:
|
||||
print("Couldn't remove copy link button from map", e)
|
||||
#browser.execute_script("toggleFollow()")
|
||||
if conceal_pia or conceal_ac_id:
|
||||
blur_elements_by_id(browser, ["selected_callsign", "selected_icao", "selected_squawk1"])
|
||||
if conceal_ac_id:
|
||||
blur_elements_by_id(browser, ["selected_registration", "selected_country", "selected_dbFlags", "selected_ownop", "selected_typelong", "selected_icaotype", "airplanePhoto", "silhouette", "copyrightInfo"])
|
||||
if enable_labels:
|
||||
browser.find_element_by_tag_name('body').send_keys('l')
|
||||
browser.find_element(By.TAG_NAME, 'body').send_keys('l')
|
||||
if enable_track_labels:
|
||||
browser.find_element_by_tag_name('body').send_keys('k')
|
||||
browser.find_element(By.TAG_NAME, 'body').send_keys('k')
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
time.sleep(15)
|
||||
|
||||
if 'reg' in overrides.keys():
|
||||
element = browser.find_element_by_id("selected_registration")
|
||||
element = browser.find_element(By.ID, "selected_registration")
|
||||
browser.execute_script(f"arguments[0].innerText = '* {overrides['reg']}'", element)
|
||||
reg = overrides['reg']
|
||||
else:
|
||||
try:
|
||||
reg = browser.find_element_by_id("selected_registration").get_attribute("innerHTML")
|
||||
try:
|
||||
reg = browser.find_element(By.ID, "selected_registration").get_attribute("innerHTML")
|
||||
print("Reg from tar1090 is", reg)
|
||||
except Exception as e:
|
||||
print("Couldn't find reg in tar1090", e)
|
||||
reg = None
|
||||
reg = None
|
||||
if reg is not None:
|
||||
try:
|
||||
try:
|
||||
photo_box = browser.find_element_by_id("silhouette")
|
||||
photo_box = browser.find_element(By.ID, "silhouette")
|
||||
except NoSuchElementException:
|
||||
photo_box = browser.find_element_by_id("airplanePhoto")
|
||||
photo_box = browser.find_element(By.ID, "airplanePhoto")
|
||||
finally:
|
||||
import requests, json
|
||||
photo_list = json.loads(requests.get("https://raw.githubusercontent.com/Jxck-S/aircraft-photos/main/photo-list.json", timeout=20).text)
|
||||
|
@ -87,7 +101,7 @@ def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_trac
|
|||
browser.execute_script("arguments[0].style.width = '200px';", photo_box)
|
||||
browser.execute_script("arguments[0].style.float = 'left';", photo_box)
|
||||
browser.execute_script(f"arguments[0].src = 'https://raw.githubusercontent.com/Jxck-S/aircraft-photos/main/images/{reg}.jpg';", photo_box)
|
||||
image_copy_right = browser.find_element_by_id("copyrightInfo")
|
||||
image_copy_right = browser.find_element(By.ID, "copyrightInfo")
|
||||
browser.execute_cdp_cmd('Emulation.setScriptExecutionDisabled', {'value': True})
|
||||
copy_right_children = image_copy_right.find_elements(By.XPATH, "*")
|
||||
if len(copy_right_children) > 0:
|
||||
|
@ -97,16 +111,17 @@ def get_adsbx_screenshot(file_path, url_params, enable_labels=False, enable_trac
|
|||
except Exception as e:
|
||||
print("Error on changing photo", e)
|
||||
if 'type' in overrides.keys():
|
||||
element = browser.find_element_by_id("selected_icaotype")
|
||||
element = browser.find_element(By.ID, "selected_icaotype")
|
||||
browser.execute_script(f"arguments[0].innerText = '* {overrides['type']}'", element)
|
||||
if 'typelong' in overrides.keys():
|
||||
element = browser.find_element_by_id("selected_typelong")
|
||||
element = browser.find_element(By.ID, "selected_typelong")
|
||||
browser.execute_script(f"arguments[0].innerText = '* {overrides['typelong']}'", element)
|
||||
if 'ownop' in overrides.keys():
|
||||
element = browser.find_element_by_id("selected_ownop")
|
||||
element = browser.find_element(By.ID, "selected_ownop")
|
||||
browser.execute_script(f"arguments[0].innerText = '* {overrides['ownop']}'", element)
|
||||
time.sleep(5)
|
||||
browser.save_screenshot(file_path)
|
||||
browser.quit()
|
||||
def generate_adsbx_screenshot_time_params(timestamp):
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
|
|
@ -1,4 +1,17 @@
|
|||
def sendTeleg(photo, message, config):
|
||||
try:
|
||||
from telegram import __version_info__
|
||||
except ImportError:
|
||||
__version_info__ = (0, 0, 0, 0, 0)
|
||||
if __version_info__ < (20, 0, 0, "alpha", 5):
|
||||
sent = sendTelegOld(photo, message, config)
|
||||
return sent
|
||||
else:
|
||||
import asyncio
|
||||
sent = asyncio.run(t_send_photo(photo,message,config))
|
||||
return sent
|
||||
|
||||
def sendTelegOld(photo, message, config):
|
||||
import telegram
|
||||
sent = False
|
||||
retry_c = 0
|
||||
|
@ -25,14 +38,53 @@ def sendTeleg(photo, message, config):
|
|||
print('Invalid Telegram Chat ID, message not sent.')
|
||||
break
|
||||
elif str(err)[:35] == '[Errno 2] No such file or directory':
|
||||
print('Telegram module couldn\'t find an image to send.')
|
||||
print('Telegram module couldn\'t find an image to sent.')
|
||||
break
|
||||
elif str(err) == 'Media_caption_too_long':
|
||||
print('Telegram image caption lenght exceeds 1024 characters. Message not send.')
|
||||
print('Telegram image caption length exceeds 1024 characters. Message not sent.')
|
||||
break
|
||||
else:
|
||||
print('[X] Unknown Telegram error. Message not sent.')
|
||||
break
|
||||
else:
|
||||
print("Telegram message successfully sent.")
|
||||
return sent
|
||||
return sent
|
||||
|
||||
async def t_send_photo(photo,message,config):
|
||||
import telegram
|
||||
sent = False
|
||||
retry_c = 0
|
||||
while sent == False:
|
||||
try:
|
||||
bot = telegram.Bot(token=config.get('TELEGRAM', 'BOT_TOKEN'))
|
||||
sent = await bot.send_photo(chat_id=config.get('TELEGRAM', 'ROOM_ID'), photo=photo, caption=message)
|
||||
except Exception as err:
|
||||
print('err.args:')
|
||||
print(err.args)
|
||||
print(f"Unexpected {err=}, {type(err)=}")
|
||||
print("\nString err:\n"+str(err))
|
||||
if retry_c > 4:
|
||||
print('Telegram attempts exceeded. Message not sent.')
|
||||
break
|
||||
elif str(err) == 'Unauthorized':
|
||||
print('Invalid Telegram bot token, message not sent.')
|
||||
break
|
||||
elif str(err) == 'Timed out':
|
||||
retry_c += 1
|
||||
print('Telegram timeout count: '+str(retry_c))
|
||||
pass
|
||||
elif str(err) == 'Chat not found':
|
||||
print('Invalid Telegram Chat ID, message not sent.')
|
||||
break
|
||||
elif str(err)[:35] == '[Errno 2] No such file or directory':
|
||||
print('Telegram module couldn\'t find an image to send.')
|
||||
break
|
||||
elif str(err) == 'Media_caption_too_long':
|
||||
print('Telegram image caption length exceeds 1024 characters. Message not sent.')
|
||||
break
|
||||
else:
|
||||
print('[X] Unknown Telegram error. Message not sent.')
|
||||
break
|
||||
else:
|
||||
print("Telegram message successfully sent.")
|
||||
return sent
|
||||
|
|
|
@ -2,6 +2,7 @@ version: "3.9"
|
|||
services:
|
||||
plane-notify:
|
||||
platform: linux/amd64
|
||||
shm_size: 2gb
|
||||
build:
|
||||
context: .
|
||||
volumes:
|
||||
|
|
15
fuel_calc.py
15
fuel_calc.py
|
@ -14,25 +14,24 @@ def get_avg_fuel_price():
|
|||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
|
||||
def fuel_calculation(aircraft_icao_type, minutes):
|
||||
"""Calculates fuel usage, price, c02 output of a flight depending on aircraft type and flight length"""
|
||||
with open("aircraft_type_fuel_consumption_rates.json", "r") as f:
|
||||
fuellist = json.loads(f.read())
|
||||
#avg_fuel_price_per_gallon = 5.08
|
||||
fuel_flight_info = {}
|
||||
if aircraft_icao_type in fuellist.keys():
|
||||
avg_fuel_price_per_gallon = get_avg_fuel_price()
|
||||
galph = fuellist[aircraft_icao_type]["galph"]
|
||||
fuel_used_gal = round(galph * (minutes/60), 2)
|
||||
fuel_used_gal = galph * (minutes/60)
|
||||
fuel_flight_info["fuel_price"] = round(fuel_used_gal * avg_fuel_price_per_gallon)
|
||||
fuel_used_kg = fuel_used_gal * 3.04
|
||||
c02_tons = round((fuel_used_kg * 3.15 ) / 907.185)
|
||||
c02_tons = (fuel_used_kg * 3.15 ) / 907.185
|
||||
fuel_flight_info['fuel_used_kg'] = round(fuel_used_kg)
|
||||
fuel_flight_info["fuel_used_gal"] = round(fuel_used_gal)
|
||||
fuel_flight_info['fuel_used_lters'] = round(fuel_used_gal*3.78541)
|
||||
fuel_flight_info["fuel_used_lbs"] = round(fuel_used_kg * 2.20462)
|
||||
fuel_flight_info["c02_tons"] = c02_tons
|
||||
fuel_flight_info["c02_tons"] = round(c02_tons) if c02_tons > 1 else round(c02_tons, 4)
|
||||
print ("Fuel info", fuel_flight_info)
|
||||
return fuel_flight_info
|
||||
else:
|
||||
|
@ -43,10 +42,8 @@ def fuel_message(fuel_info):
|
|||
cost = "{:,}".format(fuel_info['fuel_price'])
|
||||
gallons = "{:,}".format(fuel_info['fuel_used_gal'])
|
||||
lters = "{:,}".format(fuel_info['fuel_used_lters'])
|
||||
lbs = "{:, }".format(fuel_info['fuel_used_lbs'])
|
||||
lbs = "{:,}".format(fuel_info['fuel_used_lbs'])
|
||||
kgs = "{:,}".format(fuel_info['fuel_used_kg'])
|
||||
fuel_message = f"~ {gallons} gallons ({lters} liters). \n~ {lbs} lbs ({kgs} kg) of jet fuel used. \n~ ${cost} cost of fuel. \n~ {fuel_info['c02_tons']} tons of CO2 emissions."
|
||||
fuel_message = f"\n~ {gallons} gallons ({lters} liters). \n~ {lbs} lbs ({kgs} kg) of jet fuel used. \n~ ${cost} cost of fuel. \n~ {fuel_info['c02_tons']} tons of CO2 emissions."
|
||||
print(fuel_message)
|
||||
return fuel_message
|
||||
#fuel_info = fuel_calculation("GLF6", 548.1)
|
||||
#fuel_message(fuel_info)
|
||||
|
|
|
@ -26,11 +26,6 @@ def append_airport(filename, airport, text_credit=None):
|
|||
draw.rectangle(((325, 760), (624, 800)), fill= white, outline=black)
|
||||
#Header Box
|
||||
draw.rectangle(((401, 738), (549, 760)), fill= navish)
|
||||
#ADSBX Logo
|
||||
#
|
||||
# adsbx = Image.open("./dependencies/ADSBX_Logo.png")
|
||||
# adsbx = adsbx.resize((25, 25), Image.ANTIALIAS)
|
||||
# image.paste(adsbx, (632, 757), adsbx)
|
||||
#Create Text
|
||||
#ADSBX Credit
|
||||
if text_credit is not None:
|
||||
|
|
130
planeClass.py
130
planeClass.py
|
@ -8,6 +8,7 @@ class Plane:
|
|||
self.icao = icao.upper()
|
||||
self.callsign = None
|
||||
self.config = config
|
||||
self.config_path = config_path
|
||||
self.overrides = {}
|
||||
if self.config.has_option('DATA', 'OVERRIDE_REG'):
|
||||
self.reg = self.config.get('DATA', 'OVERRIDE_REG')
|
||||
|
@ -23,6 +24,14 @@ class Plane:
|
|||
self.overrides['typelong'] = self.config.get('DATA', 'OVERRIDE_TYPELONG')
|
||||
if self.config.has_option('DATA', 'OVERRIDE_OWNER'):
|
||||
self.overrides['ownop'] = self.config.get('DATA', 'OVERRIDE_OWNER')
|
||||
if self.config.has_option('DATA', 'CONCEAL_AC_ID'):
|
||||
self.conceal_ac_id = self.config.getboolean('DATA', 'CONCEAL_AC_ID')
|
||||
else:
|
||||
self.conceal_ac_id = False
|
||||
if self.config.has_option('DATA', 'CONCEAL_PIA'):
|
||||
self.conceal_pia = self.config.getboolean('DATA', 'CONCEAL_PIA')
|
||||
else:
|
||||
self.conceal_pia = False
|
||||
self.conf_file_path = config_path
|
||||
self.alt_ft = None
|
||||
self.below_desired_ft = None
|
||||
|
@ -55,6 +64,7 @@ class Plane:
|
|||
self.track = None
|
||||
self.last_track = None
|
||||
self.circle_history = None
|
||||
self.nearest_from_airport = None
|
||||
if self.config.has_option('DATA', 'DATA_LOSS_MINS'):
|
||||
self.data_loss_mins = self.config.getint('DATA', 'DATA_LOSS_MINS')
|
||||
else:
|
||||
|
@ -69,18 +79,20 @@ class Plane:
|
|||
self.latest_tweet_id = self.tweet_api.user_timeline(count = 1)[0]
|
||||
except IndexError:
|
||||
self.latest_tweet_id = None
|
||||
#Setup PushBullet
|
||||
if self.config.getboolean('PUSHBULLET', 'ENABLE'):
|
||||
from pushbullet import Pushbullet
|
||||
self.pb = Pushbullet(self.config['PUSHBULLET']['API_KEY'])
|
||||
self.pb_channel = self.pb.get_channel(self.config.get('PUSHBULLET', 'CHANNEL_TAG'))
|
||||
def run_opens(self, ac_dict):
|
||||
#Parse OpenSky Vector
|
||||
from colorama import Fore, Back, Style
|
||||
self.print_header("BEGIN")
|
||||
#print (Fore.YELLOW + "OpenSky Sourced Data: ", ac_dict)
|
||||
try:
|
||||
self.__dict__.update({'icao' : ac_dict.icao24.upper(), 'callsign' : ac_dict.callsign, 'latitude' : ac_dict.latitude, 'longitude' : ac_dict.longitude, 'on_ground' : bool(ac_dict.on_ground), 'squawk' : ac_dict.squawk, 'track' : float(ac_dict.heading)})
|
||||
self.__dict__.update({
|
||||
'icao' : ac_dict.icao24.upper(),
|
||||
'callsign' : ac_dict.callsign,
|
||||
'latitude' : ac_dict.latitude,
|
||||
'longitude' : ac_dict.longitude,
|
||||
'on_ground' : bool(ac_dict.on_ground),
|
||||
'squawk' : ac_dict.squawk,
|
||||
'track' : float(ac_dict.true_track)})
|
||||
if ac_dict.baro_altitude != None:
|
||||
self.alt_ft = round(float(ac_dict.baro_altitude) * 3.281)
|
||||
elif self.on_ground:
|
||||
|
@ -88,7 +100,8 @@ class Plane:
|
|||
from mictronics_parse import get_aircraft_reg_by_icao, get_type_code_by_icao
|
||||
self.reg = get_aircraft_reg_by_icao(self.icao)
|
||||
self.type = get_type_code_by_icao(self.icao)
|
||||
self.last_pos_datetime = datetime.fromtimestamp(ac_dict.time_position)
|
||||
if ac_dict.time_position is not None:
|
||||
self.last_pos_datetime = datetime.fromtimestamp(ac_dict.time_position)
|
||||
except ValueError as e:
|
||||
print("Got data but some data is invalid!")
|
||||
print(e)
|
||||
|
@ -221,8 +234,11 @@ class Plane:
|
|||
def route_format(extra_route_info, type):
|
||||
from defAirport import get_airport_by_icao
|
||||
to_airport = get_airport_by_icao(self.known_to_airport)
|
||||
code = to_airport['iata_code'] if to_airport['iata_code'] != "" else to_airport['icao']
|
||||
airport_text = f"{code}, {to_airport['name']}"
|
||||
if to_airport:
|
||||
code = to_airport['iata_code'] if to_airport['iata_code'] != "" else to_airport['icao']
|
||||
airport_text = f"{code}, {to_airport['name']}"
|
||||
else:
|
||||
airport_text = f"{self.known_to_airport}"
|
||||
if 'time_to' in extra_route_info.keys() and type != "divert":
|
||||
arrival_rel = "in ~" + extra_route_info['time_to']
|
||||
else:
|
||||
|
@ -234,7 +250,10 @@ class Plane:
|
|||
header = "Now going to"
|
||||
elif type == "divert":
|
||||
header = "Now diverting to"
|
||||
area = f"{to_airport['municipality']}, {to_airport['region']}, {to_airport['iso_country']}"
|
||||
if to_airport:
|
||||
area = f"{to_airport['municipality']}, {to_airport['region']}, {to_airport['iso_country']}"
|
||||
else:
|
||||
area = ""
|
||||
route_to = f"{header} {area} ({airport_text})" + (f" arriving {arrival_rel}" if arrival_rel is not None else "")
|
||||
else:
|
||||
if type == "inital":
|
||||
|
@ -388,7 +407,7 @@ class Plane:
|
|||
else:
|
||||
self.dis_title = self.config.get('DISCORD', 'TITLE')
|
||||
#Set Twitter Title
|
||||
if self.config.getboolean('TWITTER', 'ENABLE'):
|
||||
if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'):
|
||||
if self.config.get('TWITTER', 'TITLE') in ["DYNAMIC", "callsign"]:
|
||||
self.twitter_title = dynamic_title
|
||||
else:
|
||||
|
@ -423,12 +442,8 @@ class Plane:
|
|||
elif self.landed:
|
||||
landed_time_msg = None
|
||||
landed_time = None
|
||||
if self.icao != "A835AF":
|
||||
message = (f"{type_header} {location_string}.") + ("" if route_to is None else f" {route_to}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "")
|
||||
dirty_message = None
|
||||
else:
|
||||
message = (f"{type_header} {location_string}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "")
|
||||
dirty_message = (f"{type_header} {location_string}.") + ("" if route_to is None else f" {route_to}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "")
|
||||
|
||||
message = (f"{type_header} {location_string}.") + ("" if route_to is None else f" {route_to}.") + ((f" {landed_time_msg}") if landed_time_msg != None else "")
|
||||
print (message)
|
||||
#Google Map or tar1090 screenshot
|
||||
if Plane.main_config.get('MAP', 'OPTION') == "GOOGLESTATICMAP":
|
||||
|
@ -436,8 +451,8 @@ class Plane:
|
|||
getMap((municipality + ", " + state + ", " + country_code), self.map_file_name)
|
||||
elif Plane.main_config.get('MAP', 'OPTION') == "ADSBX":
|
||||
from defSS import get_adsbx_screenshot
|
||||
url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}"
|
||||
get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides)
|
||||
url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}&limitupdates=0"
|
||||
get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides, conceal_ac_id=self.conceal_ac_id, conceal_pia=self.conceal_pia)
|
||||
from modify_image import append_airport
|
||||
text_credit = self.config.get('MAP', 'TEXT_CREDIT') if self.config.has_option('MAP', 'TEXT_CREDIT') else None
|
||||
append_airport(self.map_file_name, nearest_airport_dict, text_credit)
|
||||
|
@ -448,19 +463,17 @@ class Plane:
|
|||
from defTelegram import sendTeleg
|
||||
photo = open(self.map_file_name, "rb")
|
||||
sendTeleg(photo, message, self.config)
|
||||
#Mastodon
|
||||
if self.config.has_section('MASTODON') and self.config.getboolean('MASTODON', 'ENABLE'):
|
||||
from defMastodon import sendMastodon
|
||||
sendMastodon(self.map_file_name, message, self.config)
|
||||
|
||||
#Discord
|
||||
if self.config.getboolean('DISCORD', 'ENABLE'):
|
||||
dis_message = f"{self.dis_title} {message}".strip() if dirty_message is None else f"{self.dis_title} {dirty_message}".strip()
|
||||
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None
|
||||
sendDis(dis_message, self.config, role_id, self.map_file_name)
|
||||
#PushBullet
|
||||
if self.config.getboolean('PUSHBULLET', 'ENABLE'):
|
||||
with open(self.map_file_name, "rb") as pic:
|
||||
map_data = self.pb.upload_file(pic, "Tookoff IMG" if self.tookoff else "Landed IMG")
|
||||
self.pb_channel.push_note(self.config.get('PUSHBULLET', 'TITLE'), message)
|
||||
self.pb_channel.push_file(**map_data)
|
||||
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None
|
||||
sendDis(message, self.config, role_id, self.map_file_name)
|
||||
#Twitter
|
||||
if self.config.getboolean('TWITTER', 'ENABLE'):
|
||||
if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'):
|
||||
import tweepy
|
||||
try:
|
||||
twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name)
|
||||
|
@ -468,21 +481,20 @@ class Plane:
|
|||
self.tweet_api.create_media_metadata(media_id= twitter_media_map_obj.media_id, alt_text= alt_text)
|
||||
self.latest_tweet_id = self.tweet_api.update_status(status = ((self.twitter_title + " " + message).strip()), media_ids=[twitter_media_map_obj.media_id]).id
|
||||
except tweepy.errors.TweepyException as e:
|
||||
print(e)
|
||||
raise Exception(self.icao) from e
|
||||
raise
|
||||
#Meta
|
||||
if self.config.has_option('META', 'ENABLE') and self.config.getboolean('META', 'ENABLE'):
|
||||
from meta_toolkit import post_to_meta_both
|
||||
post_to_meta_both(self.config.get("META", "FB_PAGE_ID"), self.config.get("META", "IG_USER_ID"), self.map_file_name, message, self.config.get("META", "ACCESS_TOKEN"))
|
||||
os.remove(self.map_file_name)
|
||||
if self.landed:
|
||||
if self.known_to_airport is not None and self.nearest_from_airport is not None and self.known_to_airport != self.nearest_from_airport:
|
||||
if nearest_airport_dict is not None and self.nearest_from_airport is not None and nearest_airport_dict['icao'] != self.nearest_from_airport:
|
||||
from defAirport import get_airport_by_icao
|
||||
from geopy.distance import geodesic
|
||||
known_to_airport = get_airport_by_icao(self.known_to_airport)
|
||||
landed_airport = nearest_airport_dict
|
||||
nearest_from_airport = get_airport_by_icao(self.nearest_from_airport)
|
||||
from_coord = (nearest_from_airport['latitude_deg'], nearest_from_airport['longitude_deg'])
|
||||
to_coord = (known_to_airport['latitude_deg'], known_to_airport['longitude_deg'])
|
||||
to_coord = (landed_airport['latitude_deg'], landed_airport['longitude_deg'])
|
||||
distance_mi = float(geodesic(from_coord, to_coord).mi)
|
||||
distance_nm = distance_mi / 1.150779448
|
||||
distance_message = f"{'{:,}'.format(round(distance_mi))} mile ({'{:,}'.format(round(distance_nm))} NM) flight from {nearest_from_airport['iata_code'] if nearest_from_airport['iata_code'] != '' else nearest_from_airport['ident']} to {nearest_airport_dict['iata_code'] if nearest_airport_dict['iata_code'] != '' else nearest_airport_dict['ident']}\n"
|
||||
|
@ -497,14 +509,13 @@ class Plane:
|
|||
fuel_message = fuel_message(fuel_info)
|
||||
if self.config.getboolean('DISCORD', 'ENABLE'):
|
||||
dis_message = f"{self.dis_title} {distance_message} \nFlight Fuel Info ```{fuel_message}```".strip()
|
||||
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None
|
||||
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None
|
||||
sendDis(dis_message, self.config, role_id)
|
||||
if self.config.getboolean('TWITTER', 'ENABLE'):
|
||||
if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'):
|
||||
try:
|
||||
self.latest_tweet_id = self.tweet_api.update_status(status = ((self.twitter_title + " " + distance_message + " " + fuel_message).strip()), in_reply_to_status_id = self.latest_tweet_id).id
|
||||
except tweepy.errors.TweepyException as e:
|
||||
print(e)
|
||||
raise Exception(self.icao) from e
|
||||
raise
|
||||
self.latest_tweet_id = None
|
||||
self.recheck_route_time = None
|
||||
self.known_to_airport = None
|
||||
|
@ -524,11 +535,10 @@ class Plane:
|
|||
#Discord
|
||||
if self.config.getboolean('DISCORD', 'ENABLE'):
|
||||
dis_message = f"{self.dis_title} {route_to}".strip()
|
||||
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None
|
||||
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None
|
||||
sendDis(dis_message, self.config, role_id)
|
||||
#Twitter
|
||||
if self.config.getboolean('TWITTER', 'ENABLE') and self.icao == 'A835AF':
|
||||
#tweet = self.tweet_api.user_timeline(count = 1)[0]
|
||||
if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'):
|
||||
self.latest_tweet_id = self.tweet_api.update_status(status = f"{self.twitter_title} {route_to}".strip(), in_reply_to_status_id = self.latest_tweet_id).id
|
||||
|
||||
if self.circle_history is not None:
|
||||
|
@ -558,8 +568,8 @@ class Plane:
|
|||
getMap((municipality + ", " + state + ", " + country_code), self.map_file_name)
|
||||
if Plane.main_config.get('MAP', 'OPTION') == "ADSBX":
|
||||
from defSS import get_adsbx_screenshot
|
||||
url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}"
|
||||
get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides)
|
||||
url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}&limitupdates=0"
|
||||
get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides, conceal_ac_id=self.conceal_ac_id, conceal_pia=self.conceal_pia)
|
||||
if self.config.getboolean('DISCORD', 'ENABLE'):
|
||||
dis_message = (self.dis_title + " " + squawk_message)
|
||||
sendDis(dis_message, self.config, None, self.map_file_name)
|
||||
|
@ -581,8 +591,8 @@ class Plane:
|
|||
dis_message = (self.dis_title + " " + mode + " mode enabled.")
|
||||
if mode == "Approach":
|
||||
from defSS import get_adsbx_screenshot
|
||||
url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}"
|
||||
get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides)
|
||||
url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}&limitupdates=0"
|
||||
get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides, conceal_ac_id=self.conceal_ac_id, conceal_pia=self.conceal_pia)
|
||||
sendDis(dis_message, self.config, None, self.map_file_name)
|
||||
#elif mode in ["Althold", "VNAV", "LNAV"] and self.sel_nav_alt != None:
|
||||
# sendDis((dis_message + ", Sel Alt. " + str(self.sel_nav_alt) + ", Current Alt. " + str(self.alt_ft)), self.config)
|
||||
|
@ -608,7 +618,8 @@ class Plane:
|
|||
from calculate_headings import calculate_deg_change
|
||||
track_change = calculate_deg_change(self.track, self.last_track)
|
||||
track_change = round(track_change, 3)
|
||||
self.circle_history["traces"].append((time.time(), self.latitude, self.longitude, track_change))
|
||||
if self.latitude is not None and self.longitude is not None:
|
||||
self.circle_history["traces"].append((time.time(), self.latitude, self.longitude, track_change))
|
||||
|
||||
total_change = 0
|
||||
coords = []
|
||||
|
@ -648,7 +659,7 @@ class Plane:
|
|||
in_tfr = None
|
||||
if Plane.main_config.getboolean("TFRS", "ENABLE"):
|
||||
tfr_url = Plane.main_config.get("TFRS", "URL")
|
||||
response = requests.get(tfr_url, timeout=30)
|
||||
response = requests.get(tfr_url, timeout=60)
|
||||
tfrs = json.loads(response.text)
|
||||
for tfr in tfrs:
|
||||
if in_tfr is not None:
|
||||
|
@ -764,9 +775,8 @@ class Plane:
|
|||
return tfr_map_filename
|
||||
|
||||
from defSS import get_adsbx_screenshot
|
||||
|
||||
url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}"
|
||||
get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides)
|
||||
url_params = f"largeMode=2&hideButtons&hideSidebar&mapDim=0&zoom=10&icao={self.icao}&overlays={self.get_adsbx_map_overlays()}&limitupdates=0"
|
||||
get_adsbx_screenshot(self.map_file_name, url_params, overrides=self.overrides, conceal_ac_id=self.conceal_ac_id, conceal_pia=self.conceal_pia)
|
||||
if nearest_airport_dict['distance_mi'] < 3:
|
||||
if "touchngo" in self.circle_history.keys():
|
||||
message = f"Doing touch and goes at {nearest_airport_dict['icao']}"
|
||||
|
@ -776,8 +786,8 @@ class Plane:
|
|||
message = f"Circling {round(nearest_airport_dict['distance_mi'], 2)}mi {cardinal} of {nearest_airport_dict['icao']}, {nearest_airport_dict['name']} at {self.alt_ft}ft. "
|
||||
tfr_map_filename = None
|
||||
if in_tfr is not None:
|
||||
context = "Inside" if 'context' not in in_tfr.keys() else "Above" if in_tfr['context'] == 'above' else "Below"
|
||||
message += f" {context} TFR {in_tfr['info']['NOTAM']}, a TFR for {in_tfr['info']['Type'].title()}"
|
||||
wording_context = "Inside" if 'context' not in in_tfr.keys() else "Above" if in_tfr['context'] == 'above' else "Below"
|
||||
message += f" {wording_context} TFR {in_tfr['info']['NOTAM']}, a TFR for {in_tfr['info']['Type'].title()}"
|
||||
tfr_map_filename = tfr_image(context, (self.latitude, self.longitude))
|
||||
elif in_tfr is None and closest_tfr is not None and "distance" in closest_tfr.keys() and closest_tfr["distance"] <= 20:
|
||||
message += f" {closest_tfr['distance']} miles from TFR {closest_tfr['info']['NOTAM']}, a TFR for {closest_tfr['info']['Type']}"
|
||||
|
@ -793,12 +803,12 @@ class Plane:
|
|||
from defTelegram import sendTeleg
|
||||
sendTeleg(photo, message, self.config)
|
||||
if self.config.getboolean('DISCORD', 'ENABLE'):
|
||||
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None
|
||||
if tfr_map_filename is not None:
|
||||
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None
|
||||
if tfr_map_filename is not None:
|
||||
sendDis(message, self.config, role_id, self.map_file_name, tfr_map_filename)
|
||||
elif tfr_map_filename is None:
|
||||
sendDis(message, self.config, role_id, self.map_file_name)
|
||||
if self.config.getboolean('TWITTER', 'ENABLE'):
|
||||
if self.config.getboolean('TWITTER', 'ENABLE') and Plane.main_config.getboolean('TWITTER', 'ENABLE'):
|
||||
twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name)
|
||||
media_ids = [twitter_media_map_obj.media_id]
|
||||
if tfr_map_filename is not None:
|
||||
|
@ -812,6 +822,10 @@ class Plane:
|
|||
if self.config.has_option('META', 'ENABLE') and self.config.getboolean('META', 'ENABLE'):
|
||||
from meta_toolkit import post_to_meta_both
|
||||
post_to_meta_both(self.config.get("META", "FB_PAGE_ID"), self.config.get("META", "IG_USER_ID"), self.map_file_name, message, self.config.get("META", "ACCESS_TOKEN"))
|
||||
#Mastodon
|
||||
if self.config.has_section('MASTODON') and self.config.getboolean('MASTODON', 'ENABLE'):
|
||||
from defMastodon import sendMastodon
|
||||
sendMastodon(self.map_file_name, message, self.config)
|
||||
self.circle_history['triggered'] = True
|
||||
elif abs(total_change) <= 360 and self.circle_history["triggered"]:
|
||||
print("No Longer Circling, trigger cleared")
|
||||
|
@ -850,7 +864,7 @@ class Plane:
|
|||
if bool(int(ra['acas_ra']['MTE'])):
|
||||
ra_message += ", Multi threat"
|
||||
from defSS import get_adsbx_screenshot, generate_adsbx_screenshot_time_params
|
||||
url_params = f"&lat={ra['lat']}&lon={ra['lon']}&zoom=11&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays={self.get_adsbx_map_overlays()}"
|
||||
url_params = f"&lat={ra['lat']}&lon={ra['lon']}&zoom=11&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays={self.get_adsbx_map_overlays()}&limitupdates=0"
|
||||
if "threat_id_hex" in ra['acas_ra'].keys():
|
||||
from mictronics_parse import get_aircraft_reg_by_icao
|
||||
threat_reg = get_aircraft_reg_by_icao(ra['acas_ra']['threat_id_hex'])
|
||||
|
@ -860,12 +874,12 @@ class Plane:
|
|||
else:
|
||||
url_params += f"&icao={self.icao.lower()}&noIsolation"
|
||||
print(url_params)
|
||||
get_adsbx_screenshot(self.map_file_name, url_params, True, True, overrides=self.overrides)
|
||||
get_adsbx_screenshot(self.map_file_name, url_params, True, True, overrides=self.overrides, conceal_ac_id=self.conceal_ac_id, conceal_pia=self.conceal_pia)
|
||||
|
||||
if self.config.getboolean('DISCORD', 'ENABLE'):
|
||||
from defDiscord import sendDis
|
||||
dis_message = f"{self.dis_title} {ra_message}"
|
||||
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None
|
||||
role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') and self.config.get('DISCORD', 'ROLE_ID').strip() != "" else None
|
||||
sendDis(dis_message, self.config, role_id, self.map_file_name)
|
||||
#if twitter
|
||||
def expire_ra_types(self):
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# requirements.txt
|
||||
colorama
|
||||
geopy
|
||||
tabulate
|
||||
pytz
|
||||
pillow
|
||||
tweepy
|
||||
discord-webhook
|
||||
selenium
|
||||
git+https://github.com/openskynetwork/opensky-api.git@master#subdirectory=python/
|
||||
webdriver-manager
|
||||
shapely
|
||||
pandas
|
||||
python-telegram-bot
|
||||
mastodon.py
|
||||
beautifulsoup4
|
||||
pycairo
|
||||
py-staticmaps
|
||||
pyproj
|
||||
lxml
|
||||
configparser
|
||||
geog
|
Loading…
Reference in New Issue