From 43a24d614b41507e0c9bd946bfd9e8ee5ca3ec3d Mon Sep 17 00:00:00 2001 From: classabbyamp <5366828+classabbyamp@users.noreply.github.com> Date: Wed, 20 Jan 2021 02:48:38 -0500 Subject: [PATCH] add METAR and TAF commands (#340) fixes #171 --- CHANGELOG.md | 1 + exts/weather.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71a4f04..3562965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added - MUF and foF2 maps from [prop.kc2g.com](https://prop.kc2g.com/). +- Commands to show METAR (`?metar`) and TAF (`?taf`) (aeronautical weather conditions). ### Changed - New colour theme for `?greyline`. - Moved great circle distance and bearing calculation from `?ungrid` to `?griddistance`. diff --git a/exts/weather.py b/exts/weather.py index f899fe7..b4871e6 100644 --- a/exts/weather.py +++ b/exts/weather.py @@ -9,7 +9,11 @@ the GNU General Public License, version 2. import re +from typing import List +import aiohttp + +from discord import Embed import discord.ext.commands as commands import common as cmn @@ -20,8 +24,10 @@ class WeatherCog(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot + self.session = aiohttp.ClientSession(connector=bot.qrm.connector) - @commands.command(aliases=["solar", "bandconditions", "cond", "condx", "conditions"], category=cmn.cat.weather) + @commands.command(name="solarweather", aliases=["solar", "bandconditions", "cond", "condx", "conditions"], + category=cmn.cat.weather) async def solarweather(self, ctx: commands.Context): """Gets a solar weather report.""" embed = cmn.embed_factory(ctx) @@ -103,6 +109,73 @@ class WeatherCog(commands.Cog): embed.set_image(url=f"http://wttr.in/{loc}_0{units}pnFQ.png") await ctx.send(embed=embed) + @commands.command(name="metar", category=cmn.cat.weather) + async def metar(self, ctx: commands.Context, airport: str, hours: int = 0): + """Gets current raw METAR (Meteorological Terminal Aviation Routine Weather Report) for an airport. \ + Optionally, a number of hours can be given to show a number of hours of historical METAR data. + + Airports should be given as an \ + [ICAO code](https://en.wikipedia.org/wiki/List_of_airports_by_IATA_and_ICAO_code).""" + await ctx.send(embed=await self.gen_metar_taf_embed(ctx, airport, hours, False)) + + @commands.command(name="taf", category=cmn.cat.weather) + async def taf(self, ctx: commands.Context, airport: str): + """Gets forecasted raw TAF (Terminal Aerodrome Forecast) data for an airport. Includes the latest METAR data. + + Airports should be given as an \ + [ICAO code](https://en.wikipedia.org/wiki/List_of_airports_by_IATA_and_ICAO_code).""" + await ctx.send(embed=await self.gen_metar_taf_embed(ctx, airport, 0, True)) + + async def gen_metar_taf_embed(self, ctx: commands.Context, airport: str, hours: int, taf: bool) -> Embed: + embed = cmn.embed_factory(ctx) + airport = airport.upper() + + if re.fullmatch(r"\w(\w|\d){2,3}", airport): + metar = await self.get_metar_taf_data(airport, hours, taf) + + if taf: + embed.title = f"Current TAF for {airport}" + elif hours > 0: + embed.title = f"METAR for {airport} for the last {hours} hour{'s' if hours > 1 else ''}" + else: + embed.title = f"Current METAR for {airport}" + + embed.description = "Data from [aviationweather.gov](https://www.aviationweather.gov/metar/data)." + embed.colour = cmn.colours.good + + data = "\n".join(metar) + embed.description += f"\n\n```\n{data}\n```" + else: + embed.title = "Invalid airport given!" + embed.colour = cmn.colours.bad + return embed + + async def get_metar_taf_data(self, airport: str, hours: int, taf: bool) -> List[str]: + url = (f"https://www.aviationweather.gov/metar/data?ids={airport}&format=raw&hours={hours}" + f"&taf={'on' if taf else 'off'}&layout=off") + async with self.session.get(url) as r: + if r.status != 200: + raise cmn.BotHTTPError(r) + page = await r.text() + + # pare down to just the data + page = page.split("")[1].split("")[0].strip() + # split at
s + data = re.split(r"", page, maxsplit=len(airport)) + + parsed = [] + for sec in data: + if sec.strip(): + for line in sec.split("\n"): + line = line.strip() + # remove HTML stuff + line = line.replace("", "").replace("", "") + line = line.replace("", "").replace("", "") + line = line.replace("
", "\n").replace(" ", " ") + line = line.strip("\n") + parsed.append(line) + return parsed + def setup(bot: commands.Bot): bot.add_cog(WeatherCog(bot))