diff --git a/README.md b/README.md
index c37f84f..d8399cc 100644
--- a/README.md
+++ b/README.md
@@ -17,8 +17,9 @@ Cog Function
| forcemention | **Alpha** | Mentions unmentionable roles
Very simple cog, mention doesn't persist |
| hangman | **Beta** | Play a game of hangman
Some visual glitches and needs more customization |
| howdoi | **Incomplete** | Ask coding questions and get results from StackExchange
Not yet functional |
-| leaver | **Beta** | Send a message in a channel when a user leaves the server
Seems to be functional, please report any bugs or suggestions |
| infochannel | **Beta** | Create a channel to display server info
Just released, please report bugs |
+| launchlib | **Beta** | Access rocket launch data
Just released, please report bugs |
+| leaver | **Beta** | Send a message in a channel when a user leaves the server
Seems to be functional, please report any bugs or suggestions |
| lovecalculator | **Alpha** | Calculate the love between two users
[Snap-Ons] Just updated to V3 |
| lseen | **Alpha** | Track when a member was last online
Alpha release, please report bugs |
| nudity | **Alpha** | Checks for NSFW images posted in non-NSFW channels
Switched libraries, now functional |
diff --git a/launchlib/__init__.py b/launchlib/__init__.py
new file mode 100644
index 0000000..8e45f6e
--- /dev/null
+++ b/launchlib/__init__.py
@@ -0,0 +1,5 @@
+from .launchlib import LaunchLib
+
+
+def setup(bot):
+ bot.add_cog(LaunchLib(bot))
diff --git a/launchlib/countrymapper.py b/launchlib/countrymapper.py
new file mode 100644
index 0000000..694fda5
--- /dev/null
+++ b/launchlib/countrymapper.py
@@ -0,0 +1,254 @@
+countryISOMapping = {
+ "AFG": "AF",
+ "ALA": "AX",
+ "ALB": "AL",
+ "DZA": "DZ",
+ "ASM": "AS",
+ "AND": "AD",
+ "AGO": "AO",
+ "AIA": "AI",
+ "ATA": "AQ",
+ "ATG": "AG",
+ "ARG": "AR",
+ "ARM": "AM",
+ "ABW": "AW",
+ "AUS": "AU",
+ "AUT": "AT",
+ "AZE": "AZ",
+ "BHS": "BS",
+ "BHR": "BH",
+ "BGD": "BD",
+ "BRB": "BB",
+ "BLR": "BY",
+ "BEL": "BE",
+ "BLZ": "BZ",
+ "BEN": "BJ",
+ "BMU": "BM",
+ "BTN": "BT",
+ "BOL": "BO",
+ "BIH": "BA",
+ "BWA": "BW",
+ "BVT": "BV",
+ "BRA": "BR",
+ "VGB": "VG",
+ "IOT": "IO",
+ "BRN": "BN",
+ "BGR": "BG",
+ "BFA": "BF",
+ "BDI": "BI",
+ "KHM": "KH",
+ "CMR": "CM",
+ "CAN": "CA",
+ "CPV": "CV",
+ "CYM": "KY",
+ "CAF": "CF",
+ "TCD": "TD",
+ "CHL": "CL",
+ "CHN": "CN",
+ "HKG": "HK",
+ "MAC": "MO",
+ "CXR": "CX",
+ "CCK": "CC",
+ "COL": "CO",
+ "COM": "KM",
+ "COG": "CG",
+ "COD": "CD",
+ "COK": "CK",
+ "CRI": "CR",
+ "CIV": "CI",
+ "HRV": "HR",
+ "CUB": "CU",
+ "CYP": "CY",
+ "CZE": "CZ",
+ "DNK": "DK",
+ "DJI": "DJ",
+ "DMA": "DM",
+ "DOM": "DO",
+ "ECU": "EC",
+ "EGY": "EG",
+ "SLV": "SV",
+ "GNQ": "GQ",
+ "ERI": "ER",
+ "EST": "EE",
+ "ETH": "ET",
+ "FLK": "FK",
+ "FRO": "FO",
+ "FJI": "FJ",
+ "FIN": "FI",
+ "FRA": "FR",
+ "GUF": "GF",
+ "PYF": "PF",
+ "ATF": "TF",
+ "GAB": "GA",
+ "GMB": "GM",
+ "GEO": "GE",
+ "DEU": "DE",
+ "GHA": "GH",
+ "GIB": "GI",
+ "GRC": "GR",
+ "GRL": "GL",
+ "GRD": "GD",
+ "GLP": "GP",
+ "GUM": "GU",
+ "GTM": "GT",
+ "GGY": "GG",
+ "GIN": "GN",
+ "GNB": "GW",
+ "GUY": "GY",
+ "HTI": "HT",
+ "HMD": "HM",
+ "VAT": "VA",
+ "HND": "HN",
+ "HUN": "HU",
+ "ISL": "IS",
+ "IND": "IN",
+ "IDN": "ID",
+ "IRN": "IR",
+ "IRQ": "IQ",
+ "IRL": "IE",
+ "IMN": "IM",
+ "ISR": "IL",
+ "ITA": "IT",
+ "JAM": "JM",
+ "JPN": "JP",
+ "JEY": "JE",
+ "JOR": "JO",
+ "KAZ": "KZ",
+ "KEN": "KE",
+ "KIR": "KI",
+ "PRK": "KP",
+ "KOR": "KR",
+ "KWT": "KW",
+ "KGZ": "KG",
+ "LAO": "LA",
+ "LVA": "LV",
+ "LBN": "LB",
+ "LSO": "LS",
+ "LBR": "LR",
+ "LBY": "LY",
+ "LIE": "LI",
+ "LTU": "LT",
+ "LUX": "LU",
+ "MKD": "MK",
+ "MDG": "MG",
+ "MWI": "MW",
+ "MYS": "MY",
+ "MDV": "MV",
+ "MLI": "ML",
+ "MLT": "MT",
+ "MHL": "MH",
+ "MTQ": "MQ",
+ "MRT": "MR",
+ "MUS": "MU",
+ "MYT": "YT",
+ "MEX": "MX",
+ "FSM": "FM",
+ "MDA": "MD",
+ "MCO": "MC",
+ "MNG": "MN",
+ "MNE": "ME",
+ "MSR": "MS",
+ "MAR": "MA",
+ "MOZ": "MZ",
+ "MMR": "MM",
+ "NAM": "NA",
+ "NRU": "NR",
+ "NPL": "NP",
+ "NLD": "NL",
+ "ANT": "AN",
+ "NCL": "NC",
+ "NZL": "NZ",
+ "NIC": "NI",
+ "NER": "NE",
+ "NGA": "NG",
+ "NIU": "NU",
+ "NFK": "NF",
+ "MNP": "MP",
+ "NOR": "NO",
+ "OMN": "OM",
+ "PAK": "PK",
+ "PLW": "PW",
+ "PSE": "PS",
+ "PAN": "PA",
+ "PNG": "PG",
+ "PRY": "PY",
+ "PER": "PE",
+ "PHL": "PH",
+ "PCN": "PN",
+ "POL": "PL",
+ "PRT": "PT",
+ "PRI": "PR",
+ "QAT": "QA",
+ "REU": "RE",
+ "ROU": "RO",
+ "RUS": "RU",
+ "RWA": "RW",
+ "BLM": "BL",
+ "SHN": "SH",
+ "KNA": "KN",
+ "LCA": "LC",
+ "MAF": "MF",
+ "SPM": "PM",
+ "VCT": "VC",
+ "WSM": "WS",
+ "SMR": "SM",
+ "STP": "ST",
+ "SAU": "SA",
+ "SEN": "SN",
+ "SRB": "RS",
+ "SYC": "SC",
+ "SLE": "SL",
+ "SGP": "SG",
+ "SVK": "SK",
+ "SVN": "SI",
+ "SLB": "SB",
+ "SOM": "SO",
+ "ZAF": "ZA",
+ "SGS": "GS",
+ "SSD": "SS",
+ "ESP": "ES",
+ "LKA": "LK",
+ "SDN": "SD",
+ "SUR": "SR",
+ "SJM": "SJ",
+ "SWZ": "SZ",
+ "SWE": "SE",
+ "CHE": "CH",
+ "SYR": "SY",
+ "TWN": "TW",
+ "TJK": "TJ",
+ "TZA": "TZ",
+ "THA": "TH",
+ "TLS": "TL",
+ "TGO": "TG",
+ "TKL": "TK",
+ "TON": "TO",
+ "TTO": "TT",
+ "TUN": "TN",
+ "TUR": "TR",
+ "TKM": "TM",
+ "TCA": "TC",
+ "TUV": "TV",
+ "UGA": "UG",
+ "UKR": "UA",
+ "ARE": "AE",
+ "GBR": "GB",
+ "USA": "US",
+ "UMI": "UM",
+ "URY": "UY",
+ "UZB": "UZ",
+ "VUT": "VU",
+ "VEN": "VE",
+ "VNM": "VN",
+ "VIR": "VI",
+ "WLF": "WF",
+ "ESH": "EH",
+ "YEM": "YE",
+ "ZMB": "ZM",
+ "ZWE": "ZW",
+ "XKX": "XK",
+}
+
+
+def country_mapping(countrycode):
+ return countryISOMapping[countrycode]
diff --git a/launchlib/info.json b/launchlib/info.json
new file mode 100644
index 0000000..8df1059
--- /dev/null
+++ b/launchlib/info.json
@@ -0,0 +1,20 @@
+{
+ "author": [
+ "Bobloy"
+ ],
+ "min_bot_version": "3.4.0",
+ "description": "Pull information from the Launch Library API for space flights",
+ "hidden": false,
+ "install_msg": "Thank you for installing LaunchLib. Get started with `[p]load launchlib`, then `[p]help LaunchLib`",
+ "short": "Access launch data for space flights",
+ "end_user_data_statement": "This cog does not store any End User Data",
+ "requirements": ["python-launch-library"],
+ "tags": [
+ "bobloy",
+ "utils",
+ "launch",
+ "space",
+ "api",
+ "library"
+ ]
+}
\ No newline at end of file
diff --git a/launchlib/launchlib.py b/launchlib/launchlib.py
new file mode 100644
index 0000000..0c6eeef
--- /dev/null
+++ b/launchlib/launchlib.py
@@ -0,0 +1,130 @@
+import asyncio
+import functools
+import logging
+
+import discord
+import launchlibrary as ll
+from redbot.core import Config, commands
+from redbot.core.bot import Red
+
+from launchlib.countrymapper import country_mapping
+
+log = logging.getLogger("red.fox_v3.launchlib")
+
+
+class LaunchLib(commands.Cog):
+ """
+ Cog Description
+
+ Less important information about the cog
+ """
+
+ def __init__(self, bot: Red):
+ super().__init__()
+ self.bot = bot
+ self.config = Config.get_conf(self, identifier=0, force_registration=True)
+
+ default_guild = {}
+
+ self.config.register_guild(**default_guild)
+
+ self.api = ll.Api()
+
+ async def red_delete_data_for_user(self, **kwargs):
+ """Nothing to delete"""
+ return
+
+ async def _embed_launch_data(self, launch: ll.Launch):
+ status: ll.LaunchStatus = launch.get_status()
+
+ rocket: ll.Rocket = launch.rocket
+
+ title = launch.name
+ description = status.description
+
+ urls = launch.vid_urls + launch.info_urls
+ if not urls and rocket:
+ urls = rocket.info_urls + [rocket.wiki_url]
+ if urls:
+ url = urls[0]
+ else:
+ url = None
+
+ color = discord.Color.green() if status.id in [1, 3] else discord.Color.red()
+
+ em = discord.Embed(title=title, description=description, url=url, color=color)
+
+ if rocket and rocket.image_url and rocket.image_url != "Array":
+ em.set_image(url=rocket.image_url)
+
+ agency = getattr(launch, "agency", None)
+ if agency is not None:
+ em.set_author(
+ name=agency.name,
+ url=agency.wiki_url,
+ icon_url=f"https://www.countryflags.io/{country_mapping(agency.country_code)}/flat/64.png",
+ )
+
+ field_options = [
+ ("failreason", "Fail Reason"),
+ ("holdreason", "Hold Reason"),
+ ("id", "ID"),
+ ("hashtag", "Hashtag"),
+ ]
+ for f in field_options:
+ data = getattr(launch, f[0], None)
+ if data is not None and data:
+ em.add_field(name=f[1], value=data)
+
+ if launch.missions:
+ field_options = [
+ ("description", "Mission Description"),
+ ("typeName", "Mission Type"),
+ ("name", "Mission Name"),
+ ]
+ for mission in launch.missions:
+ for f in field_options:
+ data = mission.get(f[0], None)
+ if data is not None and data:
+ em.add_field(name=f[1], value=data)
+
+ if rocket and rocket.family:
+ em.add_field(name="Rocket Family", value=rocket.family)
+
+ em.timestamp = launch.windowstart
+
+ em.set_footer()
+
+ return em
+
+ @commands.group()
+ async def launchlib(self, ctx: commands.Context):
+ if ctx.invoked_subcommand is None:
+ pass
+
+ @launchlib.command()
+ async def next(self, ctx: commands.Context, num_launches: int = 1):
+ # launches = await api.async_next_launches(num_launches)
+ loop = asyncio.get_running_loop()
+
+ launches = await loop.run_in_executor(
+ None, functools.partial(self.api.fetch_launch, num=num_launches)
+ )
+
+ # launches = self.api.fetch_launch(num=num_launches)
+
+ print(len(launches))
+
+ async with ctx.typing():
+ for x, launch in enumerate(launches):
+ if x >= num_launches:
+ return
+
+ em = await self._embed_launch_data(launch)
+ if em is not None:
+ try:
+ await ctx.send(embed=em)
+ except discord.HTTPException:
+ await ctx.send(str(launch))
+ log.exception("Failed to send embed")
+ await asyncio.sleep(2)