From 516d0eabc153b2e87fbc5b0bf267f01e39e4eb15 Mon Sep 17 00:00:00 2001 From: LBlend <24893890+LBlend@users.noreply.github.com> Date: Tue, 16 Jan 2024 03:24:56 +0100 Subject: [PATCH] migrate to yr's api good code is overrated --- src/cogs/aurora.py | 166 ++++++++++++++------------------------------- 1 file changed, 50 insertions(+), 116 deletions(-) diff --git a/src/cogs/aurora.py b/src/cogs/aurora.py index 8905059..94eb36f 100644 --- a/src/cogs/aurora.py +++ b/src/cogs/aurora.py @@ -2,13 +2,12 @@ from datetime import datetime from datetime import timedelta import discord +import pytz import requests from discord import app_commands from discord.ext import commands from discord.ext import tasks -from cogs.utils import embed_templates - class Aurora(commands.Cog): """Aurora Borealis forecasts and alerts""" @@ -22,11 +21,8 @@ class Aurora(commands.Cog): self.bot = bot - self.NEUF_LAT = 59.9323960544179 - self.NEUF_LON = 10.712627459170472 - self.notified = datetime(2000, 9, 11) # Used to prevent spamming aurora alerts - self.aurora_alarm.start() + # self.aurora_alarm.start() async def get_forecast(self) -> dict | None: """ @@ -35,88 +31,33 @@ class Aurora(commands.Cog): Returns ------- (dict) The forecast data | None - { - "aurora_forecast_time": (str) Time the aurora forecast was made formatted as a discord timestamp - "aurora_percentage": (int) Percentage chance of aurora - "cloud_forecast_time": (str) Time the cloud cover forecast was made formatted as a discord timestamp - "cloud_cover_percentage": (int) Percentage cloud cover - } """ - # Get the aurora forecast data from NOAA - forecast_data = requests.get("https://services.swpc.noaa.gov/json/ovation_aurora_latest.json").json() - if not forecast_data: - self.bot.logger.warning("Failed to get aurora forecast data from NOAA") - return None - aurora_forecast_time = forecast_data.get("Observation Time") - - # Make sure longitude is in range 0-360. For some reason we need to do this. - # I don't know why. Maybe because I ~~stole~~ borrowed the code :P - longitude = self.NEUF_LON % 360 - - forecast_dict = {} - for forecast_item in forecast_data["coordinates"]: - forecast_dict[forecast_item[0], forecast_item[1]] = forecast_item[2] - aurora_percentage = forecast_dict.get((int(longitude), int(self.NEUF_LAT)), 0) - - # Get the cloud cover data from MET Norway - cloud_data = requests.get( - "https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=59.9323960544179&lon=10.712627459170472", - headers={"User-Agent": "UiO Gaming Bot"}, - ).json() - if not cloud_data: - self.bot.logger.warning("Failed to get cloud cover data from MET Norway") + data = requests.get("https://www.yr.no/api/v0/locations/1-72837/auroraforecast?language=nb").json() + if not data or data["status"]["code"] != "Ok": + self.bot.logger.warning("Failed to get forecast data") return None - cloud_cover = cloud_data["properties"]["timeseries"][0]["data"]["instant"]["details"]["cloud_area_fraction"] - cloud_forecast_time = cloud_data["properties"]["timeseries"][0]["time"] + valid_sightings = [] + for interval in data["shortIntervals"]: + # Convert to datetime objects, convert timezone to utc and strip timezone info + start = datetime.fromisoformat(interval["start"]).astimezone(pytz.utc).replace(tzinfo=None) + end = datetime.fromisoformat(interval["end"]).astimezone(pytz.utc).replace(tzinfo=None) - # Convert time to discord timestamps - try: - aurora_forecast_time = discord.utils.format_dt(datetime.fromisoformat(aurora_forecast_time), style="R") - except ValueError as e: - self.bot.logger.warning(e) - aurora_forecast_time = f"`{aurora_forecast_time}`" + # If sighting is not in within the next 12 hours ignore + if (start - datetime.now()) > timedelta(hours=12): + continue - try: - cloud_forecast_time = discord.utils.format_dt(datetime.fromisoformat(cloud_forecast_time), style="R") - except ValueError as e: - self.bot.logger.warning(e) - cloud_forecast_time = f"`{cloud_forecast_time}`" + # We're gonna trust this value. Hopefully it takes sunlight, cloud cover as well as solar activity into account + # We're using an undocumented API so who knows. They probably know what they're doing over there. + if interval["auroraValue"] >= 0.5: + valid_sightings.append(interval) - return { - "aurora_forecast_time": aurora_forecast_time, - "aurora_percentage": aurora_percentage, - "cloud_forecast_time": cloud_forecast_time, - "cloud_cover_percentage": cloud_cover, - } + if not valid_sightings: + return None - def get_forecast_embed(self, forecast: dict) -> discord.Embed: - """ - Format the forecast data into a standaridized embed. DRY - - Parameters - ---------- - forecast (dict): The forecast data - { - "aurora_forecast_time": (str) Time the aurora forecast was made formatted as a discord timestamp - "aurora_percentage": (int) Percentage chance of aurora - "cloud_forecast_time": (str) Time the cloud cover forecast was made formatted as a discord timestamp - "cloud_cover_percentage": (int) Percentage cloud cover - } - """ - - aurora_time = forecast.get("aurora_forecast_time") - cloud_time = forecast.get("cloud_forecast_time") - aurora_percentage = forecast.get("aurora_percentage") - cloud_percentage = forecast.get("cloud_cover_percentage") - - embed = discord.Embed(title="Nordlysvarsel for Chateau Neuf", color=0x00EA8D) - embed.description = f"Nordlysvarsel hentet: {aurora_time}\nSkydekkevarsel hentet: {cloud_time}" - embed.add_field(name="Sjanse for å se nordlys", value=f"{aurora_percentage}%") - embed.add_field(name="Skydekke", value=f"{cloud_percentage}%") - embed.set_footer(text="Nordlysdata fra NOAA | Skydekkedata fra MET Norge") - return embed + most_likely_sighting = max(valid_sightings, key=lambda x: x["auroraValue"]) + return most_likely_sighting @tasks.loop(minutes=10) async def aurora_alarm(self): @@ -132,47 +73,40 @@ class Aurora(commands.Cog): ) return - forecast = await self.get_forecast() - if not forecast: - self.bot.logger.warning("aurora_alarm: Failed to get forecast data") + if not (forecast := await self.get_forecast()): + self.bot.logger.info("aurora_alarm: Fetch forecast") return - if forecast.get("aurora_percentage") >= 50 and forecast.get("cloud_cover_percentage") <= 50: - channel = self.bot.get_channel(747542544291987597) - if not channel: - self.bot.logger.warning("aurora_alarm: Failed to get aurora channel") - return + channel = self.bot.get_channel(747542544291987599) + if not channel: + self.bot.logger.warning("aurora_alarm: Failed to get aurora channel") + return - embed = self.get_forecast_embed(forecast) - await channel.send( - content="SJANSE FOR NORDLYS AKKURAT NÅ PÅ NEUF! Se ut vinduet istedenfor på den dumme skjermen din", - embed=embed, - ) - self.notified = datetime.now() # Update the last notification time + try: + start = discord.utils.format_dt(datetime.fromisoformat(forecast["start"]), style="t") + except ValueError as e: + self.bot.logger.warning(e) + start = f"`{forecast['start']}`" - @app_commands.checks.bot_has_permissions(embed_links=True) - @app_commands.checks.cooldown(1, 2) - @app_commands.command(name="nordlys", description="Se nordlysvarselet for Neuf akkurat nå") - async def aurora_forecast(self, interaction: discord.Interaction): - """ - See the aurora forecast at Chateau Neuf right now + try: + end = discord.utils.format_dt(datetime.fromisoformat(forecast["end"]), style="t") + except ValueError as e: + self.bot.logger.warning(e) + end = f"`{forecast['end']}`" - Parameters - ---------- - interaction (discord.Interaction): Slash command context object - """ - - forecast = await self.get_forecast() - if not forecast: - return await interaction.response.send_message( - embed=embed_templates.error_fatal( - interaction, - text="Kunne ikke hente nordlysvarsel akkurat nå. Et av APIene feilet (ikke min feil :P)", - ) - ) - - embed = self.get_forecast_embed(forecast) - await interaction.response.send_message(embed=embed) + embed = discord.Embed( + title="Nordlysvarsel", + description=f"### Størst sjanse mellom {start} og {end}", + color=discord.Color.purple(), + ) + embed.add_field(name="Sjanse for Nordlys", value=f"{round(forecast['auroraValue'] * 100)}%", inline=False) + embed.add_field(name="KP-Indeks", value=forecast["kpIndex"]) + embed.add_field(name="Skydekke", value=f"{forecast['cloudCover']['value']}%") + await channel.send( + content="SJANSE FOR NORDLYS AKKURAT NÅ PÅ NEUF! Se ut vinduet istedenfor på den dumme skjermen din", + embed=embed, + ) + self.notified = datetime.now() # Update the last notification time async def setup(bot: commands.Bot):