Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Space Cog implementing #364

Merged
merged 42 commits into from Mar 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e4296ed
Added Space Cog
ks129 Feb 29, 2020
5b05592
Added NASA API Key to constants.py
ks129 Feb 29, 2020
0aef9fe
(Space Cog): Added NASA API key check on load, added logger
ks129 Feb 29, 2020
1fc0b1c
(Space Cog): Added NASA API base URL and .apod command HTTP request d…
ks129 Feb 29, 2020
1b3f274
(Space Cog): Added fetch_from_nasa function to space.py, what do requ…
ks129 Feb 29, 2020
7c15f14
(Space Cog): Created .apod command that support date parameter.
ks129 Feb 29, 2020
1575b79
(Space Cog): Created get_random_nasa_item function what will be used …
ks129 Feb 29, 2020
698f78e
(Space Cog): Added .nasa command that show information and facts abou…
ks129 Feb 29, 2020
2d82c35
(Space Cog): Added .earth command, created new base URL: NASA Epic AP…
ks129 Feb 29, 2020
0c33dc3
(Space Cog): Added .mars command, removed get_random_nasa_command (no…
ks129 Feb 29, 2020
106bbcc
(Space Cog): Modified base URLs (removed slash at end, added to queri…
ks129 Feb 29, 2020
95a43dd
(Space Cog): Renamed + fixed base URLs constants
ks129 Mar 2, 2020
904e465
(Space Cog): .apod Command Fixes: Renamed default parameters constant…
ks129 Mar 2, 2020
3efc262
(Space Cog): Fixes with random module. Removed manual random list ite…
ks129 Mar 2, 2020
e5f6ed6
(Space Cog): Created head command `.space` and added all other comman…
ks129 Mar 7, 2020
2d95e9b
(Space Cog): Renamed `.space earth` command to `.space epic` for upco…
ks129 Mar 7, 2020
8d0f141
(Space Cog): Improved `.space mars` command, added possibility for SO…
ks129 Mar 7, 2020
769ee66
(Space Cog): Added `invoke_without_command` parameter to `.space` com…
ks129 Mar 7, 2020
266bcaa
(Space Cog): Added date parameter to `.space epic` command.
ks129 Mar 7, 2020
ca77b34
(Space Cog): Added optional search term to `.space nasa` command, rem…
ks129 Mar 7, 2020
7bf4984
Merge branch 'master' into space-cog
ks129 Mar 7, 2020
2c4c472
(Space Cog): Removed unnecessary sentence from `.mars` command docstring
ks129 Mar 10, 2020
6e7e873
(Space Cog): Add base URL parameter to helper function `fetch_from_nasa`
ks129 Mar 11, 2020
4ecacdc
(Space Cog): Created task for `.mars` command rovers fetching one tim…
ks129 Mar 11, 2020
a3e6e5c
(Space Cog): Added `.space mars dates` command to see what rovers is …
ks129 Mar 11, 2020
4b4616a
(Space Cog): Removed unnecessary part of `.space mars` docstring due …
ks129 Mar 11, 2020
5cf725a
(Space Cog): Add `self.rovers` using to `.space mars` command check.
ks129 Mar 11, 2020
906288a
(Space Cog): Fix `DateConverter`'s docstring
ks129 Mar 11, 2020
1aaeec4
(Space Cog): Add `.space mars dates` information to `.space mars` com…
ks129 Mar 11, 2020
4e3f756
(Space Cog): Added rovers aliases to `.space mars dates` command
ks129 Mar 11, 2020
90ecfb0
(Space Cog): Added information about when date is not specified to `.…
ks129 Mar 11, 2020
9e3f043
(Space Cog): Create new helper function `create_nasa_embed` and apply…
ks129 Mar 11, 2020
5761cc5
(Space Cog): Use `fetch_from_nasa` in every command + make modificati…
ks129 Mar 11, 2020
4ec0093
(Space Cog): Clean comments
ks129 Mar 11, 2020
570cb89
Merge remote-tracking branch 'origin/space-cog' into space-cog
ks129 Mar 11, 2020
7950640
(Space Cog): Fixed function signatures and `ctx.send` `embed` value f…
ks129 Mar 14, 2020
783b85d
(Space Cog): Refactored url query parameters system, apply changes to…
ks129 Mar 14, 2020
0745886
(Space Cog): Removed date requirement from `.space mars` command. Now…
ks129 Mar 14, 2020
0acd912
Merge branch 'master' into space-cog
sco1 Mar 20, 2020
522e1c6
(Space Cog): Added `get_rovers` task canceling with `cog_unload` func…
ks129 Mar 31, 2020
e82daf0
(Space Cog): Removed `async` from `create_nasa_embed` function.
ks129 Mar 31, 2020
0a29c39
Merge branch 'master' into space-cog
ikuyarihS Mar 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions bot/constants.py
Expand Up @@ -139,6 +139,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")


Expand Down
240 changes: 240 additions & 0 deletions bot/seasons/evergreen/space.py
@@ -0,0 +1,240 @@
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 import tasks
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"


class DateConverter(Converter):
"""Parse SOL or earth date (in format YYYY-MM-DD) into `int` or `datetime`. When invalid input, raise error."""

async def convert(self, ctx: Context, argument: str) -> Union[int, datetime]:
"""Parse date (SOL or earth) 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

self.rovers = {}
self.get_rovers.start()
ks129 marked this conversation as resolved.
Show resolved Hide resolved

def cog_unload(self) -> None:
"""Cancel `get_rovers` task when Cog will unload."""
self.get_rovers.cancel()

@tasks.loop(hours=24)
async def get_rovers(self) -> None:
"""Get listing of rovers from NASA API and info about their start and end dates."""
data = await self.fetch_from_nasa("mars-photos/api/v1/rovers")

for rover in data["rovers"]:
self.rovers[rover["name"].lower()] = {
"min_date": rover["landing_date"],
"max_date": rover["max_date"],
"max_sol": rover["max_sol"]
}

@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.

If date is not specified, this will get today APOD.
"""
params = {}
# 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

result = await self.fetch_from_nasa("planetary/apod", params)

await ctx.send(
embed=self.create_nasa_embed(
f"Astronomy Picture of the Day - {result['date']}",
result["explanation"],
result["url"]
)
)

@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."""
params = {
"media_type": "image"
}
if search_term:
params["q"] = search_term

# Don't use API key, no need for this.
data = await self.fetch_from_nasa("search", params, NASA_IMAGES_BASE_URL, use_api_key=False)
if len(data["collection"]["items"]) == 0:
await ctx.send(f"Can't find any items with search term `{search_term}`.")
return

item = random.choice(data["collection"]["items"])

await ctx.send(
embed=self.create_nasa_embed(
item["data"][0]["title"],
item["data"][0]["description"],
item["links"][0]["href"]
)
)

@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."""
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

# Don't use API key, no need for this.
data = await self.fetch_from_nasa(
f"api/natural{f'/date/{show_date}' if show_date else ''}",
base=NASA_EPIC_BASE_URL,
use_api_key=False
)
if len(data) < 1:
await ctx.send("Can't find any images in this date.")
return

item = random.choice(data)

year, month, day = item["date"].split(" ")[0].split("-")
image_url = f"{NASA_EPIC_BASE_URL}/archive/natural/{year}/{month}/{day}/jpg/{item['image']}.jpg"

await ctx.send(
embed=self.create_nasa_embed(
"Earth Image", item["caption"], image_url, f" \u2022 Identifier: {item['identifier']}"
)
)

@space.group(name="mars", invoke_without_command=True)
async def mars(
self,
ctx: Context,
date: Optional[DateConverter] = None,
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. Use `.space mars dates` to get all currently available rovers.
"""
rover = rover.lower()
if rover not in self.rovers:
await ctx.send(
(
f"Invalid rover `{rover}`.\n"
f"**Rovers:** `{'`, `'.join(f'{r.capitalize()}' for r in self.rovers)}`"
)
)
return

# When date not provided, get random SOL date between 0 and rover's max.
if date is None:
date = random.randint(0, self.rovers[rover]["max_sol"])

params = {}
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)
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}space mars dates` to "
"see working dates for each rover."
)
await ctx.send(err_msg)
return

item = random.choice(result["photos"])
await ctx.send(
embed=self.create_nasa_embed(
f"{item['rover']['name']}'s {item['camera']['full_name']} Mars Image", "", item["img_src"],
)
)

@mars.command(name="dates", aliases=["d", "date", "rover", "rovers", "r"])
async def dates(self, ctx: Context) -> None:
"""Get current available rovers photo date ranges."""
await ctx.send("\n".join(
f"**{r.capitalize()}:** {i['min_date']} **-** {i['max_date']}" for r, i in self.rovers.items()
))

async def fetch_from_nasa(
self,
endpoint: str,
additional_params: Optional[Dict[str, Any]] = None,
base: Optional[str] = NASA_BASE_URL,
use_api_key: bool = True
) -> Dict[str, Any]:
"""Fetch information from NASA API, return result."""
params = {}
if use_api_key:
params["api_key"] = Tokens.nasa

# Add additional parameters to request parameters only when they provided by user
if additional_params is not None:
params.update(additional_params)

async with self.http_session.get(url=f"{base}/{endpoint}?{urlencode(params)}") as resp:
return await resp.json()

def create_nasa_embed(self, title: str, description: str, image: str, footer: Optional[str] = "") -> Embed:
"""Generate NASA commands embeds. Required: title, description and image URL, footer (addition) is optional."""
return Embed(
title=title,
description=description
).set_image(url=image).set_footer(text="Powered by NASA API" + footer)


def setup(bot: SeasonalBot) -> None:
"""Load Space Cog."""
if not Tokens.nasa:
logger.warning("Can't find NASA API key. Not loading Space Cog.")
return

bot.add_cog(Space(bot))