aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/constants.py1
-rw-r--r--bot/seasons/evergreen/space.py205
2 files changed, 206 insertions, 0 deletions
diff --git a/bot/constants.py b/bot/constants.py
index 6d4a50f1..b9dbeb39 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -134,6 +134,7 @@ class Tokens(NamedTuple):
omdb = environ.get("OMDB_API_KEY")
youtube = environ.get("YOUTUBE_API_KEY")
tmdb = environ.get("TMDB_API_KEY")
+ nasa = environ.get("NASA_API_KEY")
igdb = environ.get("IGDB_API_KEY")
diff --git a/bot/seasons/evergreen/space.py b/bot/seasons/evergreen/space.py
new file mode 100644
index 00000000..d94dcbf4
--- /dev/null
+++ b/bot/seasons/evergreen/space.py
@@ -0,0 +1,205 @@
+import logging
+import random
+from datetime import datetime
+from typing import Any, Dict, Optional, Union
+from urllib.parse import urlencode
+
+from discord import Embed
+from discord.ext.commands import BadArgument, Cog, Context, Converter, group
+
+from bot.bot import SeasonalBot
+from bot.constants import Tokens
+
+logger = logging.getLogger(__name__)
+
+NASA_BASE_URL = "https://api.nasa.gov"
+NASA_IMAGES_BASE_URL = "https://images-api.nasa.gov"
+NASA_EPIC_BASE_URL = "https://epic.gsfc.nasa.gov"
+
+APOD_DEFAULT_PARAMS = {
+ "api_key": Tokens.nasa
+}
+
+
+class DateConverter(Converter):
+ """Parse `str` into `datetime` or `int` object."""
+
+ async def convert(self, ctx: Context, argument: str) -> Union[int, datetime]:
+ """Parse `str` into `datetime` or `int`. When invalid value, raise error."""
+ if argument.isdigit():
+ return int(argument)
+ try:
+ date = datetime.strptime(argument, "%Y-%m-%d")
+ except ValueError:
+ raise BadArgument(f"Can't convert `{argument}` to `datetime` in format `YYYY-MM-DD` or `int` in SOL.")
+ return date
+
+
+class Space(Cog):
+ """Space Cog contains commands, that show images, facts or other information about space."""
+
+ def __init__(self, bot: SeasonalBot):
+ self.bot = bot
+ self.http_session = bot.http_session
+
+ @group(name="space", invoke_without_command=True)
+ async def space(self, ctx: Context) -> None:
+ """Head command that contains commands about space."""
+ await ctx.send_help("space")
+
+ @space.command(name="apod")
+ async def apod(self, ctx: Context, date: Optional[str] = None) -> None:
+ """Get Astronomy Picture of Day from NASA API. Date is optional parameter, what formatting is YYYY-MM-DD."""
+ # Make copy of parameters
+ params = APOD_DEFAULT_PARAMS.copy()
+ # Parse date to params, when provided. Show error message when invalid formatting
+ if date:
+ try:
+ params["date"] = datetime.strptime(date, "%Y-%m-%d").date().isoformat()
+ except ValueError:
+ await ctx.send(f"Invalid date {date}. Please make sure your date is in format YYYY-MM-DD.")
+ return
+
+ # Do request to NASA API
+ result = await self.fetch_from_nasa("planetary/apod", params)
+
+ # Create embed from result
+ embed = Embed(title=f"Astronomy Picture of the Day - {result['date']}", description=result["explanation"])
+ embed.set_image(url=result["url"])
+ embed.set_footer(text="Powered by NASA API")
+
+ await ctx.send(embed=embed)
+
+ @space.command(name="nasa")
+ async def nasa(self, ctx: Context, *, search_term: Optional[str] = None) -> None:
+ """Get random NASA information/facts + image. Support `search_term` parameter for more specific search."""
+ # Create params for request, create URL and do request
+ params = {
+ "media_type": "image"
+ }
+ if search_term:
+ params["q"] = search_term
+
+ async with self.http_session.get(url=f"{NASA_IMAGES_BASE_URL}/search?{urlencode(params)}") as resp:
+ data = await resp.json()
+
+ # Check is there any items returned
+ if len(data["collection"]["items"]) == 0:
+ await ctx.send(f"Can't find any items with search term `{search_term}`.")
+ return
+
+ # Get (random) item from result, that will be shown
+ item = random.choice(data["collection"]["items"])
+
+ # Create embed and send it
+ embed = Embed(title=item["data"][0]["title"], description=item["data"][0]["description"])
+ embed.set_image(url=item["links"][0]["href"])
+ embed.set_footer(text="Powered by NASA API")
+
+ await ctx.send(embed=embed)
+
+ @space.command(name="epic")
+ async def epic(self, ctx: Context, date: Optional[str] = None) -> None:
+ """Get one of latest random image of earth from NASA EPIC API. Support date parameter, format is YYYY-MM-DD."""
+ # Parse date if provided
+ if date:
+ try:
+ show_date = datetime.strptime(date, "%Y-%m-%d").date().isoformat()
+ except ValueError:
+ await ctx.send(f"Invalid date {date}. Please make sure your date is in format YYYY-MM-DD.")
+ return
+ else:
+ show_date = None
+
+ # Generate URL and make request to API
+ async with self.http_session.get(
+ url=f"{NASA_EPIC_BASE_URL}/api/natural{f'/date/{show_date}' if show_date else ''}"
+ ) as resp:
+ data = await resp.json()
+
+ if len(data) < 1:
+ await ctx.send("Can't find any images in this date.")
+ return
+
+ # Get random item from result that will be shown
+ item = random.choice(data)
+
+ # Split date for image URL
+ year, month, day = item["date"].split(" ")[0].split("-")
+
+ image_url = f"{NASA_EPIC_BASE_URL}/archive/natural/{year}/{month}/{day}/jpg/{item['image']}.jpg"
+
+ # Create embed, fill and send it
+ embed = Embed(title="Earth Image", description=item["caption"])
+ embed.set_image(url=image_url)
+ embed.set_footer(text=f"Identifier: {item['identifier']} \u2022 Powered by NASA API")
+
+ await ctx.send(embed=embed)
+
+ @space.command(name="mars")
+ async def mars(self,
+ ctx: Context,
+ date: DateConverter,
+ rover: Optional[str] = "curiosity"
+ ) -> None:
+ """
+ Get random Mars image by date. Support both SOL (martian solar day) and earth date and rovers.
+
+ Earth date formatting is YYYY-MM-DD. Current max date is 2019-09-28 and min 2012-08-06.
+ Rovers images dates:
+ - Curiosity -> 2012-08-06 until 2019-09-28
+ - Opportunity -> 2004-01-25 until 2018-06-11
+ - Spirit -> 2004-01-04 until 2010-03-21
+ """
+ # Check does user provided correct rover
+ rover = rover.lower()
+ if rover not in ["curiosity", "opportunity", "spirit"]:
+ await ctx.send(f"Invalid rover `{rover}`. Rovers: `Curiosity`, `Opportunity`, `Spirit`")
+ return
+
+ # Create API request parameters, try to parse date
+ params = {
+ "api_key": Tokens.nasa
+ }
+ if isinstance(date, int):
+ params["sol"] = date
+ else:
+ params["earth_date"] = date.date().isoformat()
+
+ result = await self.fetch_from_nasa(f"mars-photos/api/v1/rovers/{rover}/photos", params)
+
+ # Check for empty result
+ if len(result["photos"]) < 1:
+ err_msg = (
+ f"We can't find result in date "
+ f"{date.date().isoformat() if isinstance(date, datetime) else f'{date} SOL'}.\n"
+ f"**Note:** Dates must match with rover's working dates. Please use `{ctx.prefix}help space mars` to "
+ "see working dates for each rover."
+ )
+ await ctx.send(err_msg)
+ return
+
+ # Get random item from result, generate embed with it and send
+ item = random.choice(result["photos"])
+
+ embed = Embed(title=f"{item['rover']['name']}'s {item['camera']['full_name']} Mars Image")
+ embed.set_image(url=item["img_src"])
+ embed.set_footer(text="Powered by NASA API")
+
+ await ctx.send(embed=embed)
+
+ async def fetch_from_nasa(self, endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]:
+ """Fetch information from NASA API, return result."""
+ # Generate request URL from base URL, endpoint and parsed params
+ async with self.http_session.get(url=f"{NASA_BASE_URL}/{endpoint}?{urlencode(params)}") as resp:
+ return await resp.json()
+
+
+def setup(bot: SeasonalBot) -> None:
+ """Load Space Cog."""
+ # Check does bot have NASA API key in .env, when not, don't load Cog and print warning
+ if not Tokens.nasa:
+ logger.warning("Can't find NASA API key. Not loading Space Cog.")
+ return
+
+ bot.add_cog(Space(bot))