2019-10-28 00:23:56 -04:00
|
|
|
"""
|
2019-12-07 17:26:55 -05:00
|
|
|
Weather extension for qrm
|
2019-10-28 00:23:56 -04:00
|
|
|
---
|
2020-01-06 23:27:48 -05:00
|
|
|
Copyright (C) 2019-2020 Abigail Gold, 0x5c
|
2019-10-28 00:23:56 -04:00
|
|
|
|
2020-02-15 06:27:48 -05:00
|
|
|
This file is part of qrm2 and is released under the terms of
|
2020-01-31 06:50:50 -05:00
|
|
|
the GNU General Public License, version 2.
|
2019-10-28 00:23:56 -04:00
|
|
|
"""
|
|
|
|
|
2020-01-31 06:50:50 -05:00
|
|
|
|
2019-10-28 00:23:56 -04:00
|
|
|
import re
|
2021-01-20 02:48:38 -05:00
|
|
|
from typing import List
|
2019-10-28 00:23:56 -04:00
|
|
|
|
2021-01-20 02:48:38 -05:00
|
|
|
import aiohttp
|
|
|
|
|
|
|
|
from discord import Embed
|
2019-10-28 00:23:56 -04:00
|
|
|
import discord.ext.commands as commands
|
|
|
|
|
2019-12-06 01:19:42 -05:00
|
|
|
import common as cmn
|
|
|
|
|
2019-10-28 00:23:56 -04:00
|
|
|
|
|
|
|
class WeatherCog(commands.Cog):
|
|
|
|
wttr_units_regex = re.compile(r"\B-([cCfF])\b")
|
|
|
|
|
|
|
|
def __init__(self, bot: commands.Bot):
|
|
|
|
self.bot = bot
|
2021-01-20 02:48:38 -05:00
|
|
|
self.session = aiohttp.ClientSession(connector=bot.qrm.connector)
|
2019-10-28 00:23:56 -04:00
|
|
|
|
2021-01-20 02:48:38 -05:00
|
|
|
@commands.command(name="solarweather", aliases=["solar", "bandconditions", "cond", "condx", "conditions"],
|
|
|
|
category=cmn.cat.weather)
|
2021-01-17 23:55:23 -05:00
|
|
|
async def solarweather(self, ctx: commands.Context):
|
|
|
|
"""Gets a solar weather report."""
|
2020-10-30 05:22:16 -04:00
|
|
|
embed = cmn.embed_factory(ctx)
|
2021-01-17 23:55:23 -05:00
|
|
|
embed.title = "☀️ Current Solar Weather"
|
|
|
|
if ctx.invoked_with in ["bandconditions", "cond", "condx", "conditions"]:
|
|
|
|
embed.add_field(name="⚠️ Deprecated Command Alias",
|
|
|
|
value=(f"This command has been renamed to `{ctx.prefix}solar`!\n"
|
|
|
|
"The alias you used will be removed in the next version."))
|
2020-10-30 05:22:16 -04:00
|
|
|
embed.colour = cmn.colours.good
|
|
|
|
embed.set_image(url="http://www.hamqsl.com/solarsun.php")
|
|
|
|
await ctx.send(embed=embed)
|
2019-10-28 00:23:56 -04:00
|
|
|
|
2020-03-28 19:07:23 -04:00
|
|
|
@commands.group(name="weather", aliases=["wttr"], case_insensitive=True, category=cmn.cat.weather)
|
2019-10-28 00:23:56 -04:00
|
|
|
async def _weather_conditions(self, ctx: commands.Context):
|
2020-02-15 04:59:25 -05:00
|
|
|
"""Gets local weather conditions from [wttr.in](http://wttr.in/).
|
|
|
|
|
|
|
|
*Supported location types:*
|
|
|
|
city name: `paris`
|
|
|
|
any location: `~Eiffel Tower`
|
|
|
|
Unicode name of any location in any language: `Москва`
|
|
|
|
airport code (3 letters): `muc`
|
|
|
|
domain name `@stackoverflow.com`
|
|
|
|
area codes: `12345`
|
|
|
|
GPS coordinates: `-78.46,106.79`
|
|
|
|
|
|
|
|
Add a `-c` or `-f` to use Celcius or Fahrenheit: `-c YSC`"""
|
2019-10-28 00:23:56 -04:00
|
|
|
if ctx.invoked_subcommand is None:
|
|
|
|
await ctx.send_help(ctx.command)
|
|
|
|
|
2020-01-30 06:15:42 -05:00
|
|
|
@_weather_conditions.command(name="forecast", aliases=["fc", "future"], category=cmn.cat.weather)
|
2019-12-06 01:19:42 -05:00
|
|
|
async def _weather_conditions_forecast(self, ctx: commands.Context, *, location: str):
|
2020-02-15 04:59:25 -05:00
|
|
|
"""Gets local weather forecast for the next three days from [wttr.in](http://wttr.in/).
|
|
|
|
See help of the `weather` command for possible location types and options."""
|
2020-10-30 05:22:16 -04:00
|
|
|
try:
|
|
|
|
units_arg = re.search(self.wttr_units_regex, location).group(1)
|
|
|
|
except AttributeError:
|
|
|
|
units_arg = ""
|
|
|
|
if units_arg.lower() == "f":
|
|
|
|
units = "u"
|
|
|
|
elif units_arg.lower() == "c":
|
|
|
|
units = "m"
|
|
|
|
else:
|
|
|
|
units = ""
|
|
|
|
|
|
|
|
loc = self.wttr_units_regex.sub("", location).strip()
|
|
|
|
|
|
|
|
embed = cmn.embed_factory(ctx)
|
|
|
|
embed.title = f"Weather Forecast for {loc}"
|
|
|
|
embed.description = "Data from [wttr.in](http://wttr.in/)."
|
|
|
|
embed.colour = cmn.colours.good
|
|
|
|
|
|
|
|
loc = loc.replace(" ", "+")
|
|
|
|
embed.set_image(url=f"http://wttr.in/{loc}_{units}pnFQ.png")
|
|
|
|
await ctx.send(embed=embed)
|
2019-10-28 00:23:56 -04:00
|
|
|
|
2020-01-30 06:15:42 -05:00
|
|
|
@_weather_conditions.command(name="now", aliases=["n"], category=cmn.cat.weather)
|
2019-12-06 01:19:42 -05:00
|
|
|
async def _weather_conditions_now(self, ctx: commands.Context, *, location: str):
|
2020-02-15 04:59:25 -05:00
|
|
|
"""Gets current local weather conditions from [wttr.in](http://wttr.in/).
|
|
|
|
See help of the `weather` command for possible location types and options."""
|
2020-10-30 05:22:16 -04:00
|
|
|
try:
|
|
|
|
units_arg = re.search(self.wttr_units_regex, location).group(1)
|
|
|
|
except AttributeError:
|
|
|
|
units_arg = ""
|
|
|
|
if units_arg.lower() == "f":
|
|
|
|
units = "u"
|
|
|
|
elif units_arg.lower() == "c":
|
|
|
|
units = "m"
|
|
|
|
else:
|
|
|
|
units = ""
|
|
|
|
|
|
|
|
loc = self.wttr_units_regex.sub("", location).strip()
|
|
|
|
|
|
|
|
embed = cmn.embed_factory(ctx)
|
|
|
|
embed.title = f"Current Weather for {loc}"
|
|
|
|
embed.description = "Data from [wttr.in](http://wttr.in/)."
|
|
|
|
embed.colour = cmn.colours.good
|
|
|
|
|
|
|
|
loc = loc.replace(" ", "+")
|
|
|
|
embed.set_image(url=f"http://wttr.in/{loc}_0{units}pnFQ.png")
|
|
|
|
await ctx.send(embed=embed)
|
2019-10-28 00:23:56 -04:00
|
|
|
|
2021-01-20 02:48:38 -05:00
|
|
|
@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("<!-- Data starts here -->")[1].split("<!-- Data ends here -->")[0].strip()
|
|
|
|
# split at <hr>s
|
|
|
|
data = re.split(r"<hr.*>", 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("<code>", "").replace("</code>", "")
|
|
|
|
line = line.replace("<strong>", "").replace("</strong>", "")
|
|
|
|
line = line.replace("<br/>", "\n").replace(" ", " ")
|
|
|
|
line = line.strip("\n")
|
|
|
|
parsed.append(line)
|
|
|
|
return parsed
|
|
|
|
|
2019-10-28 00:23:56 -04:00
|
|
|
|
|
|
|
def setup(bot: commands.Bot):
|
|
|
|
bot.add_cog(WeatherCog(bot))
|